From c18486e81ff765a743b3b536cce59cd650da46ba Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 4 Jun 2022 00:50:54 +0100 Subject: [PATCH] Add support for custom comboboxes in wxFileDialogCustomize Allow using simple (i.e. not editable) comboboxes, known as wxChoice in wx API, in the dialog too. Demonstrate their use in the dialogs sample. --- include/wx/filedlgcustomize.h | 21 ++++++ include/wx/private/filedlgcustomize.h | 8 ++ interface/wx/filedlgcustomize.h | 41 ++++++++++ samples/dialogs/dialogs.cpp | 33 ++++++++- src/common/fldlgcmn.cpp | 103 ++++++++++++++++++++++++++ src/msw/filedlg.cpp | 82 ++++++++++++++++++++ 6 files changed, 285 insertions(+), 3 deletions(-) diff --git a/include/wx/filedlgcustomize.h b/include/wx/filedlgcustomize.h index 20b7149632..4821e5ed8e 100644 --- a/include/wx/filedlgcustomize.h +++ b/include/wx/filedlgcustomize.h @@ -16,6 +16,7 @@ class wxFileDialogCustomControlImpl; class wxFileDialogButtonImpl; class wxFileDialogCheckBoxImpl; class wxFileDialogRadioButtonImpl; +class wxFileDialogChoiceImpl; class wxFileDialogTextCtrlImpl; class wxFileDialogStaticTextImpl; class wxFileDialogCustomizeImpl; @@ -110,6 +111,25 @@ private: wxDECLARE_NO_COPY_CLASS(wxFileDialogRadioButton); }; +// A class representing a custom combobox button. +class WXDLLIMPEXP_CORE wxFileDialogChoice : public wxFileDialogCustomControl +{ +public: + int GetSelection() const; + void SetSelection(int n); + + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogChoice(wxFileDialogChoiceImpl* impl); + +protected: + virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE; + +private: + wxFileDialogChoiceImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogChoice); +}; + // A class representing a custom text control. class WXDLLIMPEXP_CORE wxFileDialogTextCtrl : public wxFileDialogCustomControl { @@ -151,6 +171,7 @@ public: wxFileDialogButton* AddButton(const wxString& label); wxFileDialogCheckBox* AddCheckBox(const wxString& label); wxFileDialogRadioButton* AddRadioButton(const wxString& label); + wxFileDialogChoice* AddChoice(size_t n, const wxString* strings); wxFileDialogTextCtrl* AddTextCtrl(const wxString& label = wxString()); wxFileDialogStaticText* AddStaticText(const wxString& label); diff --git a/include/wx/private/filedlgcustomize.h b/include/wx/private/filedlgcustomize.h index fbd59ccc4a..ffa8ec3192 100644 --- a/include/wx/private/filedlgcustomize.h +++ b/include/wx/private/filedlgcustomize.h @@ -45,6 +45,13 @@ public: virtual void SetValue(bool value) = 0; }; +class wxFileDialogChoiceImpl : public wxFileDialogCustomControlImpl +{ +public: + virtual int GetSelection() = 0; + virtual void SetSelection(int n) = 0; +}; + class wxFileDialogTextCtrlImpl : public wxFileDialogCustomControlImpl { public: @@ -68,6 +75,7 @@ public: virtual wxFileDialogButtonImpl* AddButton(const wxString& label) = 0; virtual wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) = 0; virtual wxFileDialogRadioButtonImpl* AddRadioButton(const wxString& label) = 0; + virtual wxFileDialogChoiceImpl* AddChoice(size_t n, const wxString* strings) = 0; virtual wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) = 0; virtual wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) = 0; diff --git a/interface/wx/filedlgcustomize.h b/interface/wx/filedlgcustomize.h index 56e819cb34..e49bbbbf44 100644 --- a/interface/wx/filedlgcustomize.h +++ b/interface/wx/filedlgcustomize.h @@ -109,6 +109,32 @@ public: void SetValue(bool value); }; +/** + Represents a custom read-only combobox inside wxFileDialog. + + Objects of this class can only be created by + wxFileDialogCustomize::AddChoice(). + + It is possible to bind to wxEVT_CHOICE events on this object, which + will be generated when the selection in the combobox changes. + + See wxFileDialogCustomControl for more information. + + @since 3.1.7 + */ +class wxFileDialogChoice : public wxFileDialogCustomControl +{ +public: + /// Return the index of the selected item, possibly wxNOT_FOUND. + int GetSelection() const; + + /// Set the selection to the item with the given index. + /// + /// Using @c wxNOT_FOUND for @a n is not supported, once a selection is + /// made it cannot be undone. + void SetSelection(int n); +}; + /** Represents a custom text control inside wxFileDialog. @@ -203,6 +229,21 @@ public: */ wxFileDialogRadioButton* AddRadioButton(const wxString& label); + /** + Add a read-only combobox with the specified contents. + + The combobox doesn't have any initial selection, i.e. + wxFileDialogChoice::GetSelection() returns @c wxNOT_FOUND, if some item + must be selected, use wxFileDialogChoice::SetSelection() explicitly to + do it. + + @param n The number of strings, must be positive, as there is no way to + add more strings later and creating an empty combobox is not very + useful. + @param strings A non-@NULL pointer to an array of @a n strings. + */ + wxFileDialogChoice* AddChoice(size_t n, const wxString* strings); + /** Add a text control with an optional label preceding it. diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index e6e3044e66..81458a6d06 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -1598,6 +1598,18 @@ wxString GetFileDialogStateDescription(wxFileDialogBase* dialog) return msg; } +// Another helper translating demo combobox selection. +wxString GetFileDialogPaperSize(int selection) +{ + switch ( selection ) + { + case -1: return ""; + case 0: return "A4"; + case 1: return "Letter"; + default: return "INVALID"; + } +} + // panel with custom controls for file dialog class MyExtraPanel : public wxPanel { @@ -1605,8 +1617,8 @@ public: MyExtraPanel(wxWindow *parent); wxString GetInfo() const { - return wxString::Format("paper=%s, enabled=%d, text=\"%s\"", - m_paperOrient, m_checked, m_str); + return wxString::Format("paper=%s (%s), enabled=%d, text=\"%s\"", + m_paperSize, m_paperOrient, m_checked, m_str); } private: @@ -1626,6 +1638,11 @@ private: m_paperOrient = "unknown"; } + void OnChoice(wxCommandEvent& event) + { + m_paperSize = GetFileDialogPaperSize(event.GetSelection()); + } + void OnText(wxCommandEvent& event) { m_str = event.GetString(); @@ -1643,6 +1660,7 @@ private: wxString m_str; bool m_checked; + wxString m_paperSize; wxString m_paperOrient; wxButton *m_btn; @@ -1662,6 +1680,10 @@ MyExtraPanel::MyExtraPanel(wxWindow *parent) m_btn->Enable(false); m_cb = new wxCheckBox(this, -1, "Enable Custom Button"); m_cb->Bind(wxEVT_CHECKBOX, &MyExtraPanel::OnCheckBox, this); + wxChoice* choiceSize = new wxChoice(this, wxID_ANY); + choiceSize->Append("A4"); + choiceSize->Append("Letter"); + choiceSize->Bind(wxEVT_CHOICE, &MyExtraPanel::OnChoice, this); m_radioPortrait = new wxRadioButton(this, wxID_ANY, "&Portrait", wxDefaultPosition, wxDefaultSize, wxRB_GROUP); m_radioPortrait->Bind(wxEVT_RADIOBUTTON, &MyExtraPanel::OnRadioButton, this); @@ -1679,6 +1701,7 @@ MyExtraPanel::MyExtraPanel(wxWindow *parent) wxSizerFlags().Centre().Border()); sizerTop->Add(m_text, wxSizerFlags(1).Centre().Border()); sizerTop->AddSpacer(10); + sizerTop->Add(choiceSize, wxSizerFlags().Centre().Border(wxRIGHT)); sizerTop->Add(m_radioPortrait, wxSizerFlags().Centre().Border()); sizerTop->Add(m_radioLandscape, wxSizerFlags().Centre().Border()); sizerTop->Add(m_cb, wxSizerFlags().Centre().Border()); @@ -1716,6 +1739,8 @@ public: // ShowModal() returns, TransferDataFromCustomControls() is the latest // moment when they can still be used. m_text = customizer.AddTextCtrl("Just some extra text:"); + const wxString sizes[] = { "A4", "Letter" }; + m_choiceSize = customizer.AddChoice(WXSIZEOF(sizes), sizes); m_radioPortrait = customizer.AddRadioButton("&Portrait"); m_radioLandscape = customizer.AddRadioButton("&Landscape"); m_cb = customizer.AddCheckBox("Enable Custom Button"); @@ -1745,7 +1770,8 @@ public: // And another one called when the dialog is accepted. virtual void TransferDataFromCustomControls() wxOVERRIDE { - m_info.Printf("paper=%s, enabled=%d, text=\"%s\"", + m_info.Printf("paper=%s (%s), enabled=%d, text=\"%s\"", + GetFileDialogPaperSize(m_choiceSize->GetSelection()), m_radioPortrait->GetValue() ? "portrait" : "landscape", m_cb->GetValue(), m_text->GetValue()); } @@ -1771,6 +1797,7 @@ private: wxFileDialogButton* m_btn; wxFileDialogCheckBox* m_cb; + wxFileDialogChoice* m_choiceSize; wxFileDialogRadioButton* m_radioPortrait; wxFileDialogRadioButton* m_radioLandscape; wxFileDialogTextCtrl* m_text; diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index 496c409da8..71370891ad 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -27,6 +27,7 @@ #include "wx/button.h" #include "wx/checkbox.h" + #include "wx/choice.h" #include "wx/radiobut.h" #include "wx/stattext.h" #include "wx/textctrl.h" @@ -172,6 +173,34 @@ void wxFileDialogRadioButton::SetValue(bool value) GetImpl()->SetValue(value); } +wxFileDialogChoice::wxFileDialogChoice(wxFileDialogChoiceImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +bool wxFileDialogChoice::OnDynamicBind(wxDynamicEventTableEntry& entry) +{ + if ( entry.m_eventType == wxEVT_CHOICE ) + return GetImpl()->DoBind(this); + + return wxFileDialogCustomControl::OnDynamicBind(entry); +} + +wxFileDialogChoiceImpl* wxFileDialogChoice::GetImpl() const +{ + return static_cast(m_impl); +} + +int wxFileDialogChoice::GetSelection() const +{ + return GetImpl()->GetSelection(); +} + +void wxFileDialogChoice::SetSelection(int n) +{ + GetImpl()->SetSelection(n); +} + wxFileDialogTextCtrl::wxFileDialogTextCtrl(wxFileDialogTextCtrlImpl* impl) : wxFileDialogCustomControl(impl) { @@ -257,6 +286,12 @@ wxFileDialogCustomize::AddRadioButton(const wxString& label) return StoreAndReturn(new wxFileDialogRadioButton(m_impl->AddRadioButton(label))); } +wxFileDialogChoice* +wxFileDialogCustomize::AddChoice(size_t n, const wxString* strings) +{ + return StoreAndReturn(new wxFileDialogChoice(m_impl->AddChoice(n, strings))); +} + wxFileDialogTextCtrl* wxFileDialogCustomize::AddTextCtrl(const wxString& label) { @@ -461,6 +496,60 @@ private: wxEvtHandler* m_handler; }; +class ChoiceImpl : public ControlImplBase +{ +public: + ChoiceImpl(wxWindow* parent, size_t n, const wxString* strings) + : ControlImplBase + ( + new wxChoice(parent, wxID_ANY, + wxDefaultPosition, wxDefaultSize, + n, strings) + ) + { + m_handler = NULL; + } + + virtual int GetSelection() wxOVERRIDE + { + return GetChoice()->GetSelection(); + } + + virtual void SetSelection(int selection) wxOVERRIDE + { + GetChoice()->SetSelection(selection); + } + + virtual bool DoBind(wxEvtHandler* handler) wxOVERRIDE + { + if ( !m_handler ) + { + m_handler = handler; + m_win->Bind(wxEVT_CHOICE, &ChoiceImpl::OnChoice, this); + } + + return true; + } + +private: + wxChoice* GetChoice() const + { + return static_cast(m_win); + } + + void OnChoice(wxCommandEvent& event) + { + // See comments in OnButton() above, they also apply here. + + wxCommandEvent eventCopy(event); + eventCopy.SetEventObject(m_handler); + + m_handler->ProcessEvent(eventCopy); + } + + wxEvtHandler* m_handler; +}; + class TextCtrlImpl : public ControlImplBase { public: @@ -581,6 +670,20 @@ public: return impl; } + wxFileDialogChoiceImpl* AddChoice(size_t n, const wxString* strings) wxOVERRIDE + { + m_lastWasRadio = false; + + // TODO-C++11: Can't use AddToLayoutAndReturn() here easily without + // variadic templates. + ChoiceImpl* const impl = new ChoiceImpl(this, n, strings); + + AddToLayout(impl->m_win); + + return impl; + } + + wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) wxOVERRIDE { m_lastWasRadio = false; diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 564244cad9..936d2f50b9 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -57,6 +57,7 @@ #include "wx/button.h" #include "wx/checkbox.h" + #include "wx/choice.h" #include "wx/radiobut.h" #include "wx/stattext.h" #include "wx/textctrl.h" @@ -374,6 +375,54 @@ private: const DWORD m_item; }; +class wxFileDialogChoiceImplFDC + : public wxFileDialogImplFDC +{ +public: + wxFileDialogChoiceImplFDC(IFileDialogCustomize* fdc, DWORD id, DWORD item) + : wxFileDialogImplFDC(fdc, id), + m_firstItem(item) + { + } + + virtual int GetSelection() wxOVERRIDE + { + DWORD selected = 0; + HRESULT hr = m_fdc->GetSelectedControlItem(m_id, &selected); + if ( hr == E_FAIL ) + { + // This seems to be returned if there is no selection. + return -1; + } + + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::GetSelectedControlItem"), hr); + + // See m_firstItem comment for the explanation of subtraction order. + return m_firstItem - selected; + } + + virtual void SetSelection(int n) wxOVERRIDE + { + // As above, see m_firstItem comment. + HRESULT hr = m_fdc->SetSelectedControlItem(m_id, m_firstItem - n); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::SetSelectedControlItem"), hr); + } + + virtual bool DoBind(wxEvtHandler* WXUNUSED(handler)) wxOVERRIDE + { + // We don't need to do anything special to get the events here. + return true; + } + +private: + // The ID of the first item of the combobox. The subsequent items are + // consecutive numbers _smaller_ than this one, because auxiliary IDs are + // assigned in decreasing order by decrementing them. + const DWORD m_firstItem; +}; + class wxFileDialogTextCtrlImplFDC : public wxFileDialogImplFDC { @@ -528,6 +577,33 @@ public: return impl; } + wxFileDialogChoiceImpl* AddChoice(size_t n, const wxString* strings) wxOVERRIDE + { + HRESULT hr = m_fdc->AddComboBox(++m_lastId); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddComboBox"), hr); + return NULL; + } + + // We pass the ID of the first control that will be added to the + // combobox as the ctor argument. + wxScopedPtr + impl(new wxFileDialogChoiceImplFDC(m_fdc, m_lastId, m_lastAuxId - 1)); + + for ( size_t i = 0; i < n; ++i ) + { + hr = m_fdc->AddControlItem(m_lastId, --m_lastAuxId, strings[i].wc_str()); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddControlItem"), hr); + return NULL; + } + } + + return impl.release(); + } + wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) wxOVERRIDE { m_radioListId = 0; @@ -582,6 +658,12 @@ private: // IDs used for any other controls, they're negative (which means they // decrement from USHORT_MAX down). + // + // Note that auxiliary IDs are sometimes used for the main control, at + // native level, as with the radio buttons, that are represented by + // separate controls at wx level, and sometimes for the control elements, + // such as for the combobox, which itself uses a normal ID, as it + // corresponds to the wx level control. DWORD m_lastAuxId; // ID of the current radio button list, i.e. the one to which the next call