summaryrefslogtreecommitdiff
path: root/wx+
diff options
context:
space:
mode:
authorDaniel Wilhelm <shieldwed@outlook.com>2017-02-13 21:25:04 -0700
committerDaniel Wilhelm <shieldwed@outlook.com>2017-02-13 21:25:04 -0700
commit9d071d2a2cec9a7662a02669488569a017f0ea35 (patch)
treec83a623fbdff098339b66d21ea2e81f3f67344ae /wx+
parent8.8 (diff)
downloadFreeFileSync-9d071d2a2cec9a7662a02669488569a017f0ea35.tar.gz
FreeFileSync-9d071d2a2cec9a7662a02669488569a017f0ea35.tar.bz2
FreeFileSync-9d071d2a2cec9a7662a02669488569a017f0ea35.zip
8.9
Diffstat (limited to 'wx+')
-rwxr-xr-x[-rw-r--r--]wx+/app_main.h100
-rwxr-xr-x[-rw-r--r--]wx+/async_task.h293
-rwxr-xr-x[-rw-r--r--]wx+/bitmap_button.h176
-rwxr-xr-x[-rw-r--r--]wx+/choice_enum.h230
-rwxr-xr-x[-rw-r--r--]wx+/context_menu.h204
-rwxr-xr-x[-rw-r--r--]wx+/dc.h274
-rwxr-xr-xwx+/file_drop.cpp56
-rwxr-xr-x[-rw-r--r--]wx+/file_drop.h229
-rwxr-xr-x[-rw-r--r--]wx+/font_size.h132
-rwxr-xr-x[-rw-r--r--]wx+/graph.cpp1694
-rwxr-xr-x[-rw-r--r--]wx+/graph.h708
-rwxr-xr-x[-rw-r--r--]wx+/grid.cpp4494
-rwxr-xr-x[-rw-r--r--]wx+/grid.h729
-rwxr-xr-x[-rw-r--r--]wx+/http.cpp701
-rwxr-xr-x[-rw-r--r--]wx+/http.h97
-rwxr-xr-x[-rw-r--r--]wx+/image_resources.cpp339
-rwxr-xr-x[-rw-r--r--]wx+/image_resources.h46
-rwxr-xr-x[-rw-r--r--]wx+/image_tools.cpp466
-rwxr-xr-x[-rw-r--r--]wx+/image_tools.h516
-rwxr-xr-x[-rw-r--r--]wx+/no_flicker.h82
-rwxr-xr-x[-rw-r--r--]wx+/popup_dlg.cpp614
-rwxr-xr-x[-rw-r--r--]wx+/popup_dlg.h191
-rwxr-xr-x[-rw-r--r--]wx+/popup_dlg_generated.cpp194
-rwxr-xr-x[-rw-r--r--]wx+/popup_dlg_generated.h142
-rwxr-xr-x[-rw-r--r--]wx+/rtl.h190
-rwxr-xr-x[-rw-r--r--]wx+/std_button_layout.h263
-rwxr-xr-x[-rw-r--r--]wx+/string_conv.h56
-rwxr-xr-x[-rw-r--r--]wx+/timespan.h332
-rwxr-xr-x[-rw-r--r--]wx+/toggle_button.h134
-rwxr-xr-x[-rw-r--r--]wx+/tooltip.cpp201
-rwxr-xr-x[-rw-r--r--]wx+/tooltip.h64
-rwxr-xr-x[-rw-r--r--]wx+/zlib_wrap.cpp104
-rwxr-xr-x[-rw-r--r--]wx+/zlib_wrap.h228
33 files changed, 6959 insertions, 7320 deletions
diff --git a/wx+/app_main.h b/wx+/app_main.h
index 02d08709..2202ebc5 100644..100755
--- a/wx+/app_main.h
+++ b/wx+/app_main.h
@@ -1,50 +1,50 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef APP_MAIN_H_08215601837818347575856
-#define APP_MAIN_H_08215601837818347575856
-
-#include <wx/window.h>
-#include <wx/app.h>
-
-namespace zen
-{
-//just some wrapper around a global variable representing the (logical) main application window
-void setMainWindow(wxWindow* window); //set main window and enable "exit on frame delete"
-bool mainWindowWasSet();
-
-
-
-
-
-
-
-
-
-
-
-
-//######################## implementation ########################
-inline
-bool& refMainWndStatus()
-{
- static bool status = false; //external linkage!
- return status;
-}
-
-inline
-void setMainWindow(wxWindow* window)
-{
- wxTheApp->SetTopWindow(window);
- wxTheApp->SetExitOnFrameDelete(true);
-
- refMainWndStatus() = true;
-}
-
-inline bool mainWindowWasSet() { return refMainWndStatus(); }
-}
-
-#endif //APP_MAIN_H_08215601837818347575856
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef APP_MAIN_H_08215601837818347575856
+#define APP_MAIN_H_08215601837818347575856
+
+#include <wx/window.h>
+#include <wx/app.h>
+
+namespace zen
+{
+//just some wrapper around a global variable representing the (logical) main application window
+void setMainWindow(wxWindow* window); //set main window and enable "exit on frame delete"
+bool mainWindowWasSet();
+
+
+
+
+
+
+
+
+
+
+
+
+//######################## implementation ########################
+inline
+bool& refMainWndStatus()
+{
+ static bool status = false; //external linkage!
+ return status;
+}
+
+inline
+void setMainWindow(wxWindow* window)
+{
+ wxTheApp->SetTopWindow(window);
+ wxTheApp->SetExitOnFrameDelete(true);
+
+ refMainWndStatus() = true;
+}
+
+inline bool mainWindowWasSet() { return refMainWndStatus(); }
+}
+
+#endif //APP_MAIN_H_08215601837818347575856
diff --git a/wx+/async_task.h b/wx+/async_task.h
index 7ac03949..36d9068e 100644..100755
--- a/wx+/async_task.h
+++ b/wx+/async_task.h
@@ -1,150 +1,143 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#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>
-#include <wx/timer.h>
-
-
-namespace zen
-{
-/*
-Run a task in an async thread, but process result in GUI event loop
--------------------------------------------------------------------
-1. put AsyncGuiQueue instance inside a dialog:
- AsyncGuiQueue guiQueue;
-
-2. schedule async task and synchronous continuation:
- guiQueue.processAsync(evalAsync, evalOnGui);
-
-Alternative: use wxWidgets' inter-thread communication (wxEvtHandler::QueueEvent) https://wiki.wxwidgets.org/Inter-Thread_and_Inter-Process_communication
- => don't bother, probably too many MT race conditions lurking around
-*/
-
-namespace impl
-{
-struct Task
-{
- virtual ~Task() {}
- virtual bool resultReady () const = 0;
- virtual void evaluateResult() = 0;
-};
-
-
-template <class ResultType, class Fun>
-class ConcreteTask : public Task
-{
-public:
- template <class Fun2>
- ConcreteTask(std::future<ResultType>&& asyncResult, Fun2&& evalOnGui) : asyncResult_(std::move(asyncResult)), evalOnGui_(std::forward<Fun2>(evalOnGui)) {}
-
- bool resultReady () const override { return isReady(asyncResult_); }
- void evaluateResult() override
- {
-#ifndef NDEBUG
- assert(resultReady() && !resultEvaluated_);
- resultEvaluated_ = true;
-#endif
- evalResult(IsSameType<ResultType, void>());
- }
-
-private:
- void evalResult(FalseType /*void result type*/) { evalOnGui_(asyncResult_.get()); }
- void evalResult(TrueType /*void result type*/) { asyncResult_.get(); evalOnGui_(); }
-
- std::future<ResultType> asyncResult_;
- Fun evalOnGui_; //keep "evalOnGui" strictly separated from async thread: in particular do not copy in thread!
-#ifndef NDEBUG
- bool resultEvaluated_ = false;
-#endif
-};
-
-
-class AsyncTasks
-{
-public:
- AsyncTasks() {}
-
- template <class Fun, class Fun2>
- void add(Fun&& evalAsync, Fun2&& evalOnGui)
- {
- using ResultType = decltype(evalAsync());
- tasks_.push_back(std::make_unique<ConcreteTask<ResultType, Fun2>>(zen::runAsync(std::forward<Fun>(evalAsync)), std::forward<Fun2>(evalOnGui)));
- }
- //equivalent to "evalOnGui(evalAsync())"
- // -> evalAsync: the usual thread-safety requirements apply!
- // -> evalOnGui: no thread-safety concerns, but must only reference variables with greater-equal lifetime than the AsyncTask instance!
-
- void evalResults() //call from gui thread repreatedly
- {
- if (!inRecursion_) //prevent implicit recursion, e.g. if we're called from an idle event and spawn another one within the callback below
- {
- inRecursion_ = true;
- ZEN_ON_SCOPE_EXIT(inRecursion_ = false);
-
- std::vector<std::unique_ptr<Task>> readyTasks; //Reentrancy; access to AsyncTasks::add is not protected! => evaluate outside erase_if
-
- erase_if(tasks_, [&](std::unique_ptr<Task>& task)
- {
- if (task->resultReady())
- {
- readyTasks.push_back(std::move(task));
- return true;
- }
- return false;
- });
-
- for (auto& task : readyTasks)
- task->evaluateResult();
- }
- }
-
- bool empty() const { return tasks_.empty(); }
-
-private:
- AsyncTasks (const AsyncTasks&) = delete;
- AsyncTasks& operator=(const AsyncTasks&) = delete;
-
- bool inRecursion_ = false;
- std::vector<std::unique_ptr<Task>> tasks_;
-};
-}
-
-
-class AsyncGuiQueue : private wxEvtHandler
-{
-public:
- AsyncGuiQueue() { timer_.Connect(wxEVT_TIMER, wxEventHandler(AsyncGuiQueue::onTimerEvent), nullptr, this); }
-
- template <class Fun, class Fun2>
- void processAsync(Fun&& evalAsync, Fun2&& evalOnGui)
- {
- asyncTasks_.add(std::forward<Fun >(evalAsync),
- std::forward<Fun2>(evalOnGui));
- if (!timer_.IsRunning())
- timer_.Start(50 /*unit: [ms]*/);
- }
-
-private:
- void onTimerEvent(wxEvent& event) //schedule and run long-running tasks asynchronously
- {
- asyncTasks_.evalResults(); //process results on GUI queue
- if (asyncTasks_.empty())
- timer_.Stop();
- }
-
- impl::AsyncTasks asyncTasks_;
- wxTimer timer_; //don't use wxWidgets' idle handling => repeated idle requests/consumption hogs 100% cpu!
-};
-
-}
-
-#endif //ASYNC_TASK_H_839147839170432143214321
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#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>
+#include <wx/timer.h>
+
+
+namespace zen
+{
+/*
+Run a task in an async thread, but process result in GUI event loop
+-------------------------------------------------------------------
+1. put AsyncGuiQueue instance inside a dialog:
+ AsyncGuiQueue guiQueue;
+
+2. schedule async task and synchronous continuation:
+ guiQueue.processAsync(evalAsync, evalOnGui);
+
+Alternative: use wxWidgets' inter-thread communication (wxEvtHandler::QueueEvent) https://wiki.wxwidgets.org/Inter-Thread_and_Inter-Process_communication
+ => don't bother, probably too many MT race conditions lurking around
+*/
+
+namespace impl
+{
+struct Task
+{
+ virtual ~Task() {}
+ virtual bool resultReady () const = 0;
+ virtual void evaluateResult() = 0;
+};
+
+
+template <class ResultType, class Fun>
+class ConcreteTask : public Task
+{
+public:
+ template <class Fun2>
+ ConcreteTask(std::future<ResultType>&& asyncResult, Fun2&& evalOnGui) : asyncResult_(std::move(asyncResult)), evalOnGui_(std::forward<Fun2>(evalOnGui)) {}
+
+ bool resultReady () const override { return isReady(asyncResult_); }
+ void evaluateResult() override
+ {
+ evalResult(IsSameType<ResultType, void>());
+ }
+
+private:
+ void evalResult(FalseType /*void result type*/) { evalOnGui_(asyncResult_.get()); }
+ void evalResult(TrueType /*void result type*/) { asyncResult_.get(); evalOnGui_(); }
+
+ std::future<ResultType> asyncResult_;
+ Fun evalOnGui_; //keep "evalOnGui" strictly separated from async thread: in particular do not copy in thread!
+};
+
+
+class AsyncTasks
+{
+public:
+ AsyncTasks() {}
+
+ template <class Fun, class Fun2>
+ void add(Fun&& evalAsync, Fun2&& evalOnGui)
+ {
+ using ResultType = decltype(evalAsync());
+ tasks_.push_back(std::make_unique<ConcreteTask<ResultType, Fun2>>(zen::runAsync(std::forward<Fun>(evalAsync)), std::forward<Fun2>(evalOnGui)));
+ }
+ //equivalent to "evalOnGui(evalAsync())"
+ // -> evalAsync: the usual thread-safety requirements apply!
+ // -> evalOnGui: no thread-safety concerns, but must only reference variables with greater-equal lifetime than the AsyncTask instance!
+
+ void evalResults() //call from gui thread repreatedly
+ {
+ if (!inRecursion_) //prevent implicit recursion, e.g. if we're called from an idle event and spawn another one within the callback below
+ {
+ inRecursion_ = true;
+ ZEN_ON_SCOPE_EXIT(inRecursion_ = false);
+
+ std::vector<std::unique_ptr<Task>> readyTasks; //Reentrancy; access to AsyncTasks::add is not protected! => evaluate outside erase_if
+
+ erase_if(tasks_, [&](std::unique_ptr<Task>& task)
+ {
+ if (task->resultReady())
+ {
+ readyTasks.push_back(std::move(task));
+ return true;
+ }
+ return false;
+ });
+
+ for (auto& task : readyTasks)
+ task->evaluateResult();
+ }
+ }
+
+ bool empty() const { return tasks_.empty(); }
+
+private:
+ AsyncTasks (const AsyncTasks&) = delete;
+ AsyncTasks& operator=(const AsyncTasks&) = delete;
+
+ bool inRecursion_ = false;
+ std::vector<std::unique_ptr<Task>> tasks_;
+};
+}
+
+
+class AsyncGuiQueue : private wxEvtHandler
+{
+public:
+ AsyncGuiQueue() { timer_.Connect(wxEVT_TIMER, wxEventHandler(AsyncGuiQueue::onTimerEvent), nullptr, this); }
+
+ template <class Fun, class Fun2>
+ void processAsync(Fun&& evalAsync, Fun2&& evalOnGui)
+ {
+ asyncTasks_.add(std::forward<Fun >(evalAsync),
+ std::forward<Fun2>(evalOnGui));
+ if (!timer_.IsRunning())
+ timer_.Start(50 /*unit: [ms]*/);
+ }
+
+private:
+ void onTimerEvent(wxEvent& event) //schedule and run long-running tasks asynchronously
+ {
+ asyncTasks_.evalResults(); //process results on GUI queue
+ if (asyncTasks_.empty())
+ timer_.Stop();
+ }
+
+ impl::AsyncTasks asyncTasks_;
+ wxTimer timer_; //don't use wxWidgets' idle handling => repeated idle requests/consumption hogs 100% cpu!
+};
+
+}
+
+#endif //ASYNC_TASK_H_839147839170432143214321
diff --git a/wx+/bitmap_button.h b/wx+/bitmap_button.h
index 18702dd3..2ed8a278 100644..100755
--- a/wx+/bitmap_button.h
+++ b/wx+/bitmap_button.h
@@ -1,88 +1,88 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef BITMAP_BUTTON_H_83415718945878341563415
-#define BITMAP_BUTTON_H_83415718945878341563415
-
-#include <wx/bmpbuttn.h>
-#include "image_tools.h"
-
-
-namespace zen
-{
-//zen::BitmapTextButton is identical to wxBitmapButton, but preserves the label via SetLabel(), which wxFormbuilder would ditch!
-class BitmapTextButton : public wxBitmapButton
-{
-public:
- BitmapTextButton(wxWindow* parent,
- wxWindowID id,
- const wxString& label,
- const wxPoint& pos = wxDefaultPosition,
- const wxSize& size = wxDefaultSize,
- long style = 0,
- const wxValidator& validator = wxDefaultValidator,
- const wxString& name = wxButtonNameStr) :
- wxBitmapButton(parent, id, wxNullBitmap, pos, size, style | wxBU_AUTODRAW, validator, name) { SetLabel(label); }
-};
-
-void setBitmapTextLabel(wxBitmapButton& btn, const wxImage& img, const wxString& text, int gap = 5, int border = 5);
-
-//set bitmap label flicker free:
-void setImage(wxBitmapButton& button, const wxBitmap& bmp);
-
-
-
-
-
-
-
-
-
-
-
-//################################### implementation ###################################
-inline
-void setBitmapTextLabel(wxBitmapButton& btn, const wxImage& img, const wxString& text, int gap, int border)
-{
- assert(gap >= 0 && border >= 0);
- gap = std::max(0, gap);
- border = std::max(0, border);
-
- wxImage dynImage = createImageFromText(text, btn.GetFont(), btn.GetForegroundColour());
- if (img.IsOk())
- {
- if (btn.GetLayoutDirection() != wxLayout_RightToLeft)
- dynImage = stackImages(img, dynImage, ImageStackLayout::HORIZONTAL, ImageStackAlignment::CENTER, gap);
- else
- dynImage = stackImages(dynImage, img, ImageStackLayout::HORIZONTAL, ImageStackAlignment::CENTER, gap);
- }
-
- //SetMinSize() instead of SetSize() is needed here for wxWindows layout determination to work corretly
- const int defaultHeight = wxButton::GetDefaultSize().GetHeight();
- btn.SetMinSize(wxSize(dynImage.GetWidth () + 2 * border,
- std::max(dynImage.GetHeight() + 2 * border, defaultHeight)));
-
- btn.SetBitmapLabel(wxBitmap(dynImage));
- //SetLabel() calls confuse wxBitmapButton in the disabled state and it won't show the image! workaround:
- btn.SetBitmapDisabled(wxBitmap(dynImage.ConvertToDisabled()));
-}
-
-
-inline
-void setImage(wxBitmapButton& button, const wxBitmap& bmp)
-{
- if (!isEqual(button.GetBitmapLabel(), bmp))
- {
- button.SetBitmapLabel(bmp);
-
- //wxWidgets excels at screwing up consistently once again:
- //the first call to SetBitmapLabel() *implicitly* sets the disabled bitmap, too, subsequent calls, DON'T!
- button.SetBitmapDisabled(bmp.ConvertToDisabled()); //inefficiency: wxBitmap::ConvertToDisabled() implicitly converts to wxImage!
- }
-}
-}
-
-#endif //BITMAP_BUTTON_H_83415718945878341563415
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef BITMAP_BUTTON_H_83415718945878341563415
+#define BITMAP_BUTTON_H_83415718945878341563415
+
+#include <wx/bmpbuttn.h>
+#include "image_tools.h"
+
+
+namespace zen
+{
+//zen::BitmapTextButton is identical to wxBitmapButton, but preserves the label via SetLabel(), which wxFormbuilder would ditch!
+class BitmapTextButton : public wxBitmapButton
+{
+public:
+ BitmapTextButton(wxWindow* parent,
+ wxWindowID id,
+ const wxString& label,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = 0,
+ const wxValidator& validator = wxDefaultValidator,
+ const wxString& name = wxButtonNameStr) :
+ wxBitmapButton(parent, id, wxNullBitmap, pos, size, style | wxBU_AUTODRAW, validator, name) { SetLabel(label); }
+};
+
+void setBitmapTextLabel(wxBitmapButton& btn, const wxImage& img, const wxString& text, int gap = 5, int border = 5);
+
+//set bitmap label flicker free:
+void setImage(wxBitmapButton& button, const wxBitmap& bmp);
+
+
+
+
+
+
+
+
+
+
+
+//################################### implementation ###################################
+inline
+void setBitmapTextLabel(wxBitmapButton& btn, const wxImage& img, const wxString& text, int gap, int border)
+{
+ assert(gap >= 0 && border >= 0);
+ gap = std::max(0, gap);
+ border = std::max(0, border);
+
+ wxImage dynImage = createImageFromText(text, btn.GetFont(), btn.GetForegroundColour());
+ if (img.IsOk())
+ {
+ if (btn.GetLayoutDirection() != wxLayout_RightToLeft)
+ dynImage = stackImages(img, dynImage, ImageStackLayout::HORIZONTAL, ImageStackAlignment::CENTER, gap);
+ else
+ dynImage = stackImages(dynImage, img, ImageStackLayout::HORIZONTAL, ImageStackAlignment::CENTER, gap);
+ }
+
+ //SetMinSize() instead of SetSize() is needed here for wxWindows layout determination to work corretly
+ const int defaultHeight = wxButton::GetDefaultSize().GetHeight();
+ btn.SetMinSize(wxSize(dynImage.GetWidth () + 2 * border,
+ std::max(dynImage.GetHeight() + 2 * border, defaultHeight)));
+
+ btn.SetBitmapLabel(wxBitmap(dynImage));
+ //SetLabel() calls confuse wxBitmapButton in the disabled state and it won't show the image! workaround:
+ btn.SetBitmapDisabled(wxBitmap(dynImage.ConvertToDisabled()));
+}
+
+
+inline
+void setImage(wxBitmapButton& button, const wxBitmap& bmp)
+{
+ if (!isEqual(button.GetBitmapLabel(), bmp))
+ {
+ button.SetBitmapLabel(bmp);
+
+ //wxWidgets excels at screwing up consistently once again:
+ //the first call to SetBitmapLabel() *implicitly* sets the disabled bitmap, too, subsequent calls, DON'T!
+ button.SetBitmapDisabled(bmp.ConvertToDisabled()); //inefficiency: wxBitmap::ConvertToDisabled() implicitly converts to wxImage!
+ }
+}
+}
+
+#endif //BITMAP_BUTTON_H_83415718945878341563415
diff --git a/wx+/choice_enum.h b/wx+/choice_enum.h
index d564fef8..9197db9a 100644..100755
--- a/wx+/choice_enum.h
+++ b/wx+/choice_enum.h
@@ -1,115 +1,115 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef CHOICE_ENUM_H_132413545345687
-#define CHOICE_ENUM_H_132413545345687
-
-#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
-{
-template <class Enum>
-struct EnumDescrList
-{
- EnumDescrList& add(Enum value, const wxString& text, const wxString& tooltip = {})
- {
- descrList.emplace_back(value, std::make_pair(text, tooltip));
- return *this;
- }
- using DescrList = std::vector<std::pair<Enum, std::pair<wxString, wxString>>>;
- DescrList descrList;
-};
-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);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-//--------------- impelementation -------------------------------------------
-template <class Enum>
-void setEnumVal(const EnumDescrList<Enum>& mapping, wxChoice& ctrl, Enum value)
-{
- ctrl.Clear();
-
- int selectedPos = 0;
- for (auto it = mapping.descrList.begin(); it != mapping.descrList.end(); ++it)
- {
- ctrl.Append(it->second.first);
- if (it->first == value)
- {
- selectedPos = it - mapping.descrList.begin();
-
- if (!it->second.second.empty())
- ctrl.SetToolTip(it->second.second);
- }
- }
-
- ctrl.SetSelection(selectedPos);
-}
-
-template <class Enum>
-Enum getEnumVal(const EnumDescrList<Enum>& mapping, const wxChoice& ctrl)
-{
- const int selectedPos = ctrl.GetSelection();
-
- if (0 <= selectedPos && selectedPos < static_cast<int>(mapping.descrList.size()))
- return mapping.descrList[selectedPos].first;
- else
- {
- assert(false);
- return Enum(0);
- }
-}
-
-template <class Enum> void updateTooltipEnumVal(const EnumDescrList<Enum>& mapping, wxChoice& ctrl)
-{
- const Enum value = getEnumVal(mapping, ctrl);
-
- for (const auto& item : mapping.descrList)
- if (item.first == value)
- ctrl.SetToolTip(item.second.second);
-}
-
-}
-
-
-#endif //CHOICE_ENUM_H_132413545345687
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef CHOICE_ENUM_H_132413545345687
+#define CHOICE_ENUM_H_132413545345687
+
+#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
+{
+template <class Enum>
+struct EnumDescrList
+{
+ EnumDescrList& add(Enum value, const wxString& text, const wxString& tooltip = {})
+ {
+ descrList.emplace_back(value, std::make_pair(text, tooltip));
+ return *this;
+ }
+ using DescrList = std::vector<std::pair<Enum, std::pair<wxString, wxString>>>;
+ DescrList descrList;
+};
+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);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//--------------- impelementation -------------------------------------------
+template <class Enum>
+void setEnumVal(const EnumDescrList<Enum>& mapping, wxChoice& ctrl, Enum value)
+{
+ ctrl.Clear();
+
+ int selectedPos = 0;
+ for (auto it = mapping.descrList.begin(); it != mapping.descrList.end(); ++it)
+ {
+ ctrl.Append(it->second.first);
+ if (it->first == value)
+ {
+ selectedPos = it - mapping.descrList.begin();
+
+ if (!it->second.second.empty())
+ ctrl.SetToolTip(it->second.second);
+ }
+ }
+
+ ctrl.SetSelection(selectedPos);
+}
+
+template <class Enum>
+Enum getEnumVal(const EnumDescrList<Enum>& mapping, const wxChoice& ctrl)
+{
+ const int selectedPos = ctrl.GetSelection();
+
+ if (0 <= selectedPos && selectedPos < static_cast<int>(mapping.descrList.size()))
+ return mapping.descrList[selectedPos].first;
+ else
+ {
+ assert(false);
+ return Enum(0);
+ }
+}
+
+template <class Enum> void updateTooltipEnumVal(const EnumDescrList<Enum>& mapping, wxChoice& ctrl)
+{
+ const Enum value = getEnumVal(mapping, ctrl);
+
+ for (const auto& item : mapping.descrList)
+ if (item.first == value)
+ ctrl.SetToolTip(item.second.second);
+}
+
+}
+
+
+#endif //CHOICE_ENUM_H_132413545345687
diff --git a/wx+/context_menu.h b/wx+/context_menu.h
index bd77100c..9daba261 100644..100755
--- a/wx+/context_menu.h
+++ b/wx+/context_menu.h
@@ -1,102 +1,102 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef CONTEXT_MENU_H_18047302153418174632141234
-#define CONTEXT_MENU_H_18047302153418174632141234
-
-#include <map>
-#include <vector>
-#include <functional>
-#include <wx/menu.h>
-#include <wx/app.h>
-
-/*
-A context menu supporting C++11 lambda callbacks!
-
-Usage:
- ContextMenu menu;
- menu.addItem(L"Some Label", [&]{ ...do something... }); -> capture by reference is fine, as long as captured variables have at least scope of ContextMenu::popup()!
- ...
- menu.popup(wnd);
-*/
-
-namespace zen
-{
-class ContextMenu : private wxEvtHandler
-{
-public:
- ContextMenu() : menu(std::make_unique<wxMenu>()) {}
-
- void addItem(const wxString& label, const std::function<void()>& command, const wxBitmap* bmp = nullptr, bool enabled = true)
- {
- wxMenuItem* newItem = new wxMenuItem(menu.get(), wxID_ANY, label); //menu owns item!
- if (bmp) newItem->SetBitmap(*bmp); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason
- menu->Append(newItem);
- if (!enabled) newItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason
- commandList[newItem->GetId()] = command; //defer event connection, this may be a submenu only!
- }
-
- void addCheckBox(const wxString& label, const std::function<void()>& command, bool checked, bool enabled = true)
- {
- wxMenuItem* newItem = menu->AppendCheckItem(wxID_ANY, label);
- newItem->Check(checked);
- if (!enabled) newItem->Enable(false);
- commandList[newItem->GetId()] = command;
- }
-
- void addRadio(const wxString& label, const std::function<void()>& command, bool selected, bool enabled = true)
- {
- wxMenuItem* newItem = menu->AppendRadioItem(wxID_ANY, label);
- newItem->Check(selected);
- if (!enabled) newItem->Enable(false);
- commandList[newItem->GetId()] = command;
- }
-
- void addSeparator() { menu->AppendSeparator(); }
-
- void addSubmenu(const wxString& label, ContextMenu& submenu, const wxBitmap* bmp = nullptr) //invalidates submenu!
- {
- //transfer submenu commands:
- commandList.insert(submenu.commandList.begin(), submenu.commandList.end());
- submenu.commandList.clear();
-
- submenu.menu->SetNextHandler(menu.get()); //on wxGTK submenu events are not propagated to their parent menu by default!
-
- wxMenuItem* newItem = new wxMenuItem(menu.get(), wxID_ANY, label, L"", wxITEM_NORMAL, submenu.menu.release()); //menu owns item, item owns submenu!
- if (bmp) newItem->SetBitmap(*bmp); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason
- menu->Append(newItem);
- }
-
- 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
- for (const auto& item : commandList)
- menu->Connect(item.first, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(ContextMenu::onSelection), new GenericCommand(item.second) /*pass ownership*/, this);
-
- wnd.PopupMenu(menu.get(), pos);
- wxTheApp->ProcessPendingEvents(); //make sure lambdas are evaluated before going out of scope;
- //although all events seem to be processed within wxWindows::PopupMenu, we shouldn't trust wxWidgets in this regard
- }
-
-private:
- void onSelection(wxCommandEvent& event)
- {
- if (auto cmd = dynamic_cast<GenericCommand*>(event.m_callbackUserData))
- (cmd->fun_)();
- }
-
- struct GenericCommand : public wxObject
- {
- GenericCommand(const std::function<void()>& fun) : fun_(fun) {}
- std::function<void()> fun_;
- };
-
- std::unique_ptr<wxMenu> menu;
- std::map<int, std::function<void()>> commandList; //(item id, command)
-};
-}
-
-#endif //CONTEXT_MENU_H_18047302153418174632141234
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef CONTEXT_MENU_H_18047302153418174632141234
+#define CONTEXT_MENU_H_18047302153418174632141234
+
+#include <map>
+#include <vector>
+#include <functional>
+#include <wx/menu.h>
+#include <wx/app.h>
+
+/*
+A context menu supporting C++11 lambda callbacks!
+
+Usage:
+ ContextMenu menu;
+ menu.addItem(L"Some Label", [&]{ ...do something... }); -> capture by reference is fine, as long as captured variables have at least scope of ContextMenu::popup()!
+ ...
+ menu.popup(wnd);
+*/
+
+namespace zen
+{
+class ContextMenu : private wxEvtHandler
+{
+public:
+ ContextMenu() : menu(std::make_unique<wxMenu>()) {}
+
+ void addItem(const wxString& label, const std::function<void()>& command, const wxBitmap* bmp = nullptr, bool enabled = true)
+ {
+ wxMenuItem* newItem = new wxMenuItem(menu.get(), wxID_ANY, label); //menu owns item!
+ if (bmp) newItem->SetBitmap(*bmp); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason
+ menu->Append(newItem);
+ if (!enabled) newItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason
+ commandList[newItem->GetId()] = command; //defer event connection, this may be a submenu only!
+ }
+
+ void addCheckBox(const wxString& label, const std::function<void()>& command, bool checked, bool enabled = true)
+ {
+ wxMenuItem* newItem = menu->AppendCheckItem(wxID_ANY, label);
+ newItem->Check(checked);
+ if (!enabled) newItem->Enable(false);
+ commandList[newItem->GetId()] = command;
+ }
+
+ void addRadio(const wxString& label, const std::function<void()>& command, bool selected, bool enabled = true)
+ {
+ wxMenuItem* newItem = menu->AppendRadioItem(wxID_ANY, label);
+ newItem->Check(selected);
+ if (!enabled) newItem->Enable(false);
+ commandList[newItem->GetId()] = command;
+ }
+
+ void addSeparator() { menu->AppendSeparator(); }
+
+ void addSubmenu(const wxString& label, ContextMenu& submenu, const wxBitmap* bmp = nullptr) //invalidates submenu!
+ {
+ //transfer submenu commands:
+ commandList.insert(submenu.commandList.begin(), submenu.commandList.end());
+ submenu.commandList.clear();
+
+ submenu.menu->SetNextHandler(menu.get()); //on wxGTK submenu events are not propagated to their parent menu by default!
+
+ wxMenuItem* newItem = new wxMenuItem(menu.get(), wxID_ANY, label, L"", wxITEM_NORMAL, submenu.menu.release()); //menu owns item, item owns submenu!
+ if (bmp) newItem->SetBitmap(*bmp); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason
+ menu->Append(newItem);
+ }
+
+ 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
+ for (const auto& item : commandList)
+ menu->Connect(item.first, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(ContextMenu::onSelection), new GenericCommand(item.second) /*pass ownership*/, this);
+
+ wnd.PopupMenu(menu.get(), pos);
+ wxTheApp->ProcessPendingEvents(); //make sure lambdas are evaluated before going out of scope;
+ //although all events seem to be processed within wxWindows::PopupMenu, we shouldn't trust wxWidgets in this regard
+ }
+
+private:
+ void onSelection(wxCommandEvent& event)
+ {
+ if (auto cmd = dynamic_cast<GenericCommand*>(event.m_callbackUserData))
+ (cmd->fun_)();
+ }
+
+ struct GenericCommand : public wxObject
+ {
+ GenericCommand(const std::function<void()>& fun) : fun_(fun) {}
+ std::function<void()> fun_;
+ };
+
+ std::unique_ptr<wxMenu> menu;
+ std::map<int, std::function<void()>> commandList; //(item id, command)
+};
+}
+
+#endif //CONTEXT_MENU_H_18047302153418174632141234
diff --git a/wx+/dc.h b/wx+/dc.h
index e5739053..1d1e31e3 100644..100755
--- a/wx+/dc.h
+++ b/wx+/dc.h
@@ -1,137 +1,137 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef DC_H_4987123956832143243214
-#define DC_H_4987123956832143243214
-
-#include <unordered_map>
-#include <zen/optional.h>
-#include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER
-
-namespace zen
-{
-/*
-1. wxDCClipper does *not* stack: another fix for yet another poor wxWidgets implementation
-
-class RecursiveDcClipper
-{
- RecursiveDcClipper(wxDC& dc, const wxRect& r) : dc_(dc)
-};
-
-------------------------------------------------------------------------------------------------
-
-2. wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active: a fix for a poor wxWidgets implementation
-class BufferedPaintDC
-{
- BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer);
-};
-*/
-
-
-
-
-
-
-
-
-
-
-
-//---------------------- implementation ------------------------
-class RecursiveDcClipper
-{
-public:
- RecursiveDcClipper(wxDC& dc, const wxRect& r) : dc_(dc)
- {
- auto it = refDcToAreaMap().find(&dc);
- if (it != refDcToAreaMap().end())
- {
- oldRect = it->second;
-
- wxRect tmp = r;
- tmp.Intersect(*oldRect); //better safe than sorry
- dc_.SetClippingRegion(tmp); //
- it->second = tmp;
- }
- else
- {
- dc_.SetClippingRegion(r);
- refDcToAreaMap().emplace(&dc_, r);
- }
- }
-
- ~RecursiveDcClipper()
- {
- dc_.DestroyClippingRegion();
- if (oldRect)
- {
- dc_.SetClippingRegion(*oldRect);
- refDcToAreaMap()[&dc_] = *oldRect;
- }
- else
- refDcToAreaMap().erase(&dc_);
- }
-
-private:
- //associate "active" clipping area with each DC
- static std::unordered_map<wxDC*, wxRect>& refDcToAreaMap() { static std::unordered_map<wxDC*, wxRect> clippingAreas; return clippingAreas; }
-
- Opt<wxRect> oldRect;
- wxDC& dc_;
-};
-
-
-#ifndef wxALWAYS_NATIVE_DOUBLE_BUFFER
- #error we need this one!
-#endif
-
-#if wxALWAYS_NATIVE_DOUBLE_BUFFER
-struct BufferedPaintDC : public wxPaintDC { BufferedPaintDC(wxWindow& wnd, Opt<wxBitmap>& buffer) : wxPaintDC(&wnd) {} };
-
-#else
-class BufferedPaintDC : public wxMemoryDC
-{
-public:
- BufferedPaintDC(wxWindow& wnd, Opt<wxBitmap>& buffer) : buffer_(buffer), paintDc(&wnd)
- {
- const wxSize clientSize = wnd.GetClientSize();
- if (clientSize.GetWidth() > 0 && clientSize.GetHeight() > 0) //wxBitmap asserts this!! width may be 0; test case "Grid::CornerWin": compare both sides, then change config
- {
- if (!buffer_ || clientSize != wxSize(buffer->GetWidth(), buffer->GetHeight()))
- buffer = wxBitmap(clientSize.GetWidth(), clientSize.GetHeight());
-
- SelectObject(*buffer);
-
- if (paintDc.IsOk() && paintDc.GetLayoutDirection() == wxLayout_RightToLeft)
- SetLayoutDirection(wxLayout_RightToLeft);
- }
- else
- buffer = NoValue();
- }
-
- ~BufferedPaintDC()
- {
- if (buffer_)
- {
- if (GetLayoutDirection() == wxLayout_RightToLeft)
- {
- paintDc.SetLayoutDirection(wxLayout_LeftToRight); //workaround bug in wxDC::Blit()
- SetLayoutDirection(wxLayout_LeftToRight); //
- }
-
- const wxPoint origin = GetDeviceOrigin();
- paintDc.Blit(0, 0, buffer_->GetWidth(), buffer_->GetHeight(), this, -origin.x, -origin.y);
- }
- }
-
-private:
- Opt<wxBitmap>& buffer_;
- wxPaintDC paintDc;
-};
-#endif
-}
-
-#endif //DC_H_4987123956832143243214
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef DC_H_4987123956832143243214
+#define DC_H_4987123956832143243214
+
+#include <unordered_map>
+#include <zen/optional.h>
+#include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER
+
+namespace zen
+{
+/*
+1. wxDCClipper does *not* stack: another fix for yet another poor wxWidgets implementation
+
+class RecursiveDcClipper
+{
+ RecursiveDcClipper(wxDC& dc, const wxRect& r) : dc_(dc)
+};
+
+------------------------------------------------------------------------------------------------
+
+2. wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active: a fix for a poor wxWidgets implementation
+class BufferedPaintDC
+{
+ BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer);
+};
+*/
+
+
+
+
+
+
+
+
+
+
+
+//---------------------- implementation ------------------------
+class RecursiveDcClipper
+{
+public:
+ RecursiveDcClipper(wxDC& dc, const wxRect& r) : dc_(dc)
+ {
+ auto it = refDcToAreaMap().find(&dc);
+ if (it != refDcToAreaMap().end())
+ {
+ oldRect = it->second;
+
+ wxRect tmp = r;
+ tmp.Intersect(*oldRect); //better safe than sorry
+ dc_.SetClippingRegion(tmp); //
+ it->second = tmp;
+ }
+ else
+ {
+ dc_.SetClippingRegion(r);
+ refDcToAreaMap().emplace(&dc_, r);
+ }
+ }
+
+ ~RecursiveDcClipper()
+ {
+ dc_.DestroyClippingRegion();
+ if (oldRect)
+ {
+ dc_.SetClippingRegion(*oldRect);
+ refDcToAreaMap()[&dc_] = *oldRect;
+ }
+ else
+ refDcToAreaMap().erase(&dc_);
+ }
+
+private:
+ //associate "active" clipping area with each DC
+ static std::unordered_map<wxDC*, wxRect>& refDcToAreaMap() { static std::unordered_map<wxDC*, wxRect> clippingAreas; return clippingAreas; }
+
+ Opt<wxRect> oldRect;
+ wxDC& dc_;
+};
+
+
+#ifndef wxALWAYS_NATIVE_DOUBLE_BUFFER
+ #error we need this one!
+#endif
+
+#if wxALWAYS_NATIVE_DOUBLE_BUFFER
+struct BufferedPaintDC : public wxPaintDC { BufferedPaintDC(wxWindow& wnd, Opt<wxBitmap>& buffer) : wxPaintDC(&wnd) {} };
+
+#else
+class BufferedPaintDC : public wxMemoryDC
+{
+public:
+ BufferedPaintDC(wxWindow& wnd, Opt<wxBitmap>& buffer) : buffer_(buffer), paintDc(&wnd)
+ {
+ const wxSize clientSize = wnd.GetClientSize();
+ if (clientSize.GetWidth() > 0 && clientSize.GetHeight() > 0) //wxBitmap asserts this!! width may be 0; test case "Grid::CornerWin": compare both sides, then change config
+ {
+ if (!buffer_ || clientSize != wxSize(buffer->GetWidth(), buffer->GetHeight()))
+ buffer = wxBitmap(clientSize.GetWidth(), clientSize.GetHeight());
+
+ SelectObject(*buffer);
+
+ if (paintDc.IsOk() && paintDc.GetLayoutDirection() == wxLayout_RightToLeft)
+ SetLayoutDirection(wxLayout_RightToLeft);
+ }
+ else
+ buffer = NoValue();
+ }
+
+ ~BufferedPaintDC()
+ {
+ if (buffer_)
+ {
+ if (GetLayoutDirection() == wxLayout_RightToLeft)
+ {
+ paintDc.SetLayoutDirection(wxLayout_LeftToRight); //workaround bug in wxDC::Blit()
+ SetLayoutDirection(wxLayout_LeftToRight); //
+ }
+
+ const wxPoint origin = GetDeviceOrigin();
+ paintDc.Blit(0, 0, buffer_->GetWidth(), buffer_->GetHeight(), this, -origin.x, -origin.y);
+ }
+ }
+
+private:
+ Opt<wxBitmap>& buffer_;
+ wxPaintDC paintDc;
+};
+#endif
+}
+
+#endif //DC_H_4987123956832143243214
diff --git a/wx+/file_drop.cpp b/wx+/file_drop.cpp
new file mode 100755
index 00000000..cab848b9
--- /dev/null
+++ b/wx+/file_drop.cpp
@@ -0,0 +1,56 @@
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "file_drop.h"
+#include <wx/dnd.h>
+#include <zen/utf.h>
+
+
+using namespace zen;
+
+
+const wxEventType zen::EVENT_DROP_FILE = wxNewEventType();
+
+
+
+
+namespace
+{
+class WindowDropTarget : public wxFileDropTarget
+{
+public:
+ WindowDropTarget(wxWindow& dropWindow) : dropWindow_(dropWindow) {}
+
+private:
+ bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& fileArray) override
+ {
+ /*Linux, MTP: we get an empty file array
+ => switching to wxTextDropTarget won't help (much): we'd get the format
+ mtp://[usb:001,002]/Telefonspeicher/Folder/file.txt
+ instead of
+ /run/user/1000/gvfs/mtp:host=%5Busb%3A001%2C002%5D/Telefonspeicher/Folder/file.txt
+ */
+
+ //wxPoint clientDropPos(x, y)
+ std::vector<Zstring> filePaths;
+ for (const wxString& file : fileArray)
+ filePaths.push_back(utfCvrtTo<Zstring>(file));
+
+ //create a custom event on drop window: execute event after file dropping is completed! (after mouse is released)
+ if (wxEvtHandler* handler = dropWindow_.GetEventHandler())
+ handler->AddPendingEvent(FileDropEvent(filePaths));
+ return true;
+ }
+
+ wxWindow& dropWindow_;
+};
+}
+
+
+void zen::setupFileDrop(wxWindow& wnd)
+{
+ wnd.SetDropTarget(new WindowDropTarget(wnd)); /*takes ownership*/
+}
diff --git a/wx+/file_drop.h b/wx+/file_drop.h
index 57880ce2..d90ffc20 100644..100755
--- a/wx+/file_drop.h
+++ b/wx+/file_drop.h
@@ -1,164 +1,65 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef FILE_DROP_H_09457802957842560325626
-#define FILE_DROP_H_09457802957842560325626
-
-#include <vector>
-#include <functional>
-#include <zen/zstring.h>
-#include <zen/utf.h>
-#include <wx/window.h>
-#include <wx/event.h>
-#include <wx/dnd.h>
-
-#ifdef ZEN_WIN_VISTA_AND_LATER
- #include <zen/win.h>
-#endif
-
-
-namespace zen
-{
-//register simple file drop event (without issue of freezing dialogs and without wxFileDropTarget overdesign)
-//CAVEAT: a drop target window must not be directly or indirectly contained within a wxStaticBoxSizer until the following wxGTK bug
-//is fixed. According to wxWidgets release cycles this is expected to be: never http://trac.wxwidgets.org/ticket/2763
-
-/*
-1. setup a window to emit EVENT_DROP_FILE:
- - simple file system paths: setupFileDrop
- - any shell paths with validation: setupShellItemDrop
-
-2. register events:
-wnd.Connect (EVENT_DROP_FILE, FileDropEventHandler(MyDlg::OnFilesDropped), nullptr, this);
-wnd.Disconnect(EVENT_DROP_FILE, FileDropEventHandler(MyDlg::OnFilesDropped), nullptr, this);
-
-3. do something:
-void MyDlg::OnFilesDropped(FileDropEvent& event);
-*/
-
-
-
-
-
-
-
-
-
-namespace impl
-{
-inline
-wxEventType getFileDropEventType()
-{
- //inline functions have external linkage by default => this static is also extern, i.e. program wide unique! but defined in a header... ;)
- static wxEventType dummy = wxNewEventType();
- return dummy;
-}
-}
-
-//define new event type
-const wxEventType EVENT_DROP_FILE = impl::getFileDropEventType();
-
-class FileDropEvent : public wxCommandEvent
-{
-public:
- FileDropEvent(const std::vector<Zstring>& droppedPaths) : wxCommandEvent(EVENT_DROP_FILE), droppedPaths_(droppedPaths) {}
-
- const std::vector<Zstring>& getPaths() const { return droppedPaths_; }
-
-private:
- wxEvent* Clone() const override { return new FileDropEvent(*this); }
-
- const std::vector<Zstring> droppedPaths_;
-};
-
-using FileDropEventFunction = void (wxEvtHandler::*)(FileDropEvent&);
-
-#define FileDropEventHandler(func) \
- (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(FileDropEventFunction, &func)
-
-
-
-#ifdef ZEN_WIN_VISTA_AND_LATER
-using DragDropValidator = bool (*)(const std::vector<Zstring>& shellItemPaths); //plain static function => no lifetime management needed!
-using DragDropConsumer = std::function<void (const std::vector<Zstring>& shellItemPaths)>;
-
-namespace impl
-{
-void registerDragDrop(HWND hwnd, const DragDropValidator& acceptDrop, const DragDropConsumer& onDrop);
-void unregisterDragDrop(HWND hwnd);
-
-class DragDropCleanupWindow : private wxWindow
-{
-public:
- DragDropCleanupWindow(wxWindow& dropWindow) : wxWindow(&dropWindow, wxID_ANY), dropHwnd(dropWindow.GetHWND())
- {
- Hide(); //this is just a dummy window so that its parent can have ownership
- Disable();
- }
- ~DragDropCleanupWindow() { impl::unregisterDragDrop(dropHwnd); }
-
-private:
- const HWND dropHwnd;
-};
-}
-
-
-inline
-void setupShellItemDrop(wxWindow& dropWindow, const DragDropValidator& acceptDrop)
-{
- auto onDrop = [&dropWindow](const std::vector<Zstring>& shellItemPaths)
- {
- //create a custom event on drop window: execute event after file dropping is completed! (after mouse is released)
- if (wxEvtHandler* handler = dropWindow.GetEventHandler())
- handler->AddPendingEvent(FileDropEvent(shellItemPaths));
- };
-
- impl::registerDragDrop(static_cast<HWND>(dropWindow.GetHWND()), acceptDrop, onDrop);
-
- //make sure clean-up is tied to dropWindow life-time:
- new impl::DragDropCleanupWindow(dropWindow); //ownership passed to "dropWindow"
-}
-#endif
-
-
-namespace impl
-{
-class WindowDropTarget : public wxFileDropTarget
-{
-public:
- WindowDropTarget(wxWindow& dropWindow) : dropWindow_(dropWindow) {}
-
-private:
- bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& fileArray) override
- {
- /*Linux, MTP: we get an empty file array
- => switching to wxTextDropTarget won't help (much): we'd get the format
- mtp://[usb:001,002]/Telefonspeicher/Folder/file.txt
- instead of
- /run/user/1000/gvfs/mtp:host=%5Busb%3A001%2C002%5D/Telefonspeicher/Folder/file.txt
- */
-
- //wxPoint clientDropPos(x, y)
- std::vector<Zstring> filePaths;
- for (const wxString& file : fileArray)
- filePaths.push_back(utfCvrtTo<Zstring>(file));
-
- //create a custom event on drop window: execute event after file dropping is completed! (after mouse is released)
- if (wxEvtHandler* handler = dropWindow_.GetEventHandler())
- handler->AddPendingEvent(FileDropEvent(filePaths));
- return true;
- }
-
- wxWindow& dropWindow_;
-};
-}
-
-
-inline
-void setupFileDrop(wxWindow& wnd) { wnd.SetDropTarget(new impl::WindowDropTarget(wnd)); /*takes ownership*/ }
-}
-
-#endif //FILE_DROP_H_09457802957842560325626
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef FILE_DROP_H_09457802957842560325626
+#define FILE_DROP_H_09457802957842560325626
+
+#include <vector>
+#include <functional>
+#include <zen/zstring.h>
+#include <wx/window.h>
+#include <wx/event.h>
+
+
+namespace zen
+{
+//register simple file drop event (without issue of freezing dialogs and without wxFileDropTarget overdesign)
+//CAVEAT: a drop target window must not be directly or indirectly contained within a wxStaticBoxSizer until the following wxGTK bug
+//is fixed. According to wxWidgets release cycles this is expected to be: never http://trac.wxwidgets.org/ticket/2763
+
+/*
+1. setup a window to emit EVENT_DROP_FILE:
+ - simple file system paths: setupFileDrop
+ - any shell paths with validation: setupShellItemDrop
+
+2. register events:
+wnd.Connect (EVENT_DROP_FILE, FileDropEventHandler(MyDlg::OnFilesDropped), nullptr, this);
+wnd.Disconnect(EVENT_DROP_FILE, FileDropEventHandler(MyDlg::OnFilesDropped), nullptr, this);
+
+3. do something:
+void MyDlg::OnFilesDropped(FileDropEvent& event);
+*/
+
+extern const wxEventType EVENT_DROP_FILE;
+
+
+class FileDropEvent : public wxCommandEvent
+{
+public:
+ FileDropEvent(const std::vector<Zstring>& droppedPaths) : wxCommandEvent(EVENT_DROP_FILE), droppedPaths_(droppedPaths) {}
+
+ const std::vector<Zstring>& getPaths() const { return droppedPaths_; }
+
+private:
+ wxEvent* Clone() const override { return new FileDropEvent(*this); }
+
+ const std::vector<Zstring> droppedPaths_;
+};
+
+
+using FileDropEventFunction = void (wxEvtHandler::*)(FileDropEvent&);
+
+#define FileDropEventHandler(func) \
+ (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(FileDropEventFunction, &func)
+
+
+
+
+
+void setupFileDrop(wxWindow& wnd);
+}
+
+#endif //FILE_DROP_H_09457802957842560325626
diff --git a/wx+/font_size.h b/wx+/font_size.h
index 05055853..b117b09f 100644..100755
--- a/wx+/font_size.h
+++ b/wx+/font_size.h
@@ -1,81 +1,51 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef FONT_SIZE_H_23849632846734343234532
-#define FONT_SIZE_H_23849632846734343234532
-
-#include <zen/basic_math.h>
-#include <wx/window.h>
-#include <zen/scope_guard.h>
-#ifdef ZEN_WIN_VISTA_AND_LATER
- #include <zen/win.h>
- #include <Uxtheme.h>
- #include <vsstyle.h> //TEXT_MAININSTRUCTION
- #include <vssym32.h> //TMT_TEXTCOLOR
-#endif
-
-
-namespace zen
-{
-//set portable font size in multiples of the operating system's default font size
-void setRelativeFontSize(wxWindow& control, double factor);
-void setMainInstructionFont(wxWindow& control); //following Windows/Gnome/OS X guidelines
-
-
-
-
-
-
-
-
-
-
-//###################### implementation #####################
-inline
-void setRelativeFontSize(wxWindow& control, double factor)
-{
- wxFont font = control.GetFont();
- font.SetPointSize(numeric::round(wxNORMAL_FONT->GetPointSize() * factor));
- control.SetFont(font);
-};
-
-
-inline
-void setMainInstructionFont(wxWindow& control)
-{
- wxFont font = control.GetFont();
-#ifdef ZEN_WIN //http://msdn.microsoft.com/de-DE/library/windows/desktop/aa974176#fonts
- font.SetPointSize(wxNORMAL_FONT->GetPointSize() * 4 / 3); //integer round down
-
-#ifdef ZEN_WIN_VISTA_AND_LATER
- //get main instruction color: don't hard-code, respect accessibility!
- if (HTHEME hTheme = ::OpenThemeData(NULL, //__in HWND hwnd,
- L"TEXTSTYLE")) //__in LPCWSTR pszClassList
- {
- ZEN_ON_SCOPE_EXIT(::CloseThemeData(hTheme));
-
- COLORREF cr = {};
- if (::GetThemeColor(hTheme, //_In_ HTHEME hTheme,
- TEXT_MAININSTRUCTION, // _In_ int iPartId,
- 0, // _In_ int iStateId,
- TMT_TEXTCOLOR, // _In_ int iPropId,
- &cr) == S_OK) // _Out_ COLORREF *pColor
- control.SetForegroundColour(wxColor(cr));
- }
-#endif
-
-#elif defined ZEN_LINUX //https://developer.gnome.org/hig-book/3.2/hig-book.html#alert-text
- font.SetPointSize(numeric::round(wxNORMAL_FONT->GetPointSize() * 12.0 / 11));
- font.SetWeight(wxFONTWEIGHT_BOLD);
-
-#elif defined ZEN_MAC //https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/AppleHIGuidelines/Windows/Windows.html#//apple_ref/doc/uid/20000961-TP10
- font.SetWeight(wxFONTWEIGHT_BOLD);
-#endif
- control.SetFont(font);
-};
-}
-
-#endif //FONT_SIZE_H_23849632846734343234532
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef FONT_SIZE_H_23849632846734343234532
+#define FONT_SIZE_H_23849632846734343234532
+
+#include <zen/basic_math.h>
+#include <wx/window.h>
+#include <zen/scope_guard.h>
+
+
+namespace zen
+{
+//set portable font size in multiples of the operating system's default font size
+void setRelativeFontSize(wxWindow& control, double factor);
+void setMainInstructionFont(wxWindow& control); //following Windows/Gnome/OS X guidelines
+
+
+
+
+
+
+
+
+
+
+//###################### implementation #####################
+inline
+void setRelativeFontSize(wxWindow& control, double factor)
+{
+ wxFont font = control.GetFont();
+ font.SetPointSize(numeric::round(wxNORMAL_FONT->GetPointSize() * factor));
+ control.SetFont(font);
+};
+
+
+inline
+void setMainInstructionFont(wxWindow& control)
+{
+ wxFont font = control.GetFont();
+ font.SetPointSize(numeric::round(wxNORMAL_FONT->GetPointSize() * 12.0 / 11));
+ font.SetWeight(wxFONTWEIGHT_BOLD);
+
+ control.SetFont(font);
+};
+}
+
+#endif //FONT_SIZE_H_23849632846734343234532
diff --git a/wx+/graph.cpp b/wx+/graph.cpp
index d45a0e85..50c1daa6 100644..100755
--- a/wx+/graph.cpp
+++ b/wx+/graph.cpp
@@ -1,847 +1,847 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "graph.h"
-#include <cassert>
-#include <algorithm>
-#include <numeric>
-#include <zen/basic_math.h>
-#include <zen/scope_guard.h>
-#include "dc.h"
-
-using namespace zen;
-
-
-//todo: support zoom via mouse wheel?
-
-const wxEventType zen::wxEVT_GRAPH_SELECTION = wxNewEventType();
-
-
-double zen::nextNiceNumber(double blockSize) //round to next number which is a convenient to read block size
-{
- if (blockSize <= 0)
- return 0;
-
- const double k = std::floor(std::log10(blockSize));
- const double e = std::pow(10, k);
- if (numeric::isNull(e))
- return 0;
- const double a = blockSize / e; //blockSize = a * 10^k with a in [1, 10)
- assert(1 <= a && a < 10);
-
- //have a look at leading two digits: "nice" numbers start with 1, 2, 2.5 and 5
- const double steps[] = { 1, 2, 2.5, 5, 10 };
- return e * numeric::nearMatch(a, std::begin(steps), std::end(steps));
-}
-
-
-namespace
-{
-wxColor getDefaultColor(size_t pos)
-{
- switch (pos % 10)
- {
- case 0:
- return { 0, 69, 134 }; //blue
- case 1:
- return { 255, 66, 14 }; //red
- case 2:
- return { 255, 211, 32 }; //yellow
- case 3:
- return { 87, 157, 28 }; //green
- case 4:
- return { 126, 0, 33 }; //royal
- case 5:
- return { 131, 202, 255 }; //light blue
- case 6:
- return { 49, 64, 4 }; //dark green
- case 7:
- return { 174, 207, 0 }; //light green
- case 8:
- return { 75, 31, 111 }; //purple
- case 9:
- return { 255, 149, 14 }; //orange
- }
- assert(false);
- return *wxBLACK;
-}
-
-
-class ConvertCoord //convert between screen and input data coordinates
-{
-public:
- ConvertCoord(double valMin, double valMax, size_t screenSize) :
- min_(valMin),
- scaleToReal(screenSize == 0 ? 0 : (valMax - valMin) / screenSize),
- scaleToScr(numeric::isNull((valMax - valMin)) ? 0 : screenSize / (valMax - valMin)),
- outOfBoundsLow (-1 * scaleToReal + valMin),
- outOfBoundsHigh((screenSize + 1) * scaleToReal + valMin) { if (outOfBoundsLow > outOfBoundsHigh) std::swap(outOfBoundsLow, outOfBoundsHigh); }
-
- double screenToReal(double screenPos) const //map [0, screenSize] -> [valMin, valMax]
- {
- return screenPos * scaleToReal + min_;
- }
- double realToScreen(double realPos) const //return screen position in pixel (but with double precision!)
- {
- return (realPos - min_) * scaleToScr;
- }
- int realToScreenRound(double realPos) const //returns -1 and screenSize + 1 if out of bounds!
- {
- //catch large double values: if double is larger than what int can represent => undefined behavior!
- numeric::clamp(realPos, outOfBoundsLow, outOfBoundsHigh);
- return numeric::round(realToScreen(realPos));
- }
-
-private:
- double min_;
- double scaleToReal;
- double scaleToScr;
-
- double outOfBoundsLow;
- double outOfBoundsHigh;
-};
-
-
-//enlarge value range to display to a multiple of a "useful" block size
-//returns block cound
-int widenRange(double& valMin, double& valMax, //in/out
- int graphAreaSize, //in pixel
- int optimalBlockSizePx, //
- const LabelFormatter& labelFmt)
-{
- if (graphAreaSize <= 0) return 0;
-
- const double minValRangePerBlock = (valMax - valMin) / graphAreaSize;
- const double proposedValRangePerBlock = (valMax - valMin) * optimalBlockSizePx / graphAreaSize;
- double valRangePerBlock = labelFmt.getOptimalBlockSize(proposedValRangePerBlock);
- assert(numeric::isNull(proposedValRangePerBlock) || valRangePerBlock > minValRangePerBlock);
-
- if (numeric::isNull(valRangePerBlock)) //valMin == valMax or strange "optimal block size"
- return 1;
-
- //don't allow sub-pixel blocks! => avoid erroneously high GDI render work load!
- if (valRangePerBlock < minValRangePerBlock)
- valRangePerBlock = std::ceil(minValRangePerBlock / valRangePerBlock) * valRangePerBlock;
-
- double blockMin = std::floor(valMin / valRangePerBlock); //store as double, not int: truncation possible, e.g. if valRangePerBlock == 1
- double blockMax = std::ceil (valMax / valRangePerBlock); //
- int blockCount = numeric::round(blockMax - blockMin);
- assert(blockCount >= 0);
-
- //handle valMin == valMax == integer
- if (blockCount <= 0)
- {
- ++blockMax;
- blockCount = 1;
- }
-
- valMin = blockMin * valRangePerBlock;
- valMax = blockMax * valRangePerBlock;
- return blockCount;
-}
-
-
-void drawXLabel(wxDC& dc, double xMin, double xMax, int blockCount, const ConvertCoord& cvrtX, const wxRect& graphArea, const wxRect& labelArea, const LabelFormatter& labelFmt)
-{
- assert(graphArea.width == labelArea.width && graphArea.x == labelArea.x);
- if (blockCount <= 0)
- return;
-
- wxDCPenChanger dummy(dc, wxColor(192, 192, 192)); //light grey => not accessible! but no big deal...
- wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
-
- const double valRangePerBlock = (xMax - xMin) / blockCount;
-
- for (int i = 1; i < blockCount; ++i)
- {
- //draw grey vertical lines
- const double valX = xMin + i * valRangePerBlock; //step over raw data, not graph area pixels, to not lose precision
- const int x = graphArea.x + cvrtX.realToScreenRound(valX);
-
- if (graphArea.height > 0)
- dc.DrawLine(wxPoint(x, graphArea.y), wxPoint(x, graphArea.y + graphArea.height)); //wxDC::DrawLine() doesn't draw last pixel
-
- //draw x axis labels
- const wxString label = labelFmt.formatText(valX, valRangePerBlock);
- const wxSize labelExtent = dc.GetMultiLineTextExtent(label);
- dc.DrawText(label, wxPoint(x - labelExtent.GetWidth() / 2, labelArea.y + (labelArea.height - labelExtent.GetHeight()) / 2)); //center
- }
-}
-
-
-void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const ConvertCoord& cvrtY, const wxRect& graphArea, const wxRect& labelArea, const LabelFormatter& labelFmt)
-{
- assert(graphArea.height == labelArea.height && graphArea.y == labelArea.y);
- if (blockCount <= 0)
- return;
-
- wxDCPenChanger dummy(dc, wxColor(192, 192, 192)); //light grey => not accessible! but no big deal...
- wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
-
- const double valRangePerBlock = (yMax - yMin) / blockCount;
-
- for (int i = 1; i < blockCount; ++i)
- {
- //draw grey horizontal lines
- 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);
-
- if (graphArea.width > 0)
- dc.DrawLine(wxPoint(graphArea.x, y), wxPoint(graphArea.x + graphArea.width, y)); //wxDC::DrawLine() doesn't draw last pixel
-
- //draw y axis labels
- const wxString label = labelFmt.formatText(valY, valRangePerBlock);
- const wxSize labelExtent = dc.GetMultiLineTextExtent(label);
- dc.DrawText(label, wxPoint(labelArea.x + (labelArea.width - labelExtent.GetWidth()) / 2, y - labelExtent.GetHeight() / 2)); //center
- }
-}
-
-
-void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Graph2D::PosCorner pos)
-{
- 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"?
-
- wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
- wxSize txtExtent = dc.GetMultiLineTextExtent(txt);
- txtExtent.x += 2 * borderX;
- txtExtent.y += 2 * borderY;
-
- wxPoint drawPos = graphArea.GetTopLeft();
- switch (pos)
- {
- case Graph2D::CORNER_TOP_LEFT:
- break;
- case Graph2D::CORNER_TOP_RIGHT:
- drawPos.x += graphArea.width - txtExtent.GetWidth();
- break;
- case Graph2D::CORNER_BOTTOM_LEFT:
- drawPos.y += graphArea.height - txtExtent.GetHeight();
- break;
- case Graph2D::CORNER_BOTTOM_RIGHT:
- drawPos.x += graphArea.width - txtExtent.GetWidth();
- drawPos.y += graphArea.height - txtExtent.GetHeight();
- break;
- }
- dc.DrawText(txt, drawPos + wxPoint(borderX, borderY));
-}
-
-
-//calculate intersection of polygon with half-plane
-template <class Function, class Function2>
-void cutPoints(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, Function isInside, Function2 getIntersection, bool doPolygonCut)
-{
- assert(curvePoints.size() == oobMarker.size());
-
- if (curvePoints.size() != oobMarker.size() || curvePoints.empty()) return;
-
- auto isMarkedOob = [&](size_t index) { return oobMarker[index] != 0; }; //test if point is start of an OOB line
-
- std::vector<CurvePoint> curvePointsTmp;
- std::vector<char> oobMarkerTmp;
- curvePointsTmp.reserve(curvePoints.size()); //allocating memory for these containers is one
- oobMarkerTmp .reserve(oobMarker .size()); //of the more expensive operations of Graph2D!
-
- auto savePoint = [&](const CurvePoint& pt, bool markedOob) { curvePointsTmp.push_back(pt); oobMarkerTmp.push_back(markedOob); };
-
- bool pointInside = isInside(curvePoints[0]);
- if (pointInside)
- savePoint(curvePoints[0], isMarkedOob(0));
-
- for (size_t index = 1; index < curvePoints.size(); ++index)
- {
- if (isInside(curvePoints[index]) != pointInside)
- {
- pointInside = !pointInside;
- const CurvePoint is = getIntersection(curvePoints[index - 1], curvePoints[index]); //getIntersection returns "to" when delta is zero
- savePoint(is, !pointInside || isMarkedOob(index - 1));
- }
- if (pointInside)
- savePoint(curvePoints[index], isMarkedOob(index));
- }
-
- //make sure the output polygon area is correctly shaped if either begin or end points are cut
- if (doPolygonCut) //note: impacts min/max height-calculations!
- if (curvePoints.size() >= 3)
- if (isInside(curvePoints.front()) != pointInside)
- {
- assert(!oobMarkerTmp.empty());
- oobMarkerTmp.back() = true;
-
- const CurvePoint is = getIntersection(curvePoints.back(), curvePoints.front());
- savePoint(is, true);
- }
-
- curvePointsTmp.swap(curvePoints);
- oobMarkerTmp .swap(oobMarker);
-}
-
-
-struct GetIntersectionX
-{
- GetIntersectionX(double x) : x_(x) {}
- CurvePoint operator()(const CurvePoint& from, const CurvePoint& to) const
- {
- const double deltaX = to.x - from.x;
- const double deltaY = to.y - from.y;
- return numeric::isNull(deltaX) ? to : CurvePoint(x_, from.y + (x_ - from.x) / deltaX * deltaY);
- }
-
-private:
- const double x_;
-};
-
-struct GetIntersectionY
-{
- GetIntersectionY(double y) : y_(y) {}
- CurvePoint operator()(const CurvePoint& from, const CurvePoint& to) const
- {
- const double deltaX = to.x - from.x;
- const double deltaY = to.y - from.y;
- return numeric::isNull(deltaY) ? to : CurvePoint(from.x + (y_ - from.y) / deltaY * deltaX, y_);
- }
-
-private:
- const double y_;
-};
-
-void cutPointsOutsideX(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, double minX, double maxX, bool doPolygonCut)
-{
- cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.x >= minX; }, GetIntersectionX(minX), doPolygonCut);
- cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.x <= maxX; }, GetIntersectionX(maxX), doPolygonCut);
-}
-
-void cutPointsOutsideY(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, double minY, double maxY, bool doPolygonCut)
-{
- cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.y >= minY; }, GetIntersectionY(minY), doPolygonCut);
- cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.y <= maxY; }, GetIntersectionY(maxY), doPolygonCut);
-}
-}
-
-
-std::vector<CurvePoint> ContinuousCurveData::getPoints(double minX, double maxX, int pixelWidth) const
-{
- std::vector<CurvePoint> points;
-
- if (pixelWidth <= 1) return points;
- const ConvertCoord cvrtX(minX, maxX, pixelWidth - 1); //map [minX, maxX] to [0, pixelWidth - 1]
-
- const std::pair<double, double> rangeX = getRangeX();
-
- const double screenLow = cvrtX.realToScreen(std::max(rangeX.first, minX)); //=> xLow >= 0
- const double screenHigh = cvrtX.realToScreen(std::min(rangeX.second, maxX)); //=> xHigh <= pixelWidth - 1
- //if double is larger than what int can represent => undefined behavior!
- //=> convert to int *after* checking value range!
- if (screenLow <= screenHigh)
- {
- const int posFrom = std::ceil (screenLow ); //do not step outside [minX, maxX] in loop below!
- const int posTo = std::floor(screenHigh); //
- //conversion from std::floor/std::ceil double return value to int is loss-free for full value range of 32-bit int! tested successfully on MSVC
-
- for (int i = posFrom; i <= posTo; ++i)
- {
- const double x = cvrtX.screenToReal(i);
- points.emplace_back(x, getValue(x));
- }
- }
- return points;
-}
-
-
-std::vector<CurvePoint> SparseCurveData::getPoints(double minX, double maxX, int pixelWidth) const
-{
- std::vector<CurvePoint> points;
- if (pixelWidth <= 1) return points;
- const ConvertCoord cvrtX(minX, maxX, pixelWidth - 1); //map [minX, maxX] to [0, pixelWidth - 1]
- const std::pair<double, double> rangeX = getRangeX();
-
- auto addPoint = [&](const CurvePoint& pt)
- {
- if (!points.empty())
- {
- if (pt.x <= points.back().x) //allow ascending x-positions only! algorithm below may cause double-insertion after empty x-ranges!
- return;
-
- if (addSteps_)
- if (pt.y != points.back().y)
- points.emplace_back(CurvePoint(pt.x, points.back().y)); //[!] aliasing parameter not yet supported via emplace_back: VS bug! => make copy
- }
- points.push_back(pt);
- };
-
- const int posFrom = cvrtX.realToScreenRound(std::max(rangeX.first, minX));
- const int posTo = cvrtX.realToScreenRound(std::min(rangeX.second, maxX));
-
- for (int i = posFrom; i <= posTo; ++i)
- {
- const double x = cvrtX.screenToReal(i);
- Opt<CurvePoint> ptLe = getLessEq(x);
- Opt<CurvePoint> ptGe = getGreaterEq(x);
- //both non-existent and invalid return values are mapped to out of expected range: => check on posLe/posGe NOT ptLe/ptGe in the following!
- const int posLe = ptLe ? cvrtX.realToScreenRound(ptLe->x) : i + 1;
- const int posGe = ptGe ? cvrtX.realToScreenRound(ptGe->x) : i - 1;
- assert(!ptLe || posLe <= i); //check for invalid return values
- assert(!ptGe || posGe >= i); //
- /*
- Breakdown of all combinations of posLe, posGe and expected action (n >= 1)
- Note: For every empty x-range of at least one pixel, both next and previous points must be saved to keep the interpolating line stable!!!
-
- posLe | posGe | action
- +-------+-------+--------
- | none | none | break
- | i | none | save ptLe; break
- | i - n | none | break;
- +-------+-------+--------
- | none | i | save ptGe; continue
- | i | i | save one of ptLe, ptGe; continue
- | i - n | i | save ptGe; continue
- +-------+-------+--------
- | none | i + n | save ptGe; jump to position posGe + 1
- | i | i + n | save ptLe; if n == 1: continue; else: save ptGe; jump to position posGe + 1
- | i - n | i + n | save ptLe, ptGe; jump to position posGe + 1
- +-------+-------+--------
- */
- if (posGe < i)
- {
- if (posLe == i)
- addPoint(*ptLe);
- break;
- }
- else if (posGe == i) //test if point would be mapped to pixel x-position i
- {
- if (posLe == i) //
- addPoint(x - ptLe->x < ptGe->x - x ? *ptLe : *ptGe);
- else
- addPoint(*ptGe);
- }
- else
- {
- if (posLe <= i)
- addPoint(*ptLe);
-
- if (posLe != i || posGe > i + 1)
- {
- addPoint(*ptGe);
- i = posGe; //skip sparse area: +1 will be added by for-loop!
- }
- }
- }
- return points;
-}
-
-
-Graph2D::Graph2D(wxWindow* parent,
- wxWindowID winid,
- const wxPoint& pos,
- const wxSize& size,
- long style,
- const wxString& name) : wxPanel(parent, winid, pos, size, style, name)
-{
- 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);
-
- //SetDoubleBuffered(true); slow as hell!
-
- SetBackgroundStyle(wxBG_STYLE_PAINT);
-
- Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(Graph2D::OnMouseLeftDown), nullptr, this);
- Connect(wxEVT_MOTION, wxMouseEventHandler(Graph2D::OnMouseMovement), nullptr, this);
- Connect(wxEVT_LEFT_UP, wxMouseEventHandler(Graph2D::OnMouseLeftUp), nullptr, this);
- Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(Graph2D::OnMouseCaptureLost), nullptr, this);
-}
-
-
-void Graph2D::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);
- render(dc);
-}
-
-
-void Graph2D::OnMouseLeftDown(wxMouseEvent& event)
-{
- activeSel = std::make_unique<MouseSelection>(*this, event.GetPosition());
-
- if (!event.ControlDown())
- oldSel.clear();
- Refresh();
-}
-
-
-void Graph2D::OnMouseMovement(wxMouseEvent& event)
-{
- if (activeSel.get())
- {
- activeSel->refCurrentPos() = event.GetPosition(); //corresponding activeSel->refSelection() is updated in Graph2D::render()
- Refresh();
- }
-}
-
-
-void Graph2D::OnMouseLeftUp(wxMouseEvent& event)
-{
- if (activeSel.get())
- {
- if (activeSel->getStartPos() != activeSel->refCurrentPos()) //if it's just a single mouse click: discard selection
- {
- GraphSelectEvent selEvent(activeSel->refSelection()); //fire off GraphSelectEvent
- if (wxEvtHandler* handler = GetEventHandler())
- handler->AddPendingEvent(selEvent);
-
- oldSel.push_back(activeSel->refSelection()); //commit selection
- }
-
- activeSel.reset();
- Refresh();
- }
-}
-
-
-void Graph2D::OnMouseCaptureLost(wxMouseCaptureLostEvent& event)
-{
- activeSel.reset();
- Refresh();
-}
-
-
-void Graph2D::setCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca)
-{
- curves_.clear();
- addCurve(data, ca);
-}
-
-
-void Graph2D::addCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca)
-{
- CurveAttributes newAttr = ca;
- if (newAttr.autoColor)
- newAttr.setColor(getDefaultColor(curves_.size()));
- curves_.emplace_back(data, newAttr);
- Refresh();
-}
-
-
-void Graph2D::render(wxDC& dc) const
-{
- using namespace numeric;
-
- //set label font right at the start so that it is considered by wxDC::GetTextExtent() below!
- dc.SetFont(labelFont);
-
- const wxRect clientRect = GetClientRect(); //DON'T use wxDC::GetSize()! DC may be larger than visible area!
- {
- //clear complete client area; set label background color
- const wxColor backCol = GetBackgroundColour(); //user-configurable!
- //wxPanel::GetClassDefaultAttributes().colBg :
- //wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE);
- wxDCPenChanger dummy (dc, backCol);
- wxDCBrushChanger dummy2(dc, backCol);
- dc.DrawRectangle(clientRect);
- }
-
- /*
- -----------------------
- | | x-label |
- -----------------------
- |y-label | graph area |
- |----------------------
- */
- wxRect graphArea = clientRect;
- int xLabelPosY = clientRect.y;
- int yLabelPosX = clientRect.x;
-
- switch (attr.labelposX)
- {
- case LABEL_X_TOP:
- graphArea.y += attr.xLabelHeight;
- graphArea.height -= attr.xLabelHeight;
- break;
- case LABEL_X_BOTTOM:
- xLabelPosY += clientRect.height - attr.xLabelHeight;
- graphArea.height -= attr.xLabelHeight;
- break;
- case LABEL_X_NONE:
- break;
- }
- switch (attr.labelposY)
- {
- case LABEL_Y_LEFT:
- graphArea.x += attr.yLabelWidth;
- graphArea.width -= attr.yLabelWidth;
- break;
- case LABEL_Y_RIGHT:
- yLabelPosX += clientRect.width - attr.yLabelWidth;
- graphArea.width -= attr.yLabelWidth;
- break;
- case LABEL_Y_NONE:
- break;
- }
-
- {
- //paint graph background (excluding label area)
- wxDCPenChanger dummy (dc, getBorderColor());
- wxDCBrushChanger dummy2(dc, attr.backgroundColor);
- //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
-
- dc.DrawRectangle(graphArea);
- graphArea.Deflate(1, 1); //attention more wxWidgets design mistakes: behavior of wxRect::Deflate depends on object being const/non-const!!!
- }
-
- //set label areas respecting graph area border!
- const wxRect xLabelArea(graphArea.x, xLabelPosY, graphArea.width, attr.xLabelHeight);
- const wxRect yLabelArea(yLabelPosX, graphArea.y, attr.yLabelWidth, graphArea.height);
- const wxPoint graphAreaOrigin = graphArea.GetTopLeft();
-
- //detect x value range
- double minX = attr.minXauto ? std::numeric_limits<double>::infinity() : attr.minX; //automatic: ensure values are initialized by first curve
- double maxX = attr.maxXauto ? -std::numeric_limits<double>::infinity() : attr.maxX; //
- for (auto it = curves_.begin(); it != curves_.end(); ++it)
- if (const CurveData* curve = it->first.get())
- {
- const std::pair<double, double> rangeX = curve->getRangeX();
- assert(rangeX.first <= rangeX.second + 1.0e-9);
- //GCC fucks up badly when comparing two *binary identical* doubles and finds "begin > end" with diff of 1e-18
-
- if (attr.minXauto)
- minX = std::min(minX, rangeX.first);
- if (attr.maxXauto)
- maxX = std::max(maxX, rangeX.second);
- }
-
- const wxSize minimalBlockSizePx = dc.GetTextExtent(L"00");
-
- if (minX <= maxX && maxX - minX < std::numeric_limits<double>::infinity()) //valid x-range
- {
- int blockCountX = 0;
- //enlarge minX, maxX to a multiple of a "useful" block size
- if (attr.labelposX != LABEL_X_NONE && attr.labelFmtX.get())
- blockCountX = widenRange(minX, maxX, //in/out
- graphArea.width,
- minimalBlockSizePx.GetWidth() * 7,
- *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; //
-
- 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
-
- for (size_t index = 0; index < curves_.size(); ++index)
- if (const CurveData* curve = curves_[index].first.get())
- {
- std::vector<CurvePoint>& points = curvePoints[index];
- auto& marker = oobMarker [index];
-
- points = curve->getPoints(minX, maxX, graphArea.width);
- marker.resize(points.size()); //default value: false
- if (!points.empty())
- {
- //cut points outside visible x-range now in order to calculate height of visible line fragments only!
- 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)
- {
- auto itPair = std::minmax_element(points.begin(), points.end(), [](const CurvePoint& lhs, const CurvePoint& rhs) { return lhs.y < rhs.y; });
- if (attr.minYauto)
- minY = std::min(minY, itPair.first->y);
- if (attr.maxYauto)
- maxY = std::max(maxY, itPair.second->y);
- }
- }
- }
-
- if (minY <= maxY) //valid y-range
- {
- int blockCountY = 0;
- //enlarge minY, maxY to a multiple of a "useful" block size
- if (attr.labelposY != LABEL_Y_NONE && attr.labelFmtY.get())
- blockCountY = widenRange(minY, maxY, //in/out
- graphArea.height,
- minimalBlockSizePx.GetHeight() * 3,
- *attr.labelFmtY);
-
- if (graphArea.width <= 1 || graphArea.height <= 1) return;
- const ConvertCoord cvrtX(minX, maxX, graphArea.width - 1); //map [minX, maxX] to [0, pixelWidth - 1]
- const ConvertCoord cvrtY(maxY, minY, graphArea.height - 1); //map [minY, maxY] to [pixelHeight - 1, 0]
-
- //calculate curve coordinates on graph area
- std::vector<std::vector<wxPoint>> drawPoints(curves_.size());
-
- for (size_t index = 0; index < curves_.size(); ++index)
- {
- auto& cp = curvePoints[index];
-
- //add two artificial points to fill the curve area towards x-axis => do this before cutPointsOutsideY() to handle curve leaving upper bound
- if (curves_[index].second.fillMode == CurveAttributes::FILL_CURVE)
- if (!cp.empty())
- {
- cp.emplace_back(CurvePoint(cp.back ().x, minY)); //add lower right and left corners
- cp.emplace_back(CurvePoint(cp.front().x, minY)); //[!] aliasing parameter not yet supported via emplace_back: VS bug! => make copy
- oobMarker[index].back() = true;
- oobMarker[index].push_back(true);
- oobMarker[index].push_back(true);
- }
-
- //cut points outside visible y-range before calculating pixels:
- //1. realToScreenRound() deforms out-of-range values!
- //2. pixels that are grossly out of range can be a severe performance problem when drawing on the DC (Windows)
- const bool doPolygonCut = curves_[index].second.fillMode != CurveAttributes::FILL_NONE;
- cutPointsOutsideY(cp, oobMarker[index], minY, maxY, doPolygonCut);
-
- auto& dp = drawPoints[index];
- for (const CurvePoint& pt : cp)
- dp.push_back(wxPoint(cvrtX.realToScreenRound(pt.x),
- cvrtY.realToScreenRound(pt.y)) + graphAreaOrigin);
- }
-
- //update active mouse selection
- if (activeSel.get() && graphArea.width > 0 && graphArea.height > 0)
- {
- auto widen = [](double* low, double* high)
- {
- if (*low > *high)
- std::swap(low, high);
- *low -= 0.5;
- *high += 0.5;
- };
-
- const wxPoint screenStart = activeSel->getStartPos() - graphAreaOrigin; //make relative to graphArea
- const wxPoint screenCurrent = activeSel->refCurrentPos() - graphAreaOrigin;
-
- //normalize positions: a mouse selection is symmetric and *not* an half-open range!
- double screenFromX = clampCpy(screenStart .x, 0, graphArea.width - 1);
- double screenFromY = clampCpy(screenStart .y, 0, graphArea.height - 1);
- double screenToX = clampCpy(screenCurrent.x, 0, graphArea.width - 1);
- double screenToY = clampCpy(screenCurrent.y, 0, graphArea.height - 1);
- widen(&screenFromX, &screenToX); //use full pixel range for selection!
- widen(&screenFromY, &screenToY);
-
- //save current selection as "double" coordinates
- activeSel->refSelection().from = CurvePoint(cvrtX.screenToReal(screenFromX),
- cvrtY.screenToReal(screenFromY));
-
- activeSel->refSelection().to = CurvePoint(cvrtX.screenToReal(screenToX),
- cvrtY.screenToReal(screenToY));
- }
-
- //#################### begin drawing ####################
- //1. draw colored area under curves
- for (auto it = curves_.begin(); it != curves_.end(); ++it)
- if (it->second.fillMode != CurveAttributes::FILL_NONE)
- {
- const std::vector<wxPoint>& points = drawPoints[it - curves_.begin()];
- if (points.size() >= 3)
- {
- wxDCBrushChanger dummy(dc, it->second.fillColor);
- wxDCPenChanger dummy2(dc, it->second.fillColor);
- dc.DrawPolygon(static_cast<int>(points.size()), &points[0]);
- }
- }
-
- //2. draw all currently set mouse selections (including active selection)
- std::vector<SelectionBlock> allSelections = oldSel;
- if (activeSel)
- allSelections.push_back(activeSel->refSelection());
- {
- //alpha channel not supported on wxMSW, so draw selection before curves
- wxDCBrushChanger dummy(dc, wxColor(168, 202, 236)); //light blue
- wxDCPenChanger dummy2(dc, wxColor( 51, 153, 255)); //dark blue
-
- auto shrink = [](double* low, double* high)
- {
- if (*low > *high)
- std::swap(low, high);
- *low += 0.5;
- *high -= 0.5;
- if (*low > *high)
- *low = *high = (*low + *high) / 2;
- };
-
- for (const SelectionBlock& sel : allSelections)
- {
- //harmonize with active mouse selection above
- double screenFromX = cvrtX.realToScreen(sel.from.x);
- double screenFromY = cvrtY.realToScreen(sel.from.y);
- double screenToX = cvrtX.realToScreen(sel.to.x);
- double screenToY = cvrtY.realToScreen(sel.to.y);
- shrink(&screenFromX, &screenToX);
- shrink(&screenFromY, &screenToY);
-
- clamp(screenFromX, 0.0, graphArea.width - 1.0);
- clamp(screenFromY, 0.0, graphArea.height - 1.0);
- clamp(screenToX, 0.0, graphArea.width - 1.0);
- clamp(screenToY, 0.0, graphArea.height - 1.0);
-
- const wxPoint pixelFrom = wxPoint(numeric::round(screenFromX),
- numeric::round(screenFromY)) + graphAreaOrigin;
- const wxPoint pixelTo = wxPoint(numeric::round(screenToX),
- numeric::round(screenToY)) + graphAreaOrigin;
- switch (attr.mouseSelMode)
- {
- case SELECT_NONE:
- break;
- case SELECT_RECTANGLE:
- dc.DrawRectangle(wxRect(pixelFrom, pixelTo));
- break;
- case SELECT_X_AXIS:
- dc.DrawRectangle(wxRect(wxPoint(pixelFrom.x, graphArea.y), wxPoint(pixelTo.x, graphArea.y + graphArea.height - 1)));
- break;
- case SELECT_Y_AXIS:
- dc.DrawRectangle(wxRect(wxPoint(graphArea.x, pixelFrom.y), wxPoint(graphArea.x + graphArea.width - 1, pixelTo.y)));
- break;
- }
- }
- }
-
- //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);
-
- //4. finally draw curves
- {
- dc.SetClippingRegion(graphArea); //prevent thick curves from drawing slightly outside
- ZEN_ON_SCOPE_EXIT(dc.DestroyClippingRegion());
-
- for (auto it = curves_.begin(); it != curves_.end(); ++it)
- {
- wxDCPenChanger dummy(dc, wxPen(it->second.color, it->second.lineWidth));
-
- const size_t index = it - curves_.begin();
- const std::vector<wxPoint>& points = drawPoints[index];
- const auto& marker = oobMarker [index];
- assert(points.size() == marker.size());
-
- //draw all parts of the curve except for the out-of-bounds fragments
- size_t drawIndexFirst = 0;
- while (drawIndexFirst < points.size())
- {
- size_t drawIndexLast = std::find(marker.begin() + drawIndexFirst, marker.end(), true) - marker.begin();
- if (drawIndexLast < points.size()) ++drawIndexLast;
-
- const int pointCount = static_cast<int>(drawIndexLast - drawIndexFirst);
- if (pointCount > 0)
- {
- if (pointCount >= 2) //on OS X wxWidgets has a nasty assert on this
- dc.DrawLines(pointCount, &points[drawIndexFirst]);
- dc.DrawPoint(points[drawIndexLast - 1]); //wxDC::DrawLines() doesn't draw last pixel
- }
- drawIndexFirst = std::find(marker.begin() + drawIndexLast, marker.end(), false) - marker.begin();
- }
- }
- }
-
- //5. draw corner texts
- for (const auto& ct : attr.cornerTexts)
- drawCornerText(dc, graphArea, ct.second, ct.first);
- }
- }
-}
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "graph.h"
+#include <cassert>
+#include <algorithm>
+#include <numeric>
+#include <zen/basic_math.h>
+#include <zen/scope_guard.h>
+#include "dc.h"
+
+using namespace zen;
+
+
+//todo: support zoom via mouse wheel?
+
+const wxEventType zen::wxEVT_GRAPH_SELECTION = wxNewEventType();
+
+
+double zen::nextNiceNumber(double blockSize) //round to next number which is a convenient to read block size
+{
+ if (blockSize <= 0)
+ return 0;
+
+ const double k = std::floor(std::log10(blockSize));
+ const double e = std::pow(10, k);
+ if (numeric::isNull(e))
+ return 0;
+ const double a = blockSize / e; //blockSize = a * 10^k with a in [1, 10)
+ assert(1 <= a && a < 10);
+
+ //have a look at leading two digits: "nice" numbers start with 1, 2, 2.5 and 5
+ const double steps[] = { 1, 2, 2.5, 5, 10 };
+ return e * numeric::nearMatch(a, std::begin(steps), std::end(steps));
+}
+
+
+namespace
+{
+wxColor getDefaultColor(size_t pos)
+{
+ switch (pos % 10)
+ {
+ case 0:
+ return { 0, 69, 134 }; //blue
+ case 1:
+ return { 255, 66, 14 }; //red
+ case 2:
+ return { 255, 211, 32 }; //yellow
+ case 3:
+ return { 87, 157, 28 }; //green
+ case 4:
+ return { 126, 0, 33 }; //royal
+ case 5:
+ return { 131, 202, 255 }; //light blue
+ case 6:
+ return { 49, 64, 4 }; //dark green
+ case 7:
+ return { 174, 207, 0 }; //light green
+ case 8:
+ return { 75, 31, 111 }; //purple
+ case 9:
+ return { 255, 149, 14 }; //orange
+ }
+ assert(false);
+ return *wxBLACK;
+}
+
+
+class ConvertCoord //convert between screen and input data coordinates
+{
+public:
+ ConvertCoord(double valMin, double valMax, size_t screenSize) :
+ min_(valMin),
+ scaleToReal(screenSize == 0 ? 0 : (valMax - valMin) / screenSize),
+ scaleToScr(numeric::isNull((valMax - valMin)) ? 0 : screenSize / (valMax - valMin)),
+ outOfBoundsLow (-1 * scaleToReal + valMin),
+ outOfBoundsHigh((screenSize + 1) * scaleToReal + valMin) { if (outOfBoundsLow > outOfBoundsHigh) std::swap(outOfBoundsLow, outOfBoundsHigh); }
+
+ double screenToReal(double screenPos) const //map [0, screenSize] -> [valMin, valMax]
+ {
+ return screenPos * scaleToReal + min_;
+ }
+ double realToScreen(double realPos) const //return screen position in pixel (but with double precision!)
+ {
+ return (realPos - min_) * scaleToScr;
+ }
+ int realToScreenRound(double realPos) const //returns -1 and screenSize + 1 if out of bounds!
+ {
+ //catch large double values: if double is larger than what int can represent => undefined behavior!
+ numeric::clamp(realPos, outOfBoundsLow, outOfBoundsHigh);
+ return numeric::round(realToScreen(realPos));
+ }
+
+private:
+ double min_;
+ double scaleToReal;
+ double scaleToScr;
+
+ double outOfBoundsLow;
+ double outOfBoundsHigh;
+};
+
+
+//enlarge value range to display to a multiple of a "useful" block size
+//returns block cound
+int widenRange(double& valMin, double& valMax, //in/out
+ int graphAreaSize, //in pixel
+ int optimalBlockSizePx, //
+ const LabelFormatter& labelFmt)
+{
+ if (graphAreaSize <= 0) return 0;
+
+ const double minValRangePerBlock = (valMax - valMin) / graphAreaSize;
+ const double proposedValRangePerBlock = (valMax - valMin) * optimalBlockSizePx / graphAreaSize;
+ double valRangePerBlock = labelFmt.getOptimalBlockSize(proposedValRangePerBlock);
+ assert(numeric::isNull(proposedValRangePerBlock) || valRangePerBlock > minValRangePerBlock);
+
+ if (numeric::isNull(valRangePerBlock)) //valMin == valMax or strange "optimal block size"
+ return 1;
+
+ //don't allow sub-pixel blocks! => avoid erroneously high GDI render work load!
+ if (valRangePerBlock < minValRangePerBlock)
+ valRangePerBlock = std::ceil(minValRangePerBlock / valRangePerBlock) * valRangePerBlock;
+
+ double blockMin = std::floor(valMin / valRangePerBlock); //store as double, not int: truncation possible, e.g. if valRangePerBlock == 1
+ double blockMax = std::ceil (valMax / valRangePerBlock); //
+ int blockCount = numeric::round(blockMax - blockMin);
+ assert(blockCount >= 0);
+
+ //handle valMin == valMax == integer
+ if (blockCount <= 0)
+ {
+ ++blockMax;
+ blockCount = 1;
+ }
+
+ valMin = blockMin * valRangePerBlock;
+ valMax = blockMax * valRangePerBlock;
+ return blockCount;
+}
+
+
+void drawXLabel(wxDC& dc, double xMin, double xMax, int blockCount, const ConvertCoord& cvrtX, const wxRect& graphArea, const wxRect& labelArea, const LabelFormatter& labelFmt)
+{
+ assert(graphArea.width == labelArea.width && graphArea.x == labelArea.x);
+ if (blockCount <= 0)
+ return;
+
+ wxDCPenChanger dummy(dc, wxColor(192, 192, 192)); //light grey => not accessible! but no big deal...
+ wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
+
+ const double valRangePerBlock = (xMax - xMin) / blockCount;
+
+ for (int i = 1; i < blockCount; ++i)
+ {
+ //draw grey vertical lines
+ const double valX = xMin + i * valRangePerBlock; //step over raw data, not graph area pixels, to not lose precision
+ const int x = graphArea.x + cvrtX.realToScreenRound(valX);
+
+ if (graphArea.height > 0)
+ dc.DrawLine(wxPoint(x, graphArea.y), wxPoint(x, graphArea.y + graphArea.height)); //wxDC::DrawLine() doesn't draw last pixel
+
+ //draw x axis labels
+ const wxString label = labelFmt.formatText(valX, valRangePerBlock);
+ const wxSize labelExtent = dc.GetMultiLineTextExtent(label);
+ dc.DrawText(label, wxPoint(x - labelExtent.GetWidth() / 2, labelArea.y + (labelArea.height - labelExtent.GetHeight()) / 2)); //center
+ }
+}
+
+
+void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const ConvertCoord& cvrtY, const wxRect& graphArea, const wxRect& labelArea, const LabelFormatter& labelFmt)
+{
+ assert(graphArea.height == labelArea.height && graphArea.y == labelArea.y);
+ if (blockCount <= 0)
+ return;
+
+ wxDCPenChanger dummy(dc, wxColor(192, 192, 192)); //light grey => not accessible! but no big deal...
+ wxDCTextColourChanger dummy2(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
+
+ const double valRangePerBlock = (yMax - yMin) / blockCount;
+
+ for (int i = 1; i < blockCount; ++i)
+ {
+ //draw grey horizontal lines
+ 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);
+
+ if (graphArea.width > 0)
+ dc.DrawLine(wxPoint(graphArea.x, y), wxPoint(graphArea.x + graphArea.width, y)); //wxDC::DrawLine() doesn't draw last pixel
+
+ //draw y axis labels
+ const wxString label = labelFmt.formatText(valY, valRangePerBlock);
+ const wxSize labelExtent = dc.GetMultiLineTextExtent(label);
+ dc.DrawText(label, wxPoint(labelArea.x + (labelArea.width - labelExtent.GetWidth()) / 2, y - labelExtent.GetHeight() / 2)); //center
+ }
+}
+
+
+void drawCornerText(wxDC& dc, const wxRect& graphArea, const wxString& txt, Graph2D::PosCorner pos)
+{
+ 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"?
+
+ wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
+ wxSize txtExtent = dc.GetMultiLineTextExtent(txt);
+ txtExtent.x += 2 * borderX;
+ txtExtent.y += 2 * borderY;
+
+ wxPoint drawPos = graphArea.GetTopLeft();
+ switch (pos)
+ {
+ case Graph2D::CORNER_TOP_LEFT:
+ break;
+ case Graph2D::CORNER_TOP_RIGHT:
+ drawPos.x += graphArea.width - txtExtent.GetWidth();
+ break;
+ case Graph2D::CORNER_BOTTOM_LEFT:
+ drawPos.y += graphArea.height - txtExtent.GetHeight();
+ break;
+ case Graph2D::CORNER_BOTTOM_RIGHT:
+ drawPos.x += graphArea.width - txtExtent.GetWidth();
+ drawPos.y += graphArea.height - txtExtent.GetHeight();
+ break;
+ }
+ dc.DrawText(txt, drawPos + wxPoint(borderX, borderY));
+}
+
+
+//calculate intersection of polygon with half-plane
+template <class Function, class Function2>
+void cutPoints(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, Function isInside, Function2 getIntersection, bool doPolygonCut)
+{
+ assert(curvePoints.size() == oobMarker.size());
+
+ if (curvePoints.size() != oobMarker.size() || curvePoints.empty()) return;
+
+ auto isMarkedOob = [&](size_t index) { return oobMarker[index] != 0; }; //test if point is start of an OOB line
+
+ std::vector<CurvePoint> curvePointsTmp;
+ std::vector<char> oobMarkerTmp;
+ curvePointsTmp.reserve(curvePoints.size()); //allocating memory for these containers is one
+ oobMarkerTmp .reserve(oobMarker .size()); //of the more expensive operations of Graph2D!
+
+ auto savePoint = [&](const CurvePoint& pt, bool markedOob) { curvePointsTmp.push_back(pt); oobMarkerTmp.push_back(markedOob); };
+
+ bool pointInside = isInside(curvePoints[0]);
+ if (pointInside)
+ savePoint(curvePoints[0], isMarkedOob(0));
+
+ for (size_t index = 1; index < curvePoints.size(); ++index)
+ {
+ if (isInside(curvePoints[index]) != pointInside)
+ {
+ pointInside = !pointInside;
+ const CurvePoint is = getIntersection(curvePoints[index - 1], curvePoints[index]); //getIntersection returns "to" when delta is zero
+ savePoint(is, !pointInside || isMarkedOob(index - 1));
+ }
+ if (pointInside)
+ savePoint(curvePoints[index], isMarkedOob(index));
+ }
+
+ //make sure the output polygon area is correctly shaped if either begin or end points are cut
+ if (doPolygonCut) //note: impacts min/max height-calculations!
+ if (curvePoints.size() >= 3)
+ if (isInside(curvePoints.front()) != pointInside)
+ {
+ assert(!oobMarkerTmp.empty());
+ oobMarkerTmp.back() = true;
+
+ const CurvePoint is = getIntersection(curvePoints.back(), curvePoints.front());
+ savePoint(is, true);
+ }
+
+ curvePointsTmp.swap(curvePoints);
+ oobMarkerTmp .swap(oobMarker);
+}
+
+
+struct GetIntersectionX
+{
+ GetIntersectionX(double x) : x_(x) {}
+ CurvePoint operator()(const CurvePoint& from, const CurvePoint& to) const
+ {
+ const double deltaX = to.x - from.x;
+ const double deltaY = to.y - from.y;
+ return numeric::isNull(deltaX) ? to : CurvePoint(x_, from.y + (x_ - from.x) / deltaX * deltaY);
+ }
+
+private:
+ const double x_;
+};
+
+struct GetIntersectionY
+{
+ GetIntersectionY(double y) : y_(y) {}
+ CurvePoint operator()(const CurvePoint& from, const CurvePoint& to) const
+ {
+ const double deltaX = to.x - from.x;
+ const double deltaY = to.y - from.y;
+ return numeric::isNull(deltaY) ? to : CurvePoint(from.x + (y_ - from.y) / deltaY * deltaX, y_);
+ }
+
+private:
+ const double y_;
+};
+
+void cutPointsOutsideX(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, double minX, double maxX, bool doPolygonCut)
+{
+ cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.x >= minX; }, GetIntersectionX(minX), doPolygonCut);
+ cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.x <= maxX; }, GetIntersectionX(maxX), doPolygonCut);
+}
+
+void cutPointsOutsideY(std::vector<CurvePoint>& curvePoints, std::vector<char>& oobMarker, double minY, double maxY, bool doPolygonCut)
+{
+ cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.y >= minY; }, GetIntersectionY(minY), doPolygonCut);
+ cutPoints(curvePoints, oobMarker, [&](const CurvePoint& pt) { return pt.y <= maxY; }, GetIntersectionY(maxY), doPolygonCut);
+}
+}
+
+
+std::vector<CurvePoint> ContinuousCurveData::getPoints(double minX, double maxX, int pixelWidth) const
+{
+ std::vector<CurvePoint> points;
+
+ if (pixelWidth <= 1) return points;
+ const ConvertCoord cvrtX(minX, maxX, pixelWidth - 1); //map [minX, maxX] to [0, pixelWidth - 1]
+
+ const std::pair<double, double> rangeX = getRangeX();
+
+ const double screenLow = cvrtX.realToScreen(std::max(rangeX.first, minX)); //=> xLow >= 0
+ const double screenHigh = cvrtX.realToScreen(std::min(rangeX.second, maxX)); //=> xHigh <= pixelWidth - 1
+ //if double is larger than what int can represent => undefined behavior!
+ //=> convert to int *after* checking value range!
+ if (screenLow <= screenHigh)
+ {
+ const int posFrom = std::ceil (screenLow ); //do not step outside [minX, maxX] in loop below!
+ const int posTo = std::floor(screenHigh); //
+ //conversion from std::floor/std::ceil double return value to int is loss-free for full value range of 32-bit int! tested successfully on MSVC
+
+ for (int i = posFrom; i <= posTo; ++i)
+ {
+ const double x = cvrtX.screenToReal(i);
+ points.emplace_back(x, getValue(x));
+ }
+ }
+ return points;
+}
+
+
+std::vector<CurvePoint> SparseCurveData::getPoints(double minX, double maxX, int pixelWidth) const
+{
+ std::vector<CurvePoint> points;
+ if (pixelWidth <= 1) return points;
+ const ConvertCoord cvrtX(minX, maxX, pixelWidth - 1); //map [minX, maxX] to [0, pixelWidth - 1]
+ const std::pair<double, double> rangeX = getRangeX();
+
+ auto addPoint = [&](const CurvePoint& pt)
+ {
+ if (!points.empty())
+ {
+ if (pt.x <= points.back().x) //allow ascending x-positions only! algorithm below may cause double-insertion after empty x-ranges!
+ return;
+
+ if (addSteps_)
+ if (pt.y != points.back().y)
+ points.emplace_back(CurvePoint(pt.x, points.back().y)); //[!] aliasing parameter not yet supported via emplace_back: VS bug! => make copy
+ }
+ points.push_back(pt);
+ };
+
+ const int posFrom = cvrtX.realToScreenRound(std::max(rangeX.first, minX));
+ const int posTo = cvrtX.realToScreenRound(std::min(rangeX.second, maxX));
+
+ for (int i = posFrom; i <= posTo; ++i)
+ {
+ const double x = cvrtX.screenToReal(i);
+ Opt<CurvePoint> ptLe = getLessEq(x);
+ Opt<CurvePoint> ptGe = getGreaterEq(x);
+ //both non-existent and invalid return values are mapped to out of expected range: => check on posLe/posGe NOT ptLe/ptGe in the following!
+ const int posLe = ptLe ? cvrtX.realToScreenRound(ptLe->x) : i + 1;
+ const int posGe = ptGe ? cvrtX.realToScreenRound(ptGe->x) : i - 1;
+ assert(!ptLe || posLe <= i); //check for invalid return values
+ assert(!ptGe || posGe >= i); //
+ /*
+ Breakdown of all combinations of posLe, posGe and expected action (n >= 1)
+ Note: For every empty x-range of at least one pixel, both next and previous points must be saved to keep the interpolating line stable!!!
+
+ posLe | posGe | action
+ +-------+-------+--------
+ | none | none | break
+ | i | none | save ptLe; break
+ | i - n | none | break;
+ +-------+-------+--------
+ | none | i | save ptGe; continue
+ | i | i | save one of ptLe, ptGe; continue
+ | i - n | i | save ptGe; continue
+ +-------+-------+--------
+ | none | i + n | save ptGe; jump to position posGe + 1
+ | i | i + n | save ptLe; if n == 1: continue; else: save ptGe; jump to position posGe + 1
+ | i - n | i + n | save ptLe, ptGe; jump to position posGe + 1
+ +-------+-------+--------
+ */
+ if (posGe < i)
+ {
+ if (posLe == i)
+ addPoint(*ptLe);
+ break;
+ }
+ else if (posGe == i) //test if point would be mapped to pixel x-position i
+ {
+ if (posLe == i) //
+ addPoint(x - ptLe->x < ptGe->x - x ? *ptLe : *ptGe);
+ else
+ addPoint(*ptGe);
+ }
+ else
+ {
+ if (posLe <= i)
+ addPoint(*ptLe);
+
+ if (posLe != i || posGe > i + 1)
+ {
+ addPoint(*ptGe);
+ i = posGe; //skip sparse area: +1 will be added by for-loop!
+ }
+ }
+ }
+ return points;
+}
+
+
+Graph2D::Graph2D(wxWindow* parent,
+ wxWindowID winid,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) : wxPanel(parent, winid, pos, size, style, name)
+{
+ 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);
+
+ //SetDoubleBuffered(true); slow as hell!
+
+ SetBackgroundStyle(wxBG_STYLE_PAINT);
+
+ Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(Graph2D::OnMouseLeftDown), nullptr, this);
+ Connect(wxEVT_MOTION, wxMouseEventHandler(Graph2D::OnMouseMovement), nullptr, this);
+ Connect(wxEVT_LEFT_UP, wxMouseEventHandler(Graph2D::OnMouseLeftUp), nullptr, this);
+ Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(Graph2D::OnMouseCaptureLost), nullptr, this);
+}
+
+
+void Graph2D::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);
+ render(dc);
+}
+
+
+void Graph2D::OnMouseLeftDown(wxMouseEvent& event)
+{
+ activeSel = std::make_unique<MouseSelection>(*this, event.GetPosition());
+
+ if (!event.ControlDown())
+ oldSel.clear();
+ Refresh();
+}
+
+
+void Graph2D::OnMouseMovement(wxMouseEvent& event)
+{
+ if (activeSel.get())
+ {
+ activeSel->refCurrentPos() = event.GetPosition(); //corresponding activeSel->refSelection() is updated in Graph2D::render()
+ Refresh();
+ }
+}
+
+
+void Graph2D::OnMouseLeftUp(wxMouseEvent& event)
+{
+ if (activeSel.get())
+ {
+ if (activeSel->getStartPos() != activeSel->refCurrentPos()) //if it's just a single mouse click: discard selection
+ {
+ GraphSelectEvent selEvent(activeSel->refSelection()); //fire off GraphSelectEvent
+ if (wxEvtHandler* handler = GetEventHandler())
+ handler->AddPendingEvent(selEvent);
+
+ oldSel.push_back(activeSel->refSelection()); //commit selection
+ }
+
+ activeSel.reset();
+ Refresh();
+ }
+}
+
+
+void Graph2D::OnMouseCaptureLost(wxMouseCaptureLostEvent& event)
+{
+ activeSel.reset();
+ Refresh();
+}
+
+
+void Graph2D::setCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca)
+{
+ curves_.clear();
+ addCurve(data, ca);
+}
+
+
+void Graph2D::addCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca)
+{
+ CurveAttributes newAttr = ca;
+ if (newAttr.autoColor)
+ newAttr.setColor(getDefaultColor(curves_.size()));
+ curves_.emplace_back(data, newAttr);
+ Refresh();
+}
+
+
+void Graph2D::render(wxDC& dc) const
+{
+ using namespace numeric;
+
+ //set label font right at the start so that it is considered by wxDC::GetTextExtent() below!
+ dc.SetFont(labelFont);
+
+ const wxRect clientRect = GetClientRect(); //DON'T use wxDC::GetSize()! DC may be larger than visible area!
+ {
+ //clear complete client area; set label background color
+ const wxColor backCol = GetBackgroundColour(); //user-configurable!
+ //wxPanel::GetClassDefaultAttributes().colBg :
+ //wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE);
+ wxDCPenChanger dummy (dc, backCol);
+ wxDCBrushChanger dummy2(dc, backCol);
+ dc.DrawRectangle(clientRect);
+ }
+
+ /*
+ -----------------------
+ | | x-label |
+ -----------------------
+ |y-label | graph area |
+ |----------------------
+ */
+ wxRect graphArea = clientRect;
+ int xLabelPosY = clientRect.y;
+ int yLabelPosX = clientRect.x;
+
+ switch (attr.labelposX)
+ {
+ case LABEL_X_TOP:
+ graphArea.y += attr.xLabelHeight;
+ graphArea.height -= attr.xLabelHeight;
+ break;
+ case LABEL_X_BOTTOM:
+ xLabelPosY += clientRect.height - attr.xLabelHeight;
+ graphArea.height -= attr.xLabelHeight;
+ break;
+ case LABEL_X_NONE:
+ break;
+ }
+ switch (attr.labelposY)
+ {
+ case LABEL_Y_LEFT:
+ graphArea.x += attr.yLabelWidth;
+ graphArea.width -= attr.yLabelWidth;
+ break;
+ case LABEL_Y_RIGHT:
+ yLabelPosX += clientRect.width - attr.yLabelWidth;
+ graphArea.width -= attr.yLabelWidth;
+ break;
+ case LABEL_Y_NONE:
+ break;
+ }
+
+ {
+ //paint graph background (excluding label area)
+ wxDCPenChanger dummy (dc, getBorderColor());
+ wxDCBrushChanger dummy2(dc, attr.backgroundColor);
+ //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
+
+ dc.DrawRectangle(graphArea);
+ graphArea.Deflate(1, 1); //attention more wxWidgets design mistakes: behavior of wxRect::Deflate depends on object being const/non-const!!!
+ }
+
+ //set label areas respecting graph area border!
+ const wxRect xLabelArea(graphArea.x, xLabelPosY, graphArea.width, attr.xLabelHeight);
+ const wxRect yLabelArea(yLabelPosX, graphArea.y, attr.yLabelWidth, graphArea.height);
+ const wxPoint graphAreaOrigin = graphArea.GetTopLeft();
+
+ //detect x value range
+ double minX = attr.minXauto ? std::numeric_limits<double>::infinity() : attr.minX; //automatic: ensure values are initialized by first curve
+ double maxX = attr.maxXauto ? -std::numeric_limits<double>::infinity() : attr.maxX; //
+ for (auto it = curves_.begin(); it != curves_.end(); ++it)
+ if (const CurveData* curve = it->first.get())
+ {
+ const std::pair<double, double> rangeX = curve->getRangeX();
+ assert(rangeX.first <= rangeX.second + 1.0e-9);
+ //GCC fucks up badly when comparing two *binary identical* doubles and finds "begin > end" with diff of 1e-18
+
+ if (attr.minXauto)
+ minX = std::min(minX, rangeX.first);
+ if (attr.maxXauto)
+ maxX = std::max(maxX, rangeX.second);
+ }
+
+ const wxSize minimalBlockSizePx = dc.GetTextExtent(L"00");
+
+ if (minX <= maxX && maxX - minX < std::numeric_limits<double>::infinity()) //valid x-range
+ {
+ int blockCountX = 0;
+ //enlarge minX, maxX to a multiple of a "useful" block size
+ if (attr.labelposX != LABEL_X_NONE && attr.labelFmtX.get())
+ blockCountX = widenRange(minX, maxX, //in/out
+ graphArea.width,
+ minimalBlockSizePx.GetWidth() * 7,
+ *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; //
+
+ 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
+
+ for (size_t index = 0; index < curves_.size(); ++index)
+ if (const CurveData* curve = curves_[index].first.get())
+ {
+ std::vector<CurvePoint>& points = curvePoints[index];
+ auto& marker = oobMarker [index];
+
+ points = curve->getPoints(minX, maxX, graphArea.width);
+ marker.resize(points.size()); //default value: false
+ if (!points.empty())
+ {
+ //cut points outside visible x-range now in order to calculate height of visible line fragments only!
+ 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)
+ {
+ auto itPair = std::minmax_element(points.begin(), points.end(), [](const CurvePoint& lhs, const CurvePoint& rhs) { return lhs.y < rhs.y; });
+ if (attr.minYauto)
+ minY = std::min(minY, itPair.first->y);
+ if (attr.maxYauto)
+ maxY = std::max(maxY, itPair.second->y);
+ }
+ }
+ }
+
+ if (minY <= maxY) //valid y-range
+ {
+ int blockCountY = 0;
+ //enlarge minY, maxY to a multiple of a "useful" block size
+ if (attr.labelposY != LABEL_Y_NONE && attr.labelFmtY.get())
+ blockCountY = widenRange(minY, maxY, //in/out
+ graphArea.height,
+ minimalBlockSizePx.GetHeight() * 3,
+ *attr.labelFmtY);
+
+ if (graphArea.width <= 1 || graphArea.height <= 1) return;
+ const ConvertCoord cvrtX(minX, maxX, graphArea.width - 1); //map [minX, maxX] to [0, pixelWidth - 1]
+ const ConvertCoord cvrtY(maxY, minY, graphArea.height - 1); //map [minY, maxY] to [pixelHeight - 1, 0]
+
+ //calculate curve coordinates on graph area
+ std::vector<std::vector<wxPoint>> drawPoints(curves_.size());
+
+ for (size_t index = 0; index < curves_.size(); ++index)
+ {
+ auto& cp = curvePoints[index];
+
+ //add two artificial points to fill the curve area towards x-axis => do this before cutPointsOutsideY() to handle curve leaving upper bound
+ if (curves_[index].second.fillMode == CurveAttributes::FILL_CURVE)
+ if (!cp.empty())
+ {
+ cp.emplace_back(CurvePoint(cp.back ().x, minY)); //add lower right and left corners
+ cp.emplace_back(CurvePoint(cp.front().x, minY)); //[!] aliasing parameter not yet supported via emplace_back: VS bug! => make copy
+ oobMarker[index].back() = true;
+ oobMarker[index].push_back(true);
+ oobMarker[index].push_back(true);
+ }
+
+ //cut points outside visible y-range before calculating pixels:
+ //1. realToScreenRound() deforms out-of-range values!
+ //2. pixels that are grossly out of range can be a severe performance problem when drawing on the DC (Windows)
+ const bool doPolygonCut = curves_[index].second.fillMode != CurveAttributes::FILL_NONE;
+ cutPointsOutsideY(cp, oobMarker[index], minY, maxY, doPolygonCut);
+
+ auto& dp = drawPoints[index];
+ for (const CurvePoint& pt : cp)
+ dp.push_back(wxPoint(cvrtX.realToScreenRound(pt.x),
+ cvrtY.realToScreenRound(pt.y)) + graphAreaOrigin);
+ }
+
+ //update active mouse selection
+ if (activeSel.get() && graphArea.width > 0 && graphArea.height > 0)
+ {
+ auto widen = [](double* low, double* high)
+ {
+ if (*low > *high)
+ std::swap(low, high);
+ *low -= 0.5;
+ *high += 0.5;
+ };
+
+ const wxPoint screenStart = activeSel->getStartPos() - graphAreaOrigin; //make relative to graphArea
+ const wxPoint screenCurrent = activeSel->refCurrentPos() - graphAreaOrigin;
+
+ //normalize positions: a mouse selection is symmetric and *not* an half-open range!
+ double screenFromX = clampCpy(screenStart .x, 0, graphArea.width - 1);
+ double screenFromY = clampCpy(screenStart .y, 0, graphArea.height - 1);
+ double screenToX = clampCpy(screenCurrent.x, 0, graphArea.width - 1);
+ double screenToY = clampCpy(screenCurrent.y, 0, graphArea.height - 1);
+ widen(&screenFromX, &screenToX); //use full pixel range for selection!
+ widen(&screenFromY, &screenToY);
+
+ //save current selection as "double" coordinates
+ activeSel->refSelection().from = CurvePoint(cvrtX.screenToReal(screenFromX),
+ cvrtY.screenToReal(screenFromY));
+
+ activeSel->refSelection().to = CurvePoint(cvrtX.screenToReal(screenToX),
+ cvrtY.screenToReal(screenToY));
+ }
+
+ //#################### begin drawing ####################
+ //1. draw colored area under curves
+ for (auto it = curves_.begin(); it != curves_.end(); ++it)
+ if (it->second.fillMode != CurveAttributes::FILL_NONE)
+ {
+ const std::vector<wxPoint>& points = drawPoints[it - curves_.begin()];
+ if (points.size() >= 3)
+ {
+ wxDCBrushChanger dummy(dc, it->second.fillColor);
+ wxDCPenChanger dummy2(dc, it->second.fillColor);
+ dc.DrawPolygon(static_cast<int>(points.size()), &points[0]);
+ }
+ }
+
+ //2. draw all currently set mouse selections (including active selection)
+ std::vector<SelectionBlock> allSelections = oldSel;
+ if (activeSel)
+ allSelections.push_back(activeSel->refSelection());
+ {
+ //alpha channel not supported on wxMSW, so draw selection before curves
+ wxDCBrushChanger dummy(dc, wxColor(168, 202, 236)); //light blue
+ wxDCPenChanger dummy2(dc, wxColor( 51, 153, 255)); //dark blue
+
+ auto shrink = [](double* low, double* high)
+ {
+ if (*low > *high)
+ std::swap(low, high);
+ *low += 0.5;
+ *high -= 0.5;
+ if (*low > *high)
+ *low = *high = (*low + *high) / 2;
+ };
+
+ for (const SelectionBlock& sel : allSelections)
+ {
+ //harmonize with active mouse selection above
+ double screenFromX = cvrtX.realToScreen(sel.from.x);
+ double screenFromY = cvrtY.realToScreen(sel.from.y);
+ double screenToX = cvrtX.realToScreen(sel.to.x);
+ double screenToY = cvrtY.realToScreen(sel.to.y);
+ shrink(&screenFromX, &screenToX);
+ shrink(&screenFromY, &screenToY);
+
+ clamp(screenFromX, 0.0, graphArea.width - 1.0);
+ clamp(screenFromY, 0.0, graphArea.height - 1.0);
+ clamp(screenToX, 0.0, graphArea.width - 1.0);
+ clamp(screenToY, 0.0, graphArea.height - 1.0);
+
+ const wxPoint pixelFrom = wxPoint(numeric::round(screenFromX),
+ numeric::round(screenFromY)) + graphAreaOrigin;
+ const wxPoint pixelTo = wxPoint(numeric::round(screenToX),
+ numeric::round(screenToY)) + graphAreaOrigin;
+ switch (attr.mouseSelMode)
+ {
+ case SELECT_NONE:
+ break;
+ case SELECT_RECTANGLE:
+ dc.DrawRectangle(wxRect(pixelFrom, pixelTo));
+ break;
+ case SELECT_X_AXIS:
+ dc.DrawRectangle(wxRect(wxPoint(pixelFrom.x, graphArea.y), wxPoint(pixelTo.x, graphArea.y + graphArea.height - 1)));
+ break;
+ case SELECT_Y_AXIS:
+ dc.DrawRectangle(wxRect(wxPoint(graphArea.x, pixelFrom.y), wxPoint(graphArea.x + graphArea.width - 1, pixelTo.y)));
+ break;
+ }
+ }
+ }
+
+ //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);
+
+ //4. finally draw curves
+ {
+ dc.SetClippingRegion(graphArea); //prevent thick curves from drawing slightly outside
+ ZEN_ON_SCOPE_EXIT(dc.DestroyClippingRegion());
+
+ for (auto it = curves_.begin(); it != curves_.end(); ++it)
+ {
+ wxDCPenChanger dummy(dc, wxPen(it->second.color, it->second.lineWidth));
+
+ const size_t index = it - curves_.begin();
+ const std::vector<wxPoint>& points = drawPoints[index];
+ const auto& marker = oobMarker [index];
+ assert(points.size() == marker.size());
+
+ //draw all parts of the curve except for the out-of-bounds fragments
+ size_t drawIndexFirst = 0;
+ while (drawIndexFirst < points.size())
+ {
+ size_t drawIndexLast = std::find(marker.begin() + drawIndexFirst, marker.end(), true) - marker.begin();
+ if (drawIndexLast < points.size()) ++drawIndexLast;
+
+ const int pointCount = static_cast<int>(drawIndexLast - drawIndexFirst);
+ if (pointCount > 0)
+ {
+ if (pointCount >= 2) //on OS X wxWidgets has a nasty assert on this
+ dc.DrawLines(pointCount, &points[drawIndexFirst]);
+ dc.DrawPoint(points[drawIndexLast - 1]); //wxDC::DrawLines() doesn't draw last pixel
+ }
+ drawIndexFirst = std::find(marker.begin() + drawIndexLast, marker.end(), false) - marker.begin();
+ }
+ }
+ }
+
+ //5. draw corner texts
+ for (const auto& ct : attr.cornerTexts)
+ drawCornerText(dc, graphArea, ct.second, ct.first);
+ }
+ }
+}
diff --git a/wx+/graph.h b/wx+/graph.h
index 84927b6d..3b2d390d 100644..100755
--- a/wx+/graph.h
+++ b/wx+/graph.h
@@ -1,354 +1,354 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef GRAPH_H_234425245936567345799
-#define GRAPH_H_234425245936567345799
-
-#include <map>
-#include <vector>
-#include <memory>
-#include <wx/panel.h>
-#include <wx/settings.h>
-#include <wx/bitmap.h>
-#include <zen/string_tools.h>
-#include <zen/optional.h>
-
-//elegant 2D graph as wxPanel specialization
-
-namespace zen
-{
-/*
-Example:
- //init graph (optional)
- m_panelGraph->setAttributes(Graph2D::MainAttributes().
- setLabelX(Graph2D::LABEL_X_BOTTOM, 20, std::make_shared<LabelFormatterTimeElapsed>()).
- setLabelY(Graph2D::LABEL_Y_RIGHT, 60, std::make_shared<LabelFormatterBytes>()));
- //set graph data
- std::shared_ptr<CurveData> curveDataBytes = ...
- m_panelGraph->setCurve(curveDataBytes, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0)));
-*/
-
-struct CurvePoint
-{
- CurvePoint() {}
- CurvePoint(double xVal, double yVal) : x(xVal), y(yVal) {}
- double x = 0;
- double y = 0;
-};
-inline bool operator==(const CurvePoint& lhs, const CurvePoint& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; }
-inline bool operator!=(const CurvePoint& lhs, const CurvePoint& rhs) { return !(lhs == rhs); }
-
-
-struct CurveData
-{
- virtual ~CurveData() {}
-
- virtual std::pair<double, double> getRangeX() const = 0;
- virtual std::vector<CurvePoint> getPoints(double minX, double maxX, int pixelWidth) const = 0; //points outside the draw area are automatically trimmed!
-};
-
-//special curve types:
-struct ContinuousCurveData : public CurveData
-{
- virtual double getValue(double x) const = 0;
-
-private:
- std::vector<CurvePoint> getPoints(double minX, double maxX, int pixelWidth) const override;
-};
-
-struct SparseCurveData : public CurveData
-{
- SparseCurveData(bool addSteps = false) : addSteps_(addSteps) {} //addSteps: add points to get a staircase effect or connect points via a direct line
-
- virtual Opt<CurvePoint> getLessEq (double x) const = 0;
- virtual Opt<CurvePoint> getGreaterEq(double x) const = 0;
-
-private:
- std::vector<CurvePoint> getPoints(double minX, double maxX, int pixelWidth) const override;
- const bool addSteps_;
-};
-
-
-struct ArrayCurveData : public SparseCurveData
-{
- virtual double getValue(size_t pos) const = 0;
- virtual size_t getSize () const = 0;
-
-private:
- std::pair<double, double> getRangeX() const override { const size_t sz = getSize(); return std::make_pair(0.0, sz == 0 ? 0.0 : sz - 1.0); }
-
- Opt<CurvePoint> getLessEq(double x) const override
- {
- const size_t sz = getSize();
- const size_t pos = std::min<ptrdiff_t>(std::floor(x), sz - 1); //[!] expect unsigned underflow if empty!
- if (pos < sz)
- return CurvePoint(pos, getValue(pos));
- return NoValue();
- }
-
- Opt<CurvePoint> getGreaterEq(double x) const override
- {
- const size_t pos = std::max<ptrdiff_t>(std::ceil(x), 0); //[!] use std::max with signed type!
- if (pos < getSize())
- return CurvePoint(pos, getValue(pos));
- return NoValue();
- }
-};
-
-
-struct VectorCurveData : public ArrayCurveData
-{
- std::vector<double>& refData() { return data; }
-private:
- 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;
-};
-
-//------------------------------------------------------------------------------------------------------------
-
-struct LabelFormatter
-{
- virtual ~LabelFormatter() {}
-
- //determine convenient graph label block size in unit of data: usually some small deviation on "sizeProposed"
- virtual double getOptimalBlockSize(double sizeProposed) const = 0;
-
- //create human-readable text for x or y-axis position
- virtual wxString formatText(double value, double optimalBlockSize) const = 0;
-};
-
-
-double nextNiceNumber(double blockSize); //round to next number which is convenient to read, e.g. 2.13 -> 2; 2.7 -> 2.5
-
-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); }
-};
-
-//------------------------------------------------------------------------------------------------------------
-
-//emit data selection event
-//Usage: wnd.Connect(wxEVT_GRAPH_SELECTION, GraphSelectEventHandler(MyDlg::OnGraphSelection), nullptr, this);
-// void MyDlg::OnGraphSelection(GraphSelectEvent& event);
-
-extern const wxEventType wxEVT_GRAPH_SELECTION;
-
-struct SelectionBlock
-{
- CurvePoint from;
- CurvePoint to;
-};
-
-class GraphSelectEvent : public wxCommandEvent
-{
-public:
- GraphSelectEvent(const SelectionBlock& selBlock) : wxCommandEvent(wxEVT_GRAPH_SELECTION), selBlock_(selBlock) {}
- wxEvent* Clone() const override { return new GraphSelectEvent(selBlock_); }
-
- SelectionBlock getSelection() { return selBlock_; }
-
-private:
- SelectionBlock selBlock_;
-};
-
-using GraphSelectEventFunction = void (wxEvtHandler::*)(GraphSelectEvent&);
-
-#define GraphSelectEventHandler(func) \
- (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GraphSelectEventFunction, &func)
-
-//------------------------------------------------------------------------------------------------------------
-
-class Graph2D : public wxPanel
-{
-public:
- Graph2D(wxWindow* parent,
- wxWindowID winid = wxID_ANY,
- const wxPoint& pos = wxDefaultPosition,
- const wxSize& size = wxDefaultSize,
- long style = wxTAB_TRAVERSAL | wxNO_BORDER,
- const wxString& name = wxPanelNameStr);
-
- class CurveAttributes
- {
- public:
- CurveAttributes() {} //required by GCC
- 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; }
-
- private:
- friend class Graph2D;
-
- bool autoColor = true;
- wxColor color;
-
- enum FillMode
- {
- FILL_NONE,
- FILL_CURVE,
- FILL_POLYGON
- };
-
- FillMode fillMode = FILL_NONE;
- wxColor fillColor;
-
- int lineWidth = 2;
- };
-
- void setCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca = CurveAttributes());
- void addCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca = CurveAttributes());
-
- static wxColor getBorderColor() { return { 130, 135, 144 }; } //medium grey, the same Win7 uses for other frame borders => not accessible! but no big deal...
-
- enum PosLabelY
- {
- LABEL_Y_LEFT,
- LABEL_Y_RIGHT,
- LABEL_Y_NONE
- };
-
- enum PosLabelX
- {
- LABEL_X_TOP,
- LABEL_X_BOTTOM,
- LABEL_X_NONE
- };
-
- enum PosCorner
- {
- CORNER_TOP_LEFT,
- CORNER_TOP_RIGHT,
- CORNER_BOTTOM_LEFT,
- CORNER_BOTTOM_RIGHT,
- };
-
- enum SelMode
- {
- SELECT_NONE,
- SELECT_RECTANGLE,
- SELECT_X_AXIS,
- SELECT_Y_AXIS,
- };
-
- class MainAttributes
- {
- public:
- MainAttributes& setMinX(double newMinX) { minX = newMinX; minXauto = false; return *this; }
- MainAttributes& setMaxX(double newMaxX) { maxX = newMaxX; maxXauto = false; return *this; }
-
- MainAttributes& setMinY(double newMinY) { minY = newMinY; minYauto = false; return *this; }
- MainAttributes& setMaxY(double newMaxY) { maxY = newMaxY; maxYauto = false; return *this; }
-
- MainAttributes& setAutoSize() { minXauto = maxXauto = minYauto = maxYauto = true; return *this; }
-
- MainAttributes& setLabelX(PosLabelX posX, size_t height = 25, std::shared_ptr<LabelFormatter> newLabelFmt = std::make_shared<DecimalNumberFormatter>())
- {
- labelposX = posX;
- xLabelHeight = static_cast<int>(height);
- labelFmtX = newLabelFmt;
- return *this;
- }
- MainAttributes& setLabelY(PosLabelY posY, size_t width = 60, std::shared_ptr<LabelFormatter> newLabelFmt = std::make_shared<DecimalNumberFormatter>())
- {
- labelposY = posY;
- yLabelWidth = static_cast<int>(width);
- labelFmtY = newLabelFmt;
- return *this;
- }
-
- MainAttributes& setCornerText(const wxString& txt, PosCorner pos) { cornerTexts[pos] = txt; return *this; }
-
- MainAttributes& setBackgroundColor(const wxColor& col) { backgroundColor = col; return *this; }
-
- MainAttributes& setSelectionMode(SelMode mode) { mouseSelMode = mode; return *this; }
-
- 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; //
-
- bool minYauto = true; //autodetect range for Y value
- bool maxYauto = true;
- double minY = 0; //y-range to visualize
- double maxY = 0; //
-
- PosLabelX labelposX = LABEL_X_BOTTOM;
- int xLabelHeight = 25;
- std::shared_ptr<LabelFormatter> labelFmtX = std::make_shared<DecimalNumberFormatter>();
-
- PosLabelY labelposY = LABEL_Y_LEFT;
- int yLabelWidth = 60;
- std::shared_ptr<LabelFormatter> labelFmtY = std::make_shared<DecimalNumberFormatter>();
-
- std::map<PosCorner, wxString> cornerTexts;
-
- wxColor backgroundColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
- SelMode mouseSelMode = SELECT_RECTANGLE;
- };
- void setAttributes(const MainAttributes& newAttr) { attr = newAttr; Refresh(); }
- MainAttributes getAttributes() const { return attr; }
-
- std::vector<SelectionBlock> getSelections() const { return oldSel; }
- void setSelections(const std::vector<SelectionBlock>& sel)
- {
- oldSel = sel;
- activeSel.reset();
- Refresh();
- }
- void clearSelection() { oldSel.clear(); Refresh(); }
-
-private:
- void OnMouseLeftDown(wxMouseEvent& event);
- void OnMouseMovement(wxMouseEvent& event);
- void OnMouseLeftUp (wxMouseEvent& event);
- void OnMouseCaptureLost(wxMouseCaptureLostEvent& event);
-
- void onPaintEvent(wxPaintEvent& event);
- void onSizeEvent(wxSizeEvent& event) { Refresh(); event.Skip(); }
- void onEraseBackGround(wxEraseEvent& event) {}
-
- void render(wxDC& dc) const;
-
- class MouseSelection
- {
- public:
- MouseSelection(wxWindow& wnd, const wxPoint& posDragStart) : wnd_(wnd), posDragStart_(posDragStart), posDragCurrent(posDragStart) { wnd_.CaptureMouse(); }
- ~MouseSelection() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); }
-
- wxPoint getStartPos() const { return posDragStart_; }
- wxPoint& refCurrentPos() { return posDragCurrent; }
-
- SelectionBlock& refSelection() { return selBlock; } //updated in Graph2d::render(): this is fine, since only what's shown is selected!
-
- private:
- wxWindow& wnd_;
- const wxPoint posDragStart_;
- wxPoint posDragCurrent;
- SelectionBlock selBlock;
- };
- std::vector<SelectionBlock> oldSel; //applied selections
- std::shared_ptr<MouseSelection> activeSel; //set during mouse selection
-
- MainAttributes attr; //global attributes
-
- Opt<wxBitmap> doubleBuffer;
-
- using CurveList = std::vector<std::pair<std::shared_ptr<CurveData>, CurveAttributes>>;
- CurveList curves_;
-
- //perf!!! generating the font is *very* expensive! don't do this repeatedly in Graph2D::render()!
- const wxFont labelFont { wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial" };
-};
-}
-
-#endif //GRAPH_H_234425245936567345799
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef GRAPH_H_234425245936567345799
+#define GRAPH_H_234425245936567345799
+
+#include <map>
+#include <vector>
+#include <memory>
+#include <wx/panel.h>
+#include <wx/settings.h>
+#include <wx/bitmap.h>
+#include <zen/string_tools.h>
+#include <zen/optional.h>
+
+//elegant 2D graph as wxPanel specialization
+
+namespace zen
+{
+/*
+Example:
+ //init graph (optional)
+ m_panelGraph->setAttributes(Graph2D::MainAttributes().
+ setLabelX(Graph2D::LABEL_X_BOTTOM, 20, std::make_shared<LabelFormatterTimeElapsed>()).
+ setLabelY(Graph2D::LABEL_Y_RIGHT, 60, std::make_shared<LabelFormatterBytes>()));
+ //set graph data
+ std::shared_ptr<CurveData> curveDataBytes = ...
+ m_panelGraph->setCurve(curveDataBytes, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0)));
+*/
+
+struct CurvePoint
+{
+ CurvePoint() {}
+ CurvePoint(double xVal, double yVal) : x(xVal), y(yVal) {}
+ double x = 0;
+ double y = 0;
+};
+inline bool operator==(const CurvePoint& lhs, const CurvePoint& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; }
+inline bool operator!=(const CurvePoint& lhs, const CurvePoint& rhs) { return !(lhs == rhs); }
+
+
+struct CurveData
+{
+ virtual ~CurveData() {}
+
+ virtual std::pair<double, double> getRangeX() const = 0;
+ virtual std::vector<CurvePoint> getPoints(double minX, double maxX, int pixelWidth) const = 0; //points outside the draw area are automatically trimmed!
+};
+
+//special curve types:
+struct ContinuousCurveData : public CurveData
+{
+ virtual double getValue(double x) const = 0;
+
+private:
+ std::vector<CurvePoint> getPoints(double minX, double maxX, int pixelWidth) const override;
+};
+
+struct SparseCurveData : public CurveData
+{
+ SparseCurveData(bool addSteps = false) : addSteps_(addSteps) {} //addSteps: add points to get a staircase effect or connect points via a direct line
+
+ virtual Opt<CurvePoint> getLessEq (double x) const = 0;
+ virtual Opt<CurvePoint> getGreaterEq(double x) const = 0;
+
+private:
+ std::vector<CurvePoint> getPoints(double minX, double maxX, int pixelWidth) const override;
+ const bool addSteps_;
+};
+
+
+struct ArrayCurveData : public SparseCurveData
+{
+ virtual double getValue(size_t pos) const = 0;
+ virtual size_t getSize () const = 0;
+
+private:
+ std::pair<double, double> getRangeX() const override { const size_t sz = getSize(); return std::make_pair(0.0, sz == 0 ? 0.0 : sz - 1.0); }
+
+ Opt<CurvePoint> getLessEq(double x) const override
+ {
+ const size_t sz = getSize();
+ const size_t pos = std::min<ptrdiff_t>(std::floor(x), sz - 1); //[!] expect unsigned underflow if empty!
+ if (pos < sz)
+ return CurvePoint(pos, getValue(pos));
+ return NoValue();
+ }
+
+ Opt<CurvePoint> getGreaterEq(double x) const override
+ {
+ const size_t pos = std::max<ptrdiff_t>(std::ceil(x), 0); //[!] use std::max with signed type!
+ if (pos < getSize())
+ return CurvePoint(pos, getValue(pos));
+ return NoValue();
+ }
+};
+
+
+struct VectorCurveData : public ArrayCurveData
+{
+ std::vector<double>& refData() { return data; }
+private:
+ 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;
+};
+
+//------------------------------------------------------------------------------------------------------------
+
+struct LabelFormatter
+{
+ virtual ~LabelFormatter() {}
+
+ //determine convenient graph label block size in unit of data: usually some small deviation on "sizeProposed"
+ virtual double getOptimalBlockSize(double sizeProposed) const = 0;
+
+ //create human-readable text for x or y-axis position
+ virtual wxString formatText(double value, double optimalBlockSize) const = 0;
+};
+
+
+double nextNiceNumber(double blockSize); //round to next number which is convenient to read, e.g. 2.13 -> 2; 2.7 -> 2.5
+
+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); }
+};
+
+//------------------------------------------------------------------------------------------------------------
+
+//emit data selection event
+//Usage: wnd.Connect(wxEVT_GRAPH_SELECTION, GraphSelectEventHandler(MyDlg::OnGraphSelection), nullptr, this);
+// void MyDlg::OnGraphSelection(GraphSelectEvent& event);
+
+extern const wxEventType wxEVT_GRAPH_SELECTION;
+
+struct SelectionBlock
+{
+ CurvePoint from;
+ CurvePoint to;
+};
+
+class GraphSelectEvent : public wxCommandEvent
+{
+public:
+ GraphSelectEvent(const SelectionBlock& selBlock) : wxCommandEvent(wxEVT_GRAPH_SELECTION), selBlock_(selBlock) {}
+ wxEvent* Clone() const override { return new GraphSelectEvent(selBlock_); }
+
+ SelectionBlock getSelection() { return selBlock_; }
+
+private:
+ SelectionBlock selBlock_;
+};
+
+using GraphSelectEventFunction = void (wxEvtHandler::*)(GraphSelectEvent&);
+
+#define GraphSelectEventHandler(func) \
+ (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GraphSelectEventFunction, &func)
+
+//------------------------------------------------------------------------------------------------------------
+
+class Graph2D : public wxPanel
+{
+public:
+ Graph2D(wxWindow* parent,
+ wxWindowID winid = wxID_ANY,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = wxTAB_TRAVERSAL | wxNO_BORDER,
+ const wxString& name = wxPanelNameStr);
+
+ class CurveAttributes
+ {
+ public:
+ CurveAttributes() {} //required by GCC
+ 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; }
+
+ private:
+ friend class Graph2D;
+
+ bool autoColor = true;
+ wxColor color;
+
+ enum FillMode
+ {
+ FILL_NONE,
+ FILL_CURVE,
+ FILL_POLYGON
+ };
+
+ FillMode fillMode = FILL_NONE;
+ wxColor fillColor;
+
+ int lineWidth = 2;
+ };
+
+ void setCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca = CurveAttributes());
+ void addCurve(const std::shared_ptr<CurveData>& data, const CurveAttributes& ca = CurveAttributes());
+
+ static wxColor getBorderColor() { return { 130, 135, 144 }; } //medium grey, the same Win7 uses for other frame borders => not accessible! but no big deal...
+
+ enum PosLabelY
+ {
+ LABEL_Y_LEFT,
+ LABEL_Y_RIGHT,
+ LABEL_Y_NONE
+ };
+
+ enum PosLabelX
+ {
+ LABEL_X_TOP,
+ LABEL_X_BOTTOM,
+ LABEL_X_NONE
+ };
+
+ enum PosCorner
+ {
+ CORNER_TOP_LEFT,
+ CORNER_TOP_RIGHT,
+ CORNER_BOTTOM_LEFT,
+ CORNER_BOTTOM_RIGHT,
+ };
+
+ enum SelMode
+ {
+ SELECT_NONE,
+ SELECT_RECTANGLE,
+ SELECT_X_AXIS,
+ SELECT_Y_AXIS,
+ };
+
+ class MainAttributes
+ {
+ public:
+ MainAttributes& setMinX(double newMinX) { minX = newMinX; minXauto = false; return *this; }
+ MainAttributes& setMaxX(double newMaxX) { maxX = newMaxX; maxXauto = false; return *this; }
+
+ MainAttributes& setMinY(double newMinY) { minY = newMinY; minYauto = false; return *this; }
+ MainAttributes& setMaxY(double newMaxY) { maxY = newMaxY; maxYauto = false; return *this; }
+
+ MainAttributes& setAutoSize() { minXauto = maxXauto = minYauto = maxYauto = true; return *this; }
+
+ MainAttributes& setLabelX(PosLabelX posX, size_t height = 25, std::shared_ptr<LabelFormatter> newLabelFmt = std::make_shared<DecimalNumberFormatter>())
+ {
+ labelposX = posX;
+ xLabelHeight = static_cast<int>(height);
+ labelFmtX = newLabelFmt;
+ return *this;
+ }
+ MainAttributes& setLabelY(PosLabelY posY, size_t width = 60, std::shared_ptr<LabelFormatter> newLabelFmt = std::make_shared<DecimalNumberFormatter>())
+ {
+ labelposY = posY;
+ yLabelWidth = static_cast<int>(width);
+ labelFmtY = newLabelFmt;
+ return *this;
+ }
+
+ MainAttributes& setCornerText(const wxString& txt, PosCorner pos) { cornerTexts[pos] = txt; return *this; }
+
+ MainAttributes& setBackgroundColor(const wxColor& col) { backgroundColor = col; return *this; }
+
+ MainAttributes& setSelectionMode(SelMode mode) { mouseSelMode = mode; return *this; }
+
+ 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; //
+
+ bool minYauto = true; //autodetect range for Y value
+ bool maxYauto = true;
+ double minY = 0; //y-range to visualize
+ double maxY = 0; //
+
+ PosLabelX labelposX = LABEL_X_BOTTOM;
+ int xLabelHeight = 25;
+ std::shared_ptr<LabelFormatter> labelFmtX = std::make_shared<DecimalNumberFormatter>();
+
+ PosLabelY labelposY = LABEL_Y_LEFT;
+ int yLabelWidth = 60;
+ std::shared_ptr<LabelFormatter> labelFmtY = std::make_shared<DecimalNumberFormatter>();
+
+ std::map<PosCorner, wxString> cornerTexts;
+
+ wxColor backgroundColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
+ SelMode mouseSelMode = SELECT_RECTANGLE;
+ };
+ void setAttributes(const MainAttributes& newAttr) { attr = newAttr; Refresh(); }
+ MainAttributes getAttributes() const { return attr; }
+
+ std::vector<SelectionBlock> getSelections() const { return oldSel; }
+ void setSelections(const std::vector<SelectionBlock>& sel)
+ {
+ oldSel = sel;
+ activeSel.reset();
+ Refresh();
+ }
+ void clearSelection() { oldSel.clear(); Refresh(); }
+
+private:
+ void OnMouseLeftDown(wxMouseEvent& event);
+ void OnMouseMovement(wxMouseEvent& event);
+ void OnMouseLeftUp (wxMouseEvent& event);
+ void OnMouseCaptureLost(wxMouseCaptureLostEvent& event);
+
+ void onPaintEvent(wxPaintEvent& event);
+ void onSizeEvent(wxSizeEvent& event) { Refresh(); event.Skip(); }
+ void onEraseBackGround(wxEraseEvent& event) {}
+
+ void render(wxDC& dc) const;
+
+ class MouseSelection
+ {
+ public:
+ MouseSelection(wxWindow& wnd, const wxPoint& posDragStart) : wnd_(wnd), posDragStart_(posDragStart), posDragCurrent(posDragStart) { wnd_.CaptureMouse(); }
+ ~MouseSelection() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); }
+
+ wxPoint getStartPos() const { return posDragStart_; }
+ wxPoint& refCurrentPos() { return posDragCurrent; }
+
+ SelectionBlock& refSelection() { return selBlock; } //updated in Graph2d::render(): this is fine, since only what's shown is selected!
+
+ private:
+ wxWindow& wnd_;
+ const wxPoint posDragStart_;
+ wxPoint posDragCurrent;
+ SelectionBlock selBlock;
+ };
+ std::vector<SelectionBlock> oldSel; //applied selections
+ std::shared_ptr<MouseSelection> activeSel; //set during mouse selection
+
+ MainAttributes attr; //global attributes
+
+ Opt<wxBitmap> doubleBuffer;
+
+ using CurveList = std::vector<std::pair<std::shared_ptr<CurveData>, CurveAttributes>>;
+ CurveList curves_;
+
+ //perf!!! generating the font is *very* expensive! don't do this repeatedly in Graph2D::render()!
+ const wxFont labelFont { wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"Arial" };
+};
+}
+
+#endif //GRAPH_H_234425245936567345799
diff --git a/wx+/grid.cpp b/wx+/grid.cpp
index 5d393f08..75abcd2f 100644..100755
--- a/wx+/grid.cpp
+++ b/wx+/grid.cpp
@@ -1,2274 +1,2220 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#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/tick_count.h>
-#include <zen/string_tools.h>
-#include <zen/scope_guard.h>
-#include <zen/utf.h>
-#include <zen/format_unit.h>
-#include "dc.h"
-
-#ifdef ZEN_LINUX
- #include <gtk/gtk.h>
-#endif
-
-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
-
-const int GridData::COLUMN_GAP_LEFT = 4;
-
-
-void zen::clearArea(wxDC& dc, const wxRect& rect, const wxColor& col)
-{
- wxDCPenChanger dummy (dc, col);
- wxDCBrushChanger dummy2(dc, col);
- dc.DrawRectangle(rect);
-}
-
-
-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
-
-inline wxColor getColorLabelGradientFrom() { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); }
-inline wxColor getColorLabelGradientTo () { return { 200, 200, 200 }; } //light grey
-
-inline wxColor getColorLabelGradientFocusFrom() { return getColorLabelGradientFrom(); }
-inline wxColor getColorLabelGradientFocusTo () { return Grid::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 bool fillGapAfterColumns = true; //draw rows/column label to fill full window width; may become an instance variable some time?
-}
-
-//----------------------------------------------------------------------------------------------------------------
-const wxEventType zen::EVENT_GRID_MOUSE_LEFT_DOUBLE = wxNewEventType();
-const wxEventType zen::EVENT_GRID_MOUSE_LEFT_DOWN = wxNewEventType();
-const wxEventType zen::EVENT_GRID_MOUSE_LEFT_UP = wxNewEventType();
-const wxEventType zen::EVENT_GRID_MOUSE_RIGHT_DOWN = wxNewEventType();
-const wxEventType zen::EVENT_GRID_MOUSE_RIGHT_UP = wxNewEventType();
-const wxEventType zen::EVENT_GRID_SELECT_RANGE = wxNewEventType();
-const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_LEFT = wxNewEventType();
-const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_RIGHT = wxNewEventType();
-const wxEventType zen::EVENT_GRID_COL_RESIZE = wxNewEventType();
-//----------------------------------------------------------------------------------------------------------------
-
-void GridData::renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected)
-{
- drawCellBackground(dc, rect, enabled, selected, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
-}
-
-
-void GridData::renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover)
-{
- wxRect rectTmp = drawCellBorder(dc, rect);
-
- rectTmp.x += COLUMN_GAP_LEFT;
- rectTmp.width -= COLUMN_GAP_LEFT;
- 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
-}
-
-
-wxRect GridData::drawCellBorder(wxDC& dc, const wxRect& rect) //returns remaining rectangle
-{
- wxDCPenChanger dummy2(dc, getColorGridLine());
- dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight());
- dc.DrawLine(rect.GetBottomRight(), rect.GetTopRight() + wxPoint(0, -1));
-
- return wxRect(rect.GetTopLeft(), wxSize(rect.width - 1, rect.height - 1));
-}
-
-
-void GridData::drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bool selected, const wxColor& backgroundColor)
-{
- if (enabled)
- {
- if (selected)
- dc.GradientFillLinear(rect, Grid::getColorSelectionGradientFrom(), Grid::getColorSelectionGradientTo(), wxEAST);
- else
- clearArea(dc, rect, backgroundColor);
- }
- else
- clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
-}
-
-
-wxSize GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& text, int alignment)
-{
- /*
- performance notes (Windows):
- - wxDC::GetTextExtent() is by far the most expensive call (20x more expensive than wxDC::DrawText())
- - wxDC::DrawLabel() is inefficiently implemented; internally calls: wxDC::GetMultiLineTextExtent(), wxDC::GetTextExtent(), wxDC::DrawText()
- - wxDC::GetMultiLineTextExtent() calls wxDC::GetTextExtent()
- - wxDC::DrawText also calls wxDC::GetTextExtent()!!
- => wxDC::DrawLabel() boils down to 3(!) calls to wxDC::GetTextExtent()!!!
- - wxDC::DrawLabel results in GetTextExtent() call even for empty strings!!!
- => skip the wxDC::DrawLabel() cruft and directly call wxDC::DrawText!
- */
-
- //truncate large texts and add ellipsis
- assert(!contains(text, L"\n"));
- const wchar_t ELLIPSIS = L'\u2026'; //"..."
-
- std::wstring textTrunc = text;
- wxSize extentTrunc = dc.GetTextExtent(textTrunc);
- if (extentTrunc.GetWidth() > rect.width)
- {
- //unlike Windows 7 Explorer, we truncate UTF-16 correctly: e.g. CJK-Ideogramm encodes to TWO wchar_t: utfCvrtTo<std::wstring>("\xf0\xa4\xbd\x9c");
- size_t low = 0; //number of unicode chars!
- size_t high = unicodeLength(text); //
- if (high > 1)
- for (;;)
- {
- const size_t middle = (low + high) / 2; //=> never 0 when "high - low > 1"
- if (high - low <= 1)
- {
- if (low == 0)
- {
- textTrunc = ELLIPSIS;
- extentTrunc = dc.GetTextExtent(ELLIPSIS);
- }
- break;
- }
-
- const std::wstring& candidate = std::wstring(strBegin(text), findUnicodePos(text, middle)) + ELLIPSIS;
- const wxSize extentCand = dc.GetTextExtent(candidate); //perf: most expensive call of this routine!
-
- if (extentCand.GetWidth() <= rect.width)
- {
- low = middle;
- textTrunc = candidate;
- extentTrunc = extentCand;
- }
- else
- high = middle;
- }
- }
-
- wxPoint pt = rect.GetTopLeft();
- if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0!
- pt.x += rect.width - extentTrunc.GetWidth();
- else if (alignment & wxALIGN_CENTER_HORIZONTAL)
- pt.x += (rect.width - extentTrunc.GetWidth()) / 2;
-
- if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0!
- pt.y += rect.height - extentTrunc.GetHeight();
- else if (alignment & wxALIGN_CENTER_VERTICAL)
- pt.y += (rect.height - extentTrunc.GetHeight()) / 2;
-
- RecursiveDcClipper clip(dc, rect);
- dc.DrawText(textTrunc, pt);
- return extentTrunc;
-}
-
-
-void GridData::renderColumnLabel(Grid& grid, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted)
-{
- wxRect rectTmp = drawColumnLabelBorder(dc, rect);
- drawColumnLabelBackground(dc, rectTmp, highlighted);
-
- rectTmp.x += COLUMN_GAP_LEFT;
- rectTmp.width -= COLUMN_GAP_LEFT;
- drawColumnLabelText(dc, rectTmp, getColumnLabel(colType));
-}
-
-
-wxRect GridData::drawColumnLabelBorder(wxDC& dc, const wxRect& rect) //returns remaining rectangle
-{
- //draw white line
- {
- wxDCPenChanger dummy(dc, *wxWHITE_PEN);
- dc.DrawLine(rect.GetTopLeft(), rect.GetBottomLeft());
- }
-
- //draw border (with gradient)
- {
- wxDCPenChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
- dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight()), getColorLabelGradientFrom(), dc.GetPen().GetColour(), wxSOUTH);
- dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0));
- }
-
- return wxRect(rect.x + 1, rect.y, rect.width - 2, rect.height - 1); //we really don't like wxRect::Deflate, do we?
-}
-
-
-void GridData::drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool highlighted)
-{
- if (highlighted)
- dc.GradientFillLinear(rect, getColorLabelGradientFocusFrom(), getColorLabelGradientFocusTo(), wxSOUTH);
- else //regular background gradient
- dc.GradientFillLinear(rect, getColorLabelGradientFrom(), getColorLabelGradientTo(), wxSOUTH); //clear overlapping cells
-}
-
-
-void GridData::drawColumnLabelText(wxDC& dc, const wxRect& rect, const std::wstring& text)
-{
- wxDCTextColourChanger dummy(dc, getColorLabelText()); //accessibility: always set both foreground AND background colors!
- drawCellText(dc, rect, text, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
-}
-
-//----------------------------------------------------------------------------------------------------------------
-/*
- SubWindow
- /|\
- |
- -----------------------------------
- | | | |
-CornerWin RowLabelWin ColLabelWin MainWin
-
-*/
-class Grid::SubWindow : public wxWindow
-{
-public:
- SubWindow(Grid& parent) :
- wxWindow(&parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxBORDER_NONE, wxPanelNameStr),
- parent_(parent)
- {
- 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);
-
- //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_CHILD_FOCUS, wxEventHandler(SubWindow::onChildFocus), nullptr, this);
-
- Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(SubWindow::onMouseLeftDown ), nullptr, this);
- Connect(wxEVT_LEFT_UP, wxMouseEventHandler(SubWindow::onMouseLeftUp ), nullptr, this);
- Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(SubWindow::onMouseLeftDouble), nullptr, this);
- Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(SubWindow::onMouseRightDown ), nullptr, this);
- Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(SubWindow::onMouseRightUp ), nullptr, this);
- Connect(wxEVT_MOTION, wxMouseEventHandler(SubWindow::onMouseMovement ), nullptr, this);
- Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(SubWindow::onLeaveWindow ), nullptr, this);
- Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(SubWindow::onMouseWheel ), nullptr, this);
- Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(SubWindow::onMouseCaptureLost), nullptr, this);
-
- Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(SubWindow::onKeyDown), nullptr, this);
-
- assert(GetClientAreaOrigin() == wxPoint()); //generally assumed when dealing with coordinates below
- }
- Grid& refParent() { return parent_; }
- const Grid& refParent() const { return parent_; }
-
- template <class T>
- bool sendEventNow(T&& event) //take both "rvalue + lvalues", return "true" if a suitable event handler function was found and executed, and the function did not call wxEvent::Skip.
- {
- if (wxEvtHandler* evtHandler = parent_.GetEventHandler())
- return evtHandler->ProcessEvent(event);
- return false;
- }
-
-protected:
- void setToolTip(const std::wstring& text) //proper fix for wxWindow
- {
- wxToolTip* tt = GetToolTip();
-
- const wxString oldText = tt ? tt->GetTip() : wxString();
- if (text != oldText)
- {
- if (text.empty())
- SetToolTip(nullptr); //wxGTK doesn't allow wxToolTip with empty text!
- else
- {
- //wxWidgets bug: tooltip multiline property is defined by first tooltip text containing newlines or not (same is true for maximum width)
- if (!tt)
- SetToolTip(new wxToolTip(L"a b\n\
- a b")); //ugly, but working (on Windows)
- tt = GetToolTip(); //should be bound by now
- assert(tt);
- if (tt)
- tt->SetTip(text);
- }
- }
- }
-
-private:
- virtual void render(wxDC& dc, const wxRect& rect) = 0;
-
- virtual void onFocus(wxFocusEvent& event) { event.Skip(); }
- virtual void onChildFocus(wxEvent& event) {} //wxGTK::wxScrolledWindow automatically scrolls to child window when child gets focus -> prevent!
-
- virtual void onMouseLeftDown (wxMouseEvent& event) { event.Skip(); }
- virtual void onMouseLeftUp (wxMouseEvent& event) { event.Skip(); }
- virtual void onMouseLeftDouble(wxMouseEvent& event) { event.Skip(); }
- virtual void onMouseRightDown (wxMouseEvent& event) { event.Skip(); }
- virtual void onMouseRightUp (wxMouseEvent& event) { event.Skip(); }
- virtual void onMouseMovement (wxMouseEvent& event) { event.Skip(); }
- virtual void onLeaveWindow (wxMouseEvent& event) { event.Skip(); }
- virtual void onMouseCaptureLost(wxMouseCaptureLostEvent& event) { event.Skip(); }
-
- void onKeyDown(wxKeyEvent& event)
- {
- if (!sendEventNow(event)) //let parent collect all key events
- event.Skip();
- }
-
- void onMouseWheel(wxMouseEvent& event)
- {
- /*
- MSDN, WM_MOUSEWHEEL: "Sent to the focus window when the mouse wheel is rotated.
- The DefWindowProc function propagates the message to the window's parent.
- There should be no internal forwarding of the message, since DefWindowProc propagates
- it up the parent chain until it finds a window that processes it."
-
- On OS X there is no such propagation! => we need a redirection (the same wxGrid implements)
- */
-
- //new wxWidgets 3.0 screw-up for GTK2: wxScrollHelperEvtHandler::ProcessEvent() ignores wxEVT_MOUSEWHEEL events
- //thereby breaking the scenario of redirection to parent we need here (but also breaking their very own wxGrid sample)
- //=> call wxScrolledWindow mouse wheel handler directly
- parent_.HandleOnMouseWheel(event);
-
- //if (!sendEventNow(event))
- // event.Skip();
- }
-
- void onPaintEvent(wxPaintEvent& event)
- {
-#ifndef NDEBUG
-#ifdef ZEN_WIN
- if (runningPaintEvent_ == true) //looks like showing the assert window here would quit the debug session
- __debugbreak();
-#else
- assert(runningPaintEvent_ == false); //catch unexpected recursion, e.g.: getIconByIndex() seems to run a message loop in rare cases!
-#endif
- runningPaintEvent_ = true;
- ZEN_ON_SCOPE_EXIT(runningPaintEvent_ = false);
-#endif
- //wxAutoBufferedPaintDC dc(this); -> this one happily fucks up for RTL layout by not drawing the first column (x = 0)!
- BufferedPaintDC dc(*this, doubleBuffer_);
-
- assert(GetSize() == GetClientSize());
-
- const wxRegion& updateReg = GetUpdateRegion();
- for (wxRegionIterator it = updateReg; it; ++it)
- render(dc, it.GetRect());
- }
-
- void onSizeEvent(wxSizeEvent& event)
- {
- Refresh();
- event.Skip();
- }
-
- void onEraseBackGround(wxEraseEvent& event) {}
-
- Grid& parent_;
- Opt<wxBitmap> doubleBuffer_;
-#ifndef NDEBUG
- bool runningPaintEvent_ = false;
-#endif
-};
-
-//----------------------------------------------------------------------------------------------------------------
-//----------------------------------------------------------------------------------------------------------------
-
-class Grid::CornerWin : public SubWindow
-{
-public:
- CornerWin(Grid& parent) : SubWindow(parent) {}
-
-private:
- bool AcceptsFocus() const override { return false; }
-
- void render(wxDC& dc, const wxRect& rect) override
- {
- const wxRect& clientRect = GetClientRect();
-
- dc.GradientFillLinear(clientRect, getColorLabelGradientFrom(), getColorLabelGradientTo(), wxSOUTH);
-
- dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
-
- {
- wxDCPenChanger dummy(dc, getColorLabelGradientFrom());
- dc.DrawLine(clientRect.GetTopLeft(), clientRect.GetTopRight());
- }
-
- dc.GradientFillLinear(wxRect(clientRect.GetBottomLeft (), clientRect.GetTopLeft ()), getColorLabelGradientFrom(), dc.GetPen().GetColour(), wxSOUTH);
- dc.GradientFillLinear(wxRect(clientRect.GetBottomRight(), clientRect.GetTopRight()), getColorLabelGradientFrom(), dc.GetPen().GetColour(), wxSOUTH);
-
- dc.DrawLine(clientRect.GetBottomLeft(), clientRect.GetBottomRight());
-
- wxRect rectShrinked = clientRect;
- rectShrinked.Deflate(1);
- dc.SetPen(*wxWHITE_PEN);
-
- //dc.DrawLine(clientRect.GetTopLeft(), clientRect.GetTopRight() + wxPoint(1, 0));
- dc.DrawLine(rectShrinked.GetTopLeft(), rectShrinked.GetBottomLeft() + wxPoint(0, 1));
- }
-};
-
-//----------------------------------------------------------------------------------------------------------------
-//----------------------------------------------------------------------------------------------------------------
-
-class Grid::RowLabelWin : public SubWindow
-{
-public:
- RowLabelWin(Grid& parent) :
- SubWindow(parent),
- rowHeight_(parent.GetCharHeight() + 2 + 1) {} //default height; don't call any functions on "parent" other than those from wxWindow during construction!
- //2 for some more space, 1 for bottom border (gives 15 + 2 + 1 on Windows, 17 + 2 + 1 on Ubuntu)
-
- int getBestWidth(ptrdiff_t rowFrom, ptrdiff_t rowTo)
- {
- wxClientDC dc(this);
-
- wxFont labelFont = GetFont();
- //labelFont.SetWeight(wxFONTWEIGHT_BOLD);
- dc.SetFont(labelFont); //harmonize with RowLabelWin::render()!
-
- int bestWidth = 0;
- for (ptrdiff_t i = rowFrom; i <= rowTo; ++i)
- bestWidth = std::max(bestWidth, dc.GetTextExtent(formatRow(i)).GetWidth() + 2 * ROW_LABEL_BORDER);
- return bestWidth;
- }
-
- size_t getLogicalHeight() const { return refParent().getRowCount() * rowHeight_; }
-
- ptrdiff_t getRowAtPos(ptrdiff_t posY) const //returns < 0 on invalid input, else row number within: [0, rowCount]; rowCount if out of range
- {
- if (posY >= 0 && rowHeight_ > 0)
- {
- const size_t row = posY / rowHeight_;
- return std::min(row, refParent().getRowCount());
- }
- return -1;
- }
-
- int getRowHeight() const { return rowHeight_; } //guarantees to return size >= 1 !
- void setRowHeight(int height) { assert(height > 0); rowHeight_ = std::max(1, height); }
-
- wxRect getRowLabelArea(size_t row) const //returns empty rect if row not found
- {
- assert(GetClientAreaOrigin() == wxPoint());
- if (row < refParent().getRowCount())
- return wxRect(wxPoint(0, rowHeight_ * row),
- wxSize(GetClientSize().GetWidth(), rowHeight_));
- return wxRect();
- }
-
- std::pair<ptrdiff_t, ptrdiff_t> getRowsOnClient(const wxRect& clientRect) const //returns range [begin, end)
- {
- const int yFrom = refParent().CalcUnscrolledPosition(clientRect.GetTopLeft ()).y;
- const int yTo = refParent().CalcUnscrolledPosition(clientRect.GetBottomRight()).y;
-
- return std::make_pair(std::max(yFrom / rowHeight_, 0),
- std::min<ptrdiff_t>((yTo / rowHeight_) + 1, refParent().getRowCount()));
- }
-
-private:
- static std::wstring formatRow(size_t row) { return toGuiString(row + 1); } //convert number to std::wstring including thousands separator
-
- bool AcceptsFocus() const override { return false; }
-
- void render(wxDC& dc, const wxRect& rect) override
- {
- /*
- IsEnabled() vs IsThisEnabled() since wxWidgets 2.9.5:
-
- void wxWindowBase::NotifyWindowOnEnableChange(), called from bool wxWindowBase::Enable(), fails to refresh
- child elements when disabling a IsTopLevel() dialog, e.g. when showing a modal dialog.
- The unfortunate effect on XP for using IsEnabled() when rendering the grid is that the user can move the modal dialog
- and *draw* with it on the background while the grid refreshes as disabled incrementally!
-
- => Don't use IsEnabled() since it considers the top level window. The brittle wxWidgets implementation is right in their intention,
- but wrong when not refreshing child-windows: the control designer decides how his control should be rendered!
-
- => IsThisEnabled() OTOH is too shallow and does not consider parent windows which are not top level.
-
- The perfect solution would be a bool ShouldBeDrawnActive() { return "IsEnabled() but ignore effects of showing a modal dialog"; }
-
- However "IsThisEnabled()" is good enough (same like the old IsEnabled() on wxWidgets 2.8.12) and it avoids this pathetic behavior on XP.
- (Similar problem on Win 7: e.g. directly click sync button without comparing first)
- */
- if (IsThisEnabled())
- clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
- else
- clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
-
- wxFont labelFont = GetFont();
- //labelFont.SetWeight(wxFONTWEIGHT_BOLD);
- dc.SetFont(labelFont); //harmonize with RowLabelWin::getBestWidth()!
-
- auto rowRange = getRowsOnClient(rect); //returns range [begin, end)
- for (auto row = rowRange.first; row < rowRange.second; ++row)
- {
- wxRect singleLabelArea = getRowLabelArea(row); //returns empty rect if row not found
- if (singleLabelArea.height > 0)
- {
- singleLabelArea.y = refParent().CalcScrolledPosition(singleLabelArea.GetTopLeft()).y;
- drawRowLabel(dc, singleLabelArea, row);
- }
- }
- }
-
- void drawRowLabel(wxDC& dc, const wxRect& rect, size_t row)
- {
- //clearArea(dc, rect, getColorRowLabel());
- dc.GradientFillLinear(rect, getColorLabelGradientFrom(), getColorLabelGradientTo(), wxEAST); //clear overlapping cells
- wxDCTextColourChanger dummy3(dc, getColorLabelText()); //accessibility: always set both foreground AND background colors!
-
- //label text
- wxRect textRect = rect;
- textRect.Deflate(1);
-
- GridData::drawCellText(dc, textRect, formatRow(row), wxALIGN_CENTRE);
-
- //border lines
- {
- wxDCPenChanger dummy(dc, *wxWHITE_PEN);
- dc.DrawLine(rect.GetTopLeft(), rect.GetTopRight());
- }
- {
- wxDCPenChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
- dc.DrawLine(rect.GetTopLeft(), rect.GetBottomLeft());
- dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight());
- dc.DrawLine(rect.GetBottomRight(), rect.GetTopRight() + wxPoint(0, -1));
- }
- }
-
- void onMouseLeftDown(wxMouseEvent& event) override { refParent().redirectRowLabelEvent(event); }
- void onMouseMovement(wxMouseEvent& event) override { refParent().redirectRowLabelEvent(event); }
- void onMouseLeftUp (wxMouseEvent& event) override { refParent().redirectRowLabelEvent(event); }
-
- int rowHeight_;
-};
-
-
-namespace
-{
-class ColumnResizing
-{
-public:
- ColumnResizing(wxWindow& wnd, size_t col, int startWidth, int clientPosX) :
- wnd_(wnd), col_(col), startWidth_(startWidth), clientPosX_(clientPosX)
- {
- wnd_.CaptureMouse();
- }
- ~ColumnResizing()
- {
- if (wnd_.HasCapture())
- wnd_.ReleaseMouse();
- }
-
- size_t getColumn () const { return col_; }
- int getStartWidth () const { return startWidth_; }
- int getStartPosX () const { return clientPosX_; }
-
-private:
- wxWindow& wnd_;
- const size_t col_;
- const int startWidth_;
- const int clientPosX_;
-};
-
-
-class ColumnMove
-{
-public:
- ColumnMove(wxWindow& wnd, size_t colFrom, int clientPosX) :
- wnd_(wnd),
- colFrom_(colFrom),
- colTo_(colFrom),
- clientPosX_(clientPosX) { wnd_.CaptureMouse(); }
- ~ColumnMove() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); }
-
- size_t getColumnFrom() const { return colFrom_; }
- size_t& refColumnTo() { return colTo_; }
- int getStartPosX () const { return clientPosX_; }
-
- bool isRealMove() const { return !singleClick_; }
- void setRealMove() { singleClick_ = false; }
-
-private:
- wxWindow& wnd_;
- const size_t colFrom_;
- size_t colTo_;
- const int clientPosX_;
- bool singleClick_ = true;
-};
-}
-
-//----------------------------------------------------------------------------------------------------------------
-
-class Grid::ColLabelWin : public SubWindow
-{
-public:
- ColLabelWin(Grid& parent) : SubWindow(parent) {}
-
-private:
- bool AcceptsFocus() const override { return false; }
-
- void render(wxDC& dc, const wxRect& rect) override
- {
- if (IsThisEnabled())
- clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
- else
- clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
-
- //coordinate with "colLabelHeight" in Grid constructor:
- wxFont labelFont = GetFont();
- labelFont.SetWeight(wxFONTWEIGHT_BOLD);
- dc.SetFont(labelFont);
-
- wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
-
- const int colLabelHeight = refParent().colLabelHeight_;
-
- wxPoint labelAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0)).x, 0); //client coordinates
-
- std::vector<ColumnWidth> absWidths = refParent().getColWidths(); //resolve stretched widths
- for (auto it = absWidths.begin(); it != absWidths.end(); ++it)
- {
- const size_t col = it - absWidths.begin();
- const int width = it->width_; //don't use unsigned for calculations!
-
- if (labelAreaTL.x > rect.GetRight())
- return; //done, rect is fully covered
- if (labelAreaTL.x + width > rect.x)
- drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(width, colLabelHeight)), col, it->type_);
- labelAreaTL.x += width;
- }
- if (labelAreaTL.x > rect.GetRight())
- return; //done, rect is fully covered
-
- //fill gap after columns and cover full width
- if (fillGapAfterColumns)
- {
- int totalWidth = 0;
- for (const ColumnWidth& cw : absWidths)
- totalWidth += cw.width_;
- const int clientWidth = GetClientSize().GetWidth(); //need reliable, stable width in contrast to rect.width
-
- if (totalWidth < clientWidth)
- drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(clientWidth - totalWidth, colLabelHeight)), absWidths.size(), ColumnType::NONE);
- }
- }
-
- void drawColumnLabel(wxDC& dc, const wxRect& rect, size_t col, ColumnType colType)
- {
- if (auto dataView = refParent().getDataProvider())
- {
- const bool isHighlighted = activeResizing_ ? col == activeResizing_ ->getColumn () : //highlight_ column on mouse-over
- activeClickOrMove_ ? col == activeClickOrMove_->getColumnFrom() :
- highlightCol_ ? col == *highlightCol_ :
- false;
-
- RecursiveDcClipper clip(dc, rect);
- dataView->renderColumnLabel(refParent(), dc, rect, colType, isHighlighted);
-
- //draw move target location
- if (refParent().allowColumnMove_)
- if (activeClickOrMove_ && activeClickOrMove_->isRealMove())
- {
- 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);
- else if (col == activeClickOrMove_->refColumnTo() && col == 0) //pos 0
- dc.GradientFillLinear(wxRect(rect.GetTopLeft(), rect.GetBottomLeft() + wxPoint(2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH);
- }
- }
- }
-
- void onMouseLeftDown(wxMouseEvent& event) override
- {
- if (FindFocus() != &refParent().getMainWin())
- refParent().getMainWin().SetFocus();
-
- activeResizing_.reset();
- activeClickOrMove_.reset();
-
- if (Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()))
- {
- if (action->wantResize)
- {
- if (!event.LeftDClick()) //double-clicks never seem to arrive here; why is this checked at all???
- if (Opt<int> colWidth = refParent().getColWidth(action->col))
- activeResizing_ = std::make_unique<ColumnResizing>(*this, action->col, *colWidth, event.GetPosition().x);
- }
- else //a move or single click
- activeClickOrMove_ = std::make_unique<ColumnMove>(*this, action->col, event.GetPosition().x);
- }
- event.Skip();
- }
-
- void onMouseLeftUp(wxMouseEvent& event) override
- {
- activeResizing_.reset(); //nothing else to do, actual work done by onMouseMovement()
-
- if (activeClickOrMove_)
- {
- if (activeClickOrMove_->isRealMove())
- {
- if (refParent().allowColumnMove_)
- {
- const size_t colFrom = activeClickOrMove_->getColumnFrom();
- size_t colTo = activeClickOrMove_->refColumnTo();
-
- if (colTo > colFrom) //simulate "colFrom" deletion
- --colTo;
-
- refParent().moveColumn(colFrom, colTo);
- }
- }
- else //notify single label click
- {
- if (const Opt<ColumnType> colType = refParent().colToType(activeClickOrMove_->getColumnFrom()))
- sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_LEFT, event, *colType));
- }
- activeClickOrMove_.reset();
- }
-
- refParent().updateWindowSizes(); //looks strange if done during onMouseMovement()
- refParent().Refresh();
- event.Skip();
- }
-
- void onMouseCaptureLost(wxMouseCaptureLostEvent& event) override
- {
- activeResizing_.reset();
- activeClickOrMove_.reset();
- Refresh();
- //event.Skip(); -> we DID handle it!
- }
-
- void onMouseLeftDouble(wxMouseEvent& event) override
- {
- if (Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()))
- if (action->wantResize)
- {
- //auto-size visible range on double-click
- const int bestWidth = refParent().getBestColumnSize(action->col); //return -1 on error
- if (bestWidth >= 0)
- {
- refParent().setColumnWidth(bestWidth, action->col, ALLOW_GRID_EVENT);
- refParent().Refresh(); //refresh main grid as well!
- }
- }
- event.Skip();
- }
-
- void onMouseMovement(wxMouseEvent& event) override
- {
- if (activeResizing_)
- {
- const auto col = activeResizing_->getColumn();
- const int newWidth = activeResizing_->getStartWidth() + event.GetPosition().x - activeResizing_->getStartPosX();
-
- //set width tentatively
- refParent().setColumnWidth(newWidth, col, ALLOW_GRID_EVENT);
-
- //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)
- refParent().setColumnWidth(newWidth + gapWidth, col, ALLOW_GRID_EVENT);
-
- refParent().Refresh(); //refresh columns on main grid as well!
- }
- else if (activeClickOrMove_)
- {
- const int clientPosX = event.GetPosition().x;
- if (std::abs(clientPosX - activeClickOrMove_->getStartPosX()) > COLUMN_MOVE_DELAY) //real move (not a single click)
- {
- activeClickOrMove_->setRealMove();
-
- const ptrdiff_t col = refParent().clientPosToMoveTargetColumn(event.GetPosition());
- if (col >= 0)
- activeClickOrMove_->refColumnTo() = col;
- }
- }
- else
- {
- if (const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()))
- {
- highlightCol_ = action->col;
-
- if (action->wantResize)
- SetCursor(wxCURSOR_SIZEWE); //set window-local only! :)
- else
- SetCursor(*wxSTANDARD_CURSOR);
- }
- else
- {
- highlightCol_ = NoValue();
- SetCursor(*wxSTANDARD_CURSOR);
- }
- }
-
- const std::wstring toolTip = [&]
- {
- const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
- const ColumnType colType = refParent().getColumnAtPos(absPos.x).colType; //returns ColumnType::NONE if no column at x position!
- if (colType != ColumnType::NONE)
- if (auto prov = refParent().getDataProvider())
- return prov->getToolTip(colType);
- return std::wstring();
- }();
- setToolTip(toolTip);
-
- Refresh();
- event.Skip();
- }
-
- void onLeaveWindow(wxMouseEvent& event) override
- {
- highlightCol_ = NoValue(); //wxEVT_LEAVE_WINDOW does not respect mouse capture! -> however highlight_ is drawn unconditionally during move/resize!
- Refresh();
- event.Skip();
- }
-
- void onMouseRightDown(wxMouseEvent& event) override
- {
- if (const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()))
- {
- if (const Opt<ColumnType> colType = refParent().colToType(action->col))
- sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, *colType)); //notify right click
- else assert(false);
- }
- else
- //notify right click (on free space after last column)
- if (fillGapAfterColumns)
- sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, ColumnType::NONE));
-
- event.Skip();
- }
-
- std::unique_ptr<ColumnResizing> activeResizing_;
- std::unique_ptr<ColumnMove> activeClickOrMove_;
- Opt<size_t> highlightCol_; //column during mouse-over
-};
-
-//----------------------------------------------------------------------------------------------------------------
-namespace
-{
-const wxEventType EVENT_GRID_HAS_SCROLLED = wxNewEventType(); //internal to Grid::MainWin::ScrollWindow()
-}
-//----------------------------------------------------------------------------------------------------------------
-
-class Grid::MainWin : public SubWindow
-{
-public:
- MainWin(Grid& parent,
- RowLabelWin& rowLabelWin,
- ColLabelWin& colLabelWin) : SubWindow(parent),
- rowLabelWin_(rowLabelWin),
- colLabelWin_(colLabelWin)
- {
- Connect(EVENT_GRID_HAS_SCROLLED, wxEventHandler(MainWin::onRequestWindowUpdate), nullptr, this);
- }
-
- ~MainWin() { assert(!gridUpdatePending_); }
-
- size_t getCursor() const { return cursorRow_; }
- size_t getAnchor() const { return selectionAnchor_; }
-
- void setCursor(size_t newCursorRow, size_t newAnchorRow)
- {
- cursorRow_ = newCursorRow;
- selectionAnchor_ = newAnchorRow;
- activeSelection_.reset(); //e.g. user might search with F3 while holding down left mouse button
- }
-
-private:
- void render(wxDC& dc, const wxRect& rect) override
- {
- if (IsThisEnabled())
- clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
- else
- clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
-
- dc.SetFont(GetFont()); //harmonize with Grid::getBestColumnSize()
-
- wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
-
- std::vector<ColumnWidth> absWidths = refParent().getColWidths(); //resolve stretched widths
- {
- int totalRowWidth = 0;
- for (const ColumnWidth& cw : absWidths)
- totalRowWidth += cw.width_;
-
- //fill gap after columns and cover full width
- if (fillGapAfterColumns)
- totalRowWidth = std::max(totalRowWidth, GetClientSize().GetWidth());
-
- if (auto prov = refParent().getDataProvider())
- {
- RecursiveDcClipper dummy2(dc, rect); //do NOT draw background on cells outside of invalidated rect invalidating foreground text!
-
- wxPoint cellAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0))); //client coordinates
- const int rowHeight = rowLabelWin_.getRowHeight();
- const auto rowRange = rowLabelWin_.getRowsOnClient(rect); //returns range [begin, end)
-
- //draw background lines
- for (auto row = rowRange.first; row < rowRange.second; ++row)
- {
- const wxRect rowRect(cellAreaTL + wxPoint(0, row * rowHeight), wxSize(totalRowWidth, rowHeight));
- RecursiveDcClipper dummy3(dc, rowRect);
- prov->renderRowBackgound(dc, rowRect, row, refParent().IsThisEnabled(), drawAsSelected(row));
- }
-
- //draw single cells, column by column
- for (const ColumnWidth& cw : absWidths)
- {
- if (cellAreaTL.x > rect.GetRight())
- return; //done
-
- if (cellAreaTL.x + cw.width_ > rect.x)
- for (auto row = rowRange.first; row < rowRange.second; ++row)
- {
- const wxRect cellRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, cw.width_, rowHeight);
- RecursiveDcClipper dummy3(dc, cellRect);
- prov->renderCell(dc, cellRect, row, cw.type_, refParent().IsThisEnabled(), drawAsSelected(row), getRowHoverToDraw(row));
- }
- cellAreaTL.x += cw.width_;
- }
- }
- }
- }
-
- HoverArea getRowHoverToDraw(ptrdiff_t row) const
- {
- if (activeSelection_)
- {
- if (activeSelection_->getFirstClick().row_ == row)
- return activeSelection_->getFirstClick().hoverArea_;
- }
- else if (highlight_.row == row)
- return highlight_.rowHover;
- return HoverArea::NONE;
- }
-
- bool drawAsSelected(size_t row) const
- {
- if (activeSelection_) //check if user is currently selecting with mouse
- {
- const size_t rowFrom = std::min(activeSelection_->getStartRow(), activeSelection_->getCurrentRow());
- const size_t rowTo = std::max(activeSelection_->getStartRow(), activeSelection_->getCurrentRow());
-
- if (rowFrom <= row && row <= rowTo)
- return activeSelection_->isPositiveSelect(); //overwrite default
- }
- return refParent().isSelected(row);
- }
-
- void onMouseLeftDown (wxMouseEvent& event) override { onMouseDown(event); }
- void onMouseLeftUp (wxMouseEvent& event) override { onMouseUp (event); }
- void onMouseRightDown(wxMouseEvent& event) override { onMouseDown(event); }
- void onMouseRightUp (wxMouseEvent& event) override { onMouseUp (event); }
-
- void onMouseLeftDouble(wxMouseEvent& event) override
- {
- if (auto prov = refParent().getDataProvider())
- {
- const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
- const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
- const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position!
- const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth);
- //client is interested in all double-clicks, even those outside of the grid!
- sendEventNow(GridClickEvent(EVENT_GRID_MOUSE_LEFT_DOUBLE, event, row, rowHover));
- }
- event.Skip();
- }
-
- void onMouseDown(wxMouseEvent& event) //handle left and right mouse button clicks (almost) the same
- {
- if (wxWindow::FindFocus() != this) //doesn't seem to happen automatically for right mouse button
- SetFocus();
-
- if (auto prov = refParent().getDataProvider())
- {
- const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
- const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
- const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position!
- const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth);
- //row < 0 possible!!! Pressing "Menu key" simulates Mouse Right Down + Up at position 0xffff/0xffff!
-
- GridClickEvent mouseEvent(event.RightDown() ? EVENT_GRID_MOUSE_RIGHT_DOWN : EVENT_GRID_MOUSE_LEFT_DOWN, event, row, rowHover);
-
- if (row >= 0)
- if (!event.RightDown() || !refParent().isSelected(row)) //do NOT start a new selection if user right-clicks on a selected area!
- {
- if (event.ControlDown())
- activeSelection_ = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row), mouseEvent);
- else if (event.ShiftDown())
- {
- activeSelection_ = std::make_unique<MouseSelection>(*this, selectionAnchor_, true, mouseEvent);
- refParent().clearSelection(ALLOW_GRID_EVENT);
- }
- else
- {
- activeSelection_ = std::make_unique<MouseSelection>(*this, row, true, mouseEvent);
- refParent().clearSelection(ALLOW_GRID_EVENT);
- }
- }
- //notify event *after* potential "clearSelection(true)" above: a client should first receive a GridRangeSelectEvent for clearing the grid, if necessary,
- //then GridClickEvent and the associated GridRangeSelectEvent one after the other
- sendEventNow(mouseEvent);
-
- Refresh();
- }
- event.Skip(); //allow changing focus
- }
-
- void onMouseUp(wxMouseEvent& event)
- {
- if (activeSelection_)
- {
- const size_t rowCount = refParent().getRowCount();
- if (rowCount > 0)
- {
- if (activeSelection_->getCurrentRow() < rowCount)
- {
- cursorRow_ = activeSelection_->getCurrentRow();
- selectionAnchor_ = activeSelection_->getStartRow(); //allowed to be "out of range"
- }
- else if (activeSelection_->getStartRow() < rowCount) //don't change cursor if "to" and "from" are out of range
- {
- cursorRow_ = rowCount - 1;
- selectionAnchor_ = activeSelection_->getStartRow(); //allowed to be "out of range"
- }
- else //total selection "out of range"
- selectionAnchor_ = cursorRow_;
- }
- //slight deviation from Explorer: change cursor while dragging mouse! -> unify behavior with shift + direction keys
-
- refParent().selectRangeAndNotify(activeSelection_->getStartRow (), //from
- activeSelection_->getCurrentRow(), //to
- activeSelection_->isPositiveSelect(),
- &activeSelection_->getFirstClick());
- activeSelection_.reset();
- }
-
- if (auto prov = refParent().getDataProvider())
- {
- //this one may point to row which is not in visible area!
- const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
- const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
- const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position!
- const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth);
- //notify click event after the range selection! e.g. this makes sure the selection is applied before showing a context menu
- sendEventNow(GridClickEvent(event.RightUp() ? EVENT_GRID_MOUSE_RIGHT_UP : EVENT_GRID_MOUSE_LEFT_UP, event, row, rowHover));
- }
-
- //update highlight_ and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows)
- event.SetPosition(ScreenToClient(wxGetMousePosition())); //mouse position may have changed within above callbacks (e.g. context menu was shown)!
- onMouseMovement(event);
-
- Refresh();
- event.Skip(); //allow changing focus
- }
-
- void onMouseCaptureLost(wxMouseCaptureLostEvent& event) override
- {
- activeSelection_.reset();
- highlight_.row = -1;
- Refresh();
- //event.Skip(); -> we DID handle it!
- }
-
- void onMouseMovement(wxMouseEvent& event) override
- {
- if (auto prov = refParent().getDataProvider())
- {
- const ptrdiff_t rowCount = refParent().getRowCount();
- const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
- const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
- const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position!
- const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth);
-
- const std::wstring toolTip = [&]
- {
- if (cpi.colType != ColumnType::NONE && 0 <= row && row < rowCount)
- return prov->getToolTip(row, cpi.colType);
- return std::wstring();
- }();
- setToolTip(toolTip); //show even during mouse selection!
-
- if (activeSelection_)
- activeSelection_->evalMousePos(); //call on both mouse movement + timer event!
- else
- {
- refreshHighlight(highlight_);
- highlight_.row = row;
- highlight_.rowHover = rowHover;
- refreshHighlight(highlight_); //multiple Refresh() calls are condensed into single one!
- }
- }
- event.Skip();
- }
-
- void onLeaveWindow(wxMouseEvent& event) override //wxEVT_LEAVE_WINDOW does not respect mouse capture!
- {
- if (!activeSelection_)
- {
- refreshHighlight(highlight_);
- highlight_.row = -1;
- }
-
- event.Skip();
- }
-
-
- void onFocus(wxFocusEvent& event) override { Refresh(); event.Skip(); }
-
- class MouseSelection : private wxEvtHandler
- {
- public:
- MouseSelection(MainWin& wnd, size_t rowStart, bool positiveSelect, const GridClickEvent& firstClick) :
- wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positiveSelect), firstClick_(firstClick)
- {
- wnd_.CaptureMouse();
- timer_.Connect(wxEVT_TIMER, wxEventHandler(MouseSelection::onTimer), nullptr, this);
- timer_.Start(100); //timer interval in ms
- evalMousePos();
- }
- ~MouseSelection() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); }
-
- size_t getStartRow () const { return rowStart_; }
- size_t getCurrentRow () const { return rowCurrent_; }
- bool isPositiveSelect() const { return positiveSelect_; } //are we selecting or unselecting?
- const GridClickEvent& getFirstClick() const { return firstClick_; }
-
- void evalMousePos()
- {
- const auto now = std::chrono::steady_clock::now();
- const double deltaSecs = std::chrono::duration<double>(now - lastEvalTime_).count(); //unit: [sec]
- lastEvalTime_ = now;
-
- const wxPoint clientPos = wnd_.ScreenToClient(wxGetMousePosition());
- const wxSize clientSize = wnd_.GetClientSize();
- assert(wnd_.GetClientAreaOrigin() == wxPoint());
-
- //scroll while dragging mouse
- const int overlapPixY = clientPos.y < 0 ? clientPos.y :
- clientPos.y >= clientSize.GetHeight() ? clientPos.y - (clientSize.GetHeight() - 1) : 0;
- const int overlapPixX = clientPos.x < 0 ? clientPos.x :
- clientPos.x >= clientSize.GetWidth() ? clientPos.x - (clientSize.GetWidth() - 1) : 0;
-
- int pixelsPerUnitY = 0;
- 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)]
-
- auto autoScroll = [&](int overlapPix, double& toScroll)
- {
- if (overlapPix != 0)
- {
- const double scrollSpeed = overlapPix * mouseDragSpeedIncScrollU; //unit: [scroll units / sec]
- toScroll += scrollSpeed * deltaSecs;
- }
- else
- toScroll = 0;
- };
-
- autoScroll(overlapPixX, toScrollX_);
- autoScroll(overlapPixY, toScrollY_);
-
- if (static_cast<int>(toScrollX_) != 0 || static_cast<int>(toScrollY_) != 0)
- {
- wnd_.refParent().scrollDelta(static_cast<int>(toScrollX_), static_cast<int>(toScrollY_)); //
- toScrollX_ -= static_cast<int>(toScrollX_); //rounds down for positive numbers, up for negative,
- toScrollY_ -= static_cast<int>(toScrollY_); //exactly what we want
- }
-
- //select current row *after* scrolling
- wxPoint clientPosTrimmed = clientPos;
- numeric::clamp(clientPosTrimmed.y, 0, clientSize.GetHeight() - 1); //do not select row outside client window!
-
- const wxPoint absPos = wnd_.refParent().CalcUnscrolledPosition(clientPosTrimmed);
- const ptrdiff_t newRow = wnd_.rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
- if (newRow >= 0)
- if (rowCurrent_ != newRow)
- {
- rowCurrent_ = newRow;
- wnd_.Refresh();
- }
- }
-
- private:
- void onTimer(wxEvent& event) { evalMousePos(); }
-
- MainWin& wnd_;
- const size_t rowStart_;
- ptrdiff_t rowCurrent_;
- const bool positiveSelect_;
- const GridClickEvent firstClick_;
- wxTimer timer_;
- double toScrollX_ = 0; //count outstanding scroll unit fractions while dragging mouse
- double toScrollY_ = 0; //
- std::chrono::steady_clock::time_point lastEvalTime_ = std::chrono::steady_clock::now();
- };
-
- struct MouseHighlight
- {
- ptrdiff_t row = -1;
- HoverArea rowHover = HoverArea::NONE;
- };
-
- void ScrollWindow(int dx, int dy, const wxRect* rect) override
- {
- wxWindow::ScrollWindow(dx, dy, rect);
- rowLabelWin_.ScrollWindow(0, dy, rect);
- colLabelWin_.ScrollWindow(dx, 0, rect);
-
- //attention, wxGTK call sequence: wxScrolledWindow::Scroll() -> wxScrolledHelperNative::Scroll() -> wxScrolledHelperNative::DoScroll()
- //which *first* calls us, MainWin::ScrollWindow(), and *then* internally updates m_yScrollPosition
- //=> we cannot use CalcUnscrolledPosition() here which gives the wrong/outdated value!!!
- //=> we need to update asynchronously:
- //=> don't send async event repeatedly => severe performance issues on wxGTK!
- //=> can't use idle event neither: too few idle events on Windows, e.g. NO idle events while mouse drag-scrolling!
- //=> solution: send single async event at most!
- if (!gridUpdatePending_) //without guarding, the number of outstanding async events can become very high during scrolling!! test case: Ubuntu: 170; Windows: 20
- {
- gridUpdatePending_ = true;
- wxCommandEvent scrollEvent(EVENT_GRID_HAS_SCROLLED);
- AddPendingEvent(scrollEvent); //asynchronously call updateAfterScroll()
- }
- }
-
- void onRequestWindowUpdate(wxEvent& event)
- {
- assert(gridUpdatePending_);
- ZEN_ON_SCOPE_EXIT(gridUpdatePending_ = false);
-
- refParent().updateWindowSizes(false); //row label width has changed -> do *not* update scrollbars: recursion on wxGTK! -> still a problem, now that we're called async??
- rowLabelWin_.Update(); //update while dragging scroll thumb
- }
-
- void refreshRow(size_t row)
- {
- const wxRect& rowArea = rowLabelWin_.getRowLabelArea(row); //returns empty rect if row not found
- const wxPoint topLeft = refParent().CalcScrolledPosition(wxPoint(0, rowArea.y)); //absolute -> client coordinates
- wxRect cellArea(topLeft, wxSize(refParent().getColWidthsSum(GetClientSize().GetWidth()), rowArea.height));
- RefreshRect(cellArea, false);
- }
-
- void refreshHighlight(const MouseHighlight& hl)
- {
- const ptrdiff_t rowCount = refParent().getRowCount();
- if (0 <= hl.row && hl.row < rowCount && hl.rowHover != HoverArea::NONE) //no highlight_? => NOP!
- refreshRow(hl.row);
- }
-
- RowLabelWin& rowLabelWin_;
- ColLabelWin& colLabelWin_;
-
- std::unique_ptr<MouseSelection> activeSelection_; //bound while user is selecting with mouse
- MouseHighlight highlight_; //current mouse highlight_ (superseeded by activeSelection_ if available)
-
- ptrdiff_t cursorRow_ = 0;
- size_t selectionAnchor_ = 0;
- bool gridUpdatePending_ = false;
-};
-
-//----------------------------------------------------------------------------------------------------------------
-//----------------------------------------------------------------------------------------------------------------
-
-Grid::Grid(wxWindow* parent,
- wxWindowID id,
- const wxPoint& pos,
- const wxSize& size,
- long style,
- const wxString& name) : wxScrolledWindow(parent, id, pos, size, style | wxWANTS_CHARS, name)
-{
- cornerWin_ = new CornerWin (*this); //
- rowLabelWin_ = new RowLabelWin(*this); //owership handled by "this"
- colLabelWin_ = new ColLabelWin(*this); //
- mainWin_ = new MainWin (*this, *rowLabelWin_, *colLabelWin_); //
-
- colLabelHeight_ = 2 * DEFAULT_COL_LABEL_BORDER + [&]() -> int
- {
- //coordinate with ColLabelWin::render():
- wxFont labelFont = colLabelWin_->GetFont();
- labelFont.SetWeight(wxFONTWEIGHT_BOLD);
- return labelFont.GetPixelSize().GetHeight();
- }();
-
- SetTargetWindow(mainWin_);
-
- SetInitialSize(size); //"Most controls will use this to set their initial size" -> why not
-
- 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_KEY_DOWN, wxKeyEventHandler(Grid::onKeyDown), nullptr, this);
-}
-
-
-void Grid::updateWindowSizes(bool updateScrollbar)
-{
- /* We have to deal with TWO nasty circular dependencies:
- 1.
- rowLabelWidth
- /|\
- mainWin::client width
- /|\
- SetScrollbars -> show/hide horizontal scrollbar depending on client width
- /|\
- mainWin::client height -> possibly trimmed by horizontal scrollbars
- /|\
- rowLabelWidth
-
- 2.
- mainWin_->GetClientSize()
- /|\
- SetScrollbars -> show/hide scrollbars depending on whether client size is big enough
- /|\
- GetClientSize(); -> possibly trimmed by scrollbars
- /|\
- mainWin_->GetClientSize() -> also trimmed, since it's a sub-window!
- */
-
- //break this vicious circle:
-
- //harmonize with Grid::GetSizeAvailableForScrollTarget()!
-
- //1. calculate row label width independent from scrollbars
- const int mainWinHeightGross = std::max(GetSize().GetHeight() - colLabelHeight_, 0); //independent from client sizes and scrollbars!
- const ptrdiff_t logicalHeight = rowLabelWin_->getLogicalHeight(); //
-
- int rowLabelWidth = 0;
- if (drawRowLabel_ && logicalHeight > 0)
- {
- ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y;
- ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeightGross - 1)).y ;
- numeric::clamp<ptrdiff_t>(yFrom, 0, logicalHeight - 1);
- numeric::clamp<ptrdiff_t>(yTo, 0, logicalHeight - 1);
-
- const ptrdiff_t rowFrom = rowLabelWin_->getRowAtPos(yFrom);
- const ptrdiff_t rowTo = rowLabelWin_->getRowAtPos(yTo);
- if (rowFrom >= 0 && rowTo >= 0)
- rowLabelWidth = rowLabelWin_->getBestWidth(rowFrom, rowTo);
- }
-
- auto getMainWinSize = [&](const wxSize& clientSize) { return wxSize(std::max(0, clientSize.GetWidth() - rowLabelWidth), std::max(0, clientSize.GetHeight() - colLabelHeight_)); };
-
- auto setScrollbars2 = [&](int logWidth, int logHeight) //replace SetScrollbars, which loses precision of pixelsPerUnitX for some brain-dead reason
- {
- mainWin_->SetVirtualSize(logWidth, logHeight); //set before calling SetScrollRate():
- //else SetScrollRate() would fail to preserve scroll position when "new virtual pixel-pos > old virtual height"
-
- int ppsuX = 0; //pixel per scroll unit
- int ppsuY = 0;
- GetScrollPixelsPerUnit(&ppsuX, &ppsuY);
-
- const int ppsuNew = rowLabelWin_->getRowHeight();
- if (ppsuX != ppsuNew || ppsuY != ppsuNew) //support polling!
- SetScrollRate(ppsuNew, ppsuNew); //internally calls AdjustScrollbars() and GetVirtualSize()!
-
- AdjustScrollbars(); //lousy wxWidgets design decision: internally calls mainWin_->GetClientSize() without considering impact of scrollbars!
- //Attention: setting scrollbars triggers *synchronous* resize event if scrollbars are shown or hidden! => updateWindowSizes() recursion! (Windows)
- };
-
- //2. update managed windows' sizes: just assume scrollbars are already set correctly, even if they may not be (yet)!
- //this ensures mainWin_->SetVirtualSize() and AdjustScrollbars() are working with the correct main window size, unless sb change later, which triggers a recalculation anyway!
- const wxSize mainWinSize = getMainWinSize(GetClientSize());
-
- cornerWin_ ->SetSize(0, 0, rowLabelWidth, colLabelHeight_);
- rowLabelWin_->SetSize(0, colLabelHeight_, rowLabelWidth, mainWinSize.GetHeight());
- colLabelWin_->SetSize(rowLabelWidth, 0, mainWinSize.GetWidth(), colLabelHeight_);
- mainWin_ ->SetSize(rowLabelWidth, colLabelHeight_, mainWinSize.GetWidth(), mainWinSize.GetHeight());
-
- //avoid flicker in wxWindowMSW::HandleSize() when calling ::EndDeferWindowPos() where the sub-windows are moved only although they need to be redrawn!
- colLabelWin_->Refresh();
- mainWin_ ->Refresh();
-
- //3. update scrollbars: "guide wxScrolledHelper to not screw up too much"
- if (updateScrollbar)
- {
- const int mainWinWidthGross = getMainWinSize(GetSize()).GetWidth();
-
- if (logicalHeight <= mainWinHeightGross &&
- getColWidthsSum(mainWinWidthGross) <= mainWinWidthGross &&
- //this special case needs to be considered *only* when both scrollbars are flexible:
- showScrollbarX_ == SB_SHOW_AUTOMATIC &&
- showScrollbarY_ == SB_SHOW_AUTOMATIC)
- setScrollbars2(0, 0); //no scrollbars required at all! -> wxScrolledWindow requires active help to detect this special case!
- else
- {
- const int logicalWidthTmp = getColWidthsSum(mainWinSize.GetWidth()); //assuming vertical scrollbar stays as it is...
- setScrollbars2(logicalWidthTmp, logicalHeight); //if scrollbars are shown or hidden a new resize event recurses into updateWindowSizes()
- /*
- is there a risk of endless recursion? No, 2-level recursion at most, consider the following 6 cases:
-
- <----------gw---------->
- <----------nw------>
- ------------------------ /|\ /|\
- | | | | |
- | main window | | nh |
- | | | | gh
- ------------------------ \|/ |
- | | | |
- ------------------------ \|/
- gw := gross width
- nw := net width := gross width - sb size
- gh := gross height
- nh := net height := gross height - sb size
-
- There are 6 cases that can occur:
- ---------------------------------
- lw := logical width
- lh := logical height
-
- 1. lw <= gw && lh <= gh => no scrollbars needed
-
- 2. lw > gw && lh > gh => need both scrollbars
-
- 3. lh > gh
- 4.1 lw <= nw => need vertical scrollbar only
- 4.2 nw < lw <= gw => need both scrollbars
-
- 4. lw > gw
- 3.1 lh <= nh => need horizontal scrollbar only
- 3.2 nh < lh <= gh => need both scrollbars
- */
- }
- }
-}
-
-
-wxSize Grid::GetSizeAvailableForScrollTarget(const wxSize& size)
-{
- //harmonize with Grid::updateWindowSizes()!
-
- //1. calculate row label width independent from scrollbars
- const int mainWinHeightGross = std::max(size.GetHeight() - colLabelHeight_, 0); //independent from client sizes and scrollbars!
- const ptrdiff_t logicalHeight = rowLabelWin_->getLogicalHeight(); //
-
- int rowLabelWidth = 0;
- if (drawRowLabel_ && logicalHeight > 0)
- {
- ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y;
- ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeightGross - 1)).y ;
- numeric::clamp<ptrdiff_t>(yFrom, 0, logicalHeight - 1);
- numeric::clamp<ptrdiff_t>(yTo, 0, logicalHeight - 1);
-
- const ptrdiff_t rowFrom = rowLabelWin_->getRowAtPos(yFrom);
- const ptrdiff_t rowTo = rowLabelWin_->getRowAtPos(yTo);
- if (rowFrom >= 0 && rowTo >= 0)
- rowLabelWidth = rowLabelWin_->getBestWidth(rowFrom, rowTo);
- }
-
- return size - wxSize(rowLabelWidth, colLabelHeight_);
-}
-
-
-void Grid::onPaintEvent(wxPaintEvent& event) { wxPaintDC dc(this); }
-
-
-void Grid::onKeyDown(wxKeyEvent& event)
-{
- int keyCode = event.GetKeyCode();
- if (GetLayoutDirection() == wxLayout_RightToLeft)
- {
- if (keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT)
- keyCode = WXK_RIGHT;
- else if (keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT)
- keyCode = WXK_LEFT;
- }
-
- const ptrdiff_t rowCount = getRowCount();
- const ptrdiff_t cursorRow = mainWin_->getCursor();
-
- auto moveCursorTo = [&](ptrdiff_t row)
- {
- if (rowCount > 0)
- {
- numeric::clamp<ptrdiff_t>(row, 0, rowCount - 1);
- setGridCursor(row);
- }
- };
-
- auto selectWithCursorTo = [&](ptrdiff_t row)
- {
- if (rowCount > 0)
- {
- numeric::clamp<ptrdiff_t>(row, 0, rowCount - 1);
- selectWithCursor(row);
- }
- };
-
- switch (keyCode)
- {
- //case WXK_TAB:
- // if (Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward))
- // return;
- // break;
-
- case WXK_UP:
- case WXK_NUMPAD_UP:
- if (event.ShiftDown())
- selectWithCursorTo(cursorRow - 1);
- else if (event.ControlDown())
- scrollDelta(0, -1);
- else
- moveCursorTo(cursorRow - 1);
- return; //swallow event: wxScrolledWindow, wxWidgets 2.9.3 on Kubuntu x64 processes arrow keys: prevent this!
-
- case WXK_DOWN:
- case WXK_NUMPAD_DOWN:
- if (event.ShiftDown())
- selectWithCursorTo(cursorRow + 1);
- else if (event.ControlDown())
- scrollDelta(0, 1);
- else
- moveCursorTo(cursorRow + 1);
- return; //swallow event
-
- case WXK_LEFT:
- case WXK_NUMPAD_LEFT:
- if (event.ControlDown())
- scrollDelta(-1, 0);
- else if (event.ShiftDown())
- ;
- else
- moveCursorTo(cursorRow);
- return;
-
- case WXK_RIGHT:
- case WXK_NUMPAD_RIGHT:
- if (event.ControlDown())
- scrollDelta(1, 0);
- else if (event.ShiftDown())
- ;
- else
- moveCursorTo(cursorRow);
- return;
-
- case WXK_HOME:
- case WXK_NUMPAD_HOME:
- if (event.ShiftDown())
- selectWithCursorTo(0);
- //else if (event.ControlDown())
- // ;
- else
- moveCursorTo(0);
- return;
-
- case WXK_END:
- case WXK_NUMPAD_END:
- if (event.ShiftDown())
- selectWithCursorTo(rowCount - 1);
- //else if (event.ControlDown())
- // ;
- else
- moveCursorTo(rowCount - 1);
- return;
-
- case WXK_PAGEUP:
- case WXK_NUMPAD_PAGEUP:
- if (event.ShiftDown())
- selectWithCursorTo(cursorRow - GetClientSize().GetHeight() / rowLabelWin_->getRowHeight());
- //else if (event.ControlDown())
- // ;
- else
- moveCursorTo(cursorRow - GetClientSize().GetHeight() / rowLabelWin_->getRowHeight());
- return;
-
- case WXK_PAGEDOWN:
- case WXK_NUMPAD_PAGEDOWN:
- if (event.ShiftDown())
- selectWithCursorTo(cursorRow + GetClientSize().GetHeight() / rowLabelWin_->getRowHeight());
- //else if (event.ControlDown())
- // ;
- else
- moveCursorTo(cursorRow + GetClientSize().GetHeight() / rowLabelWin_->getRowHeight());
- return;
-
- case 'A': //Ctrl + A - select all
- if (event.ControlDown())
- selectRangeAndNotify(0, rowCount, true /*positive*/, nullptr /*mouseInitiated*/);
- break;
-
- case WXK_NUMPAD_ADD: //CTRL + '+' - auto-size all
- if (event.ControlDown())
- autoSizeColumns(ALLOW_GRID_EVENT);
- return;
- }
-
- event.Skip();
-}
-
-
-void Grid::setColumnLabelHeight(int height)
-{
- colLabelHeight_ = std::max(0, height);
- updateWindowSizes();
-}
-
-
-void Grid::showRowLabel(bool show)
-{
- drawRowLabel_ = show;
- updateWindowSizes();
-}
-
-
-void Grid::selectAllRows(GridEventPolicy rangeEventPolicy)
-{
- selection_.selectAll();
- mainWin_->Refresh();
-
- if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction
- {
- GridRangeSelectEvent selEvent(0, getRowCount(), true, nullptr);
- if (wxEvtHandler* evtHandler = GetEventHandler())
- evtHandler->ProcessEvent(selEvent);
- }
-}
-
-
-void Grid::clearSelection(GridEventPolicy rangeEventPolicy)
-{
- selection_.clear();
- mainWin_->Refresh();
-
- if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction
- {
- GridRangeSelectEvent unselectionEvent(0, getRowCount(), false, nullptr);
- if (wxEvtHandler* evtHandler = GetEventHandler())
- evtHandler->ProcessEvent(unselectionEvent);
- }
-}
-
-
-void Grid::scrollDelta(int deltaX, int deltaY)
-{
- int scrollPosX = 0;
- int scrollPosY = 0;
- GetViewStart(&scrollPosX, &scrollPosY);
-
- scrollPosX += deltaX;
- scrollPosY += deltaY;
-
- scrollPosX = std::max(0, scrollPosX); //wxScrollHelper::Scroll() will exit prematurely if input happens to be "-1"!
- scrollPosY = std::max(0, scrollPosY); //
-
- Scroll(scrollPosX, scrollPosY); //internally calls wxWindows::Update()!
- updateWindowSizes(); //may show horizontal scroll bar
-}
-
-
-void Grid::redirectRowLabelEvent(wxMouseEvent& event)
-{
- event.m_x = 0;
- if (wxEvtHandler* evtHandler = mainWin_->GetEventHandler())
- evtHandler->ProcessEvent(event);
-
- if (event.ButtonDown() && wxWindow::FindFocus() != mainWin_)
- mainWin_->SetFocus();
-}
-
-
-size_t Grid::getRowCount() const
-{
- return dataView_ ? dataView_->getRowCount() : 0;
-}
-
-
-void Grid::Refresh(bool eraseBackground, const wxRect* rect)
-{
- const size_t rowCountNew = getRowCount();
- if (rowCountOld_ != rowCountNew)
- {
- rowCountOld_ = rowCountNew;
- updateWindowSizes();
- }
-
- if (selection_.maxSize() != rowCountNew) //clear selection only when needed (consider setSelectedRows())
- selection_.init(rowCountNew);
-
- wxScrolledWindow::Refresh(eraseBackground, rect);
-}
-
-
-void Grid::setRowHeight(int height)
-{
- rowLabelWin_->setRowHeight(height);
- updateWindowSizes();
- Refresh();
-}
-
-
-void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr)
-{
- //hold ownership of non-visible columns
- oldColAttributes_ = attr;
-
- std::vector<VisibleColumn> visCols;
- for (const ColumnAttribute& ca : attr)
- {
- assert(ca.type_ != ColumnType::NONE);
- if (ca.visible_)
- visCols.emplace_back(ca.type_, ca.offset_, ca.stretch_);
- }
-
- //"ownership" of visible columns is now within Grid
- visibleCols_ = visCols;
-
- updateWindowSizes();
- Refresh();
-}
-
-
-std::vector<Grid::ColumnAttribute> Grid::getColumnConfig() const
-{
- //get non-visible columns (+ outdated visible ones)
- std::vector<ColumnAttribute> output = oldColAttributes_;
-
- auto iterVcols = visibleCols_.begin();
- auto iterVcolsend = visibleCols_.end();
-
- //update visible columns but keep order of non-visible ones!
- for (ColumnAttribute& ca : output)
- if (ca.visible_)
- {
- if (iterVcols != iterVcolsend)
- {
- ca.type_ = iterVcols->type_;
- ca.stretch_ = iterVcols->stretch_;
- ca.offset_ = iterVcols->offset_;
- ++iterVcols;
- }
- else
- assert(false);
- }
- assert(iterVcols == iterVcolsend);
-
- return output;
-}
-
-
-void Grid::showScrollBars(Grid::ScrollBarStatus horizontal, Grid::ScrollBarStatus vertical)
-{
- if (showScrollbarX_ == horizontal &&
- showScrollbarY_ == vertical) return; //support polling!
-
- showScrollbarX_ = horizontal;
- showScrollbarY_ = vertical;
-
-#if defined ZEN_WIN || defined ZEN_MAC
- //handled by Grid::SetScrollbar
-#elif defined ZEN_LINUX //get rid of scrollbars, but preserve scrolling behavior!
- //the following wxGTK approach is pretty much identical to wxWidgets 2.9 ShowScrollbars() code!
-
- auto mapStatus = [](ScrollBarStatus sbStatus) -> GtkPolicyType
- {
- switch (sbStatus)
- {
- case SB_SHOW_AUTOMATIC:
- return GTK_POLICY_AUTOMATIC;
- case SB_SHOW_ALWAYS:
- return GTK_POLICY_ALWAYS;
- case SB_SHOW_NEVER:
- return GTK_POLICY_NEVER;
- }
- assert(false);
- return GTK_POLICY_AUTOMATIC;
- };
-
- GtkWidget* gridWidget = wxWindow::m_widget;
- GtkScrolledWindow* scrolledWindow = GTK_SCROLLED_WINDOW(gridWidget);
- ::gtk_scrolled_window_set_policy(scrolledWindow,
- mapStatus(horizontal),
- mapStatus(vertical));
-#endif
-
- updateWindowSizes();
-}
-
-#if defined ZEN_WIN || defined ZEN_MAC
-void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh)
-{
- /*
- wxWidgets >= 2.9 ShowScrollbars() is next to useless since it doesn't
- honor wxSHOW_SB_ALWAYS on OS X, so let's ditch it and avoid more non-portability surprises
- */
-
- ScrollBarStatus sbStatus = SB_SHOW_AUTOMATIC;
- if (orientation == wxHORIZONTAL)
- sbStatus = showScrollbarX_;
- else if (orientation == wxVERTICAL)
- sbStatus = showScrollbarY_;
- else
- assert(false);
-
- switch (sbStatus)
- {
- case SB_SHOW_AUTOMATIC:
- wxScrolledWindow::SetScrollbar(orientation, position, thumbSize, range, refresh);
- break;
-
- case SB_SHOW_ALWAYS:
- if (range <= 1) //scrollbars would be hidden for range == 0 or 1!
- wxScrolledWindow::SetScrollbar(orientation, 0, 199999, 200000, refresh);
- else
- wxScrolledWindow::SetScrollbar(orientation, position, thumbSize, range, refresh);
- break;
-
- case SB_SHOW_NEVER:
- wxScrolledWindow::SetScrollbar(orientation, 0, 0, 0, refresh);
- break;
- }
-}
-#endif
-
-
-wxWindow& Grid::getCornerWin () { return *cornerWin_; }
-wxWindow& Grid::getRowLabelWin() { return *rowLabelWin_; }
-wxWindow& Grid::getColLabelWin() { return *colLabelWin_; }
-wxWindow& Grid::getMainWin () { return *mainWin_; }
-const wxWindow& Grid::getMainWin() const { return *mainWin_; }
-
-
-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;
- std::vector<ColumnWidth> absWidths = getColWidths(); //resolve stretched widths
-
- int accuWidth = 0;
- for (size_t col = 0; col < absWidths.size(); ++col)
- {
- accuWidth += absWidths[col].width_;
- if (std::abs(absPosX - accuWidth) < resizeTolerance)
- {
- ColAction out;
- out.wantResize = true;
- out.col = col;
- return out;
- }
- else if (absPosX < accuWidth)
- {
- ColAction out;
- out.wantResize = false;
- out.col = col;
- return out;
- }
- }
- }
- return NoValue();
-}
-
-
-void Grid::moveColumn(size_t colFrom, size_t colTo)
-{
- if (colFrom < visibleCols_.size() &&
- colTo < visibleCols_.size() &&
- colTo != colFrom)
- {
- const VisibleColumn colAtt = visibleCols_[colFrom];
- visibleCols_.erase (visibleCols_.begin() + colFrom);
- visibleCols_.insert(visibleCols_.begin() + colTo, colAtt);
- }
-}
-
-
-ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos) const
-{
-
- const int absPosX = CalcUnscrolledPosition(pos).x;
-
- int accWidth = 0;
- std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths
- for (auto itCol = absWidths.begin(); itCol != absWidths.end(); ++itCol)
- {
- const int width = itCol->width_; //beware dreaded unsigned conversions!
- accWidth += width;
-
- if (absPosX < accWidth - width / 2)
- return itCol - absWidths.begin();
- }
- return absWidths.size();
-}
-
-
-ColumnType Grid::colToType(size_t col) const
-{
- if (col < visibleCols_.size())
- return visibleCols_[col].type_;
- return ColumnType::NONE;
-}
-
-
-ptrdiff_t Grid::getRowAtPos(int posY) const { return rowLabelWin_->getRowAtPos(posY); }
-
-
-Grid::ColumnPosInfo Grid::getColumnAtPos(int posX) const
-{
- if (posX >= 0)
- {
- int accWidth = 0;
- for (const ColumnWidth& cw : getColWidths())
- {
- accWidth += cw.width_;
- if (posX < accWidth)
- return { cw.type_, posX + cw.width_ - accWidth, cw.width_ };
- }
- }
- return { ColumnType::NONE, 0, 0 };
-}
-
-
-wxRect Grid::getColumnLabelArea(ColumnType colType) const
-{
- std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths
-
- //colType is not unique in general, but *this* function expects it!
- assert(std::count_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; }) <= 1);
-
- auto itCol = std::find_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; });
- if (itCol != absWidths.end())
- {
- ptrdiff_t posX = 0;
- for (auto it = absWidths.begin(); it != itCol; ++it)
- posX += it->width_;
-
- return wxRect(wxPoint(posX, 0), wxSize(itCol->width_, colLabelHeight_));
- }
- return wxRect();
-}
-
-
-void Grid::refreshCell(size_t row, ColumnType colType)
-{
- const wxRect& colArea = getColumnLabelArea(colType); //returns empty rect if column not found
- const wxRect& rowArea = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found
- if (colArea.height > 0 && rowArea.height > 0)
- {
- const wxPoint topLeft = CalcScrolledPosition(wxPoint(colArea.x, rowArea.y)); //absolute -> client coordinates
- const wxRect cellArea(topLeft, wxSize(colArea.width, rowArea.height));
-
- getMainWin().RefreshRect(cellArea, false);
- }
-}
-
-
-void Grid::setGridCursor(size_t row)
-{
- mainWin_->setCursor(row, row);
- makeRowVisible(row);
-
- selection_.clear(); //clear selection, do NOT fire event
- selectRangeAndNotify(row, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event
-
- mainWin_->Refresh();
- rowLabelWin_->Refresh(); //row labels! (Kubuntu)
-}
-
-
-void Grid::selectWithCursor(ptrdiff_t row)
-{
- const size_t anchorRow = mainWin_->getAnchor();
-
- mainWin_->setCursor(row, anchorRow);
- makeRowVisible(row);
-
- selection_.clear(); //clear selection, do NOT fire event
- selectRangeAndNotify(anchorRow, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event
-
- mainWin_->Refresh();
- rowLabelWin_->Refresh();
-}
-
-
-void Grid::makeRowVisible(size_t row)
-{
- const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found
- if (labelRect.height > 0)
- {
- int scrollPosX = 0;
- GetViewStart(&scrollPosX, nullptr);
-
- int pixelsPerUnitY = 0;
- GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY);
- if (pixelsPerUnitY <= 0) return;
-
- const int clientPosY = CalcScrolledPosition(labelRect.GetTopLeft()).y;
- if (clientPosY < 0)
- {
- const int scrollPosY = labelRect.y / pixelsPerUnitY;
- Scroll(scrollPosX, scrollPosY); //internally calls wxWindows::Update()!
- updateWindowSizes(); //may show horizontal scroll bar
- }
- else if (clientPosY + labelRect.height > rowLabelWin_->GetClientSize().GetHeight())
- {
- auto execScroll = [&](int clientHeight)
- {
- const int scrollPosY = std::ceil((labelRect.y - clientHeight +
- labelRect.height) / static_cast<double>(pixelsPerUnitY));
- Scroll(scrollPosX, scrollPosY);
- updateWindowSizes(); //may show horizontal scroll bar
- };
-
- const int clientHeightBefore = rowLabelWin_->GetClientSize().GetHeight();
- execScroll(clientHeightBefore);
-
- //client height may decrease after scroll due to a new horizontal scrollbar, resulting in a partially visible last row
- const int clientHeightAfter = rowLabelWin_->GetClientSize().GetHeight();
- if (clientHeightAfter < clientHeightBefore)
- execScroll(clientHeightAfter);
- }
- }
-}
-
-
-void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated)
-{
- //sort + convert to half-open range
- auto rowFirst = std::min(rowFrom, rowTo);
- auto rowLast = std::max(rowFrom, rowTo) + 1;
-
- const size_t rowCount = getRowCount();
- numeric::clamp<ptrdiff_t>(rowFirst, 0, rowCount);
- numeric::clamp<ptrdiff_t>(rowLast, 0, rowCount);
-
- selection_.selectRange(rowFirst, rowLast, positive);
-
- //notify event
- GridRangeSelectEvent selectionEvent(rowFirst, rowLast, positive, mouseInitiated);
- if (wxEvtHandler* evtHandler = GetEventHandler())
- evtHandler->ProcessEvent(selectionEvent);
-
- mainWin_->Refresh();
-}
-
-
-void Grid::scrollTo(size_t row)
-{
- const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found
- if (labelRect.height > 0)
- {
- int pixelsPerUnitY = 0;
- GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY);
- if (pixelsPerUnitY > 0)
- {
- const int scrollPosYNew = labelRect.y / pixelsPerUnitY;
- int scrollPosXOld = 0;
- int scrollPosYOld = 0;
- GetViewStart(&scrollPosXOld, &scrollPosYOld);
-
- if (scrollPosYOld != scrollPosYNew) //support polling
- {
- Scroll(scrollPosXOld, scrollPosYNew); //internally calls wxWindows::Update()!
- updateWindowSizes(); //may show horizontal scroll bar
- Refresh();
- }
- }
- }
-}
-
-
-bool Grid::Enable(bool enable)
-{
- Refresh();
- return wxScrolledWindow::Enable(enable);
-}
-
-
-size_t Grid::getGridCursor() const
-{
- return mainWin_->getCursor();
-}
-
-
-int Grid::getBestColumnSize(size_t col) const
-{
- if (dataView_ && col < visibleCols_.size())
- {
- const ColumnType type = visibleCols_[col].type_;
-
- wxClientDC dc(mainWin_);
- dc.SetFont(mainWin_->GetFont()); //harmonize with MainWin::render()
-
- int maxSize = 0;
-
- auto rowRange = rowLabelWin_->getRowsOnClient(mainWin_->GetClientRect()); //returns range [begin, end)
- for (auto row = rowRange.first; row < rowRange.second; ++row)
- maxSize = std::max(maxSize, dataView_->getBestSize(dc, row, type));
-
- return maxSize;
- }
- return -1;
-}
-
-
-void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEventPolicy, bool notifyAsync)
-{
- if (col < visibleCols_.size())
- {
- VisibleColumn& vcRs = visibleCols_[col];
-
- const std::vector<int> stretchedWidths = getColStretchedWidths(mainWin_->GetClientSize().GetWidth());
- if (stretchedWidths.size() != visibleCols_.size())
- {
- assert(false);
- return;
- }
- //CAVEATS:
- //I. fixed-size columns: normalize offset so that resulting width is at least COLUMN_MIN_WIDTH: this is NOT enforced by getColWidths()!
- //II. stretched columns: do not allow user to set offsets so small that they result in negative (non-normalized) widths: this gives an
- //unusual delay when enlarging the column again later
- width = std::max(width, COLUMN_MIN_WIDTH);
-
- vcRs.offset_ = width - stretchedWidths[col]; //width := stretchedWidth + offset
-
- //III. resizing any column should normalize *all* other stretched columns' offsets considering current mainWinWidth!
- // test case:
- //1. have columns, both fixed-size and stretched, fit whole window width
- //2. shrink main window width so that horizontal scrollbars are shown despite the streched column
- //3. shrink a fixed-size column so that the scrollbars vanish and columns cover full width again
- //4. now verify that the stretched column is resizing immediately if main window is enlarged again
- 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]);
-
- if (columnResizeEventPolicy == ALLOW_GRID_EVENT)
- {
- GridColumnResizeEvent sizeEvent(vcRs.offset_, vcRs.type_);
- if (wxEvtHandler* evtHandler = GetEventHandler())
- {
- if (notifyAsync)
- evtHandler->AddPendingEvent(sizeEvent);
- else
- evtHandler->ProcessEvent(sizeEvent);
- }
- }
- }
- else
- assert(false);
-}
-
-
-void Grid::autoSizeColumns(GridEventPolicy columnResizeEventPolicy)
-{
- if (allowColumnResize_)
- {
- for (size_t col = 0; col < visibleCols_.size(); ++col)
- {
- const int bestWidth = getBestColumnSize(col); //return -1 on error
- if (bestWidth >= 0)
- setColumnWidth(bestWidth, col, columnResizeEventPolicy, true);
- }
- updateWindowSizes();
- Refresh();
- }
-}
-
-
-std::vector<int> Grid::getColStretchedWidths(int clientWidth) const //final width = (normalized) (stretchedWidth + offset)
-{
- assert(clientWidth >= 0);
- clientWidth = std::max(clientWidth, 0);
- int stretchTotal = 0;
- for (const VisibleColumn& vc : visibleCols_)
- {
- assert(vc.stretch_ >= 0);
- stretchTotal += vc.stretch_;
- }
-
- int remainingWidth = clientWidth;
-
- std::vector<int> output;
-
- if (stretchTotal <= 0)
- output.resize(visibleCols_.size()); //fill with zeros
- else
- {
- for (const VisibleColumn& vc : visibleCols_)
- {
- const int width = clientWidth * vc.stretch_ / stretchTotal; //rounds down!
- output.push_back(width);
- remainingWidth -= width;
- }
-
- //distribute *all* of clientWidth: should suffice to enlarge the first few stretched columns; no need to minimize total absolute error of distribution
- if (remainingWidth > 0)
- for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2)
- if (visibleCols_[col2].stretch_ > 0)
- {
- ++output[col2];
- if (--remainingWidth == 0)
- break;
- }
- assert(remainingWidth == 0);
- }
- return output;
-}
-
-
-std::vector<Grid::ColumnWidth> Grid::getColWidths() const
-{
- return getColWidths(mainWin_->GetClientSize().GetWidth());
-}
-
-
-std::vector<Grid::ColumnWidth> Grid::getColWidths(int mainWinWidth) const //evaluate stretched columns
-{
- const std::vector<int> stretchedWidths = getColStretchedWidths(mainWinWidth);
- assert(stretchedWidths.size() == visibleCols_.size());
-
- std::vector<ColumnWidth> output;
- for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2)
- {
- const auto& vc = visibleCols_[col2];
- 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
- else
- width = std::max(width, 0); //support smaller width than COLUMN_MIN_WIDTH if set via configuration
-
- output.emplace_back(vc.type_, width);
- }
- return output;
-}
-
-
-int Grid::getColWidthsSum(int mainWinWidth) const
-{
- int sum = 0;
- for (const ColumnWidth& cw : getColWidths(mainWinWidth))
- sum += cw.width_;
- return sum;
-};
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#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/tick_count.h>
+#include <zen/string_tools.h>
+#include <zen/scope_guard.h>
+#include <zen/utf.h>
+#include <zen/format_unit.h>
+#include "dc.h"
+
+ #include <gtk/gtk.h>
+
+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
+
+const int GridData::COLUMN_GAP_LEFT = 4;
+
+
+void zen::clearArea(wxDC& dc, const wxRect& rect, const wxColor& col)
+{
+ wxDCPenChanger dummy (dc, col);
+ wxDCBrushChanger dummy2(dc, col);
+ dc.DrawRectangle(rect);
+}
+
+
+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
+
+inline wxColor getColorLabelGradientFrom() { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); }
+inline wxColor getColorLabelGradientTo () { return { 200, 200, 200 }; } //light grey
+
+inline wxColor getColorLabelGradientFocusFrom() { return getColorLabelGradientFrom(); }
+inline wxColor getColorLabelGradientFocusTo () { return Grid::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 bool fillGapAfterColumns = true; //draw rows/column label to fill full window width; may become an instance variable some time?
+}
+
+//----------------------------------------------------------------------------------------------------------------
+const wxEventType zen::EVENT_GRID_MOUSE_LEFT_DOUBLE = wxNewEventType();
+const wxEventType zen::EVENT_GRID_MOUSE_LEFT_DOWN = wxNewEventType();
+const wxEventType zen::EVENT_GRID_MOUSE_LEFT_UP = wxNewEventType();
+const wxEventType zen::EVENT_GRID_MOUSE_RIGHT_DOWN = wxNewEventType();
+const wxEventType zen::EVENT_GRID_MOUSE_RIGHT_UP = wxNewEventType();
+const wxEventType zen::EVENT_GRID_SELECT_RANGE = wxNewEventType();
+const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_LEFT = wxNewEventType();
+const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_RIGHT = wxNewEventType();
+const wxEventType zen::EVENT_GRID_COL_RESIZE = wxNewEventType();
+//----------------------------------------------------------------------------------------------------------------
+
+void GridData::renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected)
+{
+ drawCellBackground(dc, rect, enabled, selected, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+}
+
+
+void GridData::renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover)
+{
+ wxRect rectTmp = drawCellBorder(dc, rect);
+
+ rectTmp.x += COLUMN_GAP_LEFT;
+ rectTmp.width -= COLUMN_GAP_LEFT;
+ 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
+}
+
+
+wxRect GridData::drawCellBorder(wxDC& dc, const wxRect& rect) //returns remaining rectangle
+{
+ wxDCPenChanger dummy2(dc, getColorGridLine());
+ dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight());
+ dc.DrawLine(rect.GetBottomRight(), rect.GetTopRight() + wxPoint(0, -1));
+
+ return wxRect(rect.GetTopLeft(), wxSize(rect.width - 1, rect.height - 1));
+}
+
+
+void GridData::drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bool selected, const wxColor& backgroundColor)
+{
+ if (enabled)
+ {
+ if (selected)
+ dc.GradientFillLinear(rect, Grid::getColorSelectionGradientFrom(), Grid::getColorSelectionGradientTo(), wxEAST);
+ else
+ clearArea(dc, rect, backgroundColor);
+ }
+ else
+ clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
+}
+
+
+wxSize GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& text, int alignment)
+{
+ /*
+ performance notes (Windows):
+ - wxDC::GetTextExtent() is by far the most expensive call (20x more expensive than wxDC::DrawText())
+ - wxDC::DrawLabel() is inefficiently implemented; internally calls: wxDC::GetMultiLineTextExtent(), wxDC::GetTextExtent(), wxDC::DrawText()
+ - wxDC::GetMultiLineTextExtent() calls wxDC::GetTextExtent()
+ - wxDC::DrawText also calls wxDC::GetTextExtent()!!
+ => wxDC::DrawLabel() boils down to 3(!) calls to wxDC::GetTextExtent()!!!
+ - wxDC::DrawLabel results in GetTextExtent() call even for empty strings!!!
+ => skip the wxDC::DrawLabel() cruft and directly call wxDC::DrawText!
+ */
+
+ //truncate large texts and add ellipsis
+ assert(!contains(text, L"\n"));
+ const wchar_t ELLIPSIS = L'\u2026'; //"..."
+
+ std::wstring textTrunc = text;
+ wxSize extentTrunc = dc.GetTextExtent(textTrunc);
+ if (extentTrunc.GetWidth() > rect.width)
+ {
+ //unlike Windows 7 Explorer, we truncate UTF-16 correctly: e.g. CJK-Ideogramm encodes to TWO wchar_t: utfCvrtTo<std::wstring>("\xf0\xa4\xbd\x9c");
+ size_t low = 0; //number of unicode chars!
+ size_t high = unicodeLength(text); //
+ if (high > 1)
+ for (;;)
+ {
+ const size_t middle = (low + high) / 2; //=> never 0 when "high - low > 1"
+ if (high - low <= 1)
+ {
+ if (low == 0)
+ {
+ textTrunc = ELLIPSIS;
+ extentTrunc = dc.GetTextExtent(ELLIPSIS);
+ }
+ break;
+ }
+
+ const std::wstring& candidate = std::wstring(strBegin(text), findUnicodePos(text, middle)) + ELLIPSIS;
+ const wxSize extentCand = dc.GetTextExtent(candidate); //perf: most expensive call of this routine!
+
+ if (extentCand.GetWidth() <= rect.width)
+ {
+ low = middle;
+ textTrunc = candidate;
+ extentTrunc = extentCand;
+ }
+ else
+ high = middle;
+ }
+ }
+
+ wxPoint pt = rect.GetTopLeft();
+ if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0!
+ pt.x += rect.width - extentTrunc.GetWidth();
+ else if (alignment & wxALIGN_CENTER_HORIZONTAL)
+ pt.x += (rect.width - extentTrunc.GetWidth()) / 2;
+
+ if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0!
+ pt.y += rect.height - extentTrunc.GetHeight();
+ else if (alignment & wxALIGN_CENTER_VERTICAL)
+ pt.y += (rect.height - extentTrunc.GetHeight()) / 2;
+
+ RecursiveDcClipper clip(dc, rect);
+ dc.DrawText(textTrunc, pt);
+ return extentTrunc;
+}
+
+
+void GridData::renderColumnLabel(Grid& grid, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted)
+{
+ wxRect rectTmp = drawColumnLabelBorder(dc, rect);
+ drawColumnLabelBackground(dc, rectTmp, highlighted);
+
+ rectTmp.x += COLUMN_GAP_LEFT;
+ rectTmp.width -= COLUMN_GAP_LEFT;
+ drawColumnLabelText(dc, rectTmp, getColumnLabel(colType));
+}
+
+
+wxRect GridData::drawColumnLabelBorder(wxDC& dc, const wxRect& rect) //returns remaining rectangle
+{
+ //draw white line
+ {
+ wxDCPenChanger dummy(dc, *wxWHITE_PEN);
+ dc.DrawLine(rect.GetTopLeft(), rect.GetBottomLeft());
+ }
+
+ //draw border (with gradient)
+ {
+ wxDCPenChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
+ dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight()), getColorLabelGradientFrom(), dc.GetPen().GetColour(), wxSOUTH);
+ dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0));
+ }
+
+ return wxRect(rect.x + 1, rect.y, rect.width - 2, rect.height - 1); //we really don't like wxRect::Deflate, do we?
+}
+
+
+void GridData::drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool highlighted)
+{
+ if (highlighted)
+ dc.GradientFillLinear(rect, getColorLabelGradientFocusFrom(), getColorLabelGradientFocusTo(), wxSOUTH);
+ else //regular background gradient
+ dc.GradientFillLinear(rect, getColorLabelGradientFrom(), getColorLabelGradientTo(), wxSOUTH); //clear overlapping cells
+}
+
+
+void GridData::drawColumnLabelText(wxDC& dc, const wxRect& rect, const std::wstring& text)
+{
+ wxDCTextColourChanger dummy(dc, getColorLabelText()); //accessibility: always set both foreground AND background colors!
+ drawCellText(dc, rect, text, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
+}
+
+//----------------------------------------------------------------------------------------------------------------
+/*
+ SubWindow
+ /|\
+ |
+ -----------------------------------
+ | | | |
+CornerWin RowLabelWin ColLabelWin MainWin
+
+*/
+class Grid::SubWindow : public wxWindow
+{
+public:
+ SubWindow(Grid& parent) :
+ wxWindow(&parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxBORDER_NONE, wxPanelNameStr),
+ parent_(parent)
+ {
+ 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);
+
+ //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_CHILD_FOCUS, wxEventHandler(SubWindow::onChildFocus), nullptr, this);
+
+ Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(SubWindow::onMouseLeftDown ), nullptr, this);
+ Connect(wxEVT_LEFT_UP, wxMouseEventHandler(SubWindow::onMouseLeftUp ), nullptr, this);
+ Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(SubWindow::onMouseLeftDouble), nullptr, this);
+ Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(SubWindow::onMouseRightDown ), nullptr, this);
+ Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(SubWindow::onMouseRightUp ), nullptr, this);
+ Connect(wxEVT_MOTION, wxMouseEventHandler(SubWindow::onMouseMovement ), nullptr, this);
+ Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(SubWindow::onLeaveWindow ), nullptr, this);
+ Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(SubWindow::onMouseWheel ), nullptr, this);
+ Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(SubWindow::onMouseCaptureLost), nullptr, this);
+
+ Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(SubWindow::onKeyDown), nullptr, this);
+
+ assert(GetClientAreaOrigin() == wxPoint()); //generally assumed when dealing with coordinates below
+ }
+ Grid& refParent() { return parent_; }
+ const Grid& refParent() const { return parent_; }
+
+ template <class T>
+ bool sendEventNow(T&& event) //take both "rvalue + lvalues", return "true" if a suitable event handler function was found and executed, and the function did not call wxEvent::Skip.
+ {
+ if (wxEvtHandler* evtHandler = parent_.GetEventHandler())
+ return evtHandler->ProcessEvent(event);
+ return false;
+ }
+
+protected:
+ void setToolTip(const std::wstring& text) //proper fix for wxWindow
+ {
+ wxToolTip* tt = GetToolTip();
+
+ const wxString oldText = tt ? tt->GetTip() : wxString();
+ if (text != oldText)
+ {
+ if (text.empty())
+ SetToolTip(nullptr); //wxGTK doesn't allow wxToolTip with empty text!
+ else
+ {
+ //wxWidgets bug: tooltip multiline property is defined by first tooltip text containing newlines or not (same is true for maximum width)
+ if (!tt)
+ SetToolTip(new wxToolTip(L"a b\n\
+ a b")); //ugly, but working (on Windows)
+ tt = GetToolTip(); //should be bound by now
+ assert(tt);
+ if (tt)
+ tt->SetTip(text);
+ }
+ }
+ }
+
+private:
+ virtual void render(wxDC& dc, const wxRect& rect) = 0;
+
+ virtual void onFocus(wxFocusEvent& event) { event.Skip(); }
+ virtual void onChildFocus(wxEvent& event) {} //wxGTK::wxScrolledWindow automatically scrolls to child window when child gets focus -> prevent!
+
+ virtual void onMouseLeftDown (wxMouseEvent& event) { event.Skip(); }
+ virtual void onMouseLeftUp (wxMouseEvent& event) { event.Skip(); }
+ virtual void onMouseLeftDouble(wxMouseEvent& event) { event.Skip(); }
+ virtual void onMouseRightDown (wxMouseEvent& event) { event.Skip(); }
+ virtual void onMouseRightUp (wxMouseEvent& event) { event.Skip(); }
+ virtual void onMouseMovement (wxMouseEvent& event) { event.Skip(); }
+ virtual void onLeaveWindow (wxMouseEvent& event) { event.Skip(); }
+ virtual void onMouseCaptureLost(wxMouseCaptureLostEvent& event) { event.Skip(); }
+
+ void onKeyDown(wxKeyEvent& event)
+ {
+ if (!sendEventNow(event)) //let parent collect all key events
+ event.Skip();
+ }
+
+ void onMouseWheel(wxMouseEvent& event)
+ {
+ /*
+ MSDN, WM_MOUSEWHEEL: "Sent to the focus window when the mouse wheel is rotated.
+ The DefWindowProc function propagates the message to the window's parent.
+ There should be no internal forwarding of the message, since DefWindowProc propagates
+ it up the parent chain until it finds a window that processes it."
+
+ On OS X there is no such propagation! => we need a redirection (the same wxGrid implements)
+ */
+
+ //new wxWidgets 3.0 screw-up for GTK2: wxScrollHelperEvtHandler::ProcessEvent() ignores wxEVT_MOUSEWHEEL events
+ //thereby breaking the scenario of redirection to parent we need here (but also breaking their very own wxGrid sample)
+ //=> call wxScrolledWindow mouse wheel handler directly
+ parent_.HandleOnMouseWheel(event);
+
+ //if (!sendEventNow(event))
+ // event.Skip();
+ }
+
+ 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_);
+
+ assert(GetSize() == GetClientSize());
+
+ const wxRegion& updateReg = GetUpdateRegion();
+ for (wxRegionIterator it = updateReg; it; ++it)
+ render(dc, it.GetRect());
+ }
+
+ void onSizeEvent(wxSizeEvent& event)
+ {
+ Refresh();
+ event.Skip();
+ }
+
+ void onEraseBackGround(wxEraseEvent& event) {}
+
+ Grid& parent_;
+ Opt<wxBitmap> doubleBuffer_;
+};
+
+//----------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------
+
+class Grid::CornerWin : public SubWindow
+{
+public:
+ CornerWin(Grid& parent) : SubWindow(parent) {}
+
+private:
+ bool AcceptsFocus() const override { return false; }
+
+ void render(wxDC& dc, const wxRect& rect) override
+ {
+ const wxRect& clientRect = GetClientRect();
+
+ dc.GradientFillLinear(clientRect, getColorLabelGradientFrom(), getColorLabelGradientTo(), wxSOUTH);
+
+ dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
+
+ {
+ wxDCPenChanger dummy(dc, getColorLabelGradientFrom());
+ dc.DrawLine(clientRect.GetTopLeft(), clientRect.GetTopRight());
+ }
+
+ dc.GradientFillLinear(wxRect(clientRect.GetBottomLeft (), clientRect.GetTopLeft ()), getColorLabelGradientFrom(), dc.GetPen().GetColour(), wxSOUTH);
+ dc.GradientFillLinear(wxRect(clientRect.GetBottomRight(), clientRect.GetTopRight()), getColorLabelGradientFrom(), dc.GetPen().GetColour(), wxSOUTH);
+
+ dc.DrawLine(clientRect.GetBottomLeft(), clientRect.GetBottomRight());
+
+ wxRect rectShrinked = clientRect;
+ rectShrinked.Deflate(1);
+ dc.SetPen(*wxWHITE_PEN);
+
+ //dc.DrawLine(clientRect.GetTopLeft(), clientRect.GetTopRight() + wxPoint(1, 0));
+ dc.DrawLine(rectShrinked.GetTopLeft(), rectShrinked.GetBottomLeft() + wxPoint(0, 1));
+ }
+};
+
+//----------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------
+
+class Grid::RowLabelWin : public SubWindow
+{
+public:
+ RowLabelWin(Grid& parent) :
+ SubWindow(parent),
+ rowHeight_(parent.GetCharHeight() + 2 + 1) {} //default height; don't call any functions on "parent" other than those from wxWindow during construction!
+ //2 for some more space, 1 for bottom border (gives 15 + 2 + 1 on Windows, 17 + 2 + 1 on Ubuntu)
+
+ int getBestWidth(ptrdiff_t rowFrom, ptrdiff_t rowTo)
+ {
+ wxClientDC dc(this);
+
+ wxFont labelFont = GetFont();
+ //labelFont.SetWeight(wxFONTWEIGHT_BOLD);
+ dc.SetFont(labelFont); //harmonize with RowLabelWin::render()!
+
+ int bestWidth = 0;
+ for (ptrdiff_t i = rowFrom; i <= rowTo; ++i)
+ bestWidth = std::max(bestWidth, dc.GetTextExtent(formatRow(i)).GetWidth() + 2 * ROW_LABEL_BORDER);
+ return bestWidth;
+ }
+
+ size_t getLogicalHeight() const { return refParent().getRowCount() * rowHeight_; }
+
+ ptrdiff_t getRowAtPos(ptrdiff_t posY) const //returns < 0 on invalid input, else row number within: [0, rowCount]; rowCount if out of range
+ {
+ if (posY >= 0 && rowHeight_ > 0)
+ {
+ const size_t row = posY / rowHeight_;
+ return std::min(row, refParent().getRowCount());
+ }
+ return -1;
+ }
+
+ int getRowHeight() const { return rowHeight_; } //guarantees to return size >= 1 !
+ void setRowHeight(int height) { assert(height > 0); rowHeight_ = std::max(1, height); }
+
+ wxRect getRowLabelArea(size_t row) const //returns empty rect if row not found
+ {
+ assert(GetClientAreaOrigin() == wxPoint());
+ if (row < refParent().getRowCount())
+ return wxRect(wxPoint(0, rowHeight_ * row),
+ wxSize(GetClientSize().GetWidth(), rowHeight_));
+ return wxRect();
+ }
+
+ std::pair<ptrdiff_t, ptrdiff_t> getRowsOnClient(const wxRect& clientRect) const //returns range [begin, end)
+ {
+ const int yFrom = refParent().CalcUnscrolledPosition(clientRect.GetTopLeft ()).y;
+ const int yTo = refParent().CalcUnscrolledPosition(clientRect.GetBottomRight()).y;
+
+ return std::make_pair(std::max(yFrom / rowHeight_, 0),
+ std::min<ptrdiff_t>((yTo / rowHeight_) + 1, refParent().getRowCount()));
+ }
+
+private:
+ static std::wstring formatRow(size_t row) { return toGuiString(row + 1); } //convert number to std::wstring including thousands separator
+
+ bool AcceptsFocus() const override { return false; }
+
+ void render(wxDC& dc, const wxRect& rect) override
+ {
+ /*
+ IsEnabled() vs IsThisEnabled() since wxWidgets 2.9.5:
+
+ void wxWindowBase::NotifyWindowOnEnableChange(), called from bool wxWindowBase::Enable(), fails to refresh
+ child elements when disabling a IsTopLevel() dialog, e.g. when showing a modal dialog.
+ The unfortunate effect on XP for using IsEnabled() when rendering the grid is that the user can move the modal dialog
+ and *draw* with it on the background while the grid refreshes as disabled incrementally!
+
+ => Don't use IsEnabled() since it considers the top level window. The brittle wxWidgets implementation is right in their intention,
+ but wrong when not refreshing child-windows: the control designer decides how his control should be rendered!
+
+ => IsThisEnabled() OTOH is too shallow and does not consider parent windows which are not top level.
+
+ The perfect solution would be a bool ShouldBeDrawnActive() { return "IsEnabled() but ignore effects of showing a modal dialog"; }
+
+ However "IsThisEnabled()" is good enough (same like the old IsEnabled() on wxWidgets 2.8.12) and it avoids this pathetic behavior on XP.
+ (Similar problem on Win 7: e.g. directly click sync button without comparing first)
+ */
+ if (IsThisEnabled())
+ clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+ else
+ clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
+
+ wxFont labelFont = GetFont();
+ //labelFont.SetWeight(wxFONTWEIGHT_BOLD);
+ dc.SetFont(labelFont); //harmonize with RowLabelWin::getBestWidth()!
+
+ auto rowRange = getRowsOnClient(rect); //returns range [begin, end)
+ for (auto row = rowRange.first; row < rowRange.second; ++row)
+ {
+ wxRect singleLabelArea = getRowLabelArea(row); //returns empty rect if row not found
+ if (singleLabelArea.height > 0)
+ {
+ singleLabelArea.y = refParent().CalcScrolledPosition(singleLabelArea.GetTopLeft()).y;
+ drawRowLabel(dc, singleLabelArea, row);
+ }
+ }
+ }
+
+ void drawRowLabel(wxDC& dc, const wxRect& rect, size_t row)
+ {
+ //clearArea(dc, rect, getColorRowLabel());
+ dc.GradientFillLinear(rect, getColorLabelGradientFrom(), getColorLabelGradientTo(), wxEAST); //clear overlapping cells
+ wxDCTextColourChanger dummy3(dc, getColorLabelText()); //accessibility: always set both foreground AND background colors!
+
+ //label text
+ wxRect textRect = rect;
+ textRect.Deflate(1);
+
+ GridData::drawCellText(dc, textRect, formatRow(row), wxALIGN_CENTRE);
+
+ //border lines
+ {
+ wxDCPenChanger dummy(dc, *wxWHITE_PEN);
+ dc.DrawLine(rect.GetTopLeft(), rect.GetTopRight());
+ }
+ {
+ wxDCPenChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
+ dc.DrawLine(rect.GetTopLeft(), rect.GetBottomLeft());
+ dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight());
+ dc.DrawLine(rect.GetBottomRight(), rect.GetTopRight() + wxPoint(0, -1));
+ }
+ }
+
+ void onMouseLeftDown(wxMouseEvent& event) override { refParent().redirectRowLabelEvent(event); }
+ void onMouseMovement(wxMouseEvent& event) override { refParent().redirectRowLabelEvent(event); }
+ void onMouseLeftUp (wxMouseEvent& event) override { refParent().redirectRowLabelEvent(event); }
+
+ int rowHeight_;
+};
+
+
+namespace
+{
+class ColumnResizing
+{
+public:
+ ColumnResizing(wxWindow& wnd, size_t col, int startWidth, int clientPosX) :
+ wnd_(wnd), col_(col), startWidth_(startWidth), clientPosX_(clientPosX)
+ {
+ wnd_.CaptureMouse();
+ }
+ ~ColumnResizing()
+ {
+ if (wnd_.HasCapture())
+ wnd_.ReleaseMouse();
+ }
+
+ size_t getColumn () const { return col_; }
+ int getStartWidth () const { return startWidth_; }
+ int getStartPosX () const { return clientPosX_; }
+
+private:
+ wxWindow& wnd_;
+ const size_t col_;
+ const int startWidth_;
+ const int clientPosX_;
+};
+
+
+class ColumnMove
+{
+public:
+ ColumnMove(wxWindow& wnd, size_t colFrom, int clientPosX) :
+ wnd_(wnd),
+ colFrom_(colFrom),
+ colTo_(colFrom),
+ clientPosX_(clientPosX) { wnd_.CaptureMouse(); }
+ ~ColumnMove() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); }
+
+ size_t getColumnFrom() const { return colFrom_; }
+ size_t& refColumnTo() { return colTo_; }
+ int getStartPosX () const { return clientPosX_; }
+
+ bool isRealMove() const { return !singleClick_; }
+ void setRealMove() { singleClick_ = false; }
+
+private:
+ wxWindow& wnd_;
+ const size_t colFrom_;
+ size_t colTo_;
+ const int clientPosX_;
+ bool singleClick_ = true;
+};
+}
+
+//----------------------------------------------------------------------------------------------------------------
+
+class Grid::ColLabelWin : public SubWindow
+{
+public:
+ ColLabelWin(Grid& parent) : SubWindow(parent) {}
+
+private:
+ bool AcceptsFocus() const override { return false; }
+
+ void render(wxDC& dc, const wxRect& rect) override
+ {
+ if (IsThisEnabled())
+ clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+ else
+ clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
+
+ //coordinate with "colLabelHeight" in Grid constructor:
+ wxFont labelFont = GetFont();
+ labelFont.SetWeight(wxFONTWEIGHT_BOLD);
+ dc.SetFont(labelFont);
+
+ wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
+
+ const int colLabelHeight = refParent().colLabelHeight_;
+
+ wxPoint labelAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0)).x, 0); //client coordinates
+
+ std::vector<ColumnWidth> absWidths = refParent().getColWidths(); //resolve stretched widths
+ for (auto it = absWidths.begin(); it != absWidths.end(); ++it)
+ {
+ const size_t col = it - absWidths.begin();
+ const int width = it->width_; //don't use unsigned for calculations!
+
+ if (labelAreaTL.x > rect.GetRight())
+ return; //done, rect is fully covered
+ if (labelAreaTL.x + width > rect.x)
+ drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(width, colLabelHeight)), col, it->type_);
+ labelAreaTL.x += width;
+ }
+ if (labelAreaTL.x > rect.GetRight())
+ return; //done, rect is fully covered
+
+ //fill gap after columns and cover full width
+ if (fillGapAfterColumns)
+ {
+ int totalWidth = 0;
+ for (const ColumnWidth& cw : absWidths)
+ totalWidth += cw.width_;
+ const int clientWidth = GetClientSize().GetWidth(); //need reliable, stable width in contrast to rect.width
+
+ if (totalWidth < clientWidth)
+ drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(clientWidth - totalWidth, colLabelHeight)), absWidths.size(), ColumnType::NONE);
+ }
+ }
+
+ void drawColumnLabel(wxDC& dc, const wxRect& rect, size_t col, ColumnType colType)
+ {
+ if (auto dataView = refParent().getDataProvider())
+ {
+ const bool isHighlighted = activeResizing_ ? col == activeResizing_ ->getColumn () : //highlight_ column on mouse-over
+ activeClickOrMove_ ? col == activeClickOrMove_->getColumnFrom() :
+ highlightCol_ ? col == *highlightCol_ :
+ false;
+
+ RecursiveDcClipper clip(dc, rect);
+ dataView->renderColumnLabel(refParent(), dc, rect, colType, isHighlighted);
+
+ //draw move target location
+ if (refParent().allowColumnMove_)
+ if (activeClickOrMove_ && activeClickOrMove_->isRealMove())
+ {
+ 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);
+ else if (col == activeClickOrMove_->refColumnTo() && col == 0) //pos 0
+ dc.GradientFillLinear(wxRect(rect.GetTopLeft(), rect.GetBottomLeft() + wxPoint(2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH);
+ }
+ }
+ }
+
+ void onMouseLeftDown(wxMouseEvent& event) override
+ {
+ if (FindFocus() != &refParent().getMainWin())
+ refParent().getMainWin().SetFocus();
+
+ activeResizing_.reset();
+ activeClickOrMove_.reset();
+
+ if (Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()))
+ {
+ if (action->wantResize)
+ {
+ if (!event.LeftDClick()) //double-clicks never seem to arrive here; why is this checked at all???
+ if (Opt<int> colWidth = refParent().getColWidth(action->col))
+ activeResizing_ = std::make_unique<ColumnResizing>(*this, action->col, *colWidth, event.GetPosition().x);
+ }
+ else //a move or single click
+ activeClickOrMove_ = std::make_unique<ColumnMove>(*this, action->col, event.GetPosition().x);
+ }
+ event.Skip();
+ }
+
+ void onMouseLeftUp(wxMouseEvent& event) override
+ {
+ activeResizing_.reset(); //nothing else to do, actual work done by onMouseMovement()
+
+ if (activeClickOrMove_)
+ {
+ if (activeClickOrMove_->isRealMove())
+ {
+ if (refParent().allowColumnMove_)
+ {
+ const size_t colFrom = activeClickOrMove_->getColumnFrom();
+ size_t colTo = activeClickOrMove_->refColumnTo();
+
+ if (colTo > colFrom) //simulate "colFrom" deletion
+ --colTo;
+
+ refParent().moveColumn(colFrom, colTo);
+ }
+ }
+ else //notify single label click
+ {
+ if (const Opt<ColumnType> colType = refParent().colToType(activeClickOrMove_->getColumnFrom()))
+ sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_LEFT, event, *colType));
+ }
+ activeClickOrMove_.reset();
+ }
+
+ refParent().updateWindowSizes(); //looks strange if done during onMouseMovement()
+ refParent().Refresh();
+ event.Skip();
+ }
+
+ void onMouseCaptureLost(wxMouseCaptureLostEvent& event) override
+ {
+ activeResizing_.reset();
+ activeClickOrMove_.reset();
+ Refresh();
+ //event.Skip(); -> we DID handle it!
+ }
+
+ void onMouseLeftDouble(wxMouseEvent& event) override
+ {
+ if (Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()))
+ if (action->wantResize)
+ {
+ //auto-size visible range on double-click
+ const int bestWidth = refParent().getBestColumnSize(action->col); //return -1 on error
+ if (bestWidth >= 0)
+ {
+ refParent().setColumnWidth(bestWidth, action->col, ALLOW_GRID_EVENT);
+ refParent().Refresh(); //refresh main grid as well!
+ }
+ }
+ event.Skip();
+ }
+
+ void onMouseMovement(wxMouseEvent& event) override
+ {
+ if (activeResizing_)
+ {
+ const auto col = activeResizing_->getColumn();
+ const int newWidth = activeResizing_->getStartWidth() + event.GetPosition().x - activeResizing_->getStartPosX();
+
+ //set width tentatively
+ refParent().setColumnWidth(newWidth, col, ALLOW_GRID_EVENT);
+
+ //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)
+ refParent().setColumnWidth(newWidth + gapWidth, col, ALLOW_GRID_EVENT);
+
+ refParent().Refresh(); //refresh columns on main grid as well!
+ }
+ else if (activeClickOrMove_)
+ {
+ const int clientPosX = event.GetPosition().x;
+ if (std::abs(clientPosX - activeClickOrMove_->getStartPosX()) > COLUMN_MOVE_DELAY) //real move (not a single click)
+ {
+ activeClickOrMove_->setRealMove();
+
+ const ptrdiff_t col = refParent().clientPosToMoveTargetColumn(event.GetPosition());
+ if (col >= 0)
+ activeClickOrMove_->refColumnTo() = col;
+ }
+ }
+ else
+ {
+ if (const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()))
+ {
+ highlightCol_ = action->col;
+
+ if (action->wantResize)
+ SetCursor(wxCURSOR_SIZEWE); //set window-local only! :)
+ else
+ SetCursor(*wxSTANDARD_CURSOR);
+ }
+ else
+ {
+ highlightCol_ = NoValue();
+ SetCursor(*wxSTANDARD_CURSOR);
+ }
+ }
+
+ const std::wstring toolTip = [&]
+ {
+ const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
+ const ColumnType colType = refParent().getColumnAtPos(absPos.x).colType; //returns ColumnType::NONE if no column at x position!
+ if (colType != ColumnType::NONE)
+ if (auto prov = refParent().getDataProvider())
+ return prov->getToolTip(colType);
+ return std::wstring();
+ }();
+ setToolTip(toolTip);
+
+ Refresh();
+ event.Skip();
+ }
+
+ void onLeaveWindow(wxMouseEvent& event) override
+ {
+ highlightCol_ = NoValue(); //wxEVT_LEAVE_WINDOW does not respect mouse capture! -> however highlight_ is drawn unconditionally during move/resize!
+ Refresh();
+ event.Skip();
+ }
+
+ void onMouseRightDown(wxMouseEvent& event) override
+ {
+ if (const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()))
+ {
+ if (const Opt<ColumnType> colType = refParent().colToType(action->col))
+ sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, *colType)); //notify right click
+ else assert(false);
+ }
+ else
+ //notify right click (on free space after last column)
+ if (fillGapAfterColumns)
+ sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, ColumnType::NONE));
+
+ event.Skip();
+ }
+
+ std::unique_ptr<ColumnResizing> activeResizing_;
+ std::unique_ptr<ColumnMove> activeClickOrMove_;
+ Opt<size_t> highlightCol_; //column during mouse-over
+};
+
+//----------------------------------------------------------------------------------------------------------------
+namespace
+{
+const wxEventType EVENT_GRID_HAS_SCROLLED = wxNewEventType(); //internal to Grid::MainWin::ScrollWindow()
+}
+//----------------------------------------------------------------------------------------------------------------
+
+class Grid::MainWin : public SubWindow
+{
+public:
+ MainWin(Grid& parent,
+ RowLabelWin& rowLabelWin,
+ ColLabelWin& colLabelWin) : SubWindow(parent),
+ rowLabelWin_(rowLabelWin),
+ colLabelWin_(colLabelWin)
+ {
+ Connect(EVENT_GRID_HAS_SCROLLED, wxEventHandler(MainWin::onRequestWindowUpdate), nullptr, this);
+ }
+
+ ~MainWin() { assert(!gridUpdatePending_); }
+
+ size_t getCursor() const { return cursorRow_; }
+ size_t getAnchor() const { return selectionAnchor_; }
+
+ void setCursor(size_t newCursorRow, size_t newAnchorRow)
+ {
+ cursorRow_ = newCursorRow;
+ selectionAnchor_ = newAnchorRow;
+ activeSelection_.reset(); //e.g. user might search with F3 while holding down left mouse button
+ }
+
+private:
+ void render(wxDC& dc, const wxRect& rect) override
+ {
+ if (IsThisEnabled())
+ clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+ else
+ clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
+
+ dc.SetFont(GetFont()); //harmonize with Grid::getBestColumnSize()
+
+ wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels
+
+ std::vector<ColumnWidth> absWidths = refParent().getColWidths(); //resolve stretched widths
+ {
+ int totalRowWidth = 0;
+ for (const ColumnWidth& cw : absWidths)
+ totalRowWidth += cw.width_;
+
+ //fill gap after columns and cover full width
+ if (fillGapAfterColumns)
+ totalRowWidth = std::max(totalRowWidth, GetClientSize().GetWidth());
+
+ if (auto prov = refParent().getDataProvider())
+ {
+ RecursiveDcClipper dummy2(dc, rect); //do NOT draw background on cells outside of invalidated rect invalidating foreground text!
+
+ wxPoint cellAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0))); //client coordinates
+ const int rowHeight = rowLabelWin_.getRowHeight();
+ const auto rowRange = rowLabelWin_.getRowsOnClient(rect); //returns range [begin, end)
+
+ //draw background lines
+ for (auto row = rowRange.first; row < rowRange.second; ++row)
+ {
+ const wxRect rowRect(cellAreaTL + wxPoint(0, row * rowHeight), wxSize(totalRowWidth, rowHeight));
+ RecursiveDcClipper dummy3(dc, rowRect);
+ prov->renderRowBackgound(dc, rowRect, row, refParent().IsThisEnabled(), drawAsSelected(row));
+ }
+
+ //draw single cells, column by column
+ for (const ColumnWidth& cw : absWidths)
+ {
+ if (cellAreaTL.x > rect.GetRight())
+ return; //done
+
+ if (cellAreaTL.x + cw.width_ > rect.x)
+ for (auto row = rowRange.first; row < rowRange.second; ++row)
+ {
+ const wxRect cellRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, cw.width_, rowHeight);
+ RecursiveDcClipper dummy3(dc, cellRect);
+ prov->renderCell(dc, cellRect, row, cw.type_, refParent().IsThisEnabled(), drawAsSelected(row), getRowHoverToDraw(row));
+ }
+ cellAreaTL.x += cw.width_;
+ }
+ }
+ }
+ }
+
+ HoverArea getRowHoverToDraw(ptrdiff_t row) const
+ {
+ if (activeSelection_)
+ {
+ if (activeSelection_->getFirstClick().row_ == row)
+ return activeSelection_->getFirstClick().hoverArea_;
+ }
+ else if (highlight_.row == row)
+ return highlight_.rowHover;
+ return HoverArea::NONE;
+ }
+
+ bool drawAsSelected(size_t row) const
+ {
+ if (activeSelection_) //check if user is currently selecting with mouse
+ {
+ const size_t rowFrom = std::min(activeSelection_->getStartRow(), activeSelection_->getCurrentRow());
+ const size_t rowTo = std::max(activeSelection_->getStartRow(), activeSelection_->getCurrentRow());
+
+ if (rowFrom <= row && row <= rowTo)
+ return activeSelection_->isPositiveSelect(); //overwrite default
+ }
+ return refParent().isSelected(row);
+ }
+
+ void onMouseLeftDown (wxMouseEvent& event) override { onMouseDown(event); }
+ void onMouseLeftUp (wxMouseEvent& event) override { onMouseUp (event); }
+ void onMouseRightDown(wxMouseEvent& event) override { onMouseDown(event); }
+ void onMouseRightUp (wxMouseEvent& event) override { onMouseUp (event); }
+
+ void onMouseLeftDouble(wxMouseEvent& event) override
+ {
+ if (auto prov = refParent().getDataProvider())
+ {
+ const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
+ const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
+ const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position!
+ const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth);
+ //client is interested in all double-clicks, even those outside of the grid!
+ sendEventNow(GridClickEvent(EVENT_GRID_MOUSE_LEFT_DOUBLE, event, row, rowHover));
+ }
+ event.Skip();
+ }
+
+ void onMouseDown(wxMouseEvent& event) //handle left and right mouse button clicks (almost) the same
+ {
+ if (wxWindow::FindFocus() != this) //doesn't seem to happen automatically for right mouse button
+ SetFocus();
+
+ if (auto prov = refParent().getDataProvider())
+ {
+ const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
+ const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
+ const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position!
+ const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth);
+ //row < 0 possible!!! Pressing "Menu key" simulates Mouse Right Down + Up at position 0xffff/0xffff!
+
+ GridClickEvent mouseEvent(event.RightDown() ? EVENT_GRID_MOUSE_RIGHT_DOWN : EVENT_GRID_MOUSE_LEFT_DOWN, event, row, rowHover);
+
+ if (row >= 0)
+ if (!event.RightDown() || !refParent().isSelected(row)) //do NOT start a new selection if user right-clicks on a selected area!
+ {
+ if (event.ControlDown())
+ activeSelection_ = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row), mouseEvent);
+ else if (event.ShiftDown())
+ {
+ activeSelection_ = std::make_unique<MouseSelection>(*this, selectionAnchor_, true, mouseEvent);
+ refParent().clearSelection(ALLOW_GRID_EVENT);
+ }
+ else
+ {
+ activeSelection_ = std::make_unique<MouseSelection>(*this, row, true, mouseEvent);
+ refParent().clearSelection(ALLOW_GRID_EVENT);
+ }
+ }
+ //notify event *after* potential "clearSelection(true)" above: a client should first receive a GridRangeSelectEvent for clearing the grid, if necessary,
+ //then GridClickEvent and the associated GridRangeSelectEvent one after the other
+ sendEventNow(mouseEvent);
+
+ Refresh();
+ }
+ event.Skip(); //allow changing focus
+ }
+
+ void onMouseUp(wxMouseEvent& event)
+ {
+ if (activeSelection_)
+ {
+ const size_t rowCount = refParent().getRowCount();
+ if (rowCount > 0)
+ {
+ if (activeSelection_->getCurrentRow() < rowCount)
+ {
+ cursorRow_ = activeSelection_->getCurrentRow();
+ selectionAnchor_ = activeSelection_->getStartRow(); //allowed to be "out of range"
+ }
+ else if (activeSelection_->getStartRow() < rowCount) //don't change cursor if "to" and "from" are out of range
+ {
+ cursorRow_ = rowCount - 1;
+ selectionAnchor_ = activeSelection_->getStartRow(); //allowed to be "out of range"
+ }
+ else //total selection "out of range"
+ selectionAnchor_ = cursorRow_;
+ }
+ //slight deviation from Explorer: change cursor while dragging mouse! -> unify behavior with shift + direction keys
+
+ refParent().selectRangeAndNotify(activeSelection_->getStartRow (), //from
+ activeSelection_->getCurrentRow(), //to
+ activeSelection_->isPositiveSelect(),
+ &activeSelection_->getFirstClick());
+ activeSelection_.reset();
+ }
+
+ if (auto prov = refParent().getDataProvider())
+ {
+ //this one may point to row which is not in visible area!
+ const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
+ const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
+ const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position!
+ const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth);
+ //notify click event after the range selection! e.g. this makes sure the selection is applied before showing a context menu
+ sendEventNow(GridClickEvent(event.RightUp() ? EVENT_GRID_MOUSE_RIGHT_UP : EVENT_GRID_MOUSE_LEFT_UP, event, row, rowHover));
+ }
+
+ //update highlight_ and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows)
+ event.SetPosition(ScreenToClient(wxGetMousePosition())); //mouse position may have changed within above callbacks (e.g. context menu was shown)!
+ onMouseMovement(event);
+
+ Refresh();
+ event.Skip(); //allow changing focus
+ }
+
+ void onMouseCaptureLost(wxMouseCaptureLostEvent& event) override
+ {
+ activeSelection_.reset();
+ highlight_.row = -1;
+ Refresh();
+ //event.Skip(); -> we DID handle it!
+ }
+
+ void onMouseMovement(wxMouseEvent& event) override
+ {
+ if (auto prov = refParent().getDataProvider())
+ {
+ const ptrdiff_t rowCount = refParent().getRowCount();
+ const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition());
+ const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
+ const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position!
+ const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth);
+
+ const std::wstring toolTip = [&]
+ {
+ if (cpi.colType != ColumnType::NONE && 0 <= row && row < rowCount)
+ return prov->getToolTip(row, cpi.colType);
+ return std::wstring();
+ }();
+ setToolTip(toolTip); //show even during mouse selection!
+
+ if (activeSelection_)
+ activeSelection_->evalMousePos(); //call on both mouse movement + timer event!
+ else
+ {
+ refreshHighlight(highlight_);
+ highlight_.row = row;
+ highlight_.rowHover = rowHover;
+ refreshHighlight(highlight_); //multiple Refresh() calls are condensed into single one!
+ }
+ }
+ event.Skip();
+ }
+
+ void onLeaveWindow(wxMouseEvent& event) override //wxEVT_LEAVE_WINDOW does not respect mouse capture!
+ {
+ if (!activeSelection_)
+ {
+ refreshHighlight(highlight_);
+ highlight_.row = -1;
+ }
+
+ event.Skip();
+ }
+
+
+ void onFocus(wxFocusEvent& event) override { Refresh(); event.Skip(); }
+
+ class MouseSelection : private wxEvtHandler
+ {
+ public:
+ MouseSelection(MainWin& wnd, size_t rowStart, bool positiveSelect, const GridClickEvent& firstClick) :
+ wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positiveSelect), firstClick_(firstClick)
+ {
+ wnd_.CaptureMouse();
+ timer_.Connect(wxEVT_TIMER, wxEventHandler(MouseSelection::onTimer), nullptr, this);
+ timer_.Start(100); //timer interval in ms
+ evalMousePos();
+ }
+ ~MouseSelection() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); }
+
+ size_t getStartRow () const { return rowStart_; }
+ size_t getCurrentRow () const { return rowCurrent_; }
+ bool isPositiveSelect() const { return positiveSelect_; } //are we selecting or unselecting?
+ const GridClickEvent& getFirstClick() const { return firstClick_; }
+
+ void evalMousePos()
+ {
+ const auto now = std::chrono::steady_clock::now();
+ const double deltaSecs = std::chrono::duration<double>(now - lastEvalTime_).count(); //unit: [sec]
+ lastEvalTime_ = now;
+
+ const wxPoint clientPos = wnd_.ScreenToClient(wxGetMousePosition());
+ const wxSize clientSize = wnd_.GetClientSize();
+ assert(wnd_.GetClientAreaOrigin() == wxPoint());
+
+ //scroll while dragging mouse
+ const int overlapPixY = clientPos.y < 0 ? clientPos.y :
+ clientPos.y >= clientSize.GetHeight() ? clientPos.y - (clientSize.GetHeight() - 1) : 0;
+ const int overlapPixX = clientPos.x < 0 ? clientPos.x :
+ clientPos.x >= clientSize.GetWidth() ? clientPos.x - (clientSize.GetWidth() - 1) : 0;
+
+ int pixelsPerUnitY = 0;
+ 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)]
+
+ auto autoScroll = [&](int overlapPix, double& toScroll)
+ {
+ if (overlapPix != 0)
+ {
+ const double scrollSpeed = overlapPix * mouseDragSpeedIncScrollU; //unit: [scroll units / sec]
+ toScroll += scrollSpeed * deltaSecs;
+ }
+ else
+ toScroll = 0;
+ };
+
+ autoScroll(overlapPixX, toScrollX_);
+ autoScroll(overlapPixY, toScrollY_);
+
+ if (static_cast<int>(toScrollX_) != 0 || static_cast<int>(toScrollY_) != 0)
+ {
+ wnd_.refParent().scrollDelta(static_cast<int>(toScrollX_), static_cast<int>(toScrollY_)); //
+ toScrollX_ -= static_cast<int>(toScrollX_); //rounds down for positive numbers, up for negative,
+ toScrollY_ -= static_cast<int>(toScrollY_); //exactly what we want
+ }
+
+ //select current row *after* scrolling
+ wxPoint clientPosTrimmed = clientPos;
+ numeric::clamp(clientPosTrimmed.y, 0, clientSize.GetHeight() - 1); //do not select row outside client window!
+
+ const wxPoint absPos = wnd_.refParent().CalcUnscrolledPosition(clientPosTrimmed);
+ const ptrdiff_t newRow = wnd_.rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range
+ if (newRow >= 0)
+ if (rowCurrent_ != newRow)
+ {
+ rowCurrent_ = newRow;
+ wnd_.Refresh();
+ }
+ }
+
+ private:
+ void onTimer(wxEvent& event) { evalMousePos(); }
+
+ MainWin& wnd_;
+ const size_t rowStart_;
+ ptrdiff_t rowCurrent_;
+ const bool positiveSelect_;
+ const GridClickEvent firstClick_;
+ wxTimer timer_;
+ double toScrollX_ = 0; //count outstanding scroll unit fractions while dragging mouse
+ double toScrollY_ = 0; //
+ std::chrono::steady_clock::time_point lastEvalTime_ = std::chrono::steady_clock::now();
+ };
+
+ struct MouseHighlight
+ {
+ ptrdiff_t row = -1;
+ HoverArea rowHover = HoverArea::NONE;
+ };
+
+ void ScrollWindow(int dx, int dy, const wxRect* rect) override
+ {
+ wxWindow::ScrollWindow(dx, dy, rect);
+ rowLabelWin_.ScrollWindow(0, dy, rect);
+ colLabelWin_.ScrollWindow(dx, 0, rect);
+
+ //attention, wxGTK call sequence: wxScrolledWindow::Scroll() -> wxScrolledHelperNative::Scroll() -> wxScrolledHelperNative::DoScroll()
+ //which *first* calls us, MainWin::ScrollWindow(), and *then* internally updates m_yScrollPosition
+ //=> we cannot use CalcUnscrolledPosition() here which gives the wrong/outdated value!!!
+ //=> we need to update asynchronously:
+ //=> don't send async event repeatedly => severe performance issues on wxGTK!
+ //=> can't use idle event neither: too few idle events on Windows, e.g. NO idle events while mouse drag-scrolling!
+ //=> solution: send single async event at most!
+ if (!gridUpdatePending_) //without guarding, the number of outstanding async events can become very high during scrolling!! test case: Ubuntu: 170; Windows: 20
+ {
+ gridUpdatePending_ = true;
+ wxCommandEvent scrollEvent(EVENT_GRID_HAS_SCROLLED);
+ AddPendingEvent(scrollEvent); //asynchronously call updateAfterScroll()
+ }
+ }
+
+ void onRequestWindowUpdate(wxEvent& event)
+ {
+ assert(gridUpdatePending_);
+ ZEN_ON_SCOPE_EXIT(gridUpdatePending_ = false);
+
+ refParent().updateWindowSizes(false); //row label width has changed -> do *not* update scrollbars: recursion on wxGTK! -> still a problem, now that we're called async??
+ rowLabelWin_.Update(); //update while dragging scroll thumb
+ }
+
+ void refreshRow(size_t row)
+ {
+ const wxRect& rowArea = rowLabelWin_.getRowLabelArea(row); //returns empty rect if row not found
+ const wxPoint topLeft = refParent().CalcScrolledPosition(wxPoint(0, rowArea.y)); //absolute -> client coordinates
+ wxRect cellArea(topLeft, wxSize(refParent().getColWidthsSum(GetClientSize().GetWidth()), rowArea.height));
+ RefreshRect(cellArea, false);
+ }
+
+ void refreshHighlight(const MouseHighlight& hl)
+ {
+ const ptrdiff_t rowCount = refParent().getRowCount();
+ if (0 <= hl.row && hl.row < rowCount && hl.rowHover != HoverArea::NONE) //no highlight_? => NOP!
+ refreshRow(hl.row);
+ }
+
+ RowLabelWin& rowLabelWin_;
+ ColLabelWin& colLabelWin_;
+
+ std::unique_ptr<MouseSelection> activeSelection_; //bound while user is selecting with mouse
+ MouseHighlight highlight_; //current mouse highlight_ (superseeded by activeSelection_ if available)
+
+ ptrdiff_t cursorRow_ = 0;
+ size_t selectionAnchor_ = 0;
+ bool gridUpdatePending_ = false;
+};
+
+//----------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------
+
+Grid::Grid(wxWindow* parent,
+ wxWindowID id,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) : wxScrolledWindow(parent, id, pos, size, style | wxWANTS_CHARS, name)
+{
+ cornerWin_ = new CornerWin (*this); //
+ rowLabelWin_ = new RowLabelWin(*this); //owership handled by "this"
+ colLabelWin_ = new ColLabelWin(*this); //
+ mainWin_ = new MainWin (*this, *rowLabelWin_, *colLabelWin_); //
+
+ colLabelHeight_ = 2 * DEFAULT_COL_LABEL_BORDER + [&]() -> int
+ {
+ //coordinate with ColLabelWin::render():
+ wxFont labelFont = colLabelWin_->GetFont();
+ labelFont.SetWeight(wxFONTWEIGHT_BOLD);
+ return labelFont.GetPixelSize().GetHeight();
+ }();
+
+ SetTargetWindow(mainWin_);
+
+ SetInitialSize(size); //"Most controls will use this to set their initial size" -> why not
+
+ 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_KEY_DOWN, wxKeyEventHandler(Grid::onKeyDown), nullptr, this);
+}
+
+
+void Grid::updateWindowSizes(bool updateScrollbar)
+{
+ /* We have to deal with TWO nasty circular dependencies:
+ 1.
+ rowLabelWidth
+ /|\
+ mainWin::client width
+ /|\
+ SetScrollbars -> show/hide horizontal scrollbar depending on client width
+ /|\
+ mainWin::client height -> possibly trimmed by horizontal scrollbars
+ /|\
+ rowLabelWidth
+
+ 2.
+ mainWin_->GetClientSize()
+ /|\
+ SetScrollbars -> show/hide scrollbars depending on whether client size is big enough
+ /|\
+ GetClientSize(); -> possibly trimmed by scrollbars
+ /|\
+ mainWin_->GetClientSize() -> also trimmed, since it's a sub-window!
+ */
+
+ //break this vicious circle:
+
+ //harmonize with Grid::GetSizeAvailableForScrollTarget()!
+
+ //1. calculate row label width independent from scrollbars
+ const int mainWinHeightGross = std::max(GetSize().GetHeight() - colLabelHeight_, 0); //independent from client sizes and scrollbars!
+ const ptrdiff_t logicalHeight = rowLabelWin_->getLogicalHeight(); //
+
+ int rowLabelWidth = 0;
+ if (drawRowLabel_ && logicalHeight > 0)
+ {
+ ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y;
+ ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeightGross - 1)).y ;
+ numeric::clamp<ptrdiff_t>(yFrom, 0, logicalHeight - 1);
+ numeric::clamp<ptrdiff_t>(yTo, 0, logicalHeight - 1);
+
+ const ptrdiff_t rowFrom = rowLabelWin_->getRowAtPos(yFrom);
+ const ptrdiff_t rowTo = rowLabelWin_->getRowAtPos(yTo);
+ if (rowFrom >= 0 && rowTo >= 0)
+ rowLabelWidth = rowLabelWin_->getBestWidth(rowFrom, rowTo);
+ }
+
+ auto getMainWinSize = [&](const wxSize& clientSize) { return wxSize(std::max(0, clientSize.GetWidth() - rowLabelWidth), std::max(0, clientSize.GetHeight() - colLabelHeight_)); };
+
+ auto setScrollbars2 = [&](int logWidth, int logHeight) //replace SetScrollbars, which loses precision of pixelsPerUnitX for some brain-dead reason
+ {
+ mainWin_->SetVirtualSize(logWidth, logHeight); //set before calling SetScrollRate():
+ //else SetScrollRate() would fail to preserve scroll position when "new virtual pixel-pos > old virtual height"
+
+ int ppsuX = 0; //pixel per scroll unit
+ int ppsuY = 0;
+ GetScrollPixelsPerUnit(&ppsuX, &ppsuY);
+
+ const int ppsuNew = rowLabelWin_->getRowHeight();
+ if (ppsuX != ppsuNew || ppsuY != ppsuNew) //support polling!
+ SetScrollRate(ppsuNew, ppsuNew); //internally calls AdjustScrollbars() and GetVirtualSize()!
+
+ AdjustScrollbars(); //lousy wxWidgets design decision: internally calls mainWin_->GetClientSize() without considering impact of scrollbars!
+ //Attention: setting scrollbars triggers *synchronous* resize event if scrollbars are shown or hidden! => updateWindowSizes() recursion! (Windows)
+ };
+
+ //2. update managed windows' sizes: just assume scrollbars are already set correctly, even if they may not be (yet)!
+ //this ensures mainWin_->SetVirtualSize() and AdjustScrollbars() are working with the correct main window size, unless sb change later, which triggers a recalculation anyway!
+ const wxSize mainWinSize = getMainWinSize(GetClientSize());
+
+ cornerWin_ ->SetSize(0, 0, rowLabelWidth, colLabelHeight_);
+ rowLabelWin_->SetSize(0, colLabelHeight_, rowLabelWidth, mainWinSize.GetHeight());
+ colLabelWin_->SetSize(rowLabelWidth, 0, mainWinSize.GetWidth(), colLabelHeight_);
+ mainWin_ ->SetSize(rowLabelWidth, colLabelHeight_, mainWinSize.GetWidth(), mainWinSize.GetHeight());
+
+ //avoid flicker in wxWindowMSW::HandleSize() when calling ::EndDeferWindowPos() where the sub-windows are moved only although they need to be redrawn!
+ colLabelWin_->Refresh();
+ mainWin_ ->Refresh();
+
+ //3. update scrollbars: "guide wxScrolledHelper to not screw up too much"
+ if (updateScrollbar)
+ {
+ const int mainWinWidthGross = getMainWinSize(GetSize()).GetWidth();
+
+ if (logicalHeight <= mainWinHeightGross &&
+ getColWidthsSum(mainWinWidthGross) <= mainWinWidthGross &&
+ //this special case needs to be considered *only* when both scrollbars are flexible:
+ showScrollbarX_ == SB_SHOW_AUTOMATIC &&
+ showScrollbarY_ == SB_SHOW_AUTOMATIC)
+ setScrollbars2(0, 0); //no scrollbars required at all! -> wxScrolledWindow requires active help to detect this special case!
+ else
+ {
+ const int logicalWidthTmp = getColWidthsSum(mainWinSize.GetWidth()); //assuming vertical scrollbar stays as it is...
+ setScrollbars2(logicalWidthTmp, logicalHeight); //if scrollbars are shown or hidden a new resize event recurses into updateWindowSizes()
+ /*
+ is there a risk of endless recursion? No, 2-level recursion at most, consider the following 6 cases:
+
+ <----------gw---------->
+ <----------nw------>
+ ------------------------ /|\ /|\
+ | | | | |
+ | main window | | nh |
+ | | | | gh
+ ------------------------ \|/ |
+ | | | |
+ ------------------------ \|/
+ gw := gross width
+ nw := net width := gross width - sb size
+ gh := gross height
+ nh := net height := gross height - sb size
+
+ There are 6 cases that can occur:
+ ---------------------------------
+ lw := logical width
+ lh := logical height
+
+ 1. lw <= gw && lh <= gh => no scrollbars needed
+
+ 2. lw > gw && lh > gh => need both scrollbars
+
+ 3. lh > gh
+ 4.1 lw <= nw => need vertical scrollbar only
+ 4.2 nw < lw <= gw => need both scrollbars
+
+ 4. lw > gw
+ 3.1 lh <= nh => need horizontal scrollbar only
+ 3.2 nh < lh <= gh => need both scrollbars
+ */
+ }
+ }
+}
+
+
+wxSize Grid::GetSizeAvailableForScrollTarget(const wxSize& size)
+{
+ //harmonize with Grid::updateWindowSizes()!
+
+ //1. calculate row label width independent from scrollbars
+ const int mainWinHeightGross = std::max(size.GetHeight() - colLabelHeight_, 0); //independent from client sizes and scrollbars!
+ const ptrdiff_t logicalHeight = rowLabelWin_->getLogicalHeight(); //
+
+ int rowLabelWidth = 0;
+ if (drawRowLabel_ && logicalHeight > 0)
+ {
+ ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y;
+ ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeightGross - 1)).y ;
+ numeric::clamp<ptrdiff_t>(yFrom, 0, logicalHeight - 1);
+ numeric::clamp<ptrdiff_t>(yTo, 0, logicalHeight - 1);
+
+ const ptrdiff_t rowFrom = rowLabelWin_->getRowAtPos(yFrom);
+ const ptrdiff_t rowTo = rowLabelWin_->getRowAtPos(yTo);
+ if (rowFrom >= 0 && rowTo >= 0)
+ rowLabelWidth = rowLabelWin_->getBestWidth(rowFrom, rowTo);
+ }
+
+ return size - wxSize(rowLabelWidth, colLabelHeight_);
+}
+
+
+void Grid::onPaintEvent(wxPaintEvent& event) { wxPaintDC dc(this); }
+
+
+void Grid::onKeyDown(wxKeyEvent& event)
+{
+ int keyCode = event.GetKeyCode();
+ if (GetLayoutDirection() == wxLayout_RightToLeft)
+ {
+ if (keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT)
+ keyCode = WXK_RIGHT;
+ else if (keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT)
+ keyCode = WXK_LEFT;
+ }
+
+ const ptrdiff_t rowCount = getRowCount();
+ const ptrdiff_t cursorRow = mainWin_->getCursor();
+
+ auto moveCursorTo = [&](ptrdiff_t row)
+ {
+ if (rowCount > 0)
+ {
+ numeric::clamp<ptrdiff_t>(row, 0, rowCount - 1);
+ setGridCursor(row);
+ }
+ };
+
+ auto selectWithCursorTo = [&](ptrdiff_t row)
+ {
+ if (rowCount > 0)
+ {
+ numeric::clamp<ptrdiff_t>(row, 0, rowCount - 1);
+ selectWithCursor(row);
+ }
+ };
+
+ switch (keyCode)
+ {
+ //case WXK_TAB:
+ // if (Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward))
+ // return;
+ // break;
+
+ case WXK_UP:
+ case WXK_NUMPAD_UP:
+ if (event.ShiftDown())
+ selectWithCursorTo(cursorRow - 1);
+ else if (event.ControlDown())
+ scrollDelta(0, -1);
+ else
+ moveCursorTo(cursorRow - 1);
+ return; //swallow event: wxScrolledWindow, wxWidgets 2.9.3 on Kubuntu x64 processes arrow keys: prevent this!
+
+ case WXK_DOWN:
+ case WXK_NUMPAD_DOWN:
+ if (event.ShiftDown())
+ selectWithCursorTo(cursorRow + 1);
+ else if (event.ControlDown())
+ scrollDelta(0, 1);
+ else
+ moveCursorTo(cursorRow + 1);
+ return; //swallow event
+
+ case WXK_LEFT:
+ case WXK_NUMPAD_LEFT:
+ if (event.ControlDown())
+ scrollDelta(-1, 0);
+ else if (event.ShiftDown())
+ ;
+ else
+ moveCursorTo(cursorRow);
+ return;
+
+ case WXK_RIGHT:
+ case WXK_NUMPAD_RIGHT:
+ if (event.ControlDown())
+ scrollDelta(1, 0);
+ else if (event.ShiftDown())
+ ;
+ else
+ moveCursorTo(cursorRow);
+ return;
+
+ case WXK_HOME:
+ case WXK_NUMPAD_HOME:
+ if (event.ShiftDown())
+ selectWithCursorTo(0);
+ //else if (event.ControlDown())
+ // ;
+ else
+ moveCursorTo(0);
+ return;
+
+ case WXK_END:
+ case WXK_NUMPAD_END:
+ if (event.ShiftDown())
+ selectWithCursorTo(rowCount - 1);
+ //else if (event.ControlDown())
+ // ;
+ else
+ moveCursorTo(rowCount - 1);
+ return;
+
+ case WXK_PAGEUP:
+ case WXK_NUMPAD_PAGEUP:
+ if (event.ShiftDown())
+ selectWithCursorTo(cursorRow - GetClientSize().GetHeight() / rowLabelWin_->getRowHeight());
+ //else if (event.ControlDown())
+ // ;
+ else
+ moveCursorTo(cursorRow - GetClientSize().GetHeight() / rowLabelWin_->getRowHeight());
+ return;
+
+ case WXK_PAGEDOWN:
+ case WXK_NUMPAD_PAGEDOWN:
+ if (event.ShiftDown())
+ selectWithCursorTo(cursorRow + GetClientSize().GetHeight() / rowLabelWin_->getRowHeight());
+ //else if (event.ControlDown())
+ // ;
+ else
+ moveCursorTo(cursorRow + GetClientSize().GetHeight() / rowLabelWin_->getRowHeight());
+ return;
+
+ case 'A': //Ctrl + A - select all
+ if (event.ControlDown())
+ selectRangeAndNotify(0, rowCount, true /*positive*/, nullptr /*mouseInitiated*/);
+ break;
+
+ case WXK_NUMPAD_ADD: //CTRL + '+' - auto-size all
+ if (event.ControlDown())
+ autoSizeColumns(ALLOW_GRID_EVENT);
+ return;
+ }
+
+ event.Skip();
+}
+
+
+void Grid::setColumnLabelHeight(int height)
+{
+ colLabelHeight_ = std::max(0, height);
+ updateWindowSizes();
+}
+
+
+void Grid::showRowLabel(bool show)
+{
+ drawRowLabel_ = show;
+ updateWindowSizes();
+}
+
+
+void Grid::selectAllRows(GridEventPolicy rangeEventPolicy)
+{
+ selection_.selectAll();
+ mainWin_->Refresh();
+
+ if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction
+ {
+ GridRangeSelectEvent selEvent(0, getRowCount(), true, nullptr);
+ if (wxEvtHandler* evtHandler = GetEventHandler())
+ evtHandler->ProcessEvent(selEvent);
+ }
+}
+
+
+void Grid::clearSelection(GridEventPolicy rangeEventPolicy)
+{
+ selection_.clear();
+ mainWin_->Refresh();
+
+ if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction
+ {
+ GridRangeSelectEvent unselectionEvent(0, getRowCount(), false, nullptr);
+ if (wxEvtHandler* evtHandler = GetEventHandler())
+ evtHandler->ProcessEvent(unselectionEvent);
+ }
+}
+
+
+void Grid::scrollDelta(int deltaX, int deltaY)
+{
+ int scrollPosX = 0;
+ int scrollPosY = 0;
+ GetViewStart(&scrollPosX, &scrollPosY);
+
+ scrollPosX += deltaX;
+ scrollPosY += deltaY;
+
+ scrollPosX = std::max(0, scrollPosX); //wxScrollHelper::Scroll() will exit prematurely if input happens to be "-1"!
+ scrollPosY = std::max(0, scrollPosY); //
+
+ Scroll(scrollPosX, scrollPosY); //internally calls wxWindows::Update()!
+ updateWindowSizes(); //may show horizontal scroll bar
+}
+
+
+void Grid::redirectRowLabelEvent(wxMouseEvent& event)
+{
+ event.m_x = 0;
+ if (wxEvtHandler* evtHandler = mainWin_->GetEventHandler())
+ evtHandler->ProcessEvent(event);
+
+ if (event.ButtonDown() && wxWindow::FindFocus() != mainWin_)
+ mainWin_->SetFocus();
+}
+
+
+size_t Grid::getRowCount() const
+{
+ return dataView_ ? dataView_->getRowCount() : 0;
+}
+
+
+void Grid::Refresh(bool eraseBackground, const wxRect* rect)
+{
+ const size_t rowCountNew = getRowCount();
+ if (rowCountOld_ != rowCountNew)
+ {
+ rowCountOld_ = rowCountNew;
+ updateWindowSizes();
+ }
+
+ if (selection_.maxSize() != rowCountNew) //clear selection only when needed (consider setSelectedRows())
+ selection_.init(rowCountNew);
+
+ wxScrolledWindow::Refresh(eraseBackground, rect);
+}
+
+
+void Grid::setRowHeight(int height)
+{
+ rowLabelWin_->setRowHeight(height);
+ updateWindowSizes();
+ Refresh();
+}
+
+
+void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr)
+{
+ //hold ownership of non-visible columns
+ oldColAttributes_ = attr;
+
+ std::vector<VisibleColumn> visCols;
+ for (const ColumnAttribute& ca : attr)
+ {
+ assert(ca.type_ != ColumnType::NONE);
+ if (ca.visible_)
+ visCols.emplace_back(ca.type_, ca.offset_, ca.stretch_);
+ }
+
+ //"ownership" of visible columns is now within Grid
+ visibleCols_ = visCols;
+
+ updateWindowSizes();
+ Refresh();
+}
+
+
+std::vector<Grid::ColumnAttribute> Grid::getColumnConfig() const
+{
+ //get non-visible columns (+ outdated visible ones)
+ std::vector<ColumnAttribute> output = oldColAttributes_;
+
+ auto iterVcols = visibleCols_.begin();
+ auto iterVcolsend = visibleCols_.end();
+
+ //update visible columns but keep order of non-visible ones!
+ for (ColumnAttribute& ca : output)
+ if (ca.visible_)
+ {
+ if (iterVcols != iterVcolsend)
+ {
+ ca.type_ = iterVcols->type_;
+ ca.stretch_ = iterVcols->stretch_;
+ ca.offset_ = iterVcols->offset_;
+ ++iterVcols;
+ }
+ else
+ assert(false);
+ }
+ assert(iterVcols == iterVcolsend);
+
+ return output;
+}
+
+
+void Grid::showScrollBars(Grid::ScrollBarStatus horizontal, Grid::ScrollBarStatus vertical)
+{
+ if (showScrollbarX_ == horizontal &&
+ showScrollbarY_ == vertical) return; //support polling!
+
+ showScrollbarX_ = horizontal;
+ showScrollbarY_ = vertical;
+
+ //the following wxGTK approach is pretty much identical to wxWidgets 2.9 ShowScrollbars() code!
+
+ auto mapStatus = [](ScrollBarStatus sbStatus) -> GtkPolicyType
+ {
+ switch (sbStatus)
+ {
+ case SB_SHOW_AUTOMATIC:
+ return GTK_POLICY_AUTOMATIC;
+ case SB_SHOW_ALWAYS:
+ return GTK_POLICY_ALWAYS;
+ case SB_SHOW_NEVER:
+ return GTK_POLICY_NEVER;
+ }
+ assert(false);
+ return GTK_POLICY_AUTOMATIC;
+ };
+
+ GtkWidget* gridWidget = wxWindow::m_widget;
+ GtkScrolledWindow* scrolledWindow = GTK_SCROLLED_WINDOW(gridWidget);
+ ::gtk_scrolled_window_set_policy(scrolledWindow,
+ mapStatus(horizontal),
+ mapStatus(vertical));
+
+ updateWindowSizes();
+}
+
+
+
+wxWindow& Grid::getCornerWin () { return *cornerWin_; }
+wxWindow& Grid::getRowLabelWin() { return *rowLabelWin_; }
+wxWindow& Grid::getColLabelWin() { return *colLabelWin_; }
+wxWindow& Grid::getMainWin () { return *mainWin_; }
+const wxWindow& Grid::getMainWin() const { return *mainWin_; }
+
+
+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;
+ std::vector<ColumnWidth> absWidths = getColWidths(); //resolve stretched widths
+
+ int accuWidth = 0;
+ for (size_t col = 0; col < absWidths.size(); ++col)
+ {
+ accuWidth += absWidths[col].width_;
+ if (std::abs(absPosX - accuWidth) < resizeTolerance)
+ {
+ ColAction out;
+ out.wantResize = true;
+ out.col = col;
+ return out;
+ }
+ else if (absPosX < accuWidth)
+ {
+ ColAction out;
+ out.wantResize = false;
+ out.col = col;
+ return out;
+ }
+ }
+ }
+ return NoValue();
+}
+
+
+void Grid::moveColumn(size_t colFrom, size_t colTo)
+{
+ if (colFrom < visibleCols_.size() &&
+ colTo < visibleCols_.size() &&
+ colTo != colFrom)
+ {
+ const VisibleColumn colAtt = visibleCols_[colFrom];
+ visibleCols_.erase (visibleCols_.begin() + colFrom);
+ visibleCols_.insert(visibleCols_.begin() + colTo, colAtt);
+ }
+}
+
+
+ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos) const
+{
+
+ const int absPosX = CalcUnscrolledPosition(pos).x;
+
+ int accWidth = 0;
+ std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths
+ for (auto itCol = absWidths.begin(); itCol != absWidths.end(); ++itCol)
+ {
+ const int width = itCol->width_; //beware dreaded unsigned conversions!
+ accWidth += width;
+
+ if (absPosX < accWidth - width / 2)
+ return itCol - absWidths.begin();
+ }
+ return absWidths.size();
+}
+
+
+ColumnType Grid::colToType(size_t col) const
+{
+ if (col < visibleCols_.size())
+ return visibleCols_[col].type_;
+ return ColumnType::NONE;
+}
+
+
+ptrdiff_t Grid::getRowAtPos(int posY) const { return rowLabelWin_->getRowAtPos(posY); }
+
+
+Grid::ColumnPosInfo Grid::getColumnAtPos(int posX) const
+{
+ if (posX >= 0)
+ {
+ int accWidth = 0;
+ for (const ColumnWidth& cw : getColWidths())
+ {
+ accWidth += cw.width_;
+ if (posX < accWidth)
+ return { cw.type_, posX + cw.width_ - accWidth, cw.width_ };
+ }
+ }
+ return { ColumnType::NONE, 0, 0 };
+}
+
+
+wxRect Grid::getColumnLabelArea(ColumnType colType) const
+{
+ std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths
+
+ //colType is not unique in general, but *this* function expects it!
+ assert(std::count_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; }) <= 1);
+
+ auto itCol = std::find_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; });
+ if (itCol != absWidths.end())
+ {
+ ptrdiff_t posX = 0;
+ for (auto it = absWidths.begin(); it != itCol; ++it)
+ posX += it->width_;
+
+ return wxRect(wxPoint(posX, 0), wxSize(itCol->width_, colLabelHeight_));
+ }
+ return wxRect();
+}
+
+
+void Grid::refreshCell(size_t row, ColumnType colType)
+{
+ const wxRect& colArea = getColumnLabelArea(colType); //returns empty rect if column not found
+ const wxRect& rowArea = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found
+ if (colArea.height > 0 && rowArea.height > 0)
+ {
+ const wxPoint topLeft = CalcScrolledPosition(wxPoint(colArea.x, rowArea.y)); //absolute -> client coordinates
+ const wxRect cellArea(topLeft, wxSize(colArea.width, rowArea.height));
+
+ getMainWin().RefreshRect(cellArea, false);
+ }
+}
+
+
+void Grid::setGridCursor(size_t row)
+{
+ mainWin_->setCursor(row, row);
+ makeRowVisible(row);
+
+ selection_.clear(); //clear selection, do NOT fire event
+ selectRangeAndNotify(row, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event
+
+ mainWin_->Refresh();
+ rowLabelWin_->Refresh(); //row labels! (Kubuntu)
+}
+
+
+void Grid::selectWithCursor(ptrdiff_t row)
+{
+ const size_t anchorRow = mainWin_->getAnchor();
+
+ mainWin_->setCursor(row, anchorRow);
+ makeRowVisible(row);
+
+ selection_.clear(); //clear selection, do NOT fire event
+ selectRangeAndNotify(anchorRow, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event
+
+ mainWin_->Refresh();
+ rowLabelWin_->Refresh();
+}
+
+
+void Grid::makeRowVisible(size_t row)
+{
+ const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found
+ if (labelRect.height > 0)
+ {
+ int scrollPosX = 0;
+ GetViewStart(&scrollPosX, nullptr);
+
+ int pixelsPerUnitY = 0;
+ GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY);
+ if (pixelsPerUnitY <= 0) return;
+
+ const int clientPosY = CalcScrolledPosition(labelRect.GetTopLeft()).y;
+ if (clientPosY < 0)
+ {
+ const int scrollPosY = labelRect.y / pixelsPerUnitY;
+ Scroll(scrollPosX, scrollPosY); //internally calls wxWindows::Update()!
+ updateWindowSizes(); //may show horizontal scroll bar
+ }
+ else if (clientPosY + labelRect.height > rowLabelWin_->GetClientSize().GetHeight())
+ {
+ auto execScroll = [&](int clientHeight)
+ {
+ const int scrollPosY = std::ceil((labelRect.y - clientHeight +
+ labelRect.height) / static_cast<double>(pixelsPerUnitY));
+ Scroll(scrollPosX, scrollPosY);
+ updateWindowSizes(); //may show horizontal scroll bar
+ };
+
+ const int clientHeightBefore = rowLabelWin_->GetClientSize().GetHeight();
+ execScroll(clientHeightBefore);
+
+ //client height may decrease after scroll due to a new horizontal scrollbar, resulting in a partially visible last row
+ const int clientHeightAfter = rowLabelWin_->GetClientSize().GetHeight();
+ if (clientHeightAfter < clientHeightBefore)
+ execScroll(clientHeightAfter);
+ }
+ }
+}
+
+
+void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated)
+{
+ //sort + convert to half-open range
+ auto rowFirst = std::min(rowFrom, rowTo);
+ auto rowLast = std::max(rowFrom, rowTo) + 1;
+
+ const size_t rowCount = getRowCount();
+ numeric::clamp<ptrdiff_t>(rowFirst, 0, rowCount);
+ numeric::clamp<ptrdiff_t>(rowLast, 0, rowCount);
+
+ selection_.selectRange(rowFirst, rowLast, positive);
+
+ //notify event
+ GridRangeSelectEvent selectionEvent(rowFirst, rowLast, positive, mouseInitiated);
+ if (wxEvtHandler* evtHandler = GetEventHandler())
+ evtHandler->ProcessEvent(selectionEvent);
+
+ mainWin_->Refresh();
+}
+
+
+void Grid::scrollTo(size_t row)
+{
+ const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found
+ if (labelRect.height > 0)
+ {
+ int pixelsPerUnitY = 0;
+ GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY);
+ if (pixelsPerUnitY > 0)
+ {
+ const int scrollPosYNew = labelRect.y / pixelsPerUnitY;
+ int scrollPosXOld = 0;
+ int scrollPosYOld = 0;
+ GetViewStart(&scrollPosXOld, &scrollPosYOld);
+
+ if (scrollPosYOld != scrollPosYNew) //support polling
+ {
+ Scroll(scrollPosXOld, scrollPosYNew); //internally calls wxWindows::Update()!
+ updateWindowSizes(); //may show horizontal scroll bar
+ Refresh();
+ }
+ }
+ }
+}
+
+
+bool Grid::Enable(bool enable)
+{
+ Refresh();
+ return wxScrolledWindow::Enable(enable);
+}
+
+
+size_t Grid::getGridCursor() const
+{
+ return mainWin_->getCursor();
+}
+
+
+int Grid::getBestColumnSize(size_t col) const
+{
+ if (dataView_ && col < visibleCols_.size())
+ {
+ const ColumnType type = visibleCols_[col].type_;
+
+ wxClientDC dc(mainWin_);
+ dc.SetFont(mainWin_->GetFont()); //harmonize with MainWin::render()
+
+ int maxSize = 0;
+
+ auto rowRange = rowLabelWin_->getRowsOnClient(mainWin_->GetClientRect()); //returns range [begin, end)
+ for (auto row = rowRange.first; row < rowRange.second; ++row)
+ maxSize = std::max(maxSize, dataView_->getBestSize(dc, row, type));
+
+ return maxSize;
+ }
+ return -1;
+}
+
+
+void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEventPolicy, bool notifyAsync)
+{
+ if (col < visibleCols_.size())
+ {
+ VisibleColumn& vcRs = visibleCols_[col];
+
+ const std::vector<int> stretchedWidths = getColStretchedWidths(mainWin_->GetClientSize().GetWidth());
+ if (stretchedWidths.size() != visibleCols_.size())
+ {
+ assert(false);
+ return;
+ }
+ //CAVEATS:
+ //I. fixed-size columns: normalize offset so that resulting width is at least COLUMN_MIN_WIDTH: this is NOT enforced by getColWidths()!
+ //II. stretched columns: do not allow user to set offsets so small that they result in negative (non-normalized) widths: this gives an
+ //unusual delay when enlarging the column again later
+ width = std::max(width, COLUMN_MIN_WIDTH);
+
+ vcRs.offset_ = width - stretchedWidths[col]; //width := stretchedWidth + offset
+
+ //III. resizing any column should normalize *all* other stretched columns' offsets considering current mainWinWidth!
+ // test case:
+ //1. have columns, both fixed-size and stretched, fit whole window width
+ //2. shrink main window width so that horizontal scrollbars are shown despite the streched column
+ //3. shrink a fixed-size column so that the scrollbars vanish and columns cover full width again
+ //4. now verify that the stretched column is resizing immediately if main window is enlarged again
+ 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]);
+
+ if (columnResizeEventPolicy == ALLOW_GRID_EVENT)
+ {
+ GridColumnResizeEvent sizeEvent(vcRs.offset_, vcRs.type_);
+ if (wxEvtHandler* evtHandler = GetEventHandler())
+ {
+ if (notifyAsync)
+ evtHandler->AddPendingEvent(sizeEvent);
+ else
+ evtHandler->ProcessEvent(sizeEvent);
+ }
+ }
+ }
+ else
+ assert(false);
+}
+
+
+void Grid::autoSizeColumns(GridEventPolicy columnResizeEventPolicy)
+{
+ if (allowColumnResize_)
+ {
+ for (size_t col = 0; col < visibleCols_.size(); ++col)
+ {
+ const int bestWidth = getBestColumnSize(col); //return -1 on error
+ if (bestWidth >= 0)
+ setColumnWidth(bestWidth, col, columnResizeEventPolicy, true);
+ }
+ updateWindowSizes();
+ Refresh();
+ }
+}
+
+
+std::vector<int> Grid::getColStretchedWidths(int clientWidth) const //final width = (normalized) (stretchedWidth + offset)
+{
+ assert(clientWidth >= 0);
+ clientWidth = std::max(clientWidth, 0);
+ int stretchTotal = 0;
+ for (const VisibleColumn& vc : visibleCols_)
+ {
+ assert(vc.stretch_ >= 0);
+ stretchTotal += vc.stretch_;
+ }
+
+ int remainingWidth = clientWidth;
+
+ std::vector<int> output;
+
+ if (stretchTotal <= 0)
+ output.resize(visibleCols_.size()); //fill with zeros
+ else
+ {
+ for (const VisibleColumn& vc : visibleCols_)
+ {
+ const int width = clientWidth * vc.stretch_ / stretchTotal; //rounds down!
+ output.push_back(width);
+ remainingWidth -= width;
+ }
+
+ //distribute *all* of clientWidth: should suffice to enlarge the first few stretched columns; no need to minimize total absolute error of distribution
+ if (remainingWidth > 0)
+ for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2)
+ if (visibleCols_[col2].stretch_ > 0)
+ {
+ ++output[col2];
+ if (--remainingWidth == 0)
+ break;
+ }
+ assert(remainingWidth == 0);
+ }
+ return output;
+}
+
+
+std::vector<Grid::ColumnWidth> Grid::getColWidths() const
+{
+ return getColWidths(mainWin_->GetClientSize().GetWidth());
+}
+
+
+std::vector<Grid::ColumnWidth> Grid::getColWidths(int mainWinWidth) const //evaluate stretched columns
+{
+ const std::vector<int> stretchedWidths = getColStretchedWidths(mainWinWidth);
+ assert(stretchedWidths.size() == visibleCols_.size());
+
+ std::vector<ColumnWidth> output;
+ for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2)
+ {
+ const auto& vc = visibleCols_[col2];
+ 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
+ else
+ width = std::max(width, 0); //support smaller width than COLUMN_MIN_WIDTH if set via configuration
+
+ output.emplace_back(vc.type_, width);
+ }
+ return output;
+}
+
+
+int Grid::getColWidthsSum(int mainWinWidth) const
+{
+ int sum = 0;
+ for (const ColumnWidth& cw : getColWidths(mainWinWidth))
+ sum += cw.width_;
+ return sum;
+}
diff --git a/wx+/grid.h b/wx+/grid.h
index f08d6e41..ff1d4b87 100644..100755
--- a/wx+/grid.h
+++ b/wx+/grid.h
@@ -1,366 +1,363 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef GRID_H_834702134831734869987
-#define GRID_H_834702134831734869987
-
-#include <memory>
-#include <numeric>
-#include <vector>
-#include <wx/scrolwin.h>
-#include <zen/basic_math.h>
-#include <zen/optional.h>
-
-//a user-friendly, extensible and high-performance grid control
-
-namespace zen
-{
-enum class ColumnType { NONE = -1 }; //user-defiend column type
-enum class HoverArea { NONE = -1 }; //user-defined area for mouse selections for a given row (may span multiple columns or split a single column into multiple areas)
-
-//------------------------ events ------------------------------------------------
-extern const wxEventType EVENT_GRID_MOUSE_LEFT_DOUBLE; //
-extern const wxEventType EVENT_GRID_MOUSE_LEFT_DOWN; //
-extern const wxEventType EVENT_GRID_MOUSE_LEFT_UP; //generates: GridClickEvent
-extern const wxEventType EVENT_GRID_MOUSE_RIGHT_DOWN; //
-extern const wxEventType EVENT_GRID_MOUSE_RIGHT_UP; //
-
-extern const wxEventType EVENT_GRID_SELECT_RANGE; //generates: GridRangeSelectEvent
-//NOTE: neither first nor second row need to match EVENT_GRID_MOUSE_LEFT_DOWN/EVENT_GRID_MOUSE_LEFT_UP: user holding SHIFT; moving out of window...
-
-extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_LEFT; //generates: GridLabelClickEvent
-extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_RIGHT; //
-extern const wxEventType EVENT_GRID_COL_RESIZE; //generates: GridColumnResizeEvent
-
-//example: wnd.Connect(EVENT_GRID_COL_LABEL_LEFT_CLICK, GridClickEventHandler(MyDlg::OnLeftClick), nullptr, this);
-
-struct GridClickEvent : public wxMouseEvent
-{
- GridClickEvent(wxEventType et, const wxMouseEvent& me, ptrdiff_t row, HoverArea hoverArea) :
- wxMouseEvent(me), row_(row), hoverArea_(hoverArea) { SetEventType(et); }
- wxEvent* Clone() const override { return new GridClickEvent(*this); }
-
- const ptrdiff_t row_; //-1 for invalid position, >= rowCount if out of range
- const HoverArea hoverArea_; //may be HoverArea::NONE
-};
-
-struct GridRangeSelectEvent : public wxCommandEvent
-{
- GridRangeSelectEvent(size_t rowFirst, size_t rowLast, bool positive, const GridClickEvent* mouseInitiated) :
- wxCommandEvent(EVENT_GRID_SELECT_RANGE), rowFirst_(rowFirst), rowLast_(rowLast), positive_(positive),
- mouseInitiated_(mouseInitiated ? *mouseInitiated : Opt<GridClickEvent>()) { assert(rowFirst <= rowLast); }
- wxEvent* Clone() const override { return new GridRangeSelectEvent(*this); }
-
- const size_t rowFirst_; //selected range: [rowFirst_, rowLast_)
- const size_t rowLast_;
- const bool positive_; //"false" when clearing selection!
- Opt<GridClickEvent> mouseInitiated_; //filled unless selection was performed via keyboard shortcuts or is result of Grid::clearSelection()
-};
-
-struct GridLabelClickEvent : public wxMouseEvent
-{
- GridLabelClickEvent(wxEventType et, const wxMouseEvent& me, ColumnType colType) : wxMouseEvent(me), colType_(colType) { SetEventType(et); }
- wxEvent* Clone() const override { return new GridLabelClickEvent(*this); }
-
- const ColumnType colType_; //may be ColumnType::NONE
-};
-
-
-struct GridColumnResizeEvent : public wxCommandEvent
-{
- GridColumnResizeEvent(int offset, ColumnType colType) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), offset_(offset) {}
- wxEvent* Clone() const override { return new GridColumnResizeEvent(*this); }
-
- const ColumnType colType_;
- const int offset_;
-};
-
-using GridClickEventFunction = void (wxEvtHandler::*)(GridClickEvent&);
-using GridRangeSelectEventFunction = void (wxEvtHandler::*)(GridRangeSelectEvent&);
-using GridLabelClickEventFunction = void (wxEvtHandler::*)(GridLabelClickEvent&);
-using GridColumnResizeEventFunction = void (wxEvtHandler::*)(GridColumnResizeEvent&);
-
-#define GridClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridClickEventFunction, &func)
-#define GridRangeSelectEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridRangeSelectEventFunction, &func)
-#define GridLabelClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridLabelClickEventFunction, &func)
-#define GridColumnResizeEventHandler(func)(wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridColumnResizeEventFunction, &func)
-
-//------------------------------------------------------------------------------------------------------------
-class Grid;
-
-void clearArea(wxDC& dc, const wxRect& rect, const wxColor& col);
-
-class GridData
-{
-public:
- virtual ~GridData() {}
-
- virtual size_t getRowCount() const = 0;
-
- //cell area:
- 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); //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 std::wstring getToolTip (size_t row, ColumnType colType) const { return std::wstring(); }
- virtual HoverArea getRowMouseHover(size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) { return HoverArea::NONE; }
-
- //label area:
- virtual std::wstring getColumnLabel(ColumnType colType) const = 0;
- 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
-
- //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
- static wxRect drawCellBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle
- static void drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bool selected, const wxColor& backgroundColor);
-
- static wxRect drawColumnLabelBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle
- static void drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool highlighted);
- static void drawColumnLabelText (wxDC& dc, const wxRect& rect, const std::wstring& text);
-};
-
-
-enum GridEventPolicy
-{
- ALLOW_GRID_EVENT,
- DENY_GRID_EVENT
-};
-
-
-class Grid : public wxScrolledWindow
-{
-public:
- Grid(wxWindow* parent,
- wxWindowID id = wxID_ANY,
- const wxPoint& pos = wxDefaultPosition,
- const wxSize& size = wxDefaultSize,
- long style = wxTAB_TRAVERSAL | wxNO_BORDER,
- const wxString& name = wxPanelNameStr);
-
- size_t getRowCount() const;
-
- void setRowHeight(int height);
-
- struct ColumnAttribute
- {
- ColumnAttribute(ColumnType type, int offset, int stretch, bool visible = true) : type_(type), visible_(visible), stretch_(std::max(stretch, 0)), offset_(offset) { assert(stretch >=0 ); }
- ColumnType type_;
- bool visible_;
- //first, client width is partitioned according to all available stretch factors, then "offset_" is added
- //universal model: a non-stretched column has stretch factor 0 with the "offset" becoming identical to final width!
- int stretch_; //>= 0
- int offset_;
- };
-
- void setColumnConfig(const std::vector<ColumnAttribute>& attr); //set column count + widths
- std::vector<ColumnAttribute> getColumnConfig() const;
-
- void setDataProvider(const std::shared_ptr<GridData>& dataView) { dataView_ = dataView; }
- /**/ GridData* getDataProvider() { return dataView_.get(); }
- const GridData* getDataProvider() const { return dataView_.get(); }
- //-----------------------------------------------------------------------------
-
- void setColumnLabelHeight(int height);
- void showRowLabel(bool visible);
-
- enum ScrollBarStatus
- {
- SB_SHOW_AUTOMATIC,
- SB_SHOW_ALWAYS,
- SB_SHOW_NEVER,
- };
- //alternative until wxScrollHelper::ShowScrollbars() becomes available in wxWidgets 2.9
- void showScrollBars(ScrollBarStatus horizontal, ScrollBarStatus vertical);
-
- std::vector<size_t> getSelectedRows() const { return selection_.get(); }
- void selectAllRows (GridEventPolicy rangeEventPolicy);
- void clearSelection(GridEventPolicy rangeEventPolicy); //turn off range selection event when calling this function in an event handler to avoid recursion!
-
- void scrollDelta(int deltaX, int deltaY); //in scroll units
-
- wxWindow& getCornerWin ();
- wxWindow& getRowLabelWin();
- wxWindow& getColLabelWin();
- wxWindow& getMainWin ();
- const wxWindow& getMainWin() const;
-
- ptrdiff_t getRowAtPos(int posY) const; //return -1 for invalid position, >= rowCount if out of range; absolute coordinates!
-
- struct ColumnPosInfo
- {
- ColumnType colType; //ColumnType::NONE no column at x position!
- int cellRelativePosX;
- int colWidth;
- };
- ColumnPosInfo getColumnAtPos(int posX) const; //absolute position!
-
- void refreshCell(size_t row, ColumnType colType);
-
- void enableColumnMove (bool value) { allowColumnMove_ = value; }
- void enableColumnResize(bool value) { allowColumnResize_ = value; }
-
- void setGridCursor(size_t row); //set + show + select cursor (+ emit range selection event)
- size_t getGridCursor() const; //returns row
-
- void scrollTo(size_t row);
-
- void Refresh(bool eraseBackground = true, const wxRect* rect = nullptr) override;
- bool Enable(bool enable = true) override;
-
- //############################################################################################################
-
- 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);
-
- void updateWindowSizes(bool updateScrollbar = true);
-
- void selectWithCursor(ptrdiff_t row);
- void makeRowVisible(size_t row);
-
- void redirectRowLabelEvent(wxMouseEvent& event);
-
- wxSize GetSizeAvailableForScrollTarget(const wxSize& size) override; //required since wxWidgets 2.9 if SetTargetWindow() is used
-
-#if defined ZEN_WIN || defined ZEN_MAC
- void SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh) override; //get rid of scrollbars, but preserve scrolling behavior!
-#endif
-
- int getBestColumnSize(size_t col) const; //return -1 on error
-
- void autoSizeColumns(GridEventPolicy columnResizeEventPolicy);
-
- friend class GridData;
- class SubWindow;
- class CornerWin;
- class RowLabelWin;
- class ColLabelWin;
- class MainWin;
-
- class Selection
- {
- public:
- void init(size_t rowCount) { rowSelectionValue.resize(rowCount); clear(); }
-
- size_t maxSize() const { return rowSelectionValue.size(); }
-
- std::vector<size_t> get() const
- {
- std::vector<size_t> result;
- for (size_t row = 0; row < rowSelectionValue.size(); ++row)
- if (rowSelectionValue[row] != 0)
- result.push_back(row);
- return result;
- }
-
- void selectAll() { selectRange(0, rowSelectionValue.size(), true); }
- void clear () { selectRange(0, rowSelectionValue.size(), false); }
-
- bool isSelected(size_t row) const { return row < rowSelectionValue.size() ? rowSelectionValue[row] != 0 : false; }
-
- void selectRange(size_t rowFirst, size_t rowLast, bool positive = true) //select [rowFirst, rowLast), trims if required!
- {
- if (rowFirst <= rowLast)
- {
- numeric::clamp<size_t>(rowFirst, 0, rowSelectionValue.size());
- numeric::clamp<size_t>(rowLast, 0, rowSelectionValue.size());
-
- std::fill(rowSelectionValue.begin() + rowFirst, rowSelectionValue.begin() + rowLast, positive);
- }
- else assert(false);
- }
-
- private:
- std::vector<char> rowSelectionValue; //effectively a vector<bool> of size "number of rows"
- };
-
- struct VisibleColumn
- {
- VisibleColumn(ColumnType type, int offset, int stretch) : type_(type), stretch_(stretch), offset_(offset) {}
- ColumnType type_;
- int stretch_; //>= 0
- int offset_;
- };
-
- struct ColumnWidth
- {
- ColumnWidth(ColumnType type, int width) : type_(type), width_(width) {}
- ColumnType type_;
- int width_;
- };
- std::vector<ColumnWidth> getColWidths() const; //
- std::vector<ColumnWidth> getColWidths(int mainWinWidth) const; //evaluate stretched columns
- int getColWidthsSum(int mainWinWidth) const;
- std::vector<int> getColStretchedWidths(int clientWidth) const; //final width = (normalized) (stretchedWidth + offset)
-
- Opt<int> getColWidth(size_t col) const
- {
- const auto& widths = getColWidths();
- if (col < widths.size())
- return widths[col].width_;
- return NoValue();
- }
-
- void setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEventPolicy, bool notifyAsync = false);
-
- wxRect getColumnLabelArea(ColumnType colType) const; //returns empty rect if column not found
-
- void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated); //select inclusive range [rowFrom, rowTo] + notify event!
-
- bool isSelected(size_t row) const { return selection_.isSelected(row); }
-
- struct ColAction
- {
- bool wantResize = false; //"!wantResize" means "move" or "single click"
- size_t col = 0;
- };
- Opt<ColAction> clientPosToColumnAction(const wxPoint& pos) const;
- void moveColumn(size_t colFrom, size_t colTo);
- ptrdiff_t clientPosToMoveTargetColumn(const wxPoint& pos) const; //return < 0 on error
-
- ColumnType colToType(size_t col) const; //returns ColumnType::NONE on error
-
- /*
- Visual layout:
- --------------------------------
- |CornerWin | ColLabelWin |
- |------------------------------|
- |RowLabelWin | MainWin |
- | | |
- --------------------------------
- */
- CornerWin* cornerWin_;
- RowLabelWin* rowLabelWin_;
- ColLabelWin* colLabelWin_;
- MainWin* mainWin_;
-
- ScrollBarStatus showScrollbarX_ = SB_SHOW_AUTOMATIC;
- ScrollBarStatus showScrollbarY_ = SB_SHOW_AUTOMATIC;
-
- int colLabelHeight_ = 0;
- bool drawRowLabel_ = true;
-
- std::shared_ptr<GridData> dataView_;
- Selection selection_;
- bool allowColumnMove_ = true;
- bool allowColumnResize_ = true;
-
- std::vector<VisibleColumn> visibleCols_; //individual widths, type and total column count
- std::vector<ColumnAttribute> oldColAttributes_; //visible + nonvisible columns; use for conversion in setColumnConfig()/getColumnConfig() *only*!
-
- size_t rowCountOld_ = 0; //at the time of last Grid::Refresh()
-};
-}
-
-#endif //GRID_H_834702134831734869987
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef GRID_H_834702134831734869987
+#define GRID_H_834702134831734869987
+
+#include <memory>
+#include <numeric>
+#include <vector>
+#include <wx/scrolwin.h>
+#include <zen/basic_math.h>
+#include <zen/optional.h>
+
+//a user-friendly, extensible and high-performance grid control
+
+namespace zen
+{
+enum class ColumnType { NONE = -1 }; //user-defiend column type
+enum class HoverArea { NONE = -1 }; //user-defined area for mouse selections for a given row (may span multiple columns or split a single column into multiple areas)
+
+//------------------------ events ------------------------------------------------
+extern const wxEventType EVENT_GRID_MOUSE_LEFT_DOUBLE; //
+extern const wxEventType EVENT_GRID_MOUSE_LEFT_DOWN; //
+extern const wxEventType EVENT_GRID_MOUSE_LEFT_UP; //generates: GridClickEvent
+extern const wxEventType EVENT_GRID_MOUSE_RIGHT_DOWN; //
+extern const wxEventType EVENT_GRID_MOUSE_RIGHT_UP; //
+
+extern const wxEventType EVENT_GRID_SELECT_RANGE; //generates: GridRangeSelectEvent
+//NOTE: neither first nor second row need to match EVENT_GRID_MOUSE_LEFT_DOWN/EVENT_GRID_MOUSE_LEFT_UP: user holding SHIFT; moving out of window...
+
+extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_LEFT; //generates: GridLabelClickEvent
+extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_RIGHT; //
+extern const wxEventType EVENT_GRID_COL_RESIZE; //generates: GridColumnResizeEvent
+
+//example: wnd.Connect(EVENT_GRID_COL_LABEL_LEFT_CLICK, GridClickEventHandler(MyDlg::OnLeftClick), nullptr, this);
+
+struct GridClickEvent : public wxMouseEvent
+{
+ GridClickEvent(wxEventType et, const wxMouseEvent& me, ptrdiff_t row, HoverArea hoverArea) :
+ wxMouseEvent(me), row_(row), hoverArea_(hoverArea) { SetEventType(et); }
+ wxEvent* Clone() const override { return new GridClickEvent(*this); }
+
+ const ptrdiff_t row_; //-1 for invalid position, >= rowCount if out of range
+ const HoverArea hoverArea_; //may be HoverArea::NONE
+};
+
+struct GridRangeSelectEvent : public wxCommandEvent
+{
+ GridRangeSelectEvent(size_t rowFirst, size_t rowLast, bool positive, const GridClickEvent* mouseInitiated) :
+ wxCommandEvent(EVENT_GRID_SELECT_RANGE), rowFirst_(rowFirst), rowLast_(rowLast), positive_(positive),
+ mouseInitiated_(mouseInitiated ? *mouseInitiated : Opt<GridClickEvent>()) { assert(rowFirst <= rowLast); }
+ wxEvent* Clone() const override { return new GridRangeSelectEvent(*this); }
+
+ const size_t rowFirst_; //selected range: [rowFirst_, rowLast_)
+ const size_t rowLast_;
+ const bool positive_; //"false" when clearing selection!
+ Opt<GridClickEvent> mouseInitiated_; //filled unless selection was performed via keyboard shortcuts or is result of Grid::clearSelection()
+};
+
+struct GridLabelClickEvent : public wxMouseEvent
+{
+ GridLabelClickEvent(wxEventType et, const wxMouseEvent& me, ColumnType colType) : wxMouseEvent(me), colType_(colType) { SetEventType(et); }
+ wxEvent* Clone() const override { return new GridLabelClickEvent(*this); }
+
+ const ColumnType colType_; //may be ColumnType::NONE
+};
+
+
+struct GridColumnResizeEvent : public wxCommandEvent
+{
+ GridColumnResizeEvent(int offset, ColumnType colType) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), offset_(offset) {}
+ wxEvent* Clone() const override { return new GridColumnResizeEvent(*this); }
+
+ const ColumnType colType_;
+ const int offset_;
+};
+
+using GridClickEventFunction = void (wxEvtHandler::*)(GridClickEvent&);
+using GridRangeSelectEventFunction = void (wxEvtHandler::*)(GridRangeSelectEvent&);
+using GridLabelClickEventFunction = void (wxEvtHandler::*)(GridLabelClickEvent&);
+using GridColumnResizeEventFunction = void (wxEvtHandler::*)(GridColumnResizeEvent&);
+
+#define GridClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridClickEventFunction, &func)
+#define GridRangeSelectEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridRangeSelectEventFunction, &func)
+#define GridLabelClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridLabelClickEventFunction, &func)
+#define GridColumnResizeEventHandler(func)(wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridColumnResizeEventFunction, &func)
+
+//------------------------------------------------------------------------------------------------------------
+class Grid;
+
+void clearArea(wxDC& dc, const wxRect& rect, const wxColor& col);
+
+class GridData
+{
+public:
+ virtual ~GridData() {}
+
+ virtual size_t getRowCount() const = 0;
+
+ //cell area:
+ 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); //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 std::wstring getToolTip (size_t row, ColumnType colType) const { return std::wstring(); }
+ virtual HoverArea getRowMouseHover(size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) { return HoverArea::NONE; }
+
+ //label area:
+ virtual std::wstring getColumnLabel(ColumnType colType) const = 0;
+ 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
+
+ //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
+ static wxRect drawCellBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle
+ static void drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bool selected, const wxColor& backgroundColor);
+
+ static wxRect drawColumnLabelBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle
+ static void drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool highlighted);
+ static void drawColumnLabelText (wxDC& dc, const wxRect& rect, const std::wstring& text);
+};
+
+
+enum GridEventPolicy
+{
+ ALLOW_GRID_EVENT,
+ DENY_GRID_EVENT
+};
+
+
+class Grid : public wxScrolledWindow
+{
+public:
+ Grid(wxWindow* parent,
+ wxWindowID id = wxID_ANY,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = wxTAB_TRAVERSAL | wxNO_BORDER,
+ const wxString& name = wxPanelNameStr);
+
+ size_t getRowCount() const;
+
+ void setRowHeight(int height);
+
+ struct ColumnAttribute
+ {
+ ColumnAttribute(ColumnType type, int offset, int stretch, bool visible = true) : type_(type), visible_(visible), stretch_(std::max(stretch, 0)), offset_(offset) { assert(stretch >=0 ); }
+ ColumnType type_;
+ bool visible_;
+ //first, client width is partitioned according to all available stretch factors, then "offset_" is added
+ //universal model: a non-stretched column has stretch factor 0 with the "offset" becoming identical to final width!
+ int stretch_; //>= 0
+ int offset_;
+ };
+
+ void setColumnConfig(const std::vector<ColumnAttribute>& attr); //set column count + widths
+ std::vector<ColumnAttribute> getColumnConfig() const;
+
+ void setDataProvider(const std::shared_ptr<GridData>& dataView) { dataView_ = dataView; }
+ /**/ GridData* getDataProvider() { return dataView_.get(); }
+ const GridData* getDataProvider() const { return dataView_.get(); }
+ //-----------------------------------------------------------------------------
+
+ void setColumnLabelHeight(int height);
+ void showRowLabel(bool visible);
+
+ enum ScrollBarStatus
+ {
+ SB_SHOW_AUTOMATIC,
+ SB_SHOW_ALWAYS,
+ SB_SHOW_NEVER,
+ };
+ //alternative until wxScrollHelper::ShowScrollbars() becomes available in wxWidgets 2.9
+ void showScrollBars(ScrollBarStatus horizontal, ScrollBarStatus vertical);
+
+ std::vector<size_t> getSelectedRows() const { return selection_.get(); }
+ void selectAllRows (GridEventPolicy rangeEventPolicy);
+ void clearSelection(GridEventPolicy rangeEventPolicy); //turn off range selection event when calling this function in an event handler to avoid recursion!
+
+ void scrollDelta(int deltaX, int deltaY); //in scroll units
+
+ wxWindow& getCornerWin ();
+ wxWindow& getRowLabelWin();
+ wxWindow& getColLabelWin();
+ wxWindow& getMainWin ();
+ const wxWindow& getMainWin() const;
+
+ ptrdiff_t getRowAtPos(int posY) const; //return -1 for invalid position, >= rowCount if out of range; absolute coordinates!
+
+ struct ColumnPosInfo
+ {
+ ColumnType colType; //ColumnType::NONE no column at x position!
+ int cellRelativePosX;
+ int colWidth;
+ };
+ ColumnPosInfo getColumnAtPos(int posX) const; //absolute position!
+
+ void refreshCell(size_t row, ColumnType colType);
+
+ void enableColumnMove (bool value) { allowColumnMove_ = value; }
+ void enableColumnResize(bool value) { allowColumnResize_ = value; }
+
+ void setGridCursor(size_t row); //set + show + select cursor (+ emit range selection event)
+ size_t getGridCursor() const; //returns row
+
+ void scrollTo(size_t row);
+
+ void Refresh(bool eraseBackground = true, const wxRect* rect = nullptr) override;
+ bool Enable(bool enable = true) override;
+
+ //############################################################################################################
+
+ 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);
+
+ void updateWindowSizes(bool updateScrollbar = true);
+
+ void selectWithCursor(ptrdiff_t row);
+ void makeRowVisible(size_t row);
+
+ void redirectRowLabelEvent(wxMouseEvent& event);
+
+ wxSize GetSizeAvailableForScrollTarget(const wxSize& size) override; //required since wxWidgets 2.9 if SetTargetWindow() is used
+
+
+ int getBestColumnSize(size_t col) const; //return -1 on error
+
+ void autoSizeColumns(GridEventPolicy columnResizeEventPolicy);
+
+ friend class GridData;
+ class SubWindow;
+ class CornerWin;
+ class RowLabelWin;
+ class ColLabelWin;
+ class MainWin;
+
+ class Selection
+ {
+ public:
+ void init(size_t rowCount) { rowSelectionValue.resize(rowCount); clear(); }
+
+ size_t maxSize() const { return rowSelectionValue.size(); }
+
+ std::vector<size_t> get() const
+ {
+ std::vector<size_t> result;
+ for (size_t row = 0; row < rowSelectionValue.size(); ++row)
+ if (rowSelectionValue[row] != 0)
+ result.push_back(row);
+ return result;
+ }
+
+ void selectAll() { selectRange(0, rowSelectionValue.size(), true); }
+ void clear () { selectRange(0, rowSelectionValue.size(), false); }
+
+ bool isSelected(size_t row) const { return row < rowSelectionValue.size() ? rowSelectionValue[row] != 0 : false; }
+
+ void selectRange(size_t rowFirst, size_t rowLast, bool positive = true) //select [rowFirst, rowLast), trims if required!
+ {
+ if (rowFirst <= rowLast)
+ {
+ numeric::clamp<size_t>(rowFirst, 0, rowSelectionValue.size());
+ numeric::clamp<size_t>(rowLast, 0, rowSelectionValue.size());
+
+ std::fill(rowSelectionValue.begin() + rowFirst, rowSelectionValue.begin() + rowLast, positive);
+ }
+ else assert(false);
+ }
+
+ private:
+ std::vector<char> rowSelectionValue; //effectively a vector<bool> of size "number of rows"
+ };
+
+ struct VisibleColumn
+ {
+ VisibleColumn(ColumnType type, int offset, int stretch) : type_(type), stretch_(stretch), offset_(offset) {}
+ ColumnType type_;
+ int stretch_; //>= 0
+ int offset_;
+ };
+
+ struct ColumnWidth
+ {
+ ColumnWidth(ColumnType type, int width) : type_(type), width_(width) {}
+ ColumnType type_;
+ int width_;
+ };
+ std::vector<ColumnWidth> getColWidths() const; //
+ std::vector<ColumnWidth> getColWidths(int mainWinWidth) const; //evaluate stretched columns
+ int getColWidthsSum(int mainWinWidth) const;
+ std::vector<int> getColStretchedWidths(int clientWidth) const; //final width = (normalized) (stretchedWidth + offset)
+
+ Opt<int> getColWidth(size_t col) const
+ {
+ const auto& widths = getColWidths();
+ if (col < widths.size())
+ return widths[col].width_;
+ return NoValue();
+ }
+
+ void setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEventPolicy, bool notifyAsync = false);
+
+ wxRect getColumnLabelArea(ColumnType colType) const; //returns empty rect if column not found
+
+ void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated); //select inclusive range [rowFrom, rowTo] + notify event!
+
+ bool isSelected(size_t row) const { return selection_.isSelected(row); }
+
+ struct ColAction
+ {
+ bool wantResize = false; //"!wantResize" means "move" or "single click"
+ size_t col = 0;
+ };
+ Opt<ColAction> clientPosToColumnAction(const wxPoint& pos) const;
+ void moveColumn(size_t colFrom, size_t colTo);
+ ptrdiff_t clientPosToMoveTargetColumn(const wxPoint& pos) const; //return < 0 on error
+
+ ColumnType colToType(size_t col) const; //returns ColumnType::NONE on error
+
+ /*
+ Visual layout:
+ --------------------------------
+ |CornerWin | ColLabelWin |
+ |------------------------------|
+ |RowLabelWin | MainWin |
+ | | |
+ --------------------------------
+ */
+ CornerWin* cornerWin_;
+ RowLabelWin* rowLabelWin_;
+ ColLabelWin* colLabelWin_;
+ MainWin* mainWin_;
+
+ ScrollBarStatus showScrollbarX_ = SB_SHOW_AUTOMATIC;
+ ScrollBarStatus showScrollbarY_ = SB_SHOW_AUTOMATIC;
+
+ int colLabelHeight_ = 0;
+ bool drawRowLabel_ = true;
+
+ std::shared_ptr<GridData> dataView_;
+ Selection selection_;
+ bool allowColumnMove_ = true;
+ bool allowColumnResize_ = true;
+
+ std::vector<VisibleColumn> visibleCols_; //individual widths, type and total column count
+ std::vector<ColumnAttribute> oldColAttributes_; //visible + nonvisible columns; use for conversion in setColumnConfig()/getColumnConfig() *only*!
+
+ size_t rowCountOld_ = 0; //at the time of last Grid::Refresh()
+};
+}
+
+#endif //GRID_H_834702134831734869987
diff --git a/wx+/http.cpp b/wx+/http.cpp
index 3428546e..851b3ed0 100644..100755
--- a/wx+/http.cpp
+++ b/wx+/http.cpp
@@ -1,433 +1,268 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "http.h"
-#ifdef ZEN_WIN
- #include <zen/win.h> //tame WinINet.h include
- #include <WinINet.h>
-#endif
-
-#if defined ZEN_LINUX || defined ZEN_MAC
- #include <wx/app.h>
- #include <zen/thread.h> //std::thread::id
- #include <wx/protocol/http.h>
-#endif
-
-using namespace zen;
-
-
-namespace
-{
-#ifdef ZEN_WIN
- #if defined NDEBUG && defined __WXWINDOWS__
- #error don not use wxWidgets for this component!
- #endif
-#else
- #ifndef NDEBUG
- const std::thread::id mainThreadId = std::this_thread::get_id();
- #endif
-#endif
-
-struct UrlRedirectError
-{
- UrlRedirectError(const std::wstring& url) : newUrl(url) {}
- std::wstring newUrl;
-};
-}
-
-
-class HttpInputStream::Impl
-{
-public:
- Impl(const std::wstring& url, const std::wstring& userAgent, //throw SysError, UrlRedirectError
- const std::string* postParams) //issue POST if bound, GET otherwise
- {
- ZEN_ON_SCOPE_FAIL( cleanup(); /*destructor call would lead to member double clean-up!!!*/ );
-
- assert(!startsWith(makeUpperCopy(url), L"HTTPS:")); //not supported by wxHTTP!
- const std::wstring urlFmt = startsWith(makeUpperCopy(url), L"HTTP://") ||
- startsWith(makeUpperCopy(url), L"HTTPS://") ? afterFirst(url, L"://", IF_MISSING_RETURN_NONE) : url;
- const std::wstring server = beforeFirst(urlFmt, L'/', IF_MISSING_RETURN_ALL);
- const std::wstring page = L'/' + afterFirst(urlFmt, L'/', IF_MISSING_RETURN_NONE);
-
-#ifdef ZEN_WIN
- //WinInet: 1. uses IE proxy settings! :) 2. follows HTTP redirects by default 3. swallows HTTPS if needed
- hInternet_ = ::InternetOpen(userAgent.c_str(), //_In_ LPCTSTR lpszAgent,
- INTERNET_OPEN_TYPE_PRECONFIG, //_In_ DWORD dwAccessType,
- nullptr, //_In_ LPCTSTR lpszProxyName,
- nullptr, //_In_ LPCTSTR lpszProxyBypass,
- 0); //_In_ DWORD dwFlags
- if (!hInternet_)
- THROW_LAST_SYS_ERROR(L"InternetOpen");
-
- hSession_ = ::InternetConnect(hInternet_, //_In_ HINTERNET hInternet,
- server.c_str(), //_In_ LPCTSTR lpszServerName,
- INTERNET_DEFAULT_HTTP_PORT, //_In_ INTERNET_PORT nServerPort,
- nullptr, //_In_ LPCTSTR lpszUsername,
- nullptr, //_In_ LPCTSTR lpszPassword,
- INTERNET_SERVICE_HTTP, //_In_ DWORD dwService,
- 0, //_In_ DWORD dwFlags,
- 0); //_In_ DWORD_PTR dwContext
- if (!hSession_)
- THROW_LAST_SYS_ERROR(L"InternetConnect");
-
- const wchar_t* acceptTypes[] = { L"*/*", nullptr };
- DWORD requestFlags =
- //INTERNET_FLAG_KEEP_CONNECTION |
- // the combination 1. INTERNET_FLAG_KEEP_CONNECTION (= adds "Connection: Keep-Alive" but NOT "Keep-Alive: timeout" to the header)
- // 2. *no* "Keep-Alive: timeout" header entry 3. call from within VM and 4. *no* Fiddler running 5. HTTP POST
- // leads to Godaddy blocking the IP: http://www.freefilesync.org/forum/viewtopic.php?t=3855
- // => it seems a broken keep alive header is the trigger: But why is it then working outside the VM or when Fiddler is running??? Why not a problem for HTTP GET?
- // note: HTTP/1.1 has keep-alive semantics by default, so this flag is probably useless anyway
- INTERNET_FLAG_NO_UI;
-
- if (postParams)
- {
- requestFlags |= INTERNET_FLAG_NO_AUTO_REDIRECT; //POST would be re-issued as GET during auto-redirect => handle ourselves!
- }
- else //HTTP GET
- {
- requestFlags |= INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP;
- requestFlags |= INTERNET_FLAG_RELOAD; //not relevant for POST (= never cached)
- }
-
- hRequest_ = ::HttpOpenRequest(hSession_, //_In_ HINTERNET hConnect,
- postParams ? L"POST" : L"GET", //_In_ LPCTSTR lpszVerb,
- page.c_str(), //_In_ LPCTSTR lpszObjectName,
- nullptr, //_In_ LPCTSTR lpszVersion,
- nullptr, //_In_ LPCTSTR lpszReferer,
- acceptTypes, //_In_ LPCTSTR *lplpszAcceptTypes,
- requestFlags, //_In_ DWORD dwFlags,
- 0); //_In_ DWORD_PTR dwContext
- if (!hRequest_)
- THROW_LAST_SYS_ERROR(L"HttpOpenRequest");
-
- const std::wstring headers = postParams ? L"Content-type: application/x-www-form-urlencoded" : L"";
-
- assert(std::all_of(headers.begin(), headers.end(), [](wchar_t c) { return makeUnsigned(c) < 128; }));
- //HttpSendRequest has finicky behavior for non-ASCII headers: https://msdn.microsoft.com/en-us/library/windows/desktop/aa384247
-
- std::string postParamsBuf = postParams ? *postParams : "";
-
- if (!::HttpSendRequest(hRequest_, //_In_ HINTERNET hRequest,
- headers.c_str(), //_In_ LPCTSTR lpszHeaders,
- static_cast<DWORD>(headers.size()), //_In_ DWORD dwHeadersLength,
- postParamsBuf.empty() ? nullptr : &postParamsBuf[0], //_In_ LPVOID lpOptional,
- static_cast<DWORD>(postParamsBuf.size()))) //_In_ DWORD dwOptionalLength
- THROW_LAST_SYS_ERROR(L"HttpSendRequest");
-
- DWORD sc = 0;
- {
- DWORD bufLen = sizeof(sc);
- if (!::HttpQueryInfo(hRequest_, //_In_ HINTERNET hRequest,
- HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, //_In_ DWORD dwInfoLevel,
- &sc, //_Inout_ LPVOID lpvBuffer,
- &bufLen, //_Inout_ LPDWORD lpdwBufferLength,
- nullptr)) //_Inout_ LPDWORD lpdwIndex
- THROW_LAST_SYS_ERROR(L"HttpQueryInfo: HTTP_QUERY_STATUS_CODE");
- }
-
- //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection
- if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too!
- {
- DWORD bufLen = 10000;
- std::wstring location(bufLen, L'\0');
- if (!::HttpQueryInfo(hRequest_, HTTP_QUERY_LOCATION, &*location.begin(), &bufLen, nullptr))
- THROW_LAST_SYS_ERROR(L"HttpQueryInfo: HTTP_QUERY_LOCATION");
- if (bufLen >= location.size()) //HttpQueryInfo expected to write terminating zero
- throw SysError(L"HttpQueryInfo: HTTP_QUERY_LOCATION, buffer overflow");
- location.resize(bufLen);
-
- if (location.empty())
- throw SysError(L"Unresolvable redirect. Empty target Location.");
-
- throw UrlRedirectError(location);
- }
-
- if (sc != HTTP_STATUS_OK) //200
- throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc)));
- //e.g. 404 - HTTP_STATUS_NOT_FOUND
-
-#else
- assert(std::this_thread::get_id() == mainThreadId);
- assert(wxApp::IsMainLoopRunning());
-
- webAccess_.SetHeader(L"User-Agent", userAgent);
- webAccess_.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking???
-
- if (!webAccess_.Connect(server)) //will *not* fail for non-reachable url here!
- throw SysError(L"wxHTTP::Connect");
-
- if (postParams)
- if (!webAccess_.SetPostText(L"application/x-www-form-urlencoded", utfCvrtTo<wxString>(*postParams)))
- throw SysError(L"wxHTTP::SetPostText");
-
- httpStream_.reset(webAccess_.GetInputStream(page)); //pass ownership
- const int sc = webAccess_.GetResponse();
-
- //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection
- if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too!
- {
- const std::wstring newUrl(webAccess_.GetHeader(L"Location"));
- if (newUrl.empty())
- throw SysError(L"Unresolvable redirect. Empty target Location.");
-
- throw UrlRedirectError(newUrl);
- }
-
- if (sc != 200) //HTTP_STATUS_OK
- throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc)));
-
- if (!httpStream_ || webAccess_.GetError() != wxPROTO_NOERR)
- throw SysError(L"wxHTTP::GetError (" + numberTo<std::wstring>(webAccess_.GetError()) + L")");
-#endif
- }
-
- ~Impl() { cleanup(); }
-
- size_t tryRead(void* buffer, size_t bytesToRead) //throw SysError; may return short, only 0 means EOF!
- {
- if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
-
-#ifdef ZEN_WIN
- //"HttpQueryInfo() + HTTP_QUERY_CONTENT_LENGTH" not supported by all http servers...
- DWORD bytesRead = 0;
- if (!::InternetReadFile(hRequest_, //_In_ HINTERNET hFile,
- buffer, //_Out_ LPVOID lpBuffer,
- static_cast<DWORD>(bytesToRead), //_In_ DWORD dwNumberOfBytesToRead,
- &bytesRead)) //_Out_ LPDWORD lpdwNumberOfBytesRead
- THROW_LAST_SYS_ERROR(L"InternetReadFile");
-#else
- httpStream_->Read(buffer, bytesToRead);
-
- const wxStreamError ec = httpStream_->GetLastError();
- if (ec != wxSTREAM_NO_ERROR && ec != wxSTREAM_EOF)
- throw SysError(L"wxInputStream::GetLastError (" + numberTo<std::wstring>(httpStream_->GetLastError()) + L")");
-
- const size_t bytesRead = httpStream_->LastRead();
- //"if there are not enough bytes in the stream right now, LastRead() value will be
- // less than size but greater than 0. If it is 0, it means that EOF has been reached."
- assert(bytesRead > 0 || ec == wxSTREAM_EOF);
-#endif
- if (bytesRead > bytesToRead) //better safe than sorry
- throw SysError(L"InternetReadFile: buffer overflow.");
-
- return bytesRead; //"zero indicates end of file"
- }
-
-private:
- Impl (const Impl&) = delete;
- Impl& operator=(const Impl&) = delete;
-
- void cleanup()
- {
-#ifdef ZEN_WIN
- if (hRequest_ ) ::InternetCloseHandle(hRequest_);
- if (hSession_ ) ::InternetCloseHandle(hSession_);
- if (hInternet_) ::InternetCloseHandle(hInternet_);
-#endif
- }
-
-#ifdef ZEN_WIN
- HINTERNET hInternet_ = nullptr;
- HINTERNET hSession_ = nullptr;
- HINTERNET hRequest_ = nullptr;
-#else
- wxHTTP webAccess_;
- std::unique_ptr<wxInputStream> httpStream_; //must be deleted BEFORE webAccess is closed
-#endif
-};
-
-
-HttpInputStream::HttpInputStream(std::unique_ptr<Impl>&& pimpl) : pimpl_(std::move(pimpl)) {}
-
-HttpInputStream::~HttpInputStream() {}
-
-size_t HttpInputStream::tryRead(void* buffer, size_t bytesToRead) { return pimpl_->tryRead(buffer, bytesToRead); } //throw SysError
-
-
-std::string HttpInputStream::readAll() //throw SysError
-{
- std::string buffer;
- const size_t blockSize = getBlockSize();
-
- for (;;)
- {
- buffer.resize(buffer.size() + blockSize);
-
- const size_t bytesRead = pimpl_->tryRead(&*(buffer.end() - blockSize), blockSize); //throw SysError
-
- if (bytesRead < blockSize)
- buffer.resize(buffer.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics
-
- if (bytesRead == 0)
- return buffer;
- }
-}
-
-
-namespace
-{
-std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const std::wstring& url, const std::wstring& userAgent, //throw SysError
- const std::string* postParams) //issue POST if bound, GET otherwise
-{
- std::wstring urlRed = url;
- //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop."
- for (int redirects = 0; redirects < 6; ++redirects)
- try
- {
- return std::make_unique<HttpInputStream::Impl>(urlRed, userAgent, postParams); //throw SysError, UrlRedirectError
- }
- catch (const UrlRedirectError& e) { urlRed = e.newUrl; }
- throw SysError(L"Too many redirects.");
-}
-
-
-//encode into "application/x-www-form-urlencoded"
-std::string urlencode(const std::string& str)
-{
- std::string out;
- for (const char c : str) //follow PHP spec: https://github.com/php/php-src/blob/master/ext/standard/url.c#L500
- if (c == ' ')
- out += '+';
- else if (('0' <= c && c <= '9') ||
- ('A' <= c && c <= 'Z') ||
- ('a' <= c && c <= 'z') ||
- c == '-' || c == '.' || c == '_') //note: "~" is encoded by PHP!
- out += c;
- else
- {
- const std::pair<char, char> hex = hexify(c);
-
- out += '%';
- out += hex.first;
- out += hex.second;
- }
- return out;
-}
-
-
-std::string urldecode(const std::string& str)
-{
- std::string out;
- for (size_t i = 0; i < str.size(); ++i)
- {
- const char c = str[i];
- if (c == '+')
- out += ' ';
- else if (c == '%' && str.size() - i >= 3 &&
- isHexDigit(str[i + 1]) &&
- isHexDigit(str[i + 2]))
- {
- out += unhexify(str[i + 1], str[i + 2]);
- i += 2;
- }
- else
- out += c;
- }
- return out;
-}
-}
-
-
-std::string zen::xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs)
-{
- std::string output;
- for (const auto& pair : paramPairs)
- output += urlencode(pair.first) + '=' + urlencode(pair.second) + '&';
- //encode both key and value: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
- if (!output.empty())
- output.pop_back();
- return output;
-}
-
-
-std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const std::string& str)
-{
- std::vector<std::pair<std::string, std::string>> output;
-
- for (const std::string& nvPair : split(str, '&'))
- if (!nvPair.empty())
- output.emplace_back(urldecode(beforeFirst(nvPair, '=', IF_MISSING_RETURN_ALL)),
- urldecode(afterFirst (nvPair, '=', IF_MISSING_RETURN_NONE)));
- return output;
-}
-
-
-HttpInputStream zen::sendHttpPost(const std::wstring& url, const std::wstring& userAgent,
- const std::vector<std::pair<std::string, std::string>>& postParams) //throw SysError
-{
- const std::string encodedParams = xWwwFormUrlEncode(postParams);
- return sendHttpRequestImpl(url, userAgent, &encodedParams); //throw SysError
-}
-
-
-HttpInputStream zen::sendHttpGet(const std::wstring& url, const std::wstring& userAgent) //throw SysError
-{
- return sendHttpRequestImpl(url, userAgent, nullptr); //throw SysError
-}
-
-
-bool zen::internetIsAlive() //noexcept
-{
-#ifdef ZEN_WIN
- //::InternetAttemptConnect(0) -> not working as expected: succeeds even when there is no internet connection!
-
- HINTERNET hInternet = ::InternetOpen(L"FreeFileSync", //_In_ LPCTSTR lpszAgent,
- INTERNET_OPEN_TYPE_PRECONFIG, //_In_ DWORD dwAccessType,
- nullptr, //_In_ LPCTSTR lpszProxyName,
- nullptr, //_In_ LPCTSTR lpszProxyBypass,
- 0); //_In_ DWORD dwFlags
- if (!hInternet)
- return false;
- ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hInternet));
-
- //InternetOpenUrl is shortcut for HTTP:GET with InternetConnect + HttpOpenRequest + HttpSendRequest:
- HINTERNET hRequest = ::InternetOpenUrl(hInternet, //_In_ HINTERNET hInternet,
- L"http://www.google.com/", //_In_ LPCTSTR lpszUrl,
- nullptr, //_In_ LPCTSTR lpszHeaders,
- 0, //_In_ DWORD dwHeadersLength,
- INTERNET_FLAG_KEEP_CONNECTION |
- INTERNET_FLAG_NO_UI |
- INTERNET_FLAG_RELOAD |
- INTERNET_FLAG_NO_AUTO_REDIRECT, //_In_ DWORD dwFlags,
- 0); //_In_ DWORD_PTR dwContext
- //fails with ERROR_INTERNET_NAME_NOT_RESOLVED if server not found => the server-relative part is checked by HTTP_QUERY_STATUS_CODE!!!
- if (!hRequest)
- return false;
- ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hRequest));
-
- DWORD sc = 0;
- {
- DWORD bufLen = sizeof(sc);
- if (!::HttpQueryInfo(hRequest, //_In_ HINTERNET hRequest,
- HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, //_In_ DWORD dwInfoLevel,
- &sc, //_Inout_ LPVOID lpvBuffer,
- &bufLen, //_Inout_ LPDWORD lpdwBufferLength,
- nullptr)) //_Inout_ LPDWORD lpdwIndex
- return false;
- }
-
-#else
- assert(std::this_thread::get_id() == mainThreadId);
-
- const wxString server = L"www.google.com";
- const wxString page = L"/";
-
- wxHTTP webAccess;
- webAccess.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking???
-
- if (!webAccess.Connect(server)) //will *not* fail for non-reachable url here!
- return false;
-
- std::unique_ptr<wxInputStream> httpStream(webAccess.GetInputStream(page)); //call before checking wxHTTP::GetResponse()
- const int sc = webAccess.GetResponse();
-#endif
- //attention: http://www.google.com/ might redirect to "https" => don't follow, just return "true"!!!
- return sc / 100 == 2 || //e.g. 200
- sc / 100 == 3; //e.g. 301, 302, 303, 307... when in doubt, consider internet alive!
-}
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "http.h"
+
+ #include <wx/app.h>
+ #include <zen/thread.h> //std::thread::id
+ #include <wx/protocol/http.h>
+
+using namespace zen;
+
+
+namespace
+{
+
+struct UrlRedirectError
+{
+ UrlRedirectError(const std::wstring& url) : newUrl(url) {}
+ std::wstring newUrl;
+};
+}
+
+
+class HttpInputStream::Impl
+{
+public:
+ Impl(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO, //throw SysError, UrlRedirectError
+ const std::string* postParams) : //issue POST if bound, GET otherwise
+ notifyUnbufferedIO_(notifyUnbufferedIO)
+ {
+ ZEN_ON_SCOPE_FAIL( cleanup(); /*destructor call would lead to member double clean-up!!!*/ );
+
+ assert(!startsWith(makeUpperCopy(url), L"HTTPS:")); //not supported by wxHTTP!
+ const std::wstring urlFmt = startsWith(makeUpperCopy(url), L"HTTP://") ||
+ startsWith(makeUpperCopy(url), L"HTTPS://") ? afterFirst(url, L"://", IF_MISSING_RETURN_NONE) : url;
+ const std::wstring server = beforeFirst(urlFmt, L'/', IF_MISSING_RETURN_ALL);
+ const std::wstring page = L'/' + afterFirst(urlFmt, L'/', IF_MISSING_RETURN_NONE);
+
+ assert(std::this_thread::get_id() == mainThreadId);
+ assert(wxApp::IsMainLoopRunning());
+
+ webAccess_.SetHeader(L"User-Agent", userAgent);
+ webAccess_.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking???
+
+ if (!webAccess_.Connect(server)) //will *not* fail for non-reachable url here!
+ throw SysError(L"wxHTTP::Connect");
+
+ if (postParams)
+ if (!webAccess_.SetPostText(L"application/x-www-form-urlencoded", utfCvrtTo<wxString>(*postParams)))
+ throw SysError(L"wxHTTP::SetPostText");
+
+ httpStream_.reset(webAccess_.GetInputStream(page)); //pass ownership
+ const int sc = webAccess_.GetResponse();
+
+ //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection
+ if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too!
+ {
+ const std::wstring newUrl(webAccess_.GetHeader(L"Location"));
+ if (newUrl.empty())
+ throw SysError(L"Unresolvable redirect. Empty target Location.");
+
+ throw UrlRedirectError(newUrl);
+ }
+
+ if (sc != 200) //HTTP_STATUS_OK
+ throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc)));
+
+ if (!httpStream_ || webAccess_.GetError() != wxPROTO_NOERR)
+ throw SysError(L"wxHTTP::GetError (" + numberTo<std::wstring>(webAccess_.GetError()) + L")");
+ }
+
+ ~Impl() { cleanup(); }
+
+ size_t read(void* buffer, size_t bytesToRead) //throw SysError, X; return "bytesToRead" bytes unless end of stream!
+ {
+ const size_t blockSize = getBlockSize();
+
+ while (memBuf_.size() < bytesToRead)
+ {
+ memBuf_.resize(memBuf_.size() + blockSize);
+ const size_t bytesRead = tryRead(&*(memBuf_.end() - blockSize), blockSize); //throw SysError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0
+ memBuf_.resize(memBuf_.size() - blockSize + bytesRead); //caveat: unsigned arithmetics
+
+ if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesRead); //throw X
+
+ if (bytesRead == 0) //end of file
+ bytesToRead = memBuf_.size();
+ }
+
+ std::copy(memBuf_.begin(), memBuf_.begin() + bytesToRead, static_cast<char*>(buffer));
+ memBuf_.erase(memBuf_.begin(), memBuf_.begin() + bytesToRead);
+ return bytesToRead;
+ }
+
+ size_t getBlockSize() const { return 64 * 1024; }
+
+private:
+ size_t tryRead(void* buffer, size_t bytesToRead) //throw SysError; may return short, only 0 means EOF!
+ {
+ if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
+ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
+ assert(bytesToRead == getBlockSize());
+
+ httpStream_->Read(buffer, bytesToRead);
+
+ const wxStreamError ec = httpStream_->GetLastError();
+ if (ec != wxSTREAM_NO_ERROR && ec != wxSTREAM_EOF)
+ throw SysError(L"wxInputStream::GetLastError (" + numberTo<std::wstring>(httpStream_->GetLastError()) + L")");
+
+ const size_t bytesRead = httpStream_->LastRead();
+ //"if there are not enough bytes in the stream right now, LastRead() value will be
+ // less than size but greater than 0. If it is 0, it means that EOF has been reached."
+ assert(bytesRead > 0 || ec == wxSTREAM_EOF);
+ if (bytesRead > bytesToRead) //better safe than sorry
+ throw SysError(L"InternetReadFile: buffer overflow.");
+
+ return bytesRead; //"zero indicates end of file"
+ }
+
+ Impl (const Impl&) = delete;
+ Impl& operator=(const Impl&) = delete;
+
+ void cleanup()
+ {
+ }
+
+ wxHTTP webAccess_;
+ std::unique_ptr<wxInputStream> httpStream_; //must be deleted BEFORE webAccess is closed
+
+ std::vector<char> memBuf_;
+ const IOCallback notifyUnbufferedIO_; //throw X
+};
+
+
+HttpInputStream::HttpInputStream(std::unique_ptr<Impl>&& pimpl) : pimpl_(std::move(pimpl)) {}
+
+HttpInputStream::~HttpInputStream() {}
+
+size_t HttpInputStream::read(void* buffer, size_t bytesToRead) { return pimpl_->read(buffer, bytesToRead); } //throw SysError, X; return "bytesToRead" bytes unless end of stream!
+
+size_t HttpInputStream::getBlockSize() const { return pimpl_->getBlockSize(); }
+
+std::string HttpInputStream::readAll() { return bufferedLoad<std::string>(*pimpl_); } //throw SysError, X;
+
+namespace
+{
+std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO, //throw SysError
+ const std::string* postParams) //issue POST if bound, GET otherwise
+{
+ std::wstring urlRed = url;
+ //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop."
+ for (int redirects = 0; redirects < 6; ++redirects)
+ try
+ {
+ return std::make_unique<HttpInputStream::Impl>(urlRed, userAgent, notifyUnbufferedIO, postParams); //throw SysError, UrlRedirectError
+ }
+ catch (const UrlRedirectError& e) { urlRed = e.newUrl; }
+ throw SysError(L"Too many redirects.");
+}
+
+
+//encode into "application/x-www-form-urlencoded"
+std::string urlencode(const std::string& str)
+{
+ std::string out;
+ for (const char c : str) //follow PHP spec: https://github.com/php/php-src/blob/master/ext/standard/url.c#L500
+ if (c == ' ')
+ out += '+';
+ else if (('0' <= c && c <= '9') ||
+ ('A' <= c && c <= 'Z') ||
+ ('a' <= c && c <= 'z') ||
+ c == '-' || c == '.' || c == '_') //note: "~" is encoded by PHP!
+ out += c;
+ else
+ {
+ const std::pair<char, char> hex = hexify(c);
+
+ out += '%';
+ out += hex.first;
+ out += hex.second;
+ }
+ return out;
+}
+
+
+std::string urldecode(const std::string& str)
+{
+ std::string out;
+ for (size_t i = 0; i < str.size(); ++i)
+ {
+ const char c = str[i];
+ if (c == '+')
+ out += ' ';
+ else if (c == '%' && str.size() - i >= 3 &&
+ isHexDigit(str[i + 1]) &&
+ isHexDigit(str[i + 2]))
+ {
+ out += unhexify(str[i + 1], str[i + 2]);
+ i += 2;
+ }
+ else
+ out += c;
+ }
+ return out;
+}
+}
+
+
+std::string zen::xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs)
+{
+ std::string output;
+ for (const auto& pair : paramPairs)
+ output += urlencode(pair.first) + '=' + urlencode(pair.second) + '&';
+ //encode both key and value: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
+ if (!output.empty())
+ output.pop_back();
+ return output;
+}
+
+
+std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const std::string& str)
+{
+ std::vector<std::pair<std::string, std::string>> output;
+
+ for (const std::string& nvPair : split(str, '&'))
+ if (!nvPair.empty())
+ output.emplace_back(urldecode(beforeFirst(nvPair, '=', IF_MISSING_RETURN_ALL)),
+ urldecode(afterFirst (nvPair, '=', IF_MISSING_RETURN_NONE)));
+ return output;
+}
+
+
+HttpInputStream zen::sendHttpPost(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO,
+ const std::vector<std::pair<std::string, std::string>>& postParams) //throw SysError
+{
+ const std::string encodedParams = xWwwFormUrlEncode(postParams);
+ return sendHttpRequestImpl(url, userAgent, notifyUnbufferedIO, &encodedParams); //throw SysError
+}
+
+
+HttpInputStream zen::sendHttpGet(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO) //throw SysError
+{
+ return sendHttpRequestImpl(url, userAgent, notifyUnbufferedIO, nullptr); //throw SysError
+}
+
+
+bool zen::internetIsAlive() //noexcept
+{
+ assert(std::this_thread::get_id() == mainThreadId);
+
+ const wxString server = L"www.google.com";
+ const wxString page = L"/";
+
+ wxHTTP webAccess;
+ webAccess.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking???
+
+ if (!webAccess.Connect(server)) //will *not* fail for non-reachable url here!
+ return false;
+
+ std::unique_ptr<wxInputStream> httpStream(webAccess.GetInputStream(page)); //call before checking wxHTTP::GetResponse()
+ const int sc = webAccess.GetResponse();
+ //attention: http://www.google.com/ might redirect to "https" => don't follow, just return "true"!!!
+ return sc / 100 == 2 || //e.g. 200
+ sc / 100 == 3; //e.g. 301, 302, 303, 307... when in doubt, consider internet alive!
+}
diff --git a/wx+/http.h b/wx+/http.h
index cf385d5e..97341e18 100644..100755
--- a/wx+/http.h
+++ b/wx+/http.h
@@ -1,48 +1,49 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef HTTP_h_879083425703425702
-#define HTTP_h_879083425703425702
-
-#include <zen/file_error.h>
-
-namespace zen
-{
-/*
- TREAD-SAFETY
- ------------
- Windows: WinInet-based => may be called from worker thread
- Linux: wxWidgets-based => don't call from worker thread
-*/
-class HttpInputStream
-{
-public:
- std::string readAll(); //throw SysError
-
- //support zen/serialize.h Unbuffered Input Stream Concept
- size_t tryRead(void* buffer, size_t bytesToRead); //throw SysError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0!
- size_t getBlockSize() const { return 64 * 1024; }
-
- class Impl;
- HttpInputStream(std::unique_ptr<Impl>&& pimpl);
- HttpInputStream(HttpInputStream&&) = default;
- ~HttpInputStream();
-
-private:
- std::unique_ptr<Impl> pimpl_;
-};
-
-
-HttpInputStream sendHttpGet (const std::wstring& url, const std::wstring& userAgent); //throw SysError
-HttpInputStream sendHttpPost(const std::wstring& url, const std::wstring& userAgent,
- const std::vector<std::pair<std::string, std::string>>& postParams); //throw SysError
-bool internetIsAlive(); //noexcept
-
-std::string xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs);
-std::vector<std::pair<std::string, std::string>> xWwwFormUrlDecode(const std::string& str);
-}
-
-#endif //HTTP_h_879083425703425702
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef HTTP_h_879083425703425702
+#define HTTP_h_879083425703425702
+
+#include <zen/file_error.h>
+#include <zen/serialize.h>
+
+namespace zen
+{
+/*
+ TREAD-SAFETY
+ ------------
+ Windows: WinInet-based => may be called from worker thread
+ Linux: wxWidgets-based => don't call from worker thread
+*/
+class HttpInputStream
+{
+public:
+ //support zen/serialize.h buffered input stream concept
+ size_t read(void* buffer, size_t bytesToRead); //throw SysError, X; return "bytesToRead" bytes unless end of stream!
+ std::string readAll(); //throw SysError, X
+
+ size_t getBlockSize() const;
+
+ class Impl;
+ HttpInputStream(std::unique_ptr<Impl>&& pimpl);
+ HttpInputStream(HttpInputStream&&) = default;
+ ~HttpInputStream();
+
+private:
+ std::unique_ptr<Impl> pimpl_;
+};
+
+
+HttpInputStream sendHttpGet (const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO); //throw SysError
+HttpInputStream sendHttpPost(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO,
+ const std::vector<std::pair<std::string, std::string>>& postParams); //throw SysError
+bool internetIsAlive(); //noexcept
+
+std::string xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs);
+std::vector<std::pair<std::string, std::string>> xWwwFormUrlDecode(const std::string& str);
+}
+
+#endif //HTTP_h_879083425703425702
diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp
index 3eb91ee4..5cd9df0b 100644..100755
--- a/wx+/image_resources.cpp
+++ b/wx+/image_resources.cpp
@@ -1,171 +1,168 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "image_resources.h"
-#include <memory>
-#include <map>
-#include <zen/utf.h>
-#include <zen/globals.h>
-#include <zen/thread.h>
-#include <wx/wfstream.h>
-#include <wx/zipstrm.h>
-#include <wx/image.h>
-#include <wx/mstream.h>
-#include "image_tools.h"
-
-using namespace zen;
-
-
-namespace
-{
-#ifndef NDEBUG
- const std::thread::id mainThreadId = std::this_thread::get_id();
-#endif
-
-void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation& anim)
-{
- //work around wxWidgets bug:
- //construct seekable input stream (zip-input stream is not seekable) for wxAnimation::Load()
- //luckily this method call is very fast: below measurement precision!
- std::vector<char> data;
- data.reserve(10000);
-
- int newValue = 0;
- while ((newValue = zipInput.GetC()) != wxEOF)
- data.push_back(newValue);
-
- wxMemoryInputStream seekAbleStream(&data.front(), data.size()); //stream does not take ownership of data
-
- anim.Load(seekAbleStream, wxANIMATION_TYPE_GIF);
-}
-
-
-class GlobalBitmaps
-{
-public:
- static std::shared_ptr<GlobalBitmaps> instance()
- {
- static Global<GlobalBitmaps> inst(std::make_unique<GlobalBitmaps>());
- assert(std::this_thread::get_id() == mainThreadId); //wxWidgets is not thread-safe!
- return inst.get();
- }
-
- GlobalBitmaps() {}
- ~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();
- }
-
- const wxBitmap& getImage (const wxString& name) const;
- 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;
-};
-
-
-void GlobalBitmaps::init(const Zstring& filepath)
-{
- assert(bitmaps.empty() && anims.empty());
-
- wxFFileInputStream input(utfCvrtTo<wxString>(filepath));
- if (input.IsOk()) //if not... we don't want to react too harsh here
- {
- //activate support for .png files
- wxImage::AddHandler(new wxPNGHandler); //ownership passed
-
- 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;
-
- 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!!!
- convertToVanillaImage(img);
-
- bitmaps.emplace(name, img);
- }
- else if (endsWith(name, L".gif"))
- loadAnimFromZip(streamIn, anims[name]);
- }
- }
-}
-
-
-const wxBitmap& GlobalBitmaps::getImage(const wxString& name) const
-{
- 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;
-}
-
-
-const wxAnimation& GlobalBitmaps::getAnimation(const wxString& name) const
-{
- auto it = anims.find(contains(name, L'.') ? name : name + L".gif");
- if (it != anims.end())
- return it->second;
- assert(false);
- return wxNullAnimation;
-}
-}
-
-
-void zen::initResourceImages(const Zstring& filepath)
-{
- if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance())
- inst->init(filepath);
- else
- assert(false);
-}
-
-
-void zen::cleanupResourceImages()
-{
- if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance())
- inst->cleanup();
- else
- assert(false);
-}
-
-
-const wxBitmap& zen::getResourceImage(const wxString& name)
-{
- if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance())
- return inst->getImage(name);
- assert(false);
- return wxNullBitmap;
-}
-
-
-const wxAnimation& zen::getResourceAnimation(const wxString& name)
-{
- if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance())
- return inst->getAnimation(name);
- assert(false);
- return wxNullAnimation;
-}
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "image_resources.h"
+#include <memory>
+#include <map>
+#include <zen/utf.h>
+#include <zen/globals.h>
+#include <zen/thread.h>
+#include <wx/wfstream.h>
+#include <wx/zipstrm.h>
+#include <wx/image.h>
+#include <wx/mstream.h>
+#include "image_tools.h"
+
+using namespace zen;
+
+
+namespace
+{
+
+void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation& anim)
+{
+ //work around wxWidgets bug:
+ //construct seekable input stream (zip-input stream is not seekable) for wxAnimation::Load()
+ //luckily this method call is very fast: below measurement precision!
+ std::vector<char> data;
+ data.reserve(10000);
+
+ int newValue = 0;
+ while ((newValue = zipInput.GetC()) != wxEOF)
+ data.push_back(newValue);
+
+ wxMemoryInputStream seekAbleStream(&data.front(), data.size()); //stream does not take ownership of data
+
+ anim.Load(seekAbleStream, wxANIMATION_TYPE_GIF);
+}
+
+
+class GlobalBitmaps
+{
+public:
+ static std::shared_ptr<GlobalBitmaps> instance()
+ {
+ static Global<GlobalBitmaps> inst(std::make_unique<GlobalBitmaps>());
+ assert(std::this_thread::get_id() == mainThreadId); //wxWidgets is not thread-safe!
+ return inst.get();
+ }
+
+ GlobalBitmaps() {}
+ ~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();
+ }
+
+ const wxBitmap& getImage (const wxString& name) const;
+ 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;
+};
+
+
+void GlobalBitmaps::init(const Zstring& filepath)
+{
+ assert(bitmaps.empty() && anims.empty());
+
+ wxFFileInputStream input(utfCvrtTo<wxString>(filepath));
+ if (input.IsOk()) //if not... we don't want to react too harsh here
+ {
+ //activate support for .png files
+ wxImage::AddHandler(new wxPNGHandler); //ownership passed
+
+ 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;
+
+ 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!!!
+ convertToVanillaImage(img);
+
+ bitmaps.emplace(name, img);
+ }
+ else if (endsWith(name, L".gif"))
+ loadAnimFromZip(streamIn, anims[name]);
+ }
+ }
+}
+
+
+const wxBitmap& GlobalBitmaps::getImage(const wxString& name) const
+{
+ 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;
+}
+
+
+const wxAnimation& GlobalBitmaps::getAnimation(const wxString& name) const
+{
+ auto it = anims.find(contains(name, L'.') ? name : name + L".gif");
+ if (it != anims.end())
+ return it->second;
+ assert(false);
+ return wxNullAnimation;
+}
+}
+
+
+void zen::initResourceImages(const Zstring& filepath)
+{
+ if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance())
+ inst->init(filepath);
+ else
+ assert(false);
+}
+
+
+void zen::cleanupResourceImages()
+{
+ if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance())
+ inst->cleanup();
+ else
+ assert(false);
+}
+
+
+const wxBitmap& zen::getResourceImage(const wxString& name)
+{
+ if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance())
+ return inst->getImage(name);
+ assert(false);
+ return wxNullBitmap;
+}
+
+
+const wxAnimation& zen::getResourceAnimation(const wxString& name)
+{
+ if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance())
+ return inst->getAnimation(name);
+ assert(false);
+ return wxNullAnimation;
+}
diff --git a/wx+/image_resources.h b/wx+/image_resources.h
index c29adb9f..37b94fce 100644..100755
--- a/wx+/image_resources.h
+++ b/wx+/image_resources.h
@@ -1,23 +1,23 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef IMAGE_RESOURCES_H_8740257825342532457
-#define IMAGE_RESOURCES_H_8740257825342532457
-
-#include <wx/bitmap.h>
-#include <wx/animate.h>
-#include <zen/zstring.h>
-
-namespace zen
-{
-void initResourceImages(const Zstring& filepath); //pass resources .zip file at application startup
-void cleanupResourceImages();
-
-const wxBitmap& getResourceImage (const wxString& name);
-const wxAnimation& getResourceAnimation(const wxString& name);
-}
-
-#endif //IMAGE_RESOURCES_H_8740257825342532457
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef IMAGE_RESOURCES_H_8740257825342532457
+#define IMAGE_RESOURCES_H_8740257825342532457
+
+#include <wx/bitmap.h>
+#include <wx/animate.h>
+#include <zen/zstring.h>
+
+namespace zen
+{
+void initResourceImages(const Zstring& filepath); //pass resources .zip file at application startup
+void cleanupResourceImages();
+
+const wxBitmap& getResourceImage (const wxString& name);
+const wxAnimation& getResourceAnimation(const wxString& name);
+}
+
+#endif //IMAGE_RESOURCES_H_8740257825342532457
diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp
index 90945a44..4b0f324f 100644..100755
--- a/wx+/image_tools.cpp
+++ b/wx+/image_tools.cpp
@@ -1,233 +1,233 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "image_tools.h"
-#include <zen/string_tools.h>
-#include <wx/app.h>
-
-
-using namespace zen;
-
-namespace
-{
-void writeToImage(const wxImage& source, wxImage& target, const wxPoint& pos)
-{
- const int srcWidth = source.GetWidth ();
- const int srcHeight = source.GetHeight();
- const int trgWidth = target.GetWidth ();
-
- if (srcWidth > 0 && srcHeight > 0)
- {
- assert(0 <= pos.x && pos.x + srcWidth <= trgWidth ); //draw area must be a
- assert(0 <= pos.y && pos.y + srcHeight <= target.GetHeight()); //subset of target image!
- assert(target.HasAlpha());
-
- {
- const unsigned char* sourcePtr = source.GetData();
- unsigned char* targetPtr = target.GetData() + 3 * (pos.x + pos.y * trgWidth);
-
- for (int row = 0; row < srcHeight; ++row)
- ::memcpy(targetPtr + 3 * row * trgWidth, sourcePtr + 3 * row * srcWidth, 3 * srcWidth);
- }
-
- //handle alpha channel
- {
- unsigned char* targetPtr = target.GetAlpha() + pos.x + pos.y * trgWidth;
- if (source.HasAlpha())
- {
- const unsigned char* sourcePtr = source.GetAlpha();
- for (int row = 0; row < srcHeight; ++row)
- ::memcpy(targetPtr + row * trgWidth, sourcePtr + row * srcWidth, srcWidth);
- }
- else
- for (int row = 0; row < srcHeight; ++row)
- ::memset(targetPtr + row * trgWidth, wxIMAGE_ALPHA_OPAQUE, srcWidth);
- }
- }
-}
-}
-
-
-wxImage zen::stackImages(const wxImage& img1, const wxImage& img2, ImageStackLayout dir, ImageStackAlignment align, int gap)
-{
- assert(gap >= 0);
- gap = std::max(0, gap);
-
- const int img1Width = img1.GetWidth ();
- const int img1Height = img1.GetHeight();
- const int img2Width = img2.GetWidth ();
- const int img2Height = img2.GetHeight();
-
- int width = std::max(img1Width, img2Width);
- int height = std::max(img1Height, img2Height);
- switch (dir)
- {
- case ImageStackLayout::HORIZONTAL:
- width = img1Width + gap + img2Width;
- break;
-
- case ImageStackLayout::VERTICAL:
- height = img1Height + gap + img2Height;
- break;
- }
- wxImage output(width, height);
- output.SetAlpha();
- ::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, width * height);
-
- auto calcPos = [&](int imageExtent, int totalExtent)
- {
- switch (align)
- {
- case ImageStackAlignment::CENTER:
- return (totalExtent - imageExtent) / 2;
- case ImageStackAlignment::LEFT:
- return 0;
- case ImageStackAlignment::RIGHT:
- return totalExtent - imageExtent;
- }
- assert(false);
- return 0;
- };
-
- switch (dir)
- {
- case ImageStackLayout::HORIZONTAL:
- writeToImage(img1, output, wxPoint(0, calcPos(img1Height, height)));
- writeToImage(img2, output, wxPoint(img1Width + gap, calcPos(img2Height, height)));
- break;
-
- case ImageStackLayout::VERTICAL:
- writeToImage(img1, output, wxPoint(calcPos(img1Width, width), 0));
- writeToImage(img2, output, wxPoint(calcPos(img2Width, width), img1Height + gap));
- break;
- }
- return output;
-}
-
-
-namespace
-{
-void calcAlphaForBlackWhiteImage(wxImage& image) //assume black text on white background
-{
- assert(image.HasAlpha());
- if (unsigned char* alphaPtr = image.GetAlpha())
- {
- const int pixelCount = image.GetWidth() * image.GetHeight();
- const unsigned char* dataPtr = image.GetData();
- for (int i = 0; i < pixelCount; ++ i)
- {
- const unsigned char r = *dataPtr++;
- const unsigned char g = *dataPtr++;
- const unsigned char b = *dataPtr++;
-
- //black(0,0,0) becomes fully opaque(255), while white(255,255,255) becomes transparent(0)
- alphaPtr[i] = static_cast<unsigned char>((255 - r + 255 - g + 255 - b) / 3); //mixed mode arithmetics!
- }
- }
-}
-
-
-wxSize getTextExtent(const wxString& text, const wxFont& font)
-{
- wxMemoryDC dc; //the context used for bitmaps
- dc.SetFont(font); //the font parameter of GetMultiLineTextExtent() is not evalated on OS X, wxWidgets 2.9.5, so apply it to the DC directly!
- return dc.GetMultiLineTextExtent(replaceCpy(text, L"&", L"", false)); //remove accelerator
-}
-}
-
-wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const wxColor& col)
-{
- //wxDC::DrawLabel() doesn't respect alpha channel => calculate alpha values manually:
-
- if (text.empty())
- return wxImage();
-
- wxBitmap newBitmap(getTextExtent(text, font)); //seems we don't need to pass 24-bit depth here even for high-contrast color schemes
- {
- wxMemoryDC dc(newBitmap);
- dc.SetBackground(*wxWHITE_BRUSH);
- dc.Clear();
-
- dc.SetTextForeground(*wxBLACK); //for use in calcAlphaForBlackWhiteImage
- dc.SetTextBackground(*wxWHITE); //
- dc.SetFont(font);
-
- //assert(!contains(text, L"&")); //accelerator keys not supported here; see also getTextExtent()
- wxString textFmt = replaceCpy(text, L"&", L"", false);
-
- //for some reason wxDC::DrawText messes up "weak" bidi characters even when wxLayout_RightToLeft is set! (--> arrows in hebrew/arabic)
- //=> use mark characters instead:
- const wchar_t rtlMark = L'\u200F'; //UTF-8: E2 80 8F
- if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft)
- textFmt = rtlMark + textFmt + rtlMark;
-
- dc.DrawText(textFmt, wxPoint());
- }
-
- wxImage output(newBitmap.ConvertToImage());
- output.SetAlpha();
-
- //calculate alpha channel
- calcAlphaForBlackWhiteImage(output);
-
- //apply actual text color
- unsigned char* dataPtr = output.GetData();
- const int pixelCount = output.GetWidth() * output.GetHeight();
- for (int i = 0; i < pixelCount; ++ i)
- {
- *dataPtr++ = col.Red();
- *dataPtr++ = col.Green();
- *dataPtr++ = col.Blue();
- }
- return output;
-}
-
-
-void zen::convertToVanillaImage(wxImage& img)
-{
- if (!img.HasAlpha())
- {
- const int width = img.GetWidth ();
- const int height = img.GetHeight();
- if (width <= 0 || height <= 0) return;
-
- unsigned char mask_r = 0;
- unsigned char mask_g = 0;
- unsigned char mask_b = 0;
- const bool haveMask = img.HasMask() && img.GetOrFindMaskColour(&mask_r, &mask_g, &mask_b);
- //check for mask before calling wxImage::GetOrFindMaskColour() to skip needlessly searching for new mask color
-
- img.SetAlpha();
- ::memset(img.GetAlpha(), wxIMAGE_ALPHA_OPAQUE, width * height);
-
- //wxWidgets, as always, tries to be more clever than it really is and fucks up wxStaticBitmap if wxBitmap is fully opaque:
- img.GetAlpha()[width * height - 1] = 254;
-
- if (haveMask)
- {
- img.SetMask(false);
- unsigned char* alphaPtr = img.GetAlpha();
- const unsigned char* dataPtr = img.GetData();
-
- const int pixelCount = width * height;
- for (int i = 0; i < pixelCount; ++ i)
- {
- const unsigned char r = *dataPtr++;
- const unsigned char g = *dataPtr++;
- const unsigned char b = *dataPtr++;
-
- if (r == mask_r &&
- g == mask_g &&
- b == mask_b)
- alphaPtr[i] = wxIMAGE_ALPHA_TRANSPARENT;
- }
- }
- }
- else
- {
- assert(!img.HasMask());
- }
-}
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "image_tools.h"
+#include <zen/string_tools.h>
+#include <wx/app.h>
+
+
+using namespace zen;
+
+namespace
+{
+void writeToImage(const wxImage& source, wxImage& target, const wxPoint& pos)
+{
+ const int srcWidth = source.GetWidth ();
+ const int srcHeight = source.GetHeight();
+ const int trgWidth = target.GetWidth ();
+
+ if (srcWidth > 0 && srcHeight > 0)
+ {
+ assert(0 <= pos.x && pos.x + srcWidth <= trgWidth ); //draw area must be a
+ assert(0 <= pos.y && pos.y + srcHeight <= target.GetHeight()); //subset of target image!
+ assert(target.HasAlpha());
+
+ {
+ const unsigned char* sourcePtr = source.GetData();
+ unsigned char* targetPtr = target.GetData() + 3 * (pos.x + pos.y * trgWidth);
+
+ for (int row = 0; row < srcHeight; ++row)
+ ::memcpy(targetPtr + 3 * row * trgWidth, sourcePtr + 3 * row * srcWidth, 3 * srcWidth);
+ }
+
+ //handle alpha channel
+ {
+ unsigned char* targetPtr = target.GetAlpha() + pos.x + pos.y * trgWidth;
+ if (source.HasAlpha())
+ {
+ const unsigned char* sourcePtr = source.GetAlpha();
+ for (int row = 0; row < srcHeight; ++row)
+ ::memcpy(targetPtr + row * trgWidth, sourcePtr + row * srcWidth, srcWidth);
+ }
+ else
+ for (int row = 0; row < srcHeight; ++row)
+ ::memset(targetPtr + row * trgWidth, wxIMAGE_ALPHA_OPAQUE, srcWidth);
+ }
+ }
+}
+}
+
+
+wxImage zen::stackImages(const wxImage& img1, const wxImage& img2, ImageStackLayout dir, ImageStackAlignment align, int gap)
+{
+ assert(gap >= 0);
+ gap = std::max(0, gap);
+
+ const int img1Width = img1.GetWidth ();
+ const int img1Height = img1.GetHeight();
+ const int img2Width = img2.GetWidth ();
+ const int img2Height = img2.GetHeight();
+
+ int width = std::max(img1Width, img2Width);
+ int height = std::max(img1Height, img2Height);
+ switch (dir)
+ {
+ case ImageStackLayout::HORIZONTAL:
+ width = img1Width + gap + img2Width;
+ break;
+
+ case ImageStackLayout::VERTICAL:
+ height = img1Height + gap + img2Height;
+ break;
+ }
+ wxImage output(width, height);
+ output.SetAlpha();
+ ::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, width * height);
+
+ auto calcPos = [&](int imageExtent, int totalExtent)
+ {
+ switch (align)
+ {
+ case ImageStackAlignment::CENTER:
+ return (totalExtent - imageExtent) / 2;
+ case ImageStackAlignment::LEFT:
+ return 0;
+ case ImageStackAlignment::RIGHT:
+ return totalExtent - imageExtent;
+ }
+ assert(false);
+ return 0;
+ };
+
+ switch (dir)
+ {
+ case ImageStackLayout::HORIZONTAL:
+ writeToImage(img1, output, wxPoint(0, calcPos(img1Height, height)));
+ writeToImage(img2, output, wxPoint(img1Width + gap, calcPos(img2Height, height)));
+ break;
+
+ case ImageStackLayout::VERTICAL:
+ writeToImage(img1, output, wxPoint(calcPos(img1Width, width), 0));
+ writeToImage(img2, output, wxPoint(calcPos(img2Width, width), img1Height + gap));
+ break;
+ }
+ return output;
+}
+
+
+namespace
+{
+void calcAlphaForBlackWhiteImage(wxImage& image) //assume black text on white background
+{
+ assert(image.HasAlpha());
+ if (unsigned char* alphaPtr = image.GetAlpha())
+ {
+ const int pixelCount = image.GetWidth() * image.GetHeight();
+ const unsigned char* dataPtr = image.GetData();
+ for (int i = 0; i < pixelCount; ++ i)
+ {
+ const unsigned char r = *dataPtr++;
+ const unsigned char g = *dataPtr++;
+ const unsigned char b = *dataPtr++;
+
+ //black(0,0,0) becomes fully opaque(255), while white(255,255,255) becomes transparent(0)
+ alphaPtr[i] = static_cast<unsigned char>((255 - r + 255 - g + 255 - b) / 3); //mixed mode arithmetics!
+ }
+ }
+}
+
+
+wxSize getTextExtent(const wxString& text, const wxFont& font)
+{
+ wxMemoryDC dc; //the context used for bitmaps
+ dc.SetFont(font); //the font parameter of GetMultiLineTextExtent() is not evalated on OS X, wxWidgets 2.9.5, so apply it to the DC directly!
+ return dc.GetMultiLineTextExtent(replaceCpy(text, L"&", L"", false)); //remove accelerator
+}
+}
+
+wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const wxColor& col)
+{
+ //wxDC::DrawLabel() doesn't respect alpha channel => calculate alpha values manually:
+
+ if (text.empty())
+ return wxImage();
+
+ wxBitmap newBitmap(getTextExtent(text, font)); //seems we don't need to pass 24-bit depth here even for high-contrast color schemes
+ {
+ wxMemoryDC dc(newBitmap);
+ dc.SetBackground(*wxWHITE_BRUSH);
+ dc.Clear();
+
+ dc.SetTextForeground(*wxBLACK); //for use in calcAlphaForBlackWhiteImage
+ dc.SetTextBackground(*wxWHITE); //
+ dc.SetFont(font);
+
+ //assert(!contains(text, L"&")); //accelerator keys not supported here; see also getTextExtent()
+ wxString textFmt = replaceCpy(text, L"&", L"", false);
+
+ //for some reason wxDC::DrawText messes up "weak" bidi characters even when wxLayout_RightToLeft is set! (--> arrows in hebrew/arabic)
+ //=> use mark characters instead:
+ const wchar_t rtlMark = L'\u200F'; //UTF-8: E2 80 8F
+ if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft)
+ textFmt = rtlMark + textFmt + rtlMark;
+
+ dc.DrawText(textFmt, wxPoint());
+ }
+
+ wxImage output(newBitmap.ConvertToImage());
+ output.SetAlpha();
+
+ //calculate alpha channel
+ calcAlphaForBlackWhiteImage(output);
+
+ //apply actual text color
+ unsigned char* dataPtr = output.GetData();
+ const int pixelCount = output.GetWidth() * output.GetHeight();
+ for (int i = 0; i < pixelCount; ++ i)
+ {
+ *dataPtr++ = col.Red();
+ *dataPtr++ = col.Green();
+ *dataPtr++ = col.Blue();
+ }
+ return output;
+}
+
+
+void zen::convertToVanillaImage(wxImage& img)
+{
+ if (!img.HasAlpha())
+ {
+ const int width = img.GetWidth ();
+ const int height = img.GetHeight();
+ if (width <= 0 || height <= 0) return;
+
+ unsigned char mask_r = 0;
+ unsigned char mask_g = 0;
+ unsigned char mask_b = 0;
+ const bool haveMask = img.HasMask() && img.GetOrFindMaskColour(&mask_r, &mask_g, &mask_b);
+ //check for mask before calling wxImage::GetOrFindMaskColour() to skip needlessly searching for new mask color
+
+ img.SetAlpha();
+ ::memset(img.GetAlpha(), wxIMAGE_ALPHA_OPAQUE, width * height);
+
+ //wxWidgets, as always, tries to be more clever than it really is and fucks up wxStaticBitmap if wxBitmap is fully opaque:
+ img.GetAlpha()[width * height - 1] = 254;
+
+ if (haveMask)
+ {
+ img.SetMask(false);
+ unsigned char* alphaPtr = img.GetAlpha();
+ const unsigned char* dataPtr = img.GetData();
+
+ const int pixelCount = width * height;
+ for (int i = 0; i < pixelCount; ++ i)
+ {
+ const unsigned char r = *dataPtr++;
+ const unsigned char g = *dataPtr++;
+ const unsigned char b = *dataPtr++;
+
+ if (r == mask_r &&
+ g == mask_g &&
+ b == mask_b)
+ alphaPtr[i] = wxIMAGE_ALPHA_TRANSPARENT;
+ }
+ }
+ }
+ else
+ {
+ assert(!img.HasMask());
+ }
+}
diff --git a/wx+/image_tools.h b/wx+/image_tools.h
index cd0e28f0..6287b752 100644..100755
--- a/wx+/image_tools.h
+++ b/wx+/image_tools.h
@@ -1,258 +1,258 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef IMAGE_TOOLS_H_45782456427634254
-#define IMAGE_TOOLS_H_45782456427634254
-
-#include <numeric>
-#include <wx/bitmap.h>
-#include <wx/image.h>
-#include <wx/dcmemory.h>
-#include <zen/basic_math.h>
-
-
-namespace zen
-{
-enum class ImageStackLayout
-{
- HORIZONTAL,
- VERTICAL
-};
-
-enum class ImageStackAlignment
-{
- CENTER,
- LEFT,
- RIGHT,
- TOP = LEFT,
- BOTTOM = RIGHT,
-};
-wxImage stackImages(const wxImage& img1, const wxImage& img2, ImageStackLayout dir, ImageStackAlignment align, int gap = 0);
-
-wxImage createImageFromText(const wxString& text, const wxFont& font, const wxColor& col);
-
-wxBitmap layOver(const wxBitmap& background, const wxBitmap& foreground); //merge
-
-wxImage greyScale(const wxImage& img); //greyscale + brightness adaption
-wxBitmap greyScale(const wxBitmap& bmp); //
-
-//void moveImage(wxImage& img, int right, int up);
-void adjustBrightness(wxImage& img, int targetLevel);
-double getAvgBrightness(const wxImage& img); //in [0, 255]
-void brighten(wxImage& img, int level); //level: delta per channel in points
-
-bool isEqual(const wxBitmap& lhs, const wxBitmap& rhs); //pixel-wise equality (respecting alpha channel)
-
-void convertToVanillaImage(wxImage& img); //add alpha channel if missing + remove mask if existing
-
-//wxColor gradient(const wxColor& from, const wxColor& to, double fraction); //maps fraction within [0, 1] to an intermediate color
-
-//wxColor hsvColor(double h, double s, double v); //h within [0, 360), s, v within [0, 1]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-//################################### implementation ###################################
-/*
-inline
-void moveImage(wxImage& img, int right, int up)
-{
- img = img.GetSubImage(wxRect(std::max(0, -right), std::max(0, up), img.GetWidth() - abs(right), img.GetHeight() - abs(up)));
- img.Resize(wxSize(img.GetWidth() + abs(right), img.GetHeight() + abs(up)), wxPoint(std::max(0, right), std::max(0, -up)));
-}
-*/
-
-
-inline
-wxImage greyScale(const wxImage& img)
-{
- wxImage output = img.ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3); //treat all channels equally!
- //wxImage output = bmp.ConvertToImage().ConvertToGreyscale();
- adjustBrightness(output, 160);
- return output;
-}
-
-
-inline
-wxBitmap greyScale(const wxBitmap& bmp)
-{
- assert(!bmp.GetMask()); //wxWidgets screws up for the gazillionth time applying a mask instead of alpha channel if the .png image has only 0 and 0xff opacity values!!!
- return greyScale(bmp.ConvertToImage());
-}
-
-
-inline
-double getAvgBrightness(const wxImage& img)
-{
- const int pixelCount = img.GetWidth() * img.GetHeight();
- auto pixBegin = img.GetData();
-
- if (pixelCount > 0 && pixBegin)
- {
- auto pixEnd = pixBegin + 3 * pixelCount; //RGB
-
- if (img.HasAlpha())
- {
- const unsigned char* alphaFirst = img.GetAlpha();
-
- //calculate average weighted by alpha channel
- double dividend = 0;
- for (auto it = pixBegin; it != pixEnd; ++it)
- dividend += *it * static_cast<double>(alphaFirst[(it - pixBegin) / 3]);
-
- const double divisor = 3.0 * std::accumulate(alphaFirst, alphaFirst + pixelCount, 0.0);
-
- return numeric::isNull(divisor) ? 0 : dividend / divisor;
- }
- else
- return std::accumulate(pixBegin, pixEnd, 0.0) / (3.0 * pixelCount);
- }
- return 0;
-}
-
-
-inline
-void brighten(wxImage& img, int level)
-{
- if (auto pixBegin = img.GetData())
- {
- 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)); });
- else
- std::for_each(pixBegin, pixEnd, [&](unsigned char& c) { c = static_cast<unsigned char>(std::max(0, c + level)); });
- }
-}
-
-
-inline
-void adjustBrightness(wxImage& img, int targetLevel)
-{
- brighten(img, targetLevel - getAvgBrightness(img));
-}
-
-
-inline
-wxBitmap layOver(const wxBitmap& background, const wxBitmap& foreground)
-{
- assert(foreground.HasAlpha() == background.HasAlpha()); //we don't support mixed-mode brittleness!
-
- wxBitmap output(background.ConvertToImage()); //attention: wxBitmap/wxImage use ref-counting without copy on write!
- {
- wxMemoryDC dc(output);
-
- const int offsetX = (background.GetWidth () - foreground.GetWidth ()) / 2;
- const int offsetY = (background.GetHeight() - foreground.GetHeight()) / 2;
- dc.DrawBitmap(foreground, offsetX, offsetY);
- }
- return output;
-}
-
-
-inline
-bool isEqual(const wxBitmap& lhs, const wxBitmap& rhs)
-{
- if (lhs.IsOk() != rhs.IsOk())
- return false;
- if (!lhs.IsOk())
- return true;
-
- if (lhs.GetSize() != rhs.GetSize())
- return false;
-
- wxImage imLhs = lhs.ConvertToImage();
- wxImage imRhs = rhs.ConvertToImage();
-
- if (imLhs.HasAlpha() != imRhs.HasAlpha())
- return false;
-
- const int pixelCount = lhs.GetWidth() * lhs.GetHeight();
-
- if (!std::equal(imLhs.GetData(), imLhs.GetData() + pixelCount * 3, imRhs.GetData()))
- return false;
-
- if (imLhs.HasAlpha())
- if (!std::equal(imLhs.GetAlpha(), imLhs.GetAlpha() + pixelCount, imRhs.GetAlpha()))
- return false;
-
- return true;
-}
-
-/*
-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]
-{
- //http://de.wikipedia.org/wiki/HSV-Farbraum
-
- //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 = numeric::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
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef IMAGE_TOOLS_H_45782456427634254
+#define IMAGE_TOOLS_H_45782456427634254
+
+#include <numeric>
+#include <wx/bitmap.h>
+#include <wx/image.h>
+#include <wx/dcmemory.h>
+#include <zen/basic_math.h>
+
+
+namespace zen
+{
+enum class ImageStackLayout
+{
+ HORIZONTAL,
+ VERTICAL
+};
+
+enum class ImageStackAlignment
+{
+ CENTER,
+ LEFT,
+ RIGHT,
+ TOP = LEFT,
+ BOTTOM = RIGHT,
+};
+wxImage stackImages(const wxImage& img1, const wxImage& img2, ImageStackLayout dir, ImageStackAlignment align, int gap = 0);
+
+wxImage createImageFromText(const wxString& text, const wxFont& font, const wxColor& col);
+
+wxBitmap layOver(const wxBitmap& background, const wxBitmap& foreground); //merge
+
+wxImage greyScale(const wxImage& img); //greyscale + brightness adaption
+wxBitmap greyScale(const wxBitmap& bmp); //
+
+//void moveImage(wxImage& img, int right, int up);
+void adjustBrightness(wxImage& img, int targetLevel);
+double getAvgBrightness(const wxImage& img); //in [0, 255]
+void brighten(wxImage& img, int level); //level: delta per channel in points
+
+bool isEqual(const wxBitmap& lhs, const wxBitmap& rhs); //pixel-wise equality (respecting alpha channel)
+
+void convertToVanillaImage(wxImage& img); //add alpha channel if missing + remove mask if existing
+
+//wxColor gradient(const wxColor& from, const wxColor& to, double fraction); //maps fraction within [0, 1] to an intermediate color
+
+//wxColor hsvColor(double h, double s, double v); //h within [0, 360), s, v within [0, 1]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//################################### implementation ###################################
+/*
+inline
+void moveImage(wxImage& img, int right, int up)
+{
+ img = img.GetSubImage(wxRect(std::max(0, -right), std::max(0, up), img.GetWidth() - abs(right), img.GetHeight() - abs(up)));
+ img.Resize(wxSize(img.GetWidth() + abs(right), img.GetHeight() + abs(up)), wxPoint(std::max(0, right), std::max(0, -up)));
+}
+*/
+
+
+inline
+wxImage greyScale(const wxImage& img)
+{
+ wxImage output = img.ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3); //treat all channels equally!
+ //wxImage output = bmp.ConvertToImage().ConvertToGreyscale();
+ adjustBrightness(output, 160);
+ return output;
+}
+
+
+inline
+wxBitmap greyScale(const wxBitmap& bmp)
+{
+ assert(!bmp.GetMask()); //wxWidgets screws up for the gazillionth time applying a mask instead of alpha channel if the .png image has only 0 and 0xff opacity values!!!
+ return greyScale(bmp.ConvertToImage());
+}
+
+
+inline
+double getAvgBrightness(const wxImage& img)
+{
+ const int pixelCount = img.GetWidth() * img.GetHeight();
+ auto pixBegin = img.GetData();
+
+ if (pixelCount > 0 && pixBegin)
+ {
+ auto pixEnd = pixBegin + 3 * pixelCount; //RGB
+
+ if (img.HasAlpha())
+ {
+ const unsigned char* alphaFirst = img.GetAlpha();
+
+ //calculate average weighted by alpha channel
+ double dividend = 0;
+ for (auto it = pixBegin; it != pixEnd; ++it)
+ dividend += *it * static_cast<double>(alphaFirst[(it - pixBegin) / 3]);
+
+ const double divisor = 3.0 * std::accumulate(alphaFirst, alphaFirst + pixelCount, 0.0);
+
+ return numeric::isNull(divisor) ? 0 : dividend / divisor;
+ }
+ else
+ return std::accumulate(pixBegin, pixEnd, 0.0) / (3.0 * pixelCount);
+ }
+ return 0;
+}
+
+
+inline
+void brighten(wxImage& img, int level)
+{
+ if (auto pixBegin = img.GetData())
+ {
+ 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)); });
+ else
+ std::for_each(pixBegin, pixEnd, [&](unsigned char& c) { c = static_cast<unsigned char>(std::max(0, c + level)); });
+ }
+}
+
+
+inline
+void adjustBrightness(wxImage& img, int targetLevel)
+{
+ brighten(img, targetLevel - getAvgBrightness(img));
+}
+
+
+inline
+wxBitmap layOver(const wxBitmap& background, const wxBitmap& foreground)
+{
+ assert(foreground.HasAlpha() == background.HasAlpha()); //we don't support mixed-mode brittleness!
+
+ wxBitmap output(background.ConvertToImage()); //attention: wxBitmap/wxImage use ref-counting without copy on write!
+ {
+ wxMemoryDC dc(output);
+
+ const int offsetX = (background.GetWidth () - foreground.GetWidth ()) / 2;
+ const int offsetY = (background.GetHeight() - foreground.GetHeight()) / 2;
+ dc.DrawBitmap(foreground, offsetX, offsetY);
+ }
+ return output;
+}
+
+
+inline
+bool isEqual(const wxBitmap& lhs, const wxBitmap& rhs)
+{
+ if (lhs.IsOk() != rhs.IsOk())
+ return false;
+ if (!lhs.IsOk())
+ return true;
+
+ if (lhs.GetSize() != rhs.GetSize())
+ return false;
+
+ wxImage imLhs = lhs.ConvertToImage();
+ wxImage imRhs = rhs.ConvertToImage();
+
+ if (imLhs.HasAlpha() != imRhs.HasAlpha())
+ return false;
+
+ const int pixelCount = lhs.GetWidth() * lhs.GetHeight();
+
+ if (!std::equal(imLhs.GetData(), imLhs.GetData() + pixelCount * 3, imRhs.GetData()))
+ return false;
+
+ if (imLhs.HasAlpha())
+ if (!std::equal(imLhs.GetAlpha(), imLhs.GetAlpha() + pixelCount, imRhs.GetAlpha()))
+ return false;
+
+ return true;
+}
+
+/*
+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]
+{
+ //http://de.wikipedia.org/wiki/HSV-Farbraum
+
+ //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 = numeric::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 ed3396e1..6794b8bd 100644..100755
--- a/wx+/no_flicker.h
+++ b/wx+/no_flicker.h
@@ -1,43 +1,39 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef NO_FLICKER_H_893421590321532
-#define NO_FLICKER_H_893421590321532
-
-#include <wx/textctrl.h>
-#include <wx/stattext.h>
-
-namespace zen
-{
-inline
-void setText(wxTextCtrl& control, const wxString& newText, bool* additionalLayoutChange = nullptr)
-{
- const wxString& label = control.GetValue(); //perf: don't call twice!
- if (additionalLayoutChange && !*additionalLayoutChange) //never revert from true to false!
- *additionalLayoutChange = label.length() != newText.length(); //avoid screen flicker: update layout only when necessary
-
- if (label != newText)
- control.ChangeValue(newText);
-}
-
-inline
-void setText(wxStaticText& control, wxString newText, bool* additionalLayoutChange = nullptr)
-{
-#ifdef ZEN_WIN
- //wxStaticText handles ampersands incorrectly: https://sourceforge.net/p/freefilesync/bugs/279/
- replace(newText, L'&', L"&&");
-#endif
-
- const wxString& label = control.GetLabel(); //perf: don't call twice!
- if (additionalLayoutChange && !*additionalLayoutChange)
- *additionalLayoutChange = label.length() != newText.length(); //avoid screen flicker: update layout only when necessary
-
- if (label != newText)
- control.SetLabel(newText);
-}
-}
-
-#endif //NO_FLICKER_H_893421590321532
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef NO_FLICKER_H_893421590321532
+#define NO_FLICKER_H_893421590321532
+
+#include <wx/textctrl.h>
+#include <wx/stattext.h>
+
+namespace zen
+{
+inline
+void setText(wxTextCtrl& control, const wxString& newText, bool* additionalLayoutChange = nullptr)
+{
+ const wxString& label = control.GetValue(); //perf: don't call twice!
+ if (additionalLayoutChange && !*additionalLayoutChange) //never revert from true to false!
+ *additionalLayoutChange = label.length() != newText.length(); //avoid screen flicker: update layout only when necessary
+
+ if (label != newText)
+ control.ChangeValue(newText);
+}
+
+inline
+void setText(wxStaticText& control, wxString newText, bool* additionalLayoutChange = nullptr)
+{
+
+ const wxString& label = control.GetLabel(); //perf: don't call twice!
+ if (additionalLayoutChange && !*additionalLayoutChange)
+ *additionalLayoutChange = label.length() != newText.length(); //avoid screen flicker: update layout only when necessary
+
+ if (label != newText)
+ control.SetLabel(newText);
+}
+}
+
+#endif //NO_FLICKER_H_893421590321532
diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp
index f96884d9..dca93658 100644..100755
--- a/wx+/popup_dlg.cpp
+++ b/wx+/popup_dlg.cpp
@@ -1,312 +1,302 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "popup_dlg.h"
-#include <wx/app.h>
-#include <wx/display.h>
-#include <wx+/std_button_layout.h>
-#include <wx+/font_size.h>
-#include <wx+/image_resources.h>
-#include "popup_dlg_generated.h"
-
-#ifdef ZEN_WIN
- #include <wx+/mouse_move_dlg.h>
-#endif
-
-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;
- if (maxSize.x <= scrollbarWidth) //implicitly checks for non-zero, too!
- return;
- maxSize.x -= scrollbarWidth;
-
- int bestWidth = 0;
- int rowCount = 0;
- int rowHeight = 0;
-
- auto evalLineExtent = [&](const wxSize& sz) -> bool //return true when done
- {
- if (sz.x > bestWidth)
- bestWidth = std::min(maxSize.x, sz.x);
-
- rowCount += (sz.x + maxSize.x - 1) / maxSize.x; //integer round up: consider line-wraps!
- rowHeight = std::max(rowHeight, sz.y); //all rows *should* have same height
-
- return rowCount * rowHeight >= maxSize.y;
- };
-
- for (auto it = text.begin();;)
- {
- auto itEnd = std::find(it, text.end(), L'\n');
- wxString line(it, itEnd);
- if (line.empty())
- line = L" "; //GetTextExtent() returns (0, 0) for empty strings!
-
- wxSize sz = ctrl.GetTextExtent(line); //exactly gives row height, but does *not* consider newlines
- if (evalLineExtent(sz))
- break;
-
- if (itEnd == text.end())
- break;
- it = itEnd + 1;
- }
-
-#if defined ZEN_WIN || defined ZEN_LINUX
- const int rowGap = 0;
-#elif defined ZEN_MAC
- const int rowGap = 1;
-#endif
- const wxSize bestSize(bestWidth + scrollbarWidth, std::min(rowCount * (rowHeight + rowGap), maxSize.y));
- ctrl.SetMinSize(bestSize); //alas, SetMinClientSize() is just not working!
-}
-}
-
-
-class zen::StandardPopupDialog : public PopupDialogGenerated
-{
-public:
- StandardPopupDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg) :
- PopupDialogGenerated(parent),
- checkBoxValue_(cfg.checkBoxValue)
- {
-#ifdef ZEN_WIN
- new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this"
-#endif
- wxBitmap iconTmp;
- wxString titleTmp;
- switch (type)
- {
- case DialogInfoType::INFO:
- //"Information" is meaningless as caption text!
- //confirmation doesn't use info icon
- //iconTmp = getResourceImage(L"msg_info");
- break;
- case DialogInfoType::WARNING:
- iconTmp = getResourceImage(L"msg_warning");
- titleTmp = _("Warning");
- break;
- case DialogInfoType::ERROR2:
- iconTmp = getResourceImage(L"msg_error");
- titleTmp = _("Error");
- break;
- }
- if (cfg.icon.IsOk())
- iconTmp = cfg.icon;
-
- if (!cfg.title.empty())
- titleTmp = cfg.title;
- //-----------------------------------------------
- m_bitmapMsgType->SetBitmap(iconTmp);
-
- if (titleTmp.empty())
- SetTitle(wxTheApp->GetAppDisplayName());
- else
- {
- if (parent && parent->IsShownOnScreen())
- SetTitle(titleTmp);
- else
- SetTitle(wxTheApp->GetAppDisplayName() + L" - " + titleTmp);
- }
-
- int maxWidth = 500;
- int maxHeight = 400; //try to determine better value based on actual display resolution:
-
- if (parent)
- {
- const int disPos = wxDisplay::GetFromWindow(parent); //window must be visible
- if (disPos != wxNOT_FOUND)
- maxHeight = wxDisplay(disPos).GetClientArea().GetHeight() * 2 / 3;
- }
-
- assert(!cfg.textMain.empty() || !cfg.textDetail.empty());
- if (!cfg.textMain.empty())
- {
- setMainInstructionFont(*m_staticTextMain);
- m_staticTextMain->SetLabel(cfg.textMain);
- m_staticTextMain->Wrap(maxWidth); //call *after* SetLabel()
- }
- else
- m_staticTextMain->Hide();
-
- if (!cfg.textDetail.empty())
- {
- const wxString& text = L"\n" + 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);
- }
- else
- m_textCtrlTextDetail->Hide();
-
- if (checkBoxValue_)
- {
- assert(contains(cfg.checkBoxLabel, L"&"));
- m_checkBoxCustom->SetLabel(cfg.checkBoxLabel);
- m_checkBoxCustom->SetValue(*checkBoxValue_);
- }
- else
- m_checkBoxCustom->Hide();
-
- Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(StandardPopupDialog::OnKeyPressed), nullptr, this); //dialog-specific local key events
- }
-
-private:
- void OnClose (wxCloseEvent& event) override { EndModal(static_cast<int>(ConfirmationButton3::CANCEL)); }
- void OnCancel(wxCommandEvent& event) override { EndModal(static_cast<int>(ConfirmationButton3::CANCEL)); }
-
- void OnKeyPressed(wxKeyEvent& event)
- {
- switch (event.GetKeyCode())
- {
- case WXK_RETURN:
- case WXK_NUMPAD_ENTER:
- {
- wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED);
- OnButtonAffirmative(dummy);
- return;
- }
-
- case WXK_ESCAPE: //handle case where cancel button is hidden!
- EndModal(static_cast<int>(ConfirmationButton3::CANCEL));
- return;
- }
- event.Skip();
- }
-
- void OnButtonAffirmative(wxCommandEvent& event) override
- {
- if (checkBoxValue_)
- *checkBoxValue_ = m_checkBoxCustom->GetValue();
- EndModal(static_cast<int>(ConfirmationButton3::DO_IT));
- }
-
- void OnButtonNegative(wxCommandEvent& event) override
- {
- if (checkBoxValue_)
- *checkBoxValue_ = m_checkBoxCustom->GetValue();
- EndModal(static_cast<int>(ConfirmationButton3::DONT_DO_IT));
- }
-
- bool* checkBoxValue_;
-};
-
-
-namespace
-{
-class NotificationDialog : public StandardPopupDialog
-{
-public:
- NotificationDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg) :
- StandardPopupDialog(parent, type, cfg)
- {
- m_buttonAffirmative->SetLabel(_("Close")); //UX Guide: use "Close" for errors, warnings and windows in which users can't make changes (no ampersand!)
- m_buttonNegative->Hide();
- m_buttonCancel->Hide();
-
- //set std order after button visibility was set
- setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonAffirmative));
- setAsStandard(*m_buttonAffirmative);
- GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- Center(); //needs to be re-applied after a dialog size change!
- }
-};
-
-
-class ConfirmationDialog : public StandardPopupDialog
-{
-public:
- ConfirmationDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg, const wxString& labelDoIt) :
- StandardPopupDialog(parent, type, cfg)
- {
- assert(contains(labelDoIt, L"&"));
- m_buttonAffirmative->SetLabel(labelDoIt);
- m_buttonNegative->Hide();
-
- //set std order after button visibility was set
- setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonAffirmative).setCancel(m_buttonCancel));
- setAsStandard(*m_buttonAffirmative);
- GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- Center(); //needs to be re-applied after a dialog size change!
- }
-};
-}
-
-class zen::ConfirmationDialog3 : public StandardPopupDialog
-{
-public:
- ConfirmationDialog3(wxWindow* parent, DialogInfoType type, const PopupDialogCfg3& cfg, const wxString& labelDoIt, const wxString& labelDontDoIt) :
- StandardPopupDialog(parent, type, cfg.pdCfg_),
- buttonToDisableWhenChecked_(cfg.buttonToDisableWhenChecked_)
- {
- assert(contains(labelDoIt, L"&"));
- assert(contains(labelDontDoIt, L"&"));
- m_buttonAffirmative->SetLabel(labelDoIt);
- m_buttonNegative ->SetLabel(labelDontDoIt);
-
- //m_buttonAffirmative->SetId(wxID_IGNORE); -> setting id after button creation breaks "mouse snap to" functionality
- //m_buttonNegative ->SetId(wxID_RETRY); -> also wxWidgets docs seem to hide some info: "Normally, the identifier should be provided on creation and should not be modified subsequently."
-
- updateGui();
-
- //set std order after button visibility was set
- setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonAffirmative).setNegative(m_buttonNegative).setCancel(m_buttonCancel));
- setAsStandard(*m_buttonAffirmative);
- GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
- Center(); //needs to be re-applied after a dialog size change!
- }
-
-private:
- void OnCheckBoxClick(wxCommandEvent& event) override { updateGui(); event.Skip(); }
-
- void updateGui()
- {
- switch (buttonToDisableWhenChecked_)
- {
- case ConfirmationButton3::DO_IT:
- m_buttonAffirmative->Enable(!m_checkBoxCustom->GetValue());
- break;
- case ConfirmationButton3::DONT_DO_IT:
- m_buttonNegative->Enable(!m_checkBoxCustom->GetValue());
- break;
- case ConfirmationButton3::CANCEL:
- break;
- }
- }
-
- const ConfirmationButton3 buttonToDisableWhenChecked_;
-};
-
-//########################################################################################
-
-void zen::showNotificationDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg)
-{
- NotificationDialog dlg(parent, type, cfg);
- dlg.ShowModal();
-}
-
-
-ConfirmationButton zen::showConfirmationDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg, const wxString& labelDoIt)
-{
- ConfirmationDialog dlg(parent, type, cfg, labelDoIt);
- return static_cast<ConfirmationButton>(dlg.ShowModal());
-}
-
-
-ConfirmationButton3 zen::showConfirmationDialog3(wxWindow* parent, DialogInfoType type, const PopupDialogCfg3& cfg, const wxString& labelDoIt, const wxString& labelDontDoIt)
-{
- ConfirmationDialog3 dlg(parent, type, cfg, labelDoIt, labelDontDoIt);
- return static_cast<ConfirmationButton3>(dlg.ShowModal());
-}
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "popup_dlg.h"
+#include <wx/app.h>
+#include <wx/display.h>
+#include <wx+/std_button_layout.h>
+#include <wx+/font_size.h>
+#include <wx+/image_resources.h>
+#include "popup_dlg_generated.h"
+
+
+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;
+ if (maxSize.x <= scrollbarWidth) //implicitly checks for non-zero, too!
+ return;
+ maxSize.x -= scrollbarWidth;
+
+ int bestWidth = 0;
+ int rowCount = 0;
+ int rowHeight = 0;
+
+ auto evalLineExtent = [&](const wxSize& sz) -> bool //return true when done
+ {
+ if (sz.x > bestWidth)
+ bestWidth = std::min(maxSize.x, sz.x);
+
+ rowCount += (sz.x + maxSize.x - 1) / maxSize.x; //integer round up: consider line-wraps!
+ rowHeight = std::max(rowHeight, sz.y); //all rows *should* have same height
+
+ return rowCount * rowHeight >= maxSize.y;
+ };
+
+ for (auto it = text.begin();;)
+ {
+ auto itEnd = std::find(it, text.end(), L'\n');
+ wxString line(it, itEnd);
+ if (line.empty())
+ line = L" "; //GetTextExtent() returns (0, 0) for empty strings!
+
+ wxSize sz = ctrl.GetTextExtent(line); //exactly gives row height, but does *not* consider newlines
+ if (evalLineExtent(sz))
+ break;
+
+ if (itEnd == text.end())
+ break;
+ it = itEnd + 1;
+ }
+
+ const int rowGap = 0;
+ const wxSize bestSize(bestWidth + scrollbarWidth, std::min(rowCount * (rowHeight + rowGap), maxSize.y));
+ ctrl.SetMinSize(bestSize); //alas, SetMinClientSize() is just not working!
+}
+}
+
+
+class zen::StandardPopupDialog : public PopupDialogGenerated
+{
+public:
+ StandardPopupDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg) :
+ PopupDialogGenerated(parent),
+ checkBoxValue_(cfg.checkBoxValue)
+ {
+ wxBitmap iconTmp;
+ wxString titleTmp;
+ switch (type)
+ {
+ case DialogInfoType::INFO:
+ //"Information" is meaningless as caption text!
+ //confirmation doesn't use info icon
+ //iconTmp = getResourceImage(L"msg_info");
+ break;
+ case DialogInfoType::WARNING:
+ iconTmp = getResourceImage(L"msg_warning");
+ titleTmp = _("Warning");
+ break;
+ case DialogInfoType::ERROR2:
+ iconTmp = getResourceImage(L"msg_error");
+ titleTmp = _("Error");
+ break;
+ }
+ if (cfg.icon.IsOk())
+ iconTmp = cfg.icon;
+
+ if (!cfg.title.empty())
+ titleTmp = cfg.title;
+ //-----------------------------------------------
+ m_bitmapMsgType->SetBitmap(iconTmp);
+
+ if (titleTmp.empty())
+ SetTitle(wxTheApp->GetAppDisplayName());
+ else
+ {
+ if (parent && parent->IsShownOnScreen())
+ SetTitle(titleTmp);
+ else
+ SetTitle(wxTheApp->GetAppDisplayName() + L" - " + titleTmp);
+ }
+
+ int maxWidth = 500;
+ int maxHeight = 400; //try to determine better value based on actual display resolution:
+
+ if (parent)
+ {
+ const int disPos = wxDisplay::GetFromWindow(parent); //window must be visible
+ if (disPos != wxNOT_FOUND)
+ maxHeight = wxDisplay(disPos).GetClientArea().GetHeight() * 2 / 3;
+ }
+
+ assert(!cfg.textMain.empty() || !cfg.textDetail.empty());
+ if (!cfg.textMain.empty())
+ {
+ setMainInstructionFont(*m_staticTextMain);
+ m_staticTextMain->SetLabel(cfg.textMain);
+ m_staticTextMain->Wrap(maxWidth); //call *after* SetLabel()
+ }
+ else
+ m_staticTextMain->Hide();
+
+ if (!cfg.textDetail.empty())
+ {
+ const wxString& text = L"\n" + 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);
+ }
+ else
+ m_textCtrlTextDetail->Hide();
+
+ if (checkBoxValue_)
+ {
+ assert(contains(cfg.checkBoxLabel, L"&"));
+ m_checkBoxCustom->SetLabel(cfg.checkBoxLabel);
+ m_checkBoxCustom->SetValue(*checkBoxValue_);
+ }
+ else
+ m_checkBoxCustom->Hide();
+
+ Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(StandardPopupDialog::OnKeyPressed), nullptr, this); //dialog-specific local key events
+ }
+
+private:
+ void OnClose (wxCloseEvent& event) override { EndModal(static_cast<int>(ConfirmationButton3::CANCEL)); }
+ void OnCancel(wxCommandEvent& event) override { EndModal(static_cast<int>(ConfirmationButton3::CANCEL)); }
+
+ void OnKeyPressed(wxKeyEvent& event)
+ {
+ switch (event.GetKeyCode())
+ {
+ case WXK_RETURN:
+ case WXK_NUMPAD_ENTER:
+ {
+ wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED);
+ OnButtonAffirmative(dummy);
+ return;
+ }
+
+ case WXK_ESCAPE: //handle case where cancel button is hidden!
+ EndModal(static_cast<int>(ConfirmationButton3::CANCEL));
+ return;
+ }
+ event.Skip();
+ }
+
+ void OnButtonAffirmative(wxCommandEvent& event) override
+ {
+ if (checkBoxValue_)
+ *checkBoxValue_ = m_checkBoxCustom->GetValue();
+ EndModal(static_cast<int>(ConfirmationButton3::DO_IT));
+ }
+
+ void OnButtonNegative(wxCommandEvent& event) override
+ {
+ if (checkBoxValue_)
+ *checkBoxValue_ = m_checkBoxCustom->GetValue();
+ EndModal(static_cast<int>(ConfirmationButton3::DONT_DO_IT));
+ }
+
+ bool* checkBoxValue_;
+};
+
+
+namespace
+{
+class NotificationDialog : public StandardPopupDialog
+{
+public:
+ NotificationDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg) :
+ StandardPopupDialog(parent, type, cfg)
+ {
+ m_buttonAffirmative->SetLabel(_("Close")); //UX Guide: use "Close" for errors, warnings and windows in which users can't make changes (no ampersand!)
+ m_buttonNegative->Hide();
+ m_buttonCancel->Hide();
+
+ //set std order after button visibility was set
+ setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonAffirmative));
+ setAsStandard(*m_buttonAffirmative);
+ GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
+ Center(); //needs to be re-applied after a dialog size change!
+ }
+};
+
+
+class ConfirmationDialog : public StandardPopupDialog
+{
+public:
+ ConfirmationDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg, const wxString& labelDoIt) :
+ StandardPopupDialog(parent, type, cfg)
+ {
+ assert(contains(labelDoIt, L"&"));
+ m_buttonAffirmative->SetLabel(labelDoIt);
+ m_buttonNegative->Hide();
+
+ //set std order after button visibility was set
+ setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonAffirmative).setCancel(m_buttonCancel));
+ setAsStandard(*m_buttonAffirmative);
+ GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
+ Center(); //needs to be re-applied after a dialog size change!
+ }
+};
+}
+
+class zen::ConfirmationDialog3 : public StandardPopupDialog
+{
+public:
+ ConfirmationDialog3(wxWindow* parent, DialogInfoType type, const PopupDialogCfg3& cfg, const wxString& labelDoIt, const wxString& labelDontDoIt) :
+ StandardPopupDialog(parent, type, cfg.pdCfg_),
+ buttonToDisableWhenChecked_(cfg.buttonToDisableWhenChecked_)
+ {
+ assert(contains(labelDoIt, L"&"));
+ assert(contains(labelDontDoIt, L"&"));
+ m_buttonAffirmative->SetLabel(labelDoIt);
+ m_buttonNegative ->SetLabel(labelDontDoIt);
+
+ //m_buttonAffirmative->SetId(wxID_IGNORE); -> setting id after button creation breaks "mouse snap to" functionality
+ //m_buttonNegative ->SetId(wxID_RETRY); -> also wxWidgets docs seem to hide some info: "Normally, the identifier should be provided on creation and should not be modified subsequently."
+
+ updateGui();
+
+ //set std order after button visibility was set
+ setStandardButtonLayout(*bSizerStdButtons, StdButtons().setAffirmative(m_buttonAffirmative).setNegative(m_buttonNegative).setCancel(m_buttonCancel));
+ setAsStandard(*m_buttonAffirmative);
+ GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
+ Center(); //needs to be re-applied after a dialog size change!
+ }
+
+private:
+ void OnCheckBoxClick(wxCommandEvent& event) override { updateGui(); event.Skip(); }
+
+ void updateGui()
+ {
+ switch (buttonToDisableWhenChecked_)
+ {
+ case ConfirmationButton3::DO_IT:
+ m_buttonAffirmative->Enable(!m_checkBoxCustom->GetValue());
+ break;
+ case ConfirmationButton3::DONT_DO_IT:
+ m_buttonNegative->Enable(!m_checkBoxCustom->GetValue());
+ break;
+ case ConfirmationButton3::CANCEL:
+ break;
+ }
+ }
+
+ const ConfirmationButton3 buttonToDisableWhenChecked_;
+};
+
+//########################################################################################
+
+void zen::showNotificationDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg)
+{
+ NotificationDialog dlg(parent, type, cfg);
+ dlg.ShowModal();
+}
+
+
+ConfirmationButton zen::showConfirmationDialog(wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg, const wxString& labelDoIt)
+{
+ ConfirmationDialog dlg(parent, type, cfg, labelDoIt);
+ return static_cast<ConfirmationButton>(dlg.ShowModal());
+}
+
+
+ConfirmationButton3 zen::showConfirmationDialog3(wxWindow* parent, DialogInfoType type, const PopupDialogCfg3& cfg, const wxString& labelDoIt, const wxString& labelDontDoIt)
+{
+ ConfirmationDialog3 dlg(parent, type, cfg, labelDoIt, labelDontDoIt);
+ return static_cast<ConfirmationButton3>(dlg.ShowModal());
+}
diff --git a/wx+/popup_dlg.h b/wx+/popup_dlg.h
index 67f73a11..2f0adeff 100644..100755
--- a/wx+/popup_dlg.h
+++ b/wx+/popup_dlg.h
@@ -1,97 +1,94 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef POPUP_DLG_H_820780154723456
-#define POPUP_DLG_H_820780154723456
-
-#ifdef ZEN_WIN
- #include <zen/win.h> //include before <wx/msw/wrapwin.h>
-#endif
-#include <wx/window.h>
-#include <wx/bitmap.h>
-#include <wx/string.h>
-
-
-namespace zen
-{
-//parent window, optional: support correct dialog placement above parent on multiple monitor systems
-//this module requires error, warning and info image files in resources.zip, see <wx+/image_resources.h>
-
-struct PopupDialogCfg;
-struct PopupDialogCfg3;
-
-enum class DialogInfoType
-{
- INFO,
- WARNING,
- ERROR2, //fuck the ERROR macro in WinGDI.h!
-};
-
-enum class ConfirmationButton3
-{
- DO_IT,
- DONT_DO_IT,
- CANCEL
-};
-
-enum class ConfirmationButton
-{
- DO_IT = static_cast<int>(ConfirmationButton3::DO_IT ), //[!]
- CANCEL = static_cast<int>(ConfirmationButton3::CANCEL), //Clang requires a "static_cast"
-};
-
-void showNotificationDialog (wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg);
-ConfirmationButton showConfirmationDialog (wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg, const wxString& labelDoIt);
-ConfirmationButton3 showConfirmationDialog3(wxWindow* parent, DialogInfoType type, const PopupDialogCfg3& cfg, const wxString& labelDoIt, const wxString& labelDontDoIt);
-
-//----------------------------------------------------------------------------------------------------------------
-class StandardPopupDialog;
-class ConfirmationDialog3;
-
-struct PopupDialogCfg
-{
- PopupDialogCfg& setIcon (const wxBitmap& bmp ) { icon = bmp; return *this; }
- PopupDialogCfg& setTitle (const wxString& label) { title = label; return *this; }
- PopupDialogCfg& setMainInstructions (const wxString& label) { textMain = label; return *this; } //set at least one of these!
- PopupDialogCfg& setDetailInstructions(const wxString& label) { textDetail = label; return *this; } //
- PopupDialogCfg& setCheckBox(bool& value, const wxString& label) { checkBoxValue = &value; checkBoxLabel = label; return *this; }
-
-private:
- friend class StandardPopupDialog;
-
- wxBitmap icon;
- wxString title;
- wxString textMain;
- wxString textDetail;
- bool* checkBoxValue = nullptr; //in/out
- wxString checkBoxLabel;
-};
-
-
-struct PopupDialogCfg3
-{
- PopupDialogCfg3& setIcon (const wxBitmap& bmp ) { pdCfg_.setIcon (bmp); return *this; }
- PopupDialogCfg3& setTitle (const wxString& label) { pdCfg_.setTitle (label); return *this; }
- PopupDialogCfg3& setMainInstructions (const wxString& label) { pdCfg_.setMainInstructions (label); return *this; } //set at least one of these!
- PopupDialogCfg3& setDetailInstructions(const wxString& label) { pdCfg_.setDetailInstructions(label); return *this; } //
- PopupDialogCfg3& setCheckBox(bool& value, const wxString& label) { pdCfg_.setCheckBox(value, label); return *this; }
- PopupDialogCfg3& setCheckBox(bool& value, const wxString& label, ConfirmationButton3 disableWhenChecked)
- {
- assert(disableWhenChecked != ConfirmationButton3::CANCEL);
- setCheckBox(value, label);
- buttonToDisableWhenChecked_ = disableWhenChecked;
- return *this;
- }
-
-private:
- friend class ConfirmationDialog3;
-
- PopupDialogCfg pdCfg_;
- ConfirmationButton3 buttonToDisableWhenChecked_ = ConfirmationButton3::CANCEL;
-};
-}
-
-#endif //POPUP_DLG_H_820780154723456
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef POPUP_DLG_H_820780154723456
+#define POPUP_DLG_H_820780154723456
+
+#include <wx/window.h>
+#include <wx/bitmap.h>
+#include <wx/string.h>
+
+
+namespace zen
+{
+//parent window, optional: support correct dialog placement above parent on multiple monitor systems
+//this module requires error, warning and info image files in resources.zip, see <wx+/image_resources.h>
+
+struct PopupDialogCfg;
+struct PopupDialogCfg3;
+
+enum class DialogInfoType
+{
+ INFO,
+ WARNING,
+ ERROR2, //fuck the ERROR macro in WinGDI.h!
+};
+
+enum class ConfirmationButton3
+{
+ DO_IT,
+ DONT_DO_IT,
+ CANCEL
+};
+
+enum class ConfirmationButton
+{
+ DO_IT = static_cast<int>(ConfirmationButton3::DO_IT ), //[!]
+ CANCEL = static_cast<int>(ConfirmationButton3::CANCEL), //Clang requires a "static_cast"
+};
+
+void showNotificationDialog (wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg);
+ConfirmationButton showConfirmationDialog (wxWindow* parent, DialogInfoType type, const PopupDialogCfg& cfg, const wxString& labelDoIt);
+ConfirmationButton3 showConfirmationDialog3(wxWindow* parent, DialogInfoType type, const PopupDialogCfg3& cfg, const wxString& labelDoIt, const wxString& labelDontDoIt);
+
+//----------------------------------------------------------------------------------------------------------------
+class StandardPopupDialog;
+class ConfirmationDialog3;
+
+struct PopupDialogCfg
+{
+ PopupDialogCfg& setIcon (const wxBitmap& bmp ) { icon = bmp; return *this; }
+ PopupDialogCfg& setTitle (const wxString& label) { title = label; return *this; }
+ PopupDialogCfg& setMainInstructions (const wxString& label) { textMain = label; return *this; } //set at least one of these!
+ PopupDialogCfg& setDetailInstructions(const wxString& label) { textDetail = label; return *this; } //
+ PopupDialogCfg& setCheckBox(bool& value, const wxString& label) { checkBoxValue = &value; checkBoxLabel = label; return *this; }
+
+private:
+ friend class StandardPopupDialog;
+
+ wxBitmap icon;
+ wxString title;
+ wxString textMain;
+ wxString textDetail;
+ bool* checkBoxValue = nullptr; //in/out
+ wxString checkBoxLabel;
+};
+
+
+struct PopupDialogCfg3
+{
+ PopupDialogCfg3& setIcon (const wxBitmap& bmp ) { pdCfg_.setIcon (bmp); return *this; }
+ PopupDialogCfg3& setTitle (const wxString& label) { pdCfg_.setTitle (label); return *this; }
+ PopupDialogCfg3& setMainInstructions (const wxString& label) { pdCfg_.setMainInstructions (label); return *this; } //set at least one of these!
+ PopupDialogCfg3& setDetailInstructions(const wxString& label) { pdCfg_.setDetailInstructions(label); return *this; } //
+ PopupDialogCfg3& setCheckBox(bool& value, const wxString& label) { pdCfg_.setCheckBox(value, label); return *this; }
+ PopupDialogCfg3& setCheckBox(bool& value, const wxString& label, ConfirmationButton3 disableWhenChecked)
+ {
+ assert(disableWhenChecked != ConfirmationButton3::CANCEL);
+ setCheckBox(value, label);
+ buttonToDisableWhenChecked_ = disableWhenChecked;
+ return *this;
+ }
+
+private:
+ friend class ConfirmationDialog3;
+
+ PopupDialogCfg pdCfg_;
+ ConfirmationButton3 buttonToDisableWhenChecked_ = ConfirmationButton3::CANCEL;
+};
+}
+
+#endif //POPUP_DLG_H_820780154723456
diff --git a/wx+/popup_dlg_generated.cpp b/wx+/popup_dlg_generated.cpp
index a79ffa52..3b47573e 100644..100755
--- a/wx+/popup_dlg_generated.cpp
+++ b/wx+/popup_dlg_generated.cpp
@@ -1,97 +1,97 @@
-///////////////////////////////////////////////////////////////////////////
-// C++ code generated with wxFormBuilder (version Jun 17 2015)
-// http://www.wxformbuilder.org/
-//
-// PLEASE DO "NOT" EDIT THIS FILE!
-///////////////////////////////////////////////////////////////////////////
-
-#include "popup_dlg_generated.h"
-
-///////////////////////////////////////////////////////////////////////////
-
-PopupDialogGenerated::PopupDialogGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
-{
- this->SetSizeHints( wxSize( -1, -1 ), wxDefaultSize );
- this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
-
- wxBoxSizer* bSizer24;
- bSizer24 = new wxBoxSizer( wxVERTICAL );
-
- m_panel33 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
- m_panel33->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
-
- wxBoxSizer* bSizer165;
- bSizer165 = new wxBoxSizer( wxHORIZONTAL );
-
- m_bitmapMsgType = new wxStaticBitmap( m_panel33, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 );
- bSizer165->Add( m_bitmapMsgType, 0, wxALL, 10 );
-
- wxBoxSizer* bSizer16;
- bSizer16 = new wxBoxSizer( wxVERTICAL );
-
-
- bSizer16->Add( 0, 10, 0, 0, 5 );
-
- m_staticTextMain = new wxStaticText( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticTextMain->Wrap( -1 );
- bSizer16->Add( m_staticTextMain, 0, wxRIGHT, 10 );
-
-
- bSizer16->Add( 0, 5, 0, 0, 5 );
-
- m_textCtrlTextDetail = new wxTextCtrl( m_panel33, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER );
- bSizer16->Add( m_textCtrlTextDetail, 1, wxEXPAND, 5 );
-
-
- bSizer165->Add( bSizer16, 1, wxEXPAND, 5 );
-
-
- m_panel33->SetSizer( bSizer165 );
- m_panel33->Layout();
- bSizer165->Fit( m_panel33 );
- bSizer24->Add( m_panel33, 1, wxEXPAND, 5 );
-
- m_staticline6 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
- bSizer24->Add( m_staticline6, 0, wxEXPAND, 5 );
-
- wxBoxSizer* bSizer25;
- bSizer25 = new wxBoxSizer( wxVERTICAL );
-
- m_checkBoxCustom = new wxCheckBox( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
- bSizer25->Add( m_checkBoxCustom, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 );
-
- bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL );
-
- m_buttonAffirmative = new wxButton( this, wxID_YES, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
- bSizerStdButtons->Add( m_buttonAffirmative, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
-
- m_buttonNegative = new wxButton( this, wxID_NO, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
- bSizerStdButtons->Add( m_buttonNegative, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 );
-
- m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
- bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 );
-
-
- bSizer25->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 );
-
-
- bSizer24->Add( bSizer25, 0, wxEXPAND, 5 );
-
-
- this->SetSizer( bSizer24 );
- this->Layout();
- bSizer24->Fit( this );
-
- this->Centre( wxBOTH );
-
- // Connect Events
- this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( PopupDialogGenerated::OnClose ) );
- m_checkBoxCustom->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnCheckBoxClick ), NULL, this );
- m_buttonAffirmative->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonAffirmative ), NULL, this );
- m_buttonNegative->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonNegative ), NULL, this );
- m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnCancel ), NULL, this );
-}
-
-PopupDialogGenerated::~PopupDialogGenerated()
-{
-}
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Jun 17 2015)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO "NOT" EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#include "popup_dlg_generated.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+PopupDialogGenerated::PopupDialogGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
+{
+ this->SetSizeHints( wxSize( -1, -1 ), wxDefaultSize );
+ this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
+
+ wxBoxSizer* bSizer24;
+ bSizer24 = new wxBoxSizer( wxVERTICAL );
+
+ m_panel33 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+ m_panel33->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
+
+ wxBoxSizer* bSizer165;
+ bSizer165 = new wxBoxSizer( wxHORIZONTAL );
+
+ m_bitmapMsgType = new wxStaticBitmap( m_panel33, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 );
+ bSizer165->Add( m_bitmapMsgType, 0, wxALL, 10 );
+
+ wxBoxSizer* bSizer16;
+ bSizer16 = new wxBoxSizer( wxVERTICAL );
+
+
+ bSizer16->Add( 0, 10, 0, 0, 5 );
+
+ m_staticTextMain = new wxStaticText( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticTextMain->Wrap( -1 );
+ bSizer16->Add( m_staticTextMain, 0, wxRIGHT, 10 );
+
+
+ bSizer16->Add( 0, 5, 0, 0, 5 );
+
+ m_textCtrlTextDetail = new wxTextCtrl( m_panel33, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER );
+ bSizer16->Add( m_textCtrlTextDetail, 1, wxEXPAND, 5 );
+
+
+ bSizer165->Add( bSizer16, 1, wxEXPAND, 5 );
+
+
+ m_panel33->SetSizer( bSizer165 );
+ m_panel33->Layout();
+ bSizer165->Fit( m_panel33 );
+ bSizer24->Add( m_panel33, 1, wxEXPAND, 5 );
+
+ m_staticline6 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+ bSizer24->Add( m_staticline6, 0, wxEXPAND, 5 );
+
+ wxBoxSizer* bSizer25;
+ bSizer25 = new wxBoxSizer( wxVERTICAL );
+
+ m_checkBoxCustom = new wxCheckBox( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
+ bSizer25->Add( m_checkBoxCustom, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 );
+
+ bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL );
+
+ m_buttonAffirmative = new wxButton( this, wxID_YES, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
+ bSizerStdButtons->Add( m_buttonAffirmative, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
+
+ m_buttonNegative = new wxButton( this, wxID_NO, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
+ bSizerStdButtons->Add( m_buttonNegative, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 );
+
+ m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
+ bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 );
+
+
+ bSizer25->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 );
+
+
+ bSizer24->Add( bSizer25, 0, wxEXPAND, 5 );
+
+
+ this->SetSizer( bSizer24 );
+ this->Layout();
+ bSizer24->Fit( this );
+
+ this->Centre( wxBOTH );
+
+ // Connect Events
+ this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( PopupDialogGenerated::OnClose ) );
+ m_checkBoxCustom->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnCheckBoxClick ), NULL, this );
+ m_buttonAffirmative->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonAffirmative ), NULL, this );
+ m_buttonNegative->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonNegative ), NULL, this );
+ m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnCancel ), NULL, this );
+}
+
+PopupDialogGenerated::~PopupDialogGenerated()
+{
+}
diff --git a/wx+/popup_dlg_generated.h b/wx+/popup_dlg_generated.h
index bef871c7..64e68c08 100644..100755
--- a/wx+/popup_dlg_generated.h
+++ b/wx+/popup_dlg_generated.h
@@ -1,71 +1,71 @@
-///////////////////////////////////////////////////////////////////////////
-// C++ code generated with wxFormBuilder (version Jun 17 2015)
-// http://www.wxformbuilder.org/
-//
-// PLEASE DO "NOT" EDIT THIS FILE!
-///////////////////////////////////////////////////////////////////////////
-
-#ifndef __POPUP_DLG_GENERATED_H__
-#define __POPUP_DLG_GENERATED_H__
-
-#include <wx/artprov.h>
-#include <wx/xrc/xmlres.h>
-#include <wx/intl.h>
-#include <wx/bitmap.h>
-#include <wx/image.h>
-#include <wx/icon.h>
-#include <wx/statbmp.h>
-#include <wx/gdicmn.h>
-#include <wx/font.h>
-#include <wx/colour.h>
-#include <wx/settings.h>
-#include <wx/string.h>
-#include <wx/stattext.h>
-#include <wx/textctrl.h>
-#include <wx/sizer.h>
-#include <wx/panel.h>
-#include <wx/statline.h>
-#include <wx/checkbox.h>
-#include <wx/button.h>
-#include <wx/dialog.h>
-
-#include "zen/i18n.h"
-
-///////////////////////////////////////////////////////////////////////////
-
-
-///////////////////////////////////////////////////////////////////////////////
-/// Class PopupDialogGenerated
-///////////////////////////////////////////////////////////////////////////////
-class PopupDialogGenerated : public wxDialog
-{
-private:
-
-protected:
- wxPanel* m_panel33;
- wxStaticBitmap* m_bitmapMsgType;
- wxStaticText* m_staticTextMain;
- wxTextCtrl* m_textCtrlTextDetail;
- wxStaticLine* m_staticline6;
- wxCheckBox* m_checkBoxCustom;
- wxBoxSizer* bSizerStdButtons;
- wxButton* m_buttonAffirmative;
- wxButton* m_buttonNegative;
- wxButton* m_buttonCancel;
-
- // Virtual event handlers, overide them in your derived class
- virtual void OnClose( wxCloseEvent& event ) { event.Skip(); }
- virtual void OnCheckBoxClick( wxCommandEvent& event ) { event.Skip(); }
- virtual void OnButtonAffirmative( wxCommandEvent& event ) { event.Skip(); }
- virtual void OnButtonNegative( wxCommandEvent& event ) { event.Skip(); }
- virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); }
-
-
-public:
-
- PopupDialogGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("dummy"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
- ~PopupDialogGenerated();
-
-};
-
-#endif //__POPUP_DLG_GENERATED_H__
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Jun 17 2015)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO "NOT" EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef __POPUP_DLG_GENERATED_H__
+#define __POPUP_DLG_GENERATED_H__
+
+#include <wx/artprov.h>
+#include <wx/xrc/xmlres.h>
+#include <wx/intl.h>
+#include <wx/bitmap.h>
+#include <wx/image.h>
+#include <wx/icon.h>
+#include <wx/statbmp.h>
+#include <wx/gdicmn.h>
+#include <wx/font.h>
+#include <wx/colour.h>
+#include <wx/settings.h>
+#include <wx/string.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/sizer.h>
+#include <wx/panel.h>
+#include <wx/statline.h>
+#include <wx/checkbox.h>
+#include <wx/button.h>
+#include <wx/dialog.h>
+
+#include "zen/i18n.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class PopupDialogGenerated
+///////////////////////////////////////////////////////////////////////////////
+class PopupDialogGenerated : public wxDialog
+{
+private:
+
+protected:
+ wxPanel* m_panel33;
+ wxStaticBitmap* m_bitmapMsgType;
+ wxStaticText* m_staticTextMain;
+ wxTextCtrl* m_textCtrlTextDetail;
+ wxStaticLine* m_staticline6;
+ wxCheckBox* m_checkBoxCustom;
+ wxBoxSizer* bSizerStdButtons;
+ wxButton* m_buttonAffirmative;
+ wxButton* m_buttonNegative;
+ wxButton* m_buttonCancel;
+
+ // Virtual event handlers, overide them in your derived class
+ virtual void OnClose( wxCloseEvent& event ) { event.Skip(); }
+ virtual void OnCheckBoxClick( wxCommandEvent& event ) { event.Skip(); }
+ virtual void OnButtonAffirmative( wxCommandEvent& event ) { event.Skip(); }
+ virtual void OnButtonNegative( wxCommandEvent& event ) { event.Skip(); }
+ virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); }
+
+
+public:
+
+ PopupDialogGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("dummy"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
+ ~PopupDialogGenerated();
+
+};
+
+#endif //__POPUP_DLG_GENERATED_H__
diff --git a/wx+/rtl.h b/wx+/rtl.h
index 206e8e6f..f516ca29 100644..100755
--- a/wx+/rtl.h
+++ b/wx+/rtl.h
@@ -1,95 +1,95 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef RTL_H_0183487180058718273432148
-#define RTL_H_0183487180058718273432148
-
-#include <zen/optional.h>
-#include <wx/dcmemory.h>
-#include <wx/image.h>
-#include <wx/app.h>
-
-namespace zen
-{
-//functions supporting right-to-left GUI layout
-void drawBitmapRtlMirror (wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment, Opt<wxBitmap>& buffer);
-void drawBitmapRtlNoMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment);
-//wxDC::DrawIcon DOES mirror by default -> implement RTL support when needed
-
-wxBitmap mirrorIfRtl(const wxBitmap& bmp);
-
-//manual text flow correction: http://www.w3.org/International/articles/inline-bidi-markup/
-
-
-
-
-
-
-
-
-//---------------------- implementation ------------------------
-namespace impl
-{
-//don't use wxDC::DrawLabel: it results in expensive GetTextExtent() call even when passing an empty string!!!
-//also avoid wxDC::DrawLabel 1-off alignment bugs
-inline
-void drawBitmapAligned(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment)
-{
- wxPoint pt = rect.GetTopLeft();
- if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0!
- pt.x += rect.width - image.GetWidth();
- else if (alignment & wxALIGN_CENTER_HORIZONTAL)
- pt.x += (rect.width - image.GetWidth()) / 2;
-
- if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0!
- pt.y += rect.height - image.GetHeight();
- else if (alignment & wxALIGN_CENTER_VERTICAL)
- pt.y += (rect.height - image.GetHeight()) / 2;
-
- dc.DrawBitmap(image, pt);
-}
-}
-
-
-inline
-void drawBitmapRtlMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment, Opt<wxBitmap>& buffer)
-{
- if (dc.GetLayoutDirection() == wxLayout_RightToLeft)
- {
- if (!buffer || buffer->GetWidth() != rect.width || buffer->GetHeight() < rect.height) //[!] since we do a mirror, width needs to match exactly!
- buffer = wxBitmap(rect.width, rect.height);
-
- wxMemoryDC memDc(*buffer);
- memDc.Blit(wxPoint(0, 0), rect.GetSize(), &dc, rect.GetTopLeft()); //blit in: background is mirrored due to memDc, dc having different layout direction!
-
- impl::drawBitmapAligned(memDc, image, wxRect(0, 0, rect.width, rect.height), alignment);
- //note: we cannot simply use memDc.SetLayoutDirection(wxLayout_RightToLeft) due to some strange 1 pixel bug!
-
- dc.Blit(rect.GetTopLeft(), rect.GetSize(), &memDc, wxPoint(0, 0)); //blit out: mirror once again
- }
- else
- impl::drawBitmapAligned(dc, image, rect, alignment);
-}
-
-
-inline
-void drawBitmapRtlNoMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment)
-{
- return impl::drawBitmapAligned(dc, image, rect, alignment); //wxDC::DrawBitmap does NOT mirror by default
-}
-
-
-inline
-wxBitmap mirrorIfRtl(const wxBitmap& bmp)
-{
- if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft)
- return bmp.ConvertToImage().Mirror();
- else
- return bmp;
-}
-}
-
-#endif //RTL_H_0183487180058718273432148
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef RTL_H_0183487180058718273432148
+#define RTL_H_0183487180058718273432148
+
+#include <zen/optional.h>
+#include <wx/dcmemory.h>
+#include <wx/image.h>
+#include <wx/app.h>
+
+namespace zen
+{
+//functions supporting right-to-left GUI layout
+void drawBitmapRtlMirror (wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment, Opt<wxBitmap>& buffer);
+void drawBitmapRtlNoMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment);
+//wxDC::DrawIcon DOES mirror by default -> implement RTL support when needed
+
+wxBitmap mirrorIfRtl(const wxBitmap& bmp);
+
+//manual text flow correction: http://www.w3.org/International/articles/inline-bidi-markup/
+
+
+
+
+
+
+
+
+//---------------------- implementation ------------------------
+namespace impl
+{
+//don't use wxDC::DrawLabel: it results in expensive GetTextExtent() call even when passing an empty string!!!
+//also avoid wxDC::DrawLabel 1-off alignment bugs
+inline
+void drawBitmapAligned(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment)
+{
+ wxPoint pt = rect.GetTopLeft();
+ if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0!
+ pt.x += rect.width - image.GetWidth();
+ else if (alignment & wxALIGN_CENTER_HORIZONTAL)
+ pt.x += (rect.width - image.GetWidth()) / 2;
+
+ if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0!
+ pt.y += rect.height - image.GetHeight();
+ else if (alignment & wxALIGN_CENTER_VERTICAL)
+ pt.y += (rect.height - image.GetHeight()) / 2;
+
+ dc.DrawBitmap(image, pt);
+}
+}
+
+
+inline
+void drawBitmapRtlMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment, Opt<wxBitmap>& buffer)
+{
+ if (dc.GetLayoutDirection() == wxLayout_RightToLeft)
+ {
+ if (!buffer || buffer->GetWidth() != rect.width || buffer->GetHeight() < rect.height) //[!] since we do a mirror, width needs to match exactly!
+ buffer = wxBitmap(rect.width, rect.height);
+
+ wxMemoryDC memDc(*buffer);
+ memDc.Blit(wxPoint(0, 0), rect.GetSize(), &dc, rect.GetTopLeft()); //blit in: background is mirrored due to memDc, dc having different layout direction!
+
+ impl::drawBitmapAligned(memDc, image, wxRect(0, 0, rect.width, rect.height), alignment);
+ //note: we cannot simply use memDc.SetLayoutDirection(wxLayout_RightToLeft) due to some strange 1 pixel bug!
+
+ dc.Blit(rect.GetTopLeft(), rect.GetSize(), &memDc, wxPoint(0, 0)); //blit out: mirror once again
+ }
+ else
+ impl::drawBitmapAligned(dc, image, rect, alignment);
+}
+
+
+inline
+void drawBitmapRtlNoMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment)
+{
+ return impl::drawBitmapAligned(dc, image, rect, alignment); //wxDC::DrawBitmap does NOT mirror by default
+}
+
+
+inline
+wxBitmap mirrorIfRtl(const wxBitmap& bmp)
+{
+ if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft)
+ return bmp.ConvertToImage().Mirror();
+ else
+ return bmp;
+}
+}
+
+#endif //RTL_H_0183487180058718273432148
diff --git a/wx+/std_button_layout.h b/wx+/std_button_layout.h
index 3e335882..ae9e9e1c 100644..100755
--- a/wx+/std_button_layout.h
+++ b/wx+/std_button_layout.h
@@ -1,146 +1,117 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef STD_BUTTON_LAYOUT_H_183470321478317214
-#define STD_BUTTON_LAYOUT_H_183470321478317214
-
-#include <algorithm>
-#include <wx/sizer.h>
-#include <wx/button.h>
-
-
-namespace zen
-{
-struct StdButtons
-{
- StdButtons& setAffirmative (wxButton* btn) { btnYes = btn; return *this; }
- StdButtons& setNegative (wxButton* btn) { btnNo = btn; return *this; }
- StdButtons& setCancel (wxButton* btn) { btnCancel = btn; return *this; }
-
- wxButton* btnYes = nullptr;
- wxButton* btnNo = nullptr;
- wxButton* btnCancel = nullptr;
-};
-
-void setStandardButtonLayout(wxBoxSizer& sizer, const StdButtons& buttons = StdButtons());
-//sizer width will change! => call wxWindow::Fit and wxWindow::Layout
-
-
-
-
-
-
-
-
-
-
-
-//--------------- impelementation -------------------------------------------
-inline
-void setStandardButtonLayout(wxBoxSizer& sizer, const StdButtons& buttons)
-{
- assert(sizer.GetOrientation() == wxHORIZONTAL);
-
- StdButtons buttonsTmp = buttons;
-
- auto detach = [&](wxButton*& btn)
- {
- if (btn)
- {
- assert(btn->GetContainingSizer() == &sizer);
- if (btn->IsShown())
- {
- bool rv = sizer.Detach(btn);
- assert(rv);
- if (!rv)
- btn = nullptr;
- }
- else
- btn = nullptr;
- }
- };
-
- detach(buttonsTmp.btnYes);
- detach(buttonsTmp.btnNo);
- detach(buttonsTmp.btnCancel);
-
-#if defined ZEN_WIN
- //Windows User Experience Interaction Guidelines: https://msdn.microsoft.com/en-us/library/windows/desktop/aa511453#sizing
- const int spaceH = 6; //OK
- const int spaceRimH = 10; //OK
- const int spaceRimV = 8; //compromise; consider additional top row from static line; exact values: top 8, bottom 9
-#elif defined ZEN_LINUX
- //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
-#elif defined ZEN_MAC
- //OS X Human Interface Guidelines: http://developer.apple.com/library/mac/#documentation/UserExperience/Conceptual/AppleHIGuidelines/Windows/Windows.html
- const int spaceH = 14; //OK
- const int spaceRimH = 24; //OK
- const int spaceRimV = 14; //OK
-#endif
-
- bool settingFirstButton = true;
- auto attach = [&](wxButton* btn)
- {
- if (btn)
- {
- assert(btn->GetMinSize().GetHeight() == -1); //let OS or this routine do the sizing! note: OS X does not allow changing the (visible!) button height!
-#if defined ZEN_WIN || defined ZEN_LINUX
- 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!
-#endif
-
- if (settingFirstButton)
- settingFirstButton = false;
- else
- sizer.Add(spaceH, 0);
- sizer.Add(btn, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER_VERTICAL, spaceRimV);
- }
- };
-
- //set border on left considering existing items
- if (sizer.GetChildren().GetCount() > 0) //for yet another retarded reason wxWidgets will have wxSizer::GetItem(0) cause an assert rather than just return nullptr as documented
- if (wxSizerItem* item = sizer.GetItem(static_cast<size_t>(0)))
- {
- assert(item->GetBorder() <= spaceRimV); //pragmatic check: other controls in the sizer should not have a larger border
- int flag = item->GetFlag();
- if (flag & wxLEFT)
- {
- flag &= ~wxLEFT;
- item->SetFlag(flag);
- }
- sizer.Insert(static_cast<size_t>(0), spaceRimH, 0);
- }
-
- sizer.Add(spaceRimH, 0);
-#if defined ZEN_WIN
- attach(buttonsTmp.btnYes);
- attach(buttonsTmp.btnNo);
- attach(buttonsTmp.btnCancel);
-
-#elif defined ZEN_LINUX
- attach(buttonsTmp.btnNo);
- attach(buttonsTmp.btnCancel);
- attach(buttonsTmp.btnYes);
-
-#elif defined ZEN_MAC
- if (buttonsTmp.btnNo)
- {
- attach(buttonsTmp.btnNo);
- sizer.Add(83 - spaceH, 0); //OS X Human Interface Guidelines: "position it at least 24 pixels away from the “safe” buttons" -> however 83 is used in practice!
- }
- attach(buttonsTmp.btnCancel);
- attach(buttonsTmp.btnYes);
-#endif
- sizer.Add(spaceRimH, 0);
-
- assert(buttonsTmp.btnCancel || buttonsTmp.btnYes); //OS X: there should be at least one button following the gap after the "dangerous" no-button
-}
-}
-
-#endif //STD_BUTTON_LAYOUT_H_183470321478317214
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef STD_BUTTON_LAYOUT_H_183470321478317214
+#define STD_BUTTON_LAYOUT_H_183470321478317214
+
+#include <algorithm>
+#include <wx/sizer.h>
+#include <wx/button.h>
+
+
+namespace zen
+{
+struct StdButtons
+{
+ StdButtons& setAffirmative (wxButton* btn) { btnYes = btn; return *this; }
+ StdButtons& setNegative (wxButton* btn) { btnNo = btn; return *this; }
+ StdButtons& setCancel (wxButton* btn) { btnCancel = btn; return *this; }
+
+ wxButton* btnYes = nullptr;
+ wxButton* btnNo = nullptr;
+ wxButton* btnCancel = nullptr;
+};
+
+void setStandardButtonLayout(wxBoxSizer& sizer, const StdButtons& buttons = StdButtons());
+//sizer width will change! => call wxWindow::Fit and wxWindow::Layout
+
+
+
+
+
+
+
+
+
+
+
+//--------------- impelementation -------------------------------------------
+inline
+void setStandardButtonLayout(wxBoxSizer& sizer, const StdButtons& buttons)
+{
+ assert(sizer.GetOrientation() == wxHORIZONTAL);
+
+ StdButtons buttonsTmp = buttons;
+
+ auto detach = [&](wxButton*& btn)
+ {
+ if (btn)
+ {
+ assert(btn->GetContainingSizer() == &sizer);
+ if (btn->IsShown())
+ {
+ bool rv = sizer.Detach(btn);
+ assert(rv);
+ if (!rv)
+ btn = nullptr;
+ }
+ else
+ btn = nullptr;
+ }
+ };
+
+ detach(buttonsTmp.btnYes);
+ detach(buttonsTmp.btnNo);
+ 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
+
+ bool settingFirstButton = true;
+ auto attach = [&](wxButton* btn)
+ {
+ if (btn)
+ {
+ 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!
+
+ if (settingFirstButton)
+ settingFirstButton = false;
+ else
+ sizer.Add(spaceH, 0);
+ sizer.Add(btn, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER_VERTICAL, spaceRimV);
+ }
+ };
+
+ //set border on left considering existing items
+ if (sizer.GetChildren().GetCount() > 0) //for yet another retarded reason wxWidgets will have wxSizer::GetItem(0) cause an assert rather than just return nullptr as documented
+ if (wxSizerItem* item = sizer.GetItem(static_cast<size_t>(0)))
+ {
+ assert(item->GetBorder() <= spaceRimV); //pragmatic check: other controls in the sizer should not have a larger border
+ int flag = item->GetFlag();
+ if (flag & wxLEFT)
+ {
+ flag &= ~wxLEFT;
+ item->SetFlag(flag);
+ }
+ sizer.Insert(static_cast<size_t>(0), spaceRimH, 0);
+ }
+
+ sizer.Add(spaceRimH, 0);
+ attach(buttonsTmp.btnNo);
+ attach(buttonsTmp.btnCancel);
+ attach(buttonsTmp.btnYes);
+
+ sizer.Add(spaceRimH, 0);
+
+ assert(buttonsTmp.btnCancel || buttonsTmp.btnYes); //OS X: there should be at least one button following the gap after the "dangerous" no-button
+}
+}
+
+#endif //STD_BUTTON_LAYOUT_H_183470321478317214
diff --git a/wx+/string_conv.h b/wx+/string_conv.h
index 7ec622a3..5db49ff2 100644..100755
--- a/wx+/string_conv.h
+++ b/wx+/string_conv.h
@@ -1,28 +1,28 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef STRING_CONV_H_893217450815743
-#define STRING_CONV_H_893217450815743
-
-#include <zen/utf.h>
-#include <wx/string.h>
-#include <zen/zstring.h>
-
-namespace zen
-{
-//conversion between Zstring and wxString
-inline wxString toWx(const Zstring& str) { return utfCvrtTo<wxString>(str); }
-inline Zstring toZ(const wxString& str) { return utfCvrtTo<Zstring>(str); }
-
-inline std::vector<Zstring> toZ(const std::vector<wxString>& strList)
-{
- std::vector<Zstring> tmp;
- std::transform(strList.begin(), strList.end(), std::back_inserter(tmp), [](const wxString& str) { return toZ(str); });
- return tmp;
-}
-}
-
-#endif //STRING_CONV_H_893217450815743
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef STRING_CONV_H_893217450815743
+#define STRING_CONV_H_893217450815743
+
+#include <zen/utf.h>
+#include <wx/string.h>
+#include <zen/zstring.h>
+
+namespace zen
+{
+//conversion between Zstring and wxString
+inline wxString toWx(const Zstring& str) { return utfCvrtTo<wxString>(str); }
+inline Zstring toZ(const wxString& str) { return utfCvrtTo<Zstring>(str); }
+
+inline std::vector<Zstring> toZ(const std::vector<wxString>& strList)
+{
+ std::vector<Zstring> tmp;
+ std::transform(strList.begin(), strList.end(), std::back_inserter(tmp), [](const wxString& str) { return toZ(str); });
+ return tmp;
+}
+}
+
+#endif //STRING_CONV_H_893217450815743
diff --git a/wx+/timespan.h b/wx+/timespan.h
index 5b9c2f0d..b165351f 100644..100755
--- a/wx+/timespan.h
+++ b/wx+/timespan.h
@@ -1,166 +1,166 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef TIMESPAN_H_254783756533456
-#define TIMESPAN_H_254783756533456
-
-#include <wx/textctrl.h>
-#include <wx/datetime.h>
-#include <wx/spinbutt.h>
-#include <wx/sizer.h>
-
-//user friendly time span control
-//- constructor is compatible with a wxTextControl
-//- emits change event: wxEVT_TIMESPAN_CHANGE
-
-namespace zen
-{
-inline
-wxEventType getEventType()
-{
- static wxEventType evt = wxNewEventType(); //external linkage!
- return evt;
-}
-const wxEventType wxEVT_TIMESPAN_CHANGE = getEventType();
-
-
-class TimeSpanCtrl : public wxPanel
-{
-public:
- TimeSpanCtrl(wxWindow* parent, wxWindowID id,
- const wxString& value = {},
- const wxPoint& pos = wxDefaultPosition,
- const wxSize& size = wxDefaultSize,
- long style = 0,
- const wxValidator& validator = wxDefaultValidator,
- const wxString& name = wxTextCtrlNameStr) :
- wxPanel(parent, id, pos, size, style, name),
- FORMAT_TIMESPAN(wxT("%H:%M:%S"))
- {
- wxBoxSizer* bSizer27 = new wxBoxSizer( wxHORIZONTAL );
-
- m_textCtrl = new wxTextCtrl(this, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, wxTE_CENTRE );
- bSizer27->Add(m_textCtrl, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND, 5 );
-
- m_spinBtn = new wxSpinButton(this, wxID_ANY, wxDefaultPosition, wxSize( 20, -1 ), wxSP_ARROW_KEYS );
- bSizer27->Add(m_spinBtn, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND, 5 );
-
- SetSizer(bSizer27);
- Layout();
-
- //connect events
- m_spinBtn ->Connect(wxEVT_SCROLL_LINEUP, wxEventHandler (TimeSpanCtrl::OnSpinUp), nullptr, this);
- m_spinBtn ->Connect(wxEVT_SCROLL_LINEDOWN, wxEventHandler (TimeSpanCtrl::OnSpinDown), nullptr, this);
- m_textCtrl->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (TimeSpanCtrl::OnKeyPress), nullptr, this);
- m_textCtrl->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(TimeSpanCtrl::OnMouseAction), nullptr, this);
-
- setValue(0);
- }
-
- void setValue(int span) //unit: [s]
- {
- wxString newValue;
- if (span < 0)
- {
- newValue += wxT("- ");
- span = -span;
- }
- newValue += wxTimeSpan::Seconds(span).Format(FORMAT_TIMESPAN);
-
- long pos = m_textCtrl->GetInsertionPoint();
- pos += newValue.size() - m_textCtrl->GetValue().size();
-
- m_textCtrl->ChangeValue(newValue);
- m_textCtrl->SetInsertionPoint(pos);
-
- wxCommandEvent chgEvent(wxEVT_TIMESPAN_CHANGE);
- wxPostEvent(this, chgEvent);
- }
-
- int getValue() const
- {
- wxString textVal = m_textCtrl->GetValue();
- textVal.Trim(false);
-
- bool isNegative = false;
- if (textVal.StartsWith(wxT("-")))
- {
- isNegative = true;
- textVal = textVal.substr(1);
- }
- textVal.Trim(false);
-
- wxDateTime tmp(time_t(0));
- if (tmp.ParseFormat(textVal, FORMAT_TIMESPAN, wxDateTime(tmp)) == nullptr)
- return 0;
-
- return (isNegative ? -1 : 1) *
- (tmp.GetHour () * 3600 +
- tmp.GetMinute() * 60 +
- tmp.GetSecond());
- }
-
-private:
- void OnSpinUp (wxEvent& event) { spinValue(true); }
- void OnSpinDown(wxEvent& event) { spinValue(false); }
-
- void OnKeyPress(wxKeyEvent& event)
- {
- const int keyCode = event.GetKeyCode();
- switch (keyCode)
- {
- case WXK_UP:
- case WXK_NUMPAD_UP:
- return spinValue(true);
- case WXK_DOWN:
- case WXK_NUMPAD_DOWN:
- return spinValue(false);
- default:
- event.Skip();
- }
- }
-
- void OnMouseAction(wxMouseEvent& event)
- {
- int delta = event.GetWheelRotation();
- if (delta > 0)
- spinValue(true);
- else if (delta < 0)
- spinValue(false);
- else
- event.Skip();
- }
-
- void spinValue(bool up)
- {
- wxString textval = m_textCtrl->GetValue();
- long pos = m_textCtrl->GetInsertionPoint();
-
- int stepSize = 1;
- if (pos <= static_cast<long>(textval.size()))
- {
- int delimCount = std::count(textval.begin() + pos, textval.end(), wxT(':'));
- if (delimCount == 1)
- stepSize = 60; //minute
- else if (delimCount == 2)
- stepSize = 3600; //hour
- }
-
- if (!up)
- stepSize *= -1;
-
- setValue(getValue() + stepSize);
- }
-
- wxTextCtrl* m_textCtrl;
- wxSpinButton* m_spinBtn;
-
- const wxString FORMAT_TIMESPAN;
-};
-}
-
-
-#endif //TIMESPAN_H_254783756533456
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef TIMESPAN_H_254783756533456
+#define TIMESPAN_H_254783756533456
+
+#include <wx/textctrl.h>
+#include <wx/datetime.h>
+#include <wx/spinbutt.h>
+#include <wx/sizer.h>
+
+//user friendly time span control
+//- constructor is compatible with a wxTextControl
+//- emits change event: wxEVT_TIMESPAN_CHANGE
+
+namespace zen
+{
+inline
+wxEventType getEventType()
+{
+ static wxEventType evt = wxNewEventType(); //external linkage!
+ return evt;
+}
+const wxEventType wxEVT_TIMESPAN_CHANGE = getEventType();
+
+
+class TimeSpanCtrl : public wxPanel
+{
+public:
+ TimeSpanCtrl(wxWindow* parent, wxWindowID id,
+ const wxString& value = {},
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = 0,
+ const wxValidator& validator = wxDefaultValidator,
+ const wxString& name = wxTextCtrlNameStr) :
+ wxPanel(parent, id, pos, size, style, name),
+ FORMAT_TIMESPAN(wxT("%H:%M:%S"))
+ {
+ wxBoxSizer* bSizer27 = new wxBoxSizer( wxHORIZONTAL );
+
+ m_textCtrl = new wxTextCtrl(this, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, wxTE_CENTRE );
+ bSizer27->Add(m_textCtrl, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND, 5 );
+
+ m_spinBtn = new wxSpinButton(this, wxID_ANY, wxDefaultPosition, wxSize( 20, -1 ), wxSP_ARROW_KEYS );
+ bSizer27->Add(m_spinBtn, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND, 5 );
+
+ SetSizer(bSizer27);
+ Layout();
+
+ //connect events
+ m_spinBtn ->Connect(wxEVT_SCROLL_LINEUP, wxEventHandler (TimeSpanCtrl::OnSpinUp), nullptr, this);
+ m_spinBtn ->Connect(wxEVT_SCROLL_LINEDOWN, wxEventHandler (TimeSpanCtrl::OnSpinDown), nullptr, this);
+ m_textCtrl->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (TimeSpanCtrl::OnKeyPress), nullptr, this);
+ m_textCtrl->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(TimeSpanCtrl::OnMouseAction), nullptr, this);
+
+ setValue(0);
+ }
+
+ void setValue(int span) //unit: [s]
+ {
+ wxString newValue;
+ if (span < 0)
+ {
+ newValue += wxT("- ");
+ span = -span;
+ }
+ newValue += wxTimeSpan::Seconds(span).Format(FORMAT_TIMESPAN);
+
+ long pos = m_textCtrl->GetInsertionPoint();
+ pos += newValue.size() - m_textCtrl->GetValue().size();
+
+ m_textCtrl->ChangeValue(newValue);
+ m_textCtrl->SetInsertionPoint(pos);
+
+ wxCommandEvent chgEvent(wxEVT_TIMESPAN_CHANGE);
+ wxPostEvent(this, chgEvent);
+ }
+
+ int getValue() const
+ {
+ wxString textVal = m_textCtrl->GetValue();
+ textVal.Trim(false);
+
+ bool isNegative = false;
+ if (textVal.StartsWith(wxT("-")))
+ {
+ isNegative = true;
+ textVal = textVal.substr(1);
+ }
+ textVal.Trim(false);
+
+ wxDateTime tmp(time_t(0));
+ if (tmp.ParseFormat(textVal, FORMAT_TIMESPAN, wxDateTime(tmp)) == nullptr)
+ return 0;
+
+ return (isNegative ? -1 : 1) *
+ (tmp.GetHour () * 3600 +
+ tmp.GetMinute() * 60 +
+ tmp.GetSecond());
+ }
+
+private:
+ void OnSpinUp (wxEvent& event) { spinValue(true); }
+ void OnSpinDown(wxEvent& event) { spinValue(false); }
+
+ void OnKeyPress(wxKeyEvent& event)
+ {
+ const int keyCode = event.GetKeyCode();
+ switch (keyCode)
+ {
+ case WXK_UP:
+ case WXK_NUMPAD_UP:
+ return spinValue(true);
+ case WXK_DOWN:
+ case WXK_NUMPAD_DOWN:
+ return spinValue(false);
+ default:
+ event.Skip();
+ }
+ }
+
+ void OnMouseAction(wxMouseEvent& event)
+ {
+ int delta = event.GetWheelRotation();
+ if (delta > 0)
+ spinValue(true);
+ else if (delta < 0)
+ spinValue(false);
+ else
+ event.Skip();
+ }
+
+ void spinValue(bool up)
+ {
+ wxString textval = m_textCtrl->GetValue();
+ long pos = m_textCtrl->GetInsertionPoint();
+
+ int stepSize = 1;
+ if (pos <= static_cast<long>(textval.size()))
+ {
+ int delimCount = std::count(textval.begin() + pos, textval.end(), wxT(':'));
+ if (delimCount == 1)
+ stepSize = 60; //minute
+ else if (delimCount == 2)
+ stepSize = 3600; //hour
+ }
+
+ if (!up)
+ stepSize *= -1;
+
+ setValue(getValue() + stepSize);
+ }
+
+ wxTextCtrl* m_textCtrl;
+ wxSpinButton* m_spinBtn;
+
+ const wxString FORMAT_TIMESPAN;
+};
+}
+
+
+#endif //TIMESPAN_H_254783756533456
diff --git a/wx+/toggle_button.h b/wx+/toggle_button.h
index 9c3cb3f1..5457d986 100644..100755
--- a/wx+/toggle_button.h
+++ b/wx+/toggle_button.h
@@ -1,67 +1,67 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef TOGGLE_BUTTON_H_8173024810574556
-#define TOGGLE_BUTTON_H_8173024810574556
-
-#include <wx/bmpbuttn.h>
-#include <wx+/bitmap_button.h>
-
-class ToggleButton : public wxBitmapButton
-{
-public:
- ToggleButton(wxWindow* parent,
- wxWindowID id,
- const wxBitmap& bitmap,
- const wxPoint& pos = wxDefaultPosition,
- const wxSize& size = wxDefaultSize,
- long style = 0,
- const wxValidator& validator = wxDefaultValidator,
- const wxString& name = wxButtonNameStr) : wxBitmapButton(parent, id, bitmap, pos, size, style, validator, name)
- {
- SetLayoutDirection(wxLayout_LeftToRight); //avoid mirroring RTL languages like Hebrew or Arabic
- }
-
- void init(const wxBitmap& activeBmp,
- const wxBitmap& inactiveBmp);
-
- void setActive(bool value);
- bool isActive() const { return active; }
- void toggle() { setActive(!active); }
-
-private:
- bool active = false;
-
- wxBitmap activeBmp_;
- wxBitmap inactiveBmp_;
-};
-
-
-
-
-
-
-
-//######################## implementation ########################
-inline
-void ToggleButton::init(const wxBitmap& activeBmp,
- const wxBitmap& inactiveBmp)
-{
- activeBmp_ = activeBmp;
- inactiveBmp_ = inactiveBmp;
-
- setActive(active);
-}
-
-
-inline
-void ToggleButton::setActive(bool value)
-{
- active = value;
- zen::setImage(*this, active ? activeBmp_ : inactiveBmp_);
-}
-
-#endif //TOGGLE_BUTTON_H_8173024810574556
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef TOGGLE_BUTTON_H_8173024810574556
+#define TOGGLE_BUTTON_H_8173024810574556
+
+#include <wx/bmpbuttn.h>
+#include <wx+/bitmap_button.h>
+
+class ToggleButton : public wxBitmapButton
+{
+public:
+ ToggleButton(wxWindow* parent,
+ wxWindowID id,
+ const wxBitmap& bitmap,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = 0,
+ const wxValidator& validator = wxDefaultValidator,
+ const wxString& name = wxButtonNameStr) : wxBitmapButton(parent, id, bitmap, pos, size, style, validator, name)
+ {
+ SetLayoutDirection(wxLayout_LeftToRight); //avoid mirroring RTL languages like Hebrew or Arabic
+ }
+
+ void init(const wxBitmap& activeBmp,
+ const wxBitmap& inactiveBmp);
+
+ void setActive(bool value);
+ bool isActive() const { return active; }
+ void toggle() { setActive(!active); }
+
+private:
+ bool active = false;
+
+ wxBitmap activeBmp_;
+ wxBitmap inactiveBmp_;
+};
+
+
+
+
+
+
+
+//######################## implementation ########################
+inline
+void ToggleButton::init(const wxBitmap& activeBmp,
+ const wxBitmap& inactiveBmp)
+{
+ activeBmp_ = activeBmp;
+ inactiveBmp_ = inactiveBmp;
+
+ setActive(active);
+}
+
+
+inline
+void ToggleButton::setActive(bool value)
+{
+ active = value;
+ zen::setImage(*this, active ? activeBmp_ : inactiveBmp_);
+}
+
+#endif //TOGGLE_BUTTON_H_8173024810574556
diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp
index 8dc79d73..b2946187 100644..100755
--- a/wx+/tooltip.cpp
+++ b/wx+/tooltip.cpp
@@ -1,104 +1,97 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "tooltip.h"
-#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 <wx+/image_tools.h>
-
-using namespace zen;
-
-
-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)
- {
- //Suse Linux/X11: needs parent window, else there are z-order issues
-
- this->SetSizeHints(wxDefaultSize, wxDefaultSize);
- this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); //both required: on Ubuntu background is black, foreground white!
- this->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOTEXT)); //
-
- wxBoxSizer* bSizer158 = new wxBoxSizer(wxHORIZONTAL);
- bitmapLeft_ = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0);
- bSizer158->Add(bitmapLeft_, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
-
- staticTextMain_ = new wxStaticText(this, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, 0);
- bSizer158->Add(staticTextMain_, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
-
- this->SetSizer(bSizer158);
- this->Layout();
- bSizer158->Fit(this);
-
-#ifdef ZEN_WIN //prevent window from stealing focus!
- Disable(); //= dark/grey text and image on Linux; no visible difference on OS X
-#endif
- }
-
- wxStaticText* staticTextMain_;
- wxStaticBitmap* bitmapLeft_;
-};
-
-
-void Tooltip::show(const wxString& text, wxPoint mousePos, const wxBitmap* bmp)
-{
- if (!tipWindow_)
- tipWindow_ = new TooltipDlgGenerated(&parent_); //ownership passed to parent
-
- const wxBitmap& newBmp = bmp ? *bmp : wxNullBitmap;
-
- if (!isEqual(tipWindow_->bitmapLeft_->GetBitmap(), newBmp))
- {
- tipWindow_->bitmapLeft_->SetBitmap(newBmp);
- tipWindow_->Refresh(); //needed if bitmap size changed!
- }
-
- if (text != tipWindow_->staticTextMain_->GetLabel())
- {
- tipWindow_->staticTextMain_->SetLabel(text);
- tipWindow_->staticTextMain_->Wrap(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);
-
- if (newPos != tipWindow_->GetScreenPosition())
- tipWindow_->SetSize(newPos.x, newPos.y, wxDefaultCoord, wxDefaultCoord);
- //attention!!! possible endless loop: mouse pointer must NOT be within tipWindow!
- //else it will trigger a wxEVT_LEAVE_WINDOW on middle grid which will hide the window, causing the window to be shown again via this method, etc.
-
- if (!tipWindow_->IsShown())
- tipWindow_->Show();
-}
-
-
-void Tooltip::hide()
-{
- if (tipWindow_)
- {
-#ifdef ZEN_LINUX
- //on wxGTK the tooltip is sometimes not shown again after it was hidden: e.g. drag-selection on middle grid
- tipWindow_->Destroy(); //apply brute force:
- tipWindow_ = nullptr; //
-#else
- tipWindow_->Hide();
-#endif
- }
-}
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "tooltip.h"
+#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 <wx+/image_tools.h>
+
+using namespace zen;
+
+
+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)
+ {
+ //Suse Linux/X11: needs parent window, else there are z-order issues
+
+ this->SetSizeHints(wxDefaultSize, wxDefaultSize);
+ this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); //both required: on Ubuntu background is black, foreground white!
+ this->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOTEXT)); //
+
+ wxBoxSizer* bSizer158 = new wxBoxSizer(wxHORIZONTAL);
+ bitmapLeft_ = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0);
+ bSizer158->Add(bitmapLeft_, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
+
+ staticTextMain_ = new wxStaticText(this, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, 0);
+ bSizer158->Add(staticTextMain_, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
+
+ this->SetSizer(bSizer158);
+ this->Layout();
+ bSizer158->Fit(this);
+
+ }
+
+ wxStaticText* staticTextMain_;
+ wxStaticBitmap* bitmapLeft_;
+};
+
+
+void Tooltip::show(const wxString& text, wxPoint mousePos, const wxBitmap* bmp)
+{
+ if (!tipWindow_)
+ tipWindow_ = new TooltipDlgGenerated(&parent_); //ownership passed to parent
+
+ const wxBitmap& newBmp = bmp ? *bmp : wxNullBitmap;
+
+ if (!isEqual(tipWindow_->bitmapLeft_->GetBitmap(), newBmp))
+ {
+ tipWindow_->bitmapLeft_->SetBitmap(newBmp);
+ tipWindow_->Refresh(); //needed if bitmap size changed!
+ }
+
+ if (text != tipWindow_->staticTextMain_->GetLabel())
+ {
+ tipWindow_->staticTextMain_->SetLabel(text);
+ tipWindow_->staticTextMain_->Wrap(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);
+
+ if (newPos != tipWindow_->GetScreenPosition())
+ tipWindow_->SetSize(newPos.x, newPos.y, wxDefaultCoord, wxDefaultCoord);
+ //attention!!! possible endless loop: mouse pointer must NOT be within tipWindow!
+ //else it will trigger a wxEVT_LEAVE_WINDOW on middle grid which will hide the window, causing the window to be shown again via this method, etc.
+
+ if (!tipWindow_->IsShown())
+ tipWindow_->Show();
+}
+
+
+void Tooltip::hide()
+{
+ if (tipWindow_)
+ {
+ //on wxGTK the tooltip is sometimes not shown again after it was hidden: e.g. drag-selection on middle grid
+ tipWindow_->Destroy(); //apply brute force:
+ tipWindow_ = nullptr; //
+ }
+}
diff --git a/wx+/tooltip.h b/wx+/tooltip.h
index f2a7043e..1892ba23 100644..100755
--- a/wx+/tooltip.h
+++ b/wx+/tooltip.h
@@ -1,32 +1,32 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef TOOLTIP_H_8912740832170515
-#define TOOLTIP_H_8912740832170515
-
-#include <wx/window.h>
-
-
-namespace zen
-{
-class Tooltip
-{
-public:
- Tooltip(wxWindow& parent) : parent_(parent) {} //parent needs to live at least as long as this instance!
-
- void show(const wxString& text,
- wxPoint mousePos, //absolute screen coordinates
- const wxBitmap* bmp = nullptr);
- void hide();
-
-private:
- class TooltipDlgGenerated;
- TooltipDlgGenerated* tipWindow_ = nullptr;
- wxWindow& parent_;
-};
-}
-
-#endif //TOOLTIP_H_8912740832170515
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef TOOLTIP_H_8912740832170515
+#define TOOLTIP_H_8912740832170515
+
+#include <wx/window.h>
+
+
+namespace zen
+{
+class Tooltip
+{
+public:
+ Tooltip(wxWindow& parent) : parent_(parent) {} //parent needs to live at least as long as this instance!
+
+ void show(const wxString& text,
+ wxPoint mousePos, //absolute screen coordinates
+ const wxBitmap* bmp = nullptr);
+ void hide();
+
+private:
+ class TooltipDlgGenerated;
+ TooltipDlgGenerated* tipWindow_ = nullptr;
+ wxWindow& parent_;
+};
+}
+
+#endif //TOOLTIP_H_8912740832170515
diff --git a/wx+/zlib_wrap.cpp b/wx+/zlib_wrap.cpp
index 9858a3e9..8b2e2976 100644..100755
--- a/wx+/zlib_wrap.cpp
+++ b/wx+/zlib_wrap.cpp
@@ -1,54 +1,50 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "zlib_wrap.h"
-#ifdef ZEN_WIN
- #include <wx/../../src/zlib/zlib.h> //not really a "nice" place to look for a stable solution
-#elif defined ZEN_LINUX || defined ZEN_MAC
- #include <zlib.h> //let's pray this is the same version wxWidgets is linking against!
-#endif
-
-using namespace zen;
-
-
-size_t zen::impl::zlib_compressBound(size_t len)
-{
- return ::compressBound(static_cast<uLong>(len)); //upper limit for buffer size, larger than input size!!!
-}
-
-
-size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_t trgLen, int level) //throw ZlibInternalError
-{
- uLongf bufferSize = static_cast<uLong>(trgLen);
- const int rv = ::compress2(static_cast<Bytef*>(trg), //Bytef* dest,
- &bufferSize, //uLongf* destLen,
- static_cast<const Bytef*>(src), //const Bytef* source,
- static_cast<uLong>(srcLen), //uLong sourceLen,
- level); //int level
- // Z_OK: success
- // Z_MEM_ERROR: not enough memory
- // Z_BUF_ERROR: not enough room in the output buffer
- if (rv != Z_OK || bufferSize > trgLen)
- throw ZlibInternalError();
- return bufferSize;
-}
-
-
-size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen) //throw ZlibInternalError
-{
- uLongf bufferSize = static_cast<uLong>(trgLen);
- const int rv = ::uncompress(static_cast<Bytef*>(trg), //Bytef* dest,
- &bufferSize, //uLongf* destLen,
- static_cast<const Bytef*>(src), //const Bytef* source,
- static_cast<uLong>(srcLen)); //uLong sourceLen
- // Z_OK: success
- // Z_MEM_ERROR: not enough memory
- // Z_BUF_ERROR: not enough room in the output buffer
- // Z_DATA_ERROR: input data was corrupted or incomplete
- if (rv != Z_OK || bufferSize > trgLen)
- throw ZlibInternalError();
- return bufferSize;
-}
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "zlib_wrap.h"
+ #include <zlib.h> //let's pray this is the same version wxWidgets is linking against!
+
+using namespace zen;
+
+
+size_t zen::impl::zlib_compressBound(size_t len)
+{
+ return ::compressBound(static_cast<uLong>(len)); //upper limit for buffer size, larger than input size!!!
+}
+
+
+size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_t trgLen, int level) //throw ZlibInternalError
+{
+ uLongf bufferSize = static_cast<uLong>(trgLen);
+ const int rv = ::compress2(static_cast<Bytef*>(trg), //Bytef* dest,
+ &bufferSize, //uLongf* destLen,
+ static_cast<const Bytef*>(src), //const Bytef* source,
+ static_cast<uLong>(srcLen), //uLong sourceLen,
+ level); //int level
+ // Z_OK: success
+ // Z_MEM_ERROR: not enough memory
+ // Z_BUF_ERROR: not enough room in the output buffer
+ if (rv != Z_OK || bufferSize > trgLen)
+ throw ZlibInternalError();
+ return bufferSize;
+}
+
+
+size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen) //throw ZlibInternalError
+{
+ uLongf bufferSize = static_cast<uLong>(trgLen);
+ const int rv = ::uncompress(static_cast<Bytef*>(trg), //Bytef* dest,
+ &bufferSize, //uLongf* destLen,
+ static_cast<const Bytef*>(src), //const Bytef* source,
+ static_cast<uLong>(srcLen)); //uLong sourceLen
+ // Z_OK: success
+ // Z_MEM_ERROR: not enough memory
+ // Z_BUF_ERROR: not enough room in the output buffer
+ // Z_DATA_ERROR: input data was corrupted or incomplete
+ if (rv != Z_OK || bufferSize > trgLen)
+ throw ZlibInternalError();
+ return bufferSize;
+}
diff --git a/wx+/zlib_wrap.h b/wx+/zlib_wrap.h
index 0e596f85..11d8227b 100644..100755
--- a/wx+/zlib_wrap.h
+++ b/wx+/zlib_wrap.h
@@ -1,114 +1,114 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef ZLIB_WRAP_H_428597064566
-#define ZLIB_WRAP_H_428597064566
-
-#include <zen/serialize.h>
-
-
-namespace zen
-{
-class ZlibInternalError {};
-
-// compression level must be between 0 and 9:
-// 0: no compression
-// 9: best compression
-template <class BinContainer> //as specified in serialize.h
-BinContainer compress(const BinContainer& stream, int level); //throw ZlibInternalError
-//caveat: output stream is physically larger than input! => strip additional reserved space if needed: "BinContainer(output.begin(), output.end())"
-
-template <class BinContainer>
-BinContainer decompress(const BinContainer& stream); //throw ZlibInternalError
-
-
-
-
-
-
-
-
-
-
-
-//######################## implementation ##########################
-namespace impl
-{
-size_t zlib_compressBound(size_t len);
-size_t zlib_compress (const void* src, size_t srcLen, void* trg, size_t trgLen, int level); //throw ZlibInternalError
-size_t zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen); //throw ZlibInternalError
-}
-
-
-template <class BinContainer>
-BinContainer compress(const BinContainer& stream, int level) //throw ZlibInternalError
-{
- BinContainer contOut;
- if (!stream.empty()) //don't dereference iterator into empty container!
- {
- //save uncompressed stream size for decompression
- const std::uint64_t uncompressedSize = stream.size(); //use portable number type!
- contOut.resize(sizeof(uncompressedSize));
- std::copy(reinterpret_cast<const char*>(&uncompressedSize),
- reinterpret_cast<const char*>(&uncompressedSize) + sizeof(uncompressedSize),
- &*contOut.begin());
-
- const size_t bufferEstimate = impl::zlib_compressBound(stream.size()); //upper limit for buffer size, larger than input size!!!
-
- contOut.resize(contOut.size() + bufferEstimate);
-
- const size_t bytesWritten = impl::zlib_compress(&*stream.begin(),
- stream.size(),
- &*contOut.begin() + contOut.size() - bufferEstimate,
- bufferEstimate,
- level); //throw ZlibInternalError
- if (bytesWritten < bufferEstimate)
- contOut.resize(contOut.size() - (bufferEstimate - bytesWritten)); //caveat: unsigned arithmetics
- //caveat: physical memory consumption still *unchanged*!
- }
- return contOut;
-}
-
-
-template <class BinContainer>
-BinContainer decompress(const BinContainer& stream) //throw ZlibInternalError
-{
- BinContainer contOut;
- if (!stream.empty()) //don't dereference iterator into empty container!
- {
- //retrieve size of uncompressed data
- std::uint64_t uncompressedSize = 0; //use portable number type!
- if (stream.size() < sizeof(uncompressedSize))
- throw ZlibInternalError();
- std::copy(&*stream.begin(),
- &*stream.begin() + sizeof(uncompressedSize),
- reinterpret_cast<char*>(&uncompressedSize));
- //attention: contOut MUST NOT be empty! Else it will pass a nullptr to zlib_decompress() => Z_STREAM_ERROR although "uncompressedSize == 0"!!!
- //secondary bug: don't dereference iterator into empty container!
- if (uncompressedSize == 0) //cannot be 0: compress() directly maps empty -> empty container skipping zlib!
- throw ZlibInternalError();
-
- try
- {
- contOut.resize(static_cast<size_t>(uncompressedSize)); //throw std::bad_alloc
- }
- catch (std::bad_alloc&) //most likely due to data corruption!
- {
- throw ZlibInternalError();
- }
-
- const size_t bytesWritten = impl::zlib_decompress(&*stream.begin() + sizeof(uncompressedSize),
- stream.size() - sizeof(uncompressedSize),
- &*contOut.begin(),
- static_cast<size_t>(uncompressedSize)); //throw ZlibInternalError
- if (bytesWritten != static_cast<size_t>(uncompressedSize))
- throw ZlibInternalError();
- }
- return contOut;
-}
-}
-
-#endif //ZLIB_WRAP_H_428597064566
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef ZLIB_WRAP_H_428597064566
+#define ZLIB_WRAP_H_428597064566
+
+#include <zen/serialize.h>
+
+
+namespace zen
+{
+class ZlibInternalError {};
+
+// compression level must be between 0 and 9:
+// 0: no compression
+// 9: best compression
+template <class BinContainer> //as specified in serialize.h
+BinContainer compress(const BinContainer& stream, int level); //throw ZlibInternalError
+//caveat: output stream is physically larger than input! => strip additional reserved space if needed: "BinContainer(output.begin(), output.end())"
+
+template <class BinContainer>
+BinContainer decompress(const BinContainer& stream); //throw ZlibInternalError
+
+
+
+
+
+
+
+
+
+
+
+//######################## implementation ##########################
+namespace impl
+{
+size_t zlib_compressBound(size_t len);
+size_t zlib_compress (const void* src, size_t srcLen, void* trg, size_t trgLen, int level); //throw ZlibInternalError
+size_t zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen); //throw ZlibInternalError
+}
+
+
+template <class BinContainer>
+BinContainer compress(const BinContainer& stream, int level) //throw ZlibInternalError
+{
+ BinContainer contOut;
+ if (!stream.empty()) //don't dereference iterator into empty container!
+ {
+ //save uncompressed stream size for decompression
+ const uint64_t uncompressedSize = stream.size(); //use portable number type!
+ contOut.resize(sizeof(uncompressedSize));
+ std::copy(reinterpret_cast<const char*>(&uncompressedSize),
+ reinterpret_cast<const char*>(&uncompressedSize) + sizeof(uncompressedSize),
+ &*contOut.begin());
+
+ const size_t bufferEstimate = impl::zlib_compressBound(stream.size()); //upper limit for buffer size, larger than input size!!!
+
+ contOut.resize(contOut.size() + bufferEstimate);
+
+ const size_t bytesWritten = impl::zlib_compress(&*stream.begin(),
+ stream.size(),
+ &*contOut.begin() + contOut.size() - bufferEstimate,
+ bufferEstimate,
+ level); //throw ZlibInternalError
+ if (bytesWritten < bufferEstimate)
+ contOut.resize(contOut.size() - (bufferEstimate - bytesWritten)); //caveat: unsigned arithmetics
+ //caveat: physical memory consumption still *unchanged*!
+ }
+ return contOut;
+}
+
+
+template <class BinContainer>
+BinContainer decompress(const BinContainer& stream) //throw ZlibInternalError
+{
+ BinContainer contOut;
+ if (!stream.empty()) //don't dereference iterator into empty container!
+ {
+ //retrieve size of uncompressed data
+ uint64_t uncompressedSize = 0; //use portable number type!
+ if (stream.size() < sizeof(uncompressedSize))
+ throw ZlibInternalError();
+ std::copy(&*stream.begin(),
+ &*stream.begin() + sizeof(uncompressedSize),
+ reinterpret_cast<char*>(&uncompressedSize));
+ //attention: contOut MUST NOT be empty! Else it will pass a nullptr to zlib_decompress() => Z_STREAM_ERROR although "uncompressedSize == 0"!!!
+ //secondary bug: don't dereference iterator into empty container!
+ if (uncompressedSize == 0) //cannot be 0: compress() directly maps empty -> empty container skipping zlib!
+ throw ZlibInternalError();
+
+ try
+ {
+ contOut.resize(static_cast<size_t>(uncompressedSize)); //throw std::bad_alloc
+ }
+ catch (std::bad_alloc&) //most likely due to data corruption!
+ {
+ throw ZlibInternalError();
+ }
+
+ const size_t bytesWritten = impl::zlib_decompress(&*stream.begin() + sizeof(uncompressedSize),
+ stream.size() - sizeof(uncompressedSize),
+ &*contOut.begin(),
+ static_cast<size_t>(uncompressedSize)); //throw ZlibInternalError
+ if (bytesWritten != static_cast<size_t>(uncompressedSize))
+ throw ZlibInternalError();
+ }
+ return contOut;
+}
+}
+
+#endif //ZLIB_WRAP_H_428597064566
bgstack15