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