diff --git a/include/wx/clrpicker.h b/include/wx/clrpicker.h index 0530e309de..4b51a96d0e 100644 --- a/include/wx/clrpicker.h +++ b/include/wx/clrpicker.h @@ -159,13 +159,15 @@ private: // ---------------------------------------------------------------------------- wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_CORE, wxEVT_COLOURPICKER_CHANGED, wxColourPickerEvent ); +wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_CORE, wxEVT_COLOURPICKER_CURRENT_CHANGED, wxColourPickerEvent ); +wxDECLARE_EXPORTED_EVENT( WXDLLIMPEXP_CORE, wxEVT_COLOURPICKER_DIALOG_CANCELLED, wxColourPickerEvent ); class WXDLLIMPEXP_CORE wxColourPickerEvent : public wxCommandEvent { public: wxColourPickerEvent() {} - wxColourPickerEvent(wxObject *generator, int id, const wxColour &col) - : wxCommandEvent(wxEVT_COLOURPICKER_CHANGED, id), + wxColourPickerEvent(wxObject *generator, int id, const wxColour &col, wxEventType commandType = wxEVT_COLOURPICKER_CHANGED) + : wxCommandEvent(commandType, id), m_colour(col) { SetEventObject(generator); @@ -196,6 +198,11 @@ typedef void (wxEvtHandler::*wxColourPickerEventFunction)(wxColourPickerEvent&); #define EVT_COLOURPICKER_CHANGED(id, fn) \ wx__DECLARE_EVT1(wxEVT_COLOURPICKER_CHANGED, id, wxColourPickerEventHandler(fn)) +#define EVT_COLOURPICKER_CURRENT_CHANGED(id, fn) \ + wx__DECLARE_EVT1(wxEVT_COLOURPICKER_CURRENT_CHANGED, id, wxColourPickerEventHandler(fn)) + +#define EVT_COLOURPICKER_DIALOG_CANCELLED(id, fn) \ + wx__DECLARE_EVT1(wxEVT_COLOURPICKER_DIALOG_CANCELLED, id, wxColourPickerEventHandler(fn)) // old wxEVT_COMMAND_* constant #define wxEVT_COMMAND_COLOURPICKER_CHANGED wxEVT_COLOURPICKER_CHANGED 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/generic/clrpickerg.h b/include/wx/generic/clrpickerg.h index 84076816c0..275c0494a6 100644 --- a/include/wx/generic/clrpickerg.h +++ b/include/wx/generic/clrpickerg.h @@ -15,6 +15,8 @@ #include "wx/bmpbuttn.h" #include "wx/colourdata.h" +class wxColourDialogEvent; + //----------------------------------------------------------------------------- // wxGenericColourButton: a button which brings up a wxColourDialog //----------------------------------------------------------------------------- @@ -75,6 +77,8 @@ protected: static wxColourData ms_data; private: + void OnColourChanged(wxColourDialogEvent& event); + wxDECLARE_DYNAMIC_CLASS(wxGenericColourButton); }; 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/clrpicker.h b/interface/wx/clrpicker.h index 158457f3f7..d8db2adb38 100644 --- a/interface/wx/clrpicker.h +++ b/interface/wx/clrpicker.h @@ -44,7 +44,20 @@ wxEventType wxEVT_COLOURPICKER_CHANGED; The user changed the colour selected in the control either using the button or using text control (see @c wxCLRP_USE_TEXTCTRL; note that in this case the event is fired only if the user’s input is valid, - i.e. recognizable). + i.e. recognizable). When using a popup dialog for changing the + colour, this event is sent only when the changes in the dialog are + accepted by the user, unlike @c EVT_COLOURPICKER_CURRENT_CHANGED. + @event{EVT_COLOURPICKER_CURRENT_CHANGED(id, func)} + The user changed the currently selected colour in the dialog + associated with the control. This event is sent immediately when the + selection changes and you must also handle @c EVT_COLOUR_CANCELLED + to revert to the previously selected colour if the selection ends up + not being accepted. This event is new since wxWidgets 3.1.3 and + currently is only implemented in wxMSW. + @event{EVT_COLOURPICKER_DIALOG_CANCELLED(id, func)} + The user cancelled the colour dialog associated with the control, + i.e. closed it without accepting the selection. This event is new + since wxWidgets 3.1.3 and currently is only implemented in wxMSW. @endEventTable @library{wxcore} @@ -124,6 +137,15 @@ public: @beginEventTable{wxColourPickerEvent} @event{EVT_COLOURPICKER_CHANGED(id, func)} Generated whenever the selected colour changes. + @event{EVT_COLOURPICKER_CURRENT_CHANGED(id, func)} + Generated whenever the currently selected colour in the dialog shown + by the picker changes. This event is new since wxWidgets 3.1.3 and + currently is only implemented in wxMSW. + @event{EVT_COLOURPICKER_DIALOG_CANCELLED(id, func)} + Generated when the user cancels the colour dialog associated with + the control, i.e. closes it without accepting the selection. This + event is new since wxWidgets 3.1.3 and currently is only implemented + in wxMSW. @endEventTable @library{wxcore} 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 5c1b3aecb2..be8887912e 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/samples/widgets/clrpicker.cpp b/samples/widgets/clrpicker.cpp index 5f166ee61e..c9e1776568 100644 --- a/samples/widgets/clrpicker.cpp +++ b/samples/widgets/clrpicker.cpp @@ -83,6 +83,9 @@ protected: void OnColourChange(wxColourPickerEvent &ev); + void OnColourCurrentChanged(wxColourPickerEvent &ev); + void OnColourDialogCancelled(wxColourPickerEvent &ev); + void OnCheckBox(wxCommandEvent &ev); void OnButtonReset(wxCommandEvent &ev); @@ -111,6 +114,8 @@ wxBEGIN_EVENT_TABLE(ColourPickerWidgetsPage, WidgetsPage) EVT_BUTTON(PickerPage_Reset, ColourPickerWidgetsPage::OnButtonReset) EVT_COLOURPICKER_CHANGED(PickerPage_Colour, ColourPickerWidgetsPage::OnColourChange) + EVT_COLOURPICKER_CURRENT_CHANGED(PickerPage_Colour, ColourPickerWidgetsPage::OnColourCurrentChanged) + EVT_COLOURPICKER_DIALOG_CANCELLED(PickerPage_Colour, ColourPickerWidgetsPage::OnColourDialogCancelled) EVT_CHECKBOX(wxID_ANY, ColourPickerWidgetsPage::OnCheckBox) wxEND_EVENT_TABLE() @@ -221,6 +226,18 @@ void ColourPickerWidgetsPage::OnColourChange(wxColourPickerEvent& event) event.GetColour().GetAsString(wxC2S_CSS_SYNTAX)); } +void ColourPickerWidgetsPage::OnColourCurrentChanged(wxColourPickerEvent& event) +{ + wxLogMessage("The currently selected colour changed to '%s'", + event.GetColour().GetAsString(wxC2S_CSS_SYNTAX)); +} + +void ColourPickerWidgetsPage::OnColourDialogCancelled(wxColourPickerEvent& event) +{ + wxLogMessage("Colour selection dialog cancelled, current colour is '%s'", + event.GetColour().GetAsString(wxC2S_CSS_SYNTAX)); +} + void ColourPickerWidgetsPage::OnCheckBox(wxCommandEvent &event) { if (event.GetEventObject() == m_chkColourTextCtrl || diff --git a/src/common/clrpickercmn.cpp b/src/common/clrpickercmn.cpp index f22482a2cd..ecaaf67641 100644 --- a/src/common/clrpickercmn.cpp +++ b/src/common/clrpickercmn.cpp @@ -39,6 +39,9 @@ const char wxColourPickerWidgetNameStr[] = "colourpickerwidget"; // ============================================================================ wxDEFINE_EVENT(wxEVT_COLOURPICKER_CHANGED, wxColourPickerEvent); +wxDEFINE_EVENT(wxEVT_COLOURPICKER_CURRENT_CHANGED, wxColourPickerEvent); +wxDEFINE_EVENT(wxEVT_COLOURPICKER_DIALOG_CANCELLED, wxColourPickerEvent); + wxIMPLEMENT_DYNAMIC_CLASS(wxColourPickerCtrl, wxPickerBase); wxIMPLEMENT_DYNAMIC_CLASS(wxColourPickerEvent, wxEvent); @@ -129,10 +132,7 @@ void wxColourPickerCtrl::OnColourChange(wxColourPickerEvent &ev) { UpdateTextCtrlFromPicker(); - // the wxColourPickerWidget sent us a colour-change notification. - // forward this event to our parent - wxColourPickerEvent event(this, GetId(), ev.GetColour()); - GetEventHandler()->ProcessEvent(event); + ev.Skip(); } #endif // wxUSE_COLOURPICKERCTRL 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/generic/clrpickerg.cpp b/src/generic/clrpickerg.cpp index 58cd06ab8a..d0f8434168 100644 --- a/src/generic/clrpickerg.cpp +++ b/src/generic/clrpickerg.cpp @@ -86,15 +86,36 @@ void wxGenericColourButton::OnButtonClick(wxCommandEvent& WXUNUSED(ev)) // create the colour dialog and display it wxColourDialog dlg(this, &ms_data); + dlg.Bind(wxEVT_COLOUR_CHANGED, &wxGenericColourButton::OnColourChanged, this); + + wxEventType eventType; if (dlg.ShowModal() == wxID_OK) { ms_data = dlg.GetColourData(); SetColour(ms_data.GetColour()); - // fire an event - wxColourPickerEvent event(this, GetId(), m_colour); - GetEventHandler()->ProcessEvent(event); + eventType = wxEVT_COLOURPICKER_CHANGED; } + else + { + eventType = wxEVT_COLOURPICKER_DIALOG_CANCELLED; + } + + // Fire the corresponding event: note that we want it to appear as + // originating from our parent, which is the user-visible window, and not + // this button itself, which is just an implementation detail. + wxWindow* const parent = GetParent(); + wxColourPickerEvent event(parent, parent->GetId(), m_colour, eventType); + + ProcessWindowEvent(event); +} + +void wxGenericColourButton::OnColourChanged(wxColourDialogEvent& ev) +{ + wxWindow* const parent = GetParent(); + wxColourPickerEvent event(parent, parent->GetId(), ev.GetColour(), + wxEVT_COLOURPICKER_CURRENT_CHANGED); + parent->ProcessWindowEvent(event); } void wxGenericColourButton::UpdateColour() diff --git a/src/gtk/clrpicker.cpp b/src/gtk/clrpicker.cpp index 856421e94e..54f01a4d09 100644 --- a/src/gtk/clrpicker.cpp +++ b/src/gtk/clrpicker.cpp @@ -50,8 +50,11 @@ static void gtk_clrbutton_setcolor_callback(GtkColorButton *widget, #endif p->GTKSetColour(gdkColor); - // fire the colour-changed event - wxColourPickerEvent event(p, p->GetId(), p->GetColour()); + // Fire the corresponding event: note that we want it to appear as + // originating from our parent, which is the user-visible window, and not + // this button itself, which is just an implementation detail. + wxWindow* const parent = p->GetParent(); + wxColourPickerEvent event(parent, parent->GetId(), p->GetColour()); p->HandleWindowEvent(event); } } diff --git a/src/msw/colordlg.cpp b/src/msw/colordlg.cpp index 9b4d9e55ee..c8e48b5470 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) ) { @@ -274,4 +343,15 @@ void wxColourDialog::MSWOnInitDone(WXHWND hDlg) } } +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