From 35c16935f1e9b2ef9e36fb7b1430c96379c01dd8 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 20 Apr 2019 21:47:42 +0200 Subject: [PATCH] Send wxEVT_COLOUR_CHANGED from wxColourDialog under MSW Add support for a new event sent by wxColourDialog, currently only under MSW, when the colour currently selected in it changes. Based on work by Trylz, see https://github.com/wxWidgets/wxWidgets/pull/1219 --- include/wx/colordlg.h | 46 +++++++++++++++++ include/wx/msw/colordlg.h | 6 +++ interface/wx/colordlg.h | 60 +++++++++++++++++++++- samples/dialogs/dialogs.cpp | 21 ++++++-- samples/dialogs/dialogs.h | 5 ++ src/common/colourdata.cpp | 4 ++ src/msw/colordlg.cpp | 100 ++++++++++++++++++++++++++++++++---- 7 files changed, 228 insertions(+), 14 deletions(-) diff --git a/include/wx/colordlg.h b/include/wx/colordlg.h index 7eb7cc9a42..7fb04dd097 100644 --- a/include/wx/colordlg.h +++ b/include/wx/colordlg.h @@ -31,6 +31,52 @@ #define wxColourDialog wxGenericColourDialog #endif +// Under some platforms (currently only wxMSW) wxColourDialog can send events +// of this type while it is shown. +// +// Notice that this class is almost identical to wxColourPickerEvent but it +// doesn't really sense to reuse the same class for both controls. +class WXDLLIMPEXP_CORE wxColourDialogEvent : public wxCommandEvent +{ +public: + wxColourDialogEvent() + { + } + + wxColourDialogEvent(wxEventType evtType, + wxColourDialog* dialog, + const wxColour& colour) + : wxCommandEvent(evtType, dialog->GetId()), + m_colour(colour) + { + SetEventObject(dialog); + } + + // default copy ctor and dtor are ok + + wxColour GetColour() const { return m_colour; } + void SetColour(const wxColour& colour) { m_colour = colour; } + + virtual wxEvent *Clone() const wxOVERRIDE + { + return new wxColourDialogEvent(*this); + } + +private: + wxColour m_colour; + + wxDECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxColourDialogEvent); +}; + +wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_COLOUR_CHANGED, wxColourDialogEvent); + +#define wxColourDialogEventHandler(func) \ + wxEVENT_HANDLER_CAST(wxColourDialogEventFunction, func) + +#define EVT_COLOUR_CHANGED(id, fn) \ + wx__DECLARE_EVT1(wxEVT_COLOUR_CHANGED, id, wxColourDialogEventHandler(fn)) + + // get the colour from user and return it WXDLLIMPEXP_CORE wxColour wxGetColourFromUser(wxWindow *parent = NULL, const wxColour& colInit = wxNullColour, diff --git a/include/wx/msw/colordlg.h b/include/wx/msw/colordlg.h index f49350b544..9de5883b30 100644 --- a/include/wx/msw/colordlg.h +++ b/include/wx/msw/colordlg.h @@ -44,6 +44,9 @@ public: // called from the hook procedure on WM_INITDIALOG reception virtual void MSWOnInitDone(WXHWND hDlg); + // called from the hook procedure + void MSWCheckIfCurrentChanged(WXCOLORREF currentCol); + protected: // common part of all ctors void Init(); @@ -57,6 +60,9 @@ protected: wxColourData m_colourData; wxString m_title; + // Currently selected colour, used while the dialog is being shown. + WXCOLORREF m_currentCol; + // indicates that the dialog should be centered in this direction if non 0 // (set by DoCentre(), used by MSWOnInitDone()) int m_centreDir; diff --git a/interface/wx/colordlg.h b/interface/wx/colordlg.h index 7d1faa87e3..ece8d9127b 100644 --- a/interface/wx/colordlg.h +++ b/interface/wx/colordlg.h @@ -10,11 +10,46 @@ This class represents the colour chooser dialog. + Starting from wxWidgets 3.1.3 and currently in the MSW port only, this + dialog generates wxEVT_COLOUR_CHANGED events while it is being shown, i.e. + from inside its ShowModal() method, that notify the program about the + change of the currently selected colour and allow it to e.g. preview the + effect of selecting this colour. Note that if you react to this event, you + should also correctly revert to the previously selected colour if the + dialog is cancelled by the user. + + Example of using this class with dynamic feedback for the selected colour: + @code + // Some function for redrawing using the given colour. Ideally, it + // shouldn't do anything if the colour is the same as the one used + // before. + void Redraw(const wxColour& colour); + + wxColourData data; + data.SetColour(initialColourToUse); + wxColourData dlg(this, &data); + dlg.Bind(wxEVT_COLOUR_CHANGED, [](wxColourDialogEvent& event) { + Redraw(event.GetColour()); + }); + if ( dlg.ShowModal() == wxID_OK ) { + // Colour did change. + } else { + // Colour didn't change. + } + + // This call is unnecessary under platforms generating + // wxEVT_COLOUR_CHANGED if the dialog was accepted and unnecessary + // under the platforms not generating this event if it was cancelled, + // so we could check for the different cases explicitly to avoid it, + // but it's simpler to just always call it. + Redraw(data.GetColour()); + @endcode + @library{wxcore} @category{cmndlg} @see @ref overview_cmndlg_colour, wxColour, wxColourData, - wxGetColourFromUser() + wxColourDialogEvent, wxGetColourFromUser() */ class wxColourDialog : public wxDialog { @@ -55,7 +90,30 @@ public: virtual int ShowModal(); }; +/** + This event class is used for the events generated by wxColourDialog. + @beginEventTable{wxColourPickerEvent} + @event{EVT_COLOUR_CHANGED(id, func)} + Generated whenever the currently selected colour in the dialog + changes. This event is currently only implemented in wxMSW. + @endEventTable + + @library{wxcore} + @category{events} + + @see wxColourDialog + + @since 3.1.3 + */ +class wxColourDialogEvent : public wxCommandEvent +{ +public: + /** + Retrieve the colour the user has just selected. + */ + wxColour GetColour() const; +}; // ============================================================================ // Global functions/macros diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index 0728f34dd3..fa538d98ab 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -734,20 +734,35 @@ MyFrame::~MyFrame() #if wxUSE_COLOURDLG +void MyFrame::DoApplyColour(const wxColour& colour) +{ + if ( colour == m_canvas->GetBackgroundColour() ) + return; + + m_canvas->SetBackgroundColour(colour); + m_canvas->ClearBackground(); + m_canvas->Refresh(); +} + +void MyFrame::OnColourChanged(wxColourDialogEvent& event) +{ + DoApplyColour(event.GetColour()); +} + void MyFrame::ChooseColour(wxCommandEvent& event) { m_clrData.SetColour(m_canvas->GetBackgroundColour()); m_clrData.SetChooseAlpha(event.GetId() == DIALOGS_CHOOSE_COLOUR_ALPHA); wxColourDialog dialog(this, &m_clrData); + dialog.Bind(wxEVT_COLOUR_CHANGED, &MyFrame::OnColourChanged, this); dialog.SetTitle("Please choose the background colour"); if ( dialog.ShowModal() == wxID_OK ) { m_clrData = dialog.GetColourData(); - m_canvas->SetBackgroundColour(m_clrData.GetColour()); - m_canvas->ClearBackground(); - m_canvas->Refresh(); } + + DoApplyColour(m_clrData.GetColour()); } void MyFrame::GetColour(wxCommandEvent& WXUNUSED(event)) diff --git a/samples/dialogs/dialogs.h b/samples/dialogs/dialogs.h index 9d37f4b69b..89e96557e4 100644 --- a/samples/dialogs/dialogs.h +++ b/samples/dialogs/dialogs.h @@ -506,6 +506,11 @@ public: void OnExit(wxCommandEvent& event); private: +#if wxUSE_COLOURDLG + void OnColourChanged(wxColourDialogEvent& event); + void DoApplyColour(const wxColour& colour); +#endif // wxUSE_COLOURDLG + #if wxUSE_DIRDLG void DoDirChoose(int style); #endif // wxUSE_DIRDLG diff --git a/src/common/colourdata.cpp b/src/common/colourdata.cpp index 2c2dddabf1..7d4efdb160 100644 --- a/src/common/colourdata.cpp +++ b/src/common/colourdata.cpp @@ -129,6 +129,10 @@ bool wxColourData::FromString(const wxString& str) #include "wx/colordlg.h" +wxIMPLEMENT_DYNAMIC_CLASS(wxColourDialogEvent, wxCommandEvent); + +wxDEFINE_EVENT(wxEVT_COLOUR_CHANGED, wxColourDialogEvent); + wxColour wxGetColourFromUser(wxWindow *parent, const wxColour& colInit, const wxString& caption, diff --git a/src/msw/colordlg.cpp b/src/msw/colordlg.cpp index fb9c89d16c..1a8910b207 100644 --- a/src/msw/colordlg.cpp +++ b/src/msw/colordlg.cpp @@ -37,6 +37,8 @@ #include "wx/math.h" #endif +#include "wx/scopeguard.h" + #include "wx/msw/private.h" #include @@ -51,6 +53,9 @@ // and "Define Custom Colors" extension not shown static wxRect gs_rectDialog(0, 0, 222, 324); +// The dialog currently being shown or null. +static wxColourDialog* gs_activeDialog = NULL; + // ---------------------------------------------------------------------------- // wxWin macros // ---------------------------------------------------------------------------- @@ -61,6 +66,53 @@ wxIMPLEMENT_DYNAMIC_CLASS(wxColourDialog, wxDialog); // implementation // ============================================================================ +#ifndef COLORBOXES + #define COLORBOXES 64 +#endif + +// Undocumented property storing the COLORINFO struct in the standard dialog. +#ifndef COLORPROP + #define COLORPROP (LPCTSTR) 0xA000L +#endif + +namespace +{ + +// The private and undocumented Windows structure used by the standard dialog. +// See https://social.msdn.microsoft.com/Forums/en-US/c5fcfd9f-6b27-4848-bb9d-94bec105eabd/get-the-current-clicked-color-from-choosecolor-dialog?forum=windowsgeneraldevelopmentissues +struct COLORINFO +{ + UINT ApiType; + LPCHOOSECOLOR pCC; + HANDLE hLocal; + HANDLE hDialog; + HPALETTE hPal; + DWORD currentRGB; + WORD currentHue; + WORD currentSat; + WORD currentLum; + WORD nHueWidth; + WORD nSatHeight; + WORD nLumHeight; + WORD nCurMix; + WORD nCurDsp; + WORD nCurBox; + WORD nHuePos; + WORD nSatPos; + WORD nLumPos; + RECT rOriginal; + RECT rRainbow; + RECT rLumScroll; + RECT rLumPaint; + RECT rCurrentColor; + RECT rNearestPure; + RECT rColorSamples; + BOOL bFoldOut; + DWORD rgbBoxColor[COLORBOXES]; +}; + +} // anonymous namespace + // ---------------------------------------------------------------------------- // colour dialog hook proc // ---------------------------------------------------------------------------- @@ -69,19 +121,30 @@ UINT_PTR CALLBACK wxColourDialogHookProc(HWND hwnd, UINT uiMsg, WPARAM WXUNUSED(wParam), - LPARAM lParam) + LPARAM WXUNUSED(lParam)) { - if ( uiMsg == WM_INITDIALOG ) + switch ( uiMsg ) { - CHOOSECOLOR *pCC = (CHOOSECOLOR *)lParam; - wxColourDialog * const - dialog = reinterpret_cast(pCC->lCustData); + case WM_INITDIALOG: + { + const wxString title = gs_activeDialog->GetTitle(); + if ( !title.empty() ) + ::SetWindowText(hwnd, title.t_str()); - const wxString title = dialog->GetTitle(); - if ( !title.empty() ) - ::SetWindowText(hwnd, title.t_str()); + gs_activeDialog->MSWOnInitDone((WXHWND)hwnd); + } + break; - dialog->MSWOnInitDone((WXHWND)hwnd); + default: + // Check if the currently selected colour changed. + // + // Doing it for all messages might be an overkill, we probably + // could only do it for keyboard/mouse ones. + if ( const COLORINFO* pCI = (COLORINFO*)::GetProp(hwnd, COLORPROP) ) + { + gs_activeDialog->MSWCheckIfCurrentChanged(pCI->currentRGB); + } + break; } return 0; @@ -135,9 +198,11 @@ int wxColourDialog::ShowModal() custColours[i] = RGB(255,255,255); } + m_currentCol = wxColourToRGB(m_colourData.GetColour()); + chooseColorStruct.lStructSize = sizeof(CHOOSECOLOR); chooseColorStruct.hwndOwner = hWndParent; - chooseColorStruct.rgbResult = wxColourToRGB(m_colourData.GetColour()); + chooseColorStruct.rgbResult = m_currentCol; chooseColorStruct.lpCustColors = custColours; chooseColorStruct.Flags = CC_RGBINIT | CC_ENABLEHOOK; @@ -147,6 +212,10 @@ int wxColourDialog::ShowModal() if ( m_colourData.GetChooseFull() ) chooseColorStruct.Flags |= CC_FULLOPEN; + // Set the global pointer for the duration of the modal dialog life-time. + gs_activeDialog = this; + wxON_BLOCK_EXIT_NULL(gs_activeDialog); + // do show the modal dialog if ( !::ChooseColor(&chooseColorStruct) ) { @@ -277,4 +346,15 @@ void wxColourDialog::MSWOnInitDone(WXHWND hDlg) SetHWND(NULL); } +void wxColourDialog::MSWCheckIfCurrentChanged(WXCOLORREF currentCol) +{ + if ( currentCol == m_currentCol ) + return; + + m_currentCol = currentCol; + + wxColourDialogEvent event(wxEVT_COLOUR_CHANGED, this, wxRGBToColour(currentCol)); + ProcessWindowEvent(event); +} + #endif // wxUSE_COLOURDLG