diff --git a/include/wx/filedlg.h b/include/wx/filedlg.h index 230caf4163..65731b722b 100644 --- a/include/wx/filedlg.h +++ b/include/wx/filedlg.h @@ -24,6 +24,8 @@ #define wxHAS_MULTIPLE_FILEDLG_FILTERS #endif +class WXDLLIMPEXP_FWD_CORE wxFileDialogCustomizeHook; + //---------------------------------------------------------------------------- // wxFileDialog data //---------------------------------------------------------------------------- @@ -127,6 +129,22 @@ public: virtual int GetCurrentlySelectedFilterIndex () const { return m_currentlySelectedFilterIndex; } + + // A customize hook methods will be called by wxFileDialog later if this + // function returns true, see its documentation for details. + // + // Note that the customizeHook object must remain alive at least until + // ShowModal() returns. + // + // If this function returns false, it means that customizing the file + // dialog is not supported on this platforms. + virtual bool SetCustomizeHook(wxFileDialogCustomizeHook& customizeHook); + + + // Extra controls support is deprecated now as it doesn't allow to use the + // contemporary file dialogs under MSW, use wxFileDialogCustomize-based + // API above instead in the new code. + // this function is called with wxFileDialog as parameter and should // create the window containing the extra controls we want to show in it typedef wxWindow *(*ExtraControlCreatorFunction)(wxWindow*); @@ -172,6 +190,8 @@ protected: // provide a useful implementation of GetCurrentlySelectedFilterIndex(). int m_currentlySelectedFilterIndex; + wxFileDialogCustomizeHook* m_customizeHook; + wxWindow* m_extraControl; // returns true if control is created (if it already exists returns false) diff --git a/include/wx/filedlgcustomize.h b/include/wx/filedlgcustomize.h new file mode 100644 index 0000000000..10ff297a9f --- /dev/null +++ b/include/wx/filedlgcustomize.h @@ -0,0 +1,169 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/filedlgcustomize.h +// Purpose: Classes for wxFileDialog customization. +// Author: Vadim Zeitlin +// Created: 2022-05-26 +// Copyright: (c) 2022 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_FILEDLGCUSTOMIZE_H_ +#define _WX_FILEDLGCUSTOMIZE_H_ + +#include "wx/vector.h" + +class wxFileDialogCustomControlImpl; +class wxFileDialogButtonImpl; +class wxFileDialogCheckBoxImpl; +class wxFileDialogTextCtrlImpl; +class wxFileDialogStaticTextImpl; +class wxFileDialogCustomizeImpl; + +// ---------------------------------------------------------------------------- +// wxFileDialog custom controls +// ---------------------------------------------------------------------------- + +// All these controls support a very limited set of functions, but use the same +// names for the things that they do support as the corresponding "normal" wx +// classes. + +// The base class for all wxFileDialog custom controls. +class wxFileDialogCustomControl +{ +public: + void Show(bool show = true); + void Hide() { Show(false); } + + void Enable(bool enable = true); + void Disable() { Enable(false); } + + ~wxFileDialogCustomControl(); + +protected: + explicit wxFileDialogCustomControl(wxFileDialogCustomControlImpl* impl) + : m_impl(impl) + { + } + + wxFileDialogCustomControlImpl* const m_impl; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogCustomControl); +}; + +// A class representing a custom button. +class wxFileDialogButton : public wxFileDialogCustomControl +{ +public: + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogButton(wxFileDialogButtonImpl* impl); + +private: + wxFileDialogButtonImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogButton); +}; + +// A class representing a custom checkbox. +class wxFileDialogCheckBox : public wxFileDialogCustomControl +{ +public: + bool GetValue() const; + void SetValue(bool value); + + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogCheckBox(wxFileDialogCheckBoxImpl* impl); + +private: + wxFileDialogCheckBoxImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogCheckBox); +}; + +// A class representing a custom text control. +class wxFileDialogTextCtrl : public wxFileDialogCustomControl +{ +public: + wxString GetValue() const; + void SetValue(const wxString& text); + + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogTextCtrl(wxFileDialogTextCtrlImpl* impl); + +private: + wxFileDialogTextCtrlImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogTextCtrl); +}; + +// A class representing a custom static text. +class wxFileDialogStaticText : public wxFileDialogCustomControl +{ +public: + void SetLabelText(const wxString& text); + + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogStaticText(wxFileDialogStaticTextImpl* impl); + +private: + wxFileDialogStaticTextImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogStaticText); +}; + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomizer is used by wxFileDialogCustomizeHook +// ---------------------------------------------------------------------------- + +class wxFileDialogCustomize +{ +public: + wxFileDialogButton* AddButton(const wxString& label); + wxFileDialogCheckBox* AddCheckBox(const wxString& label); + wxFileDialogTextCtrl* AddTextCtrl(); + wxFileDialogStaticText* AddStaticText(const wxString& label); + + ~wxFileDialogCustomize(); + +protected: + // Ctor is only used by wxWidgets itself. + // + // Note that we don't take ownership of the implementation pointer here, + // see the comment in the dtor for more details. + explicit wxFileDialogCustomize(wxFileDialogCustomizeImpl* impl) + : m_impl(impl) + { + } + +private: + template T* StoreAndReturn(T* control); + + wxFileDialogCustomizeImpl* const m_impl; + + wxVector m_controls; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogCustomize); +}; + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomizeHook: used by wxFileDialog itself +// ---------------------------------------------------------------------------- + +class wxFileDialogCustomizeHook +{ +public: + // This method must be overridden to add custom controls to the dialog + // using the provided customizer object. + virtual void AddCustomControls(wxFileDialogCustomize& customizer) = 0; + + // This method may be overridden to update the custom controls whenever + // something changes in the dialog. + virtual void UpdateCustomControls() { } + + // This method should typically be overridden to save the values of the + // custom controls when the dialog is accepted. + virtual void TransferDataFromCustomControls() { } + + virtual ~wxFileDialogCustomizeHook(); +}; + +#endif // _WX_FILEDLGCUSTOMIZE_H_ diff --git a/include/wx/private/filedlgcustomize.h b/include/wx/private/filedlgcustomize.h new file mode 100644 index 0000000000..d7c222951d --- /dev/null +++ b/include/wx/private/filedlgcustomize.h @@ -0,0 +1,67 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/private/filedlgcustomize.h +// Purpose: Private helpers used for wxFileDialog customization +// Author: Vadim Zeitlin +// Created: 2022-05-26 +// Copyright: (c) 2022 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_PRIVATE_FILEDLGCUSTOMIZE_H_ +#define _WX_PRIVATE_FILEDLGCUSTOMIZE_H_ + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomControlImpl: interface for all custom controls +// ---------------------------------------------------------------------------- + +class wxFileDialogCustomControlImpl +{ +public: + virtual void Show(bool show) = 0; + virtual void Enable(bool enable) = 0; + + virtual ~wxFileDialogCustomControlImpl(); +}; + +// This class is defined for symmetry with the other ones, but there are no +// button-specific methods so far. +class wxFileDialogButtonImpl : public wxFileDialogCustomControlImpl +{ +}; + +class wxFileDialogCheckBoxImpl : public wxFileDialogCustomControlImpl +{ +public: + virtual bool GetValue() = 0; + virtual void SetValue(bool value) = 0; +}; + +class wxFileDialogTextCtrlImpl : public wxFileDialogCustomControlImpl +{ +public: + virtual wxString GetValue() = 0; + virtual void SetValue(const wxString& value) = 0; +}; + +class wxFileDialogStaticTextImpl : public wxFileDialogCustomControlImpl +{ +public: + virtual void SetLabelText(const wxString& text) = 0; +}; + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomizeImpl: interface for actual customizers +// ---------------------------------------------------------------------------- + +class wxFileDialogCustomizeImpl +{ +public: + virtual wxFileDialogButtonImpl* AddButton(const wxString& label) = 0; + virtual wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) = 0; + virtual wxFileDialogTextCtrlImpl* AddTextCtrl() = 0; + virtual wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) = 0; + + virtual ~wxFileDialogCustomizeImpl(); +}; + +#endif // _WX_PRIVATE_FILEDLGCUSTOMIZE_H_ diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index 297c0d1eef..cbab4f178a 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -87,6 +87,7 @@ #if wxUSE_FILEDLG #include "wx/filedlg.h" + #include "wx/filedlgcustomize.h" #endif // wxUSE_FILEDLG #if wxUSE_DIRDLG @@ -507,6 +508,17 @@ bool MyApp::OnInit() filedlg_menu->Append(DIALOGS_FILE_SAVE_GENERIC, "Sa&ve file (generic)"); #endif // USE_FILEDLG_GENERIC + filedlg_menu->AppendSeparator(); + filedlg_menu->AppendRadioItem( + DIALOGS_FILE_USE_CUSTOMIZER, + "Use new customization API", + "Use wxFileDialog::SetCustomizeHook() for file dialog customization" + ); + filedlg_menu->AppendRadioItem( + DIALOGS_FILE_USE_EXTRA_CONTROL_CREATOR, + "Use old customization API", + "Use wxFileDialog::SetExtraControlCreator() for file dialog customization" + ); #ifdef __WXOSX_COCOA__ filedlg_menu->AppendSeparator(); filedlg_menu->AppendCheckItem(DIALOGS_MAC_TOGGLE_ALWAYS_SHOW_TYPES, @@ -1562,6 +1574,30 @@ void MyFrame::AddRemove(wxCommandEvent& WXUNUSED(event)) #if wxUSE_FILEDLG +// Simple function showing the current wxFileDialog state. +wxString GetFileDialogStateDescription(wxFileDialogBase* dialog) +{ + const wxString fn = dialog->GetCurrentlySelectedFilename(); + + wxString msg; + if ( fn.empty() ) + msg = "Nothing"; + else if ( wxFileName::FileExists(fn) ) + msg = "File"; + else if ( wxFileName::DirExists(fn) ) + msg = "Directory"; + else + msg = "Something else"; + + msg += " selected"; + + const int filter = dialog->GetCurrentlySelectedFilterIndex(); + if ( filter != wxNOT_FOUND ) + msg += wxString::Format(" (filter=%d)", filter); + + return msg; +} + // panel with custom controls for file dialog class MyExtraPanel : public wxPanel { @@ -1590,25 +1626,8 @@ private: // wxGenericFileDialog, so we need to cast to the base class. In a // typical application, we would cast to just wxFileDialog instead. wxFileDialogBase* const dialog = wxStaticCast(GetParent(), wxFileDialogBase); - const wxString fn = dialog->GetCurrentlySelectedFilename(); - wxString msg; - if ( fn.empty() ) - msg = "Nothing"; - else if ( wxFileName::FileExists(fn) ) - msg = "File"; - else if ( wxFileName::DirExists(fn) ) - msg = "Directory"; - else - msg = "Something else"; - - msg += " selected"; - - const int filter = dialog->GetCurrentlySelectedFilterIndex(); - if ( filter != wxNOT_FOUND ) - msg += wxString::Format(" (filter=%d)", filter); - - event.SetText(msg); + event.SetText(GetFileDialogStateDescription(dialog)); } wxString m_str; @@ -1656,6 +1675,64 @@ static wxWindow* createMyExtraPanel(wxWindow *parent) return new MyExtraPanel(parent); } +// This class does the same thing as MyExtraPanel above, but uses newer API for +// wxFileDialog customization. +class MyCustomizeHook : public wxFileDialogCustomizeHook +{ +public: + explicit MyCustomizeHook(wxFileDialog& dialog) + : m_dialog(&dialog) + { + } + + // Override pure virtual base class method to add our custom controls. + virtual void AddCustomControls(wxFileDialogCustomize& customizer) wxOVERRIDE + { + // Note: all the pointers created here cease to be valid once + // ShowModal() returns, TransferDataFromCustomControls() is the latest + // moment when they can still be used. + customizer.AddStaticText("Just some extra text:"); + m_text = customizer.AddTextCtrl(); + m_cb = customizer.AddCheckBox("Enable Custom Button"); + m_btn = customizer.AddButton("Custom Button"); + m_label = customizer.AddStaticText("Nothing selected"); + } + + // Override another method called whenever something changes in the dialog. + virtual void UpdateCustomControls() wxOVERRIDE + { + // Enable the button if and only if the checkbox is checked. + m_btn->Enable(m_cb->GetValue()); + + // Also show the current dialog state. + m_label->SetLabelText(GetFileDialogStateDescription(m_dialog)); + } + + // And another one called when the dialog is accepted. + virtual void TransferDataFromCustomControls() wxOVERRIDE + { + m_info.Printf("checkbox=%d, text=\"%s\"", + m_cb->GetValue(), m_text->GetValue()); + } + + + // This is just a helper function allowing to show the values of the custom + // controls. + wxString GetInfo() const { return m_info; } + +private: + wxFileDialog* const m_dialog; + + wxFileDialogButton* m_btn; + wxFileDialogCheckBox* m_cb; + wxFileDialogTextCtrl* m_text; + wxFileDialogStaticText* m_label; + + wxString m_info; + + wxDECLARE_NO_COPY_CLASS(MyCustomizeHook); +}; + void MyFrame::FileOpen(wxCommandEvent& WXUNUSED(event) ) { wxFileDialog dialog @@ -1672,14 +1749,43 @@ void MyFrame::FileOpen(wxCommandEvent& WXUNUSED(event) ) ) ); - dialog.SetExtraControlCreator(&createMyExtraPanel); + // Note: this object must remain alive until ShowModal() returns. + MyCustomizeHook myCustomizer(dialog); + + // Normal programs would use either SetCustomizeHook() (preferred) or + // SetExtraControlCreator() (if its extra flexibility is really required), + // but, for demonstration purposes, this sample allows either one or the + // other. + const bool useExtra = + GetMenuBar()->IsChecked(DIALOGS_FILE_USE_EXTRA_CONTROL_CREATOR); + const bool hasExtra = + useExtra ? dialog.SetExtraControlCreator(&createMyExtraPanel) + : dialog.SetCustomizeHook(myCustomizer); + dialog.CentreOnParent(); dialog.SetDirectory(wxGetHomeDir()); if (dialog.ShowModal() == wxID_OK) { + wxString extraInfo; + if ( hasExtra ) + { + if ( useExtra ) + { + wxWindow * const extra = dialog.GetExtraControl(); + extraInfo = static_cast(extra)->GetInfo(); + } + else + { + extraInfo = myCustomizer.GetInfo(); + } + } + else + { + extraInfo = ""; + } + wxString info; - wxWindow * const extra = dialog.GetExtraControl(); info.Printf("Full file name: %s\n" "Path: %s\n" "Name: %s\n" @@ -1687,8 +1793,7 @@ void MyFrame::FileOpen(wxCommandEvent& WXUNUSED(event) ) dialog.GetPath(), dialog.GetDirectory(), dialog.GetFilename(), - extra ? static_cast(extra)->GetInfo() - : wxString("None")); + extraInfo); wxMessageDialog dialog2(this, info, "Selected file"); dialog2.ShowModal(); } diff --git a/samples/dialogs/dialogs.h b/samples/dialogs/dialogs.h index a026b0a157..2c8cd705d5 100644 --- a/samples/dialogs/dialogs.h +++ b/samples/dialogs/dialogs.h @@ -620,6 +620,8 @@ enum DIALOGS_FILE_OPEN_GENERIC, DIALOGS_FILES_OPEN_GENERIC, DIALOGS_FILE_SAVE_GENERIC, + DIALOGS_FILE_USE_CUSTOMIZER, + DIALOGS_FILE_USE_EXTRA_CONTROL_CREATOR, DIALOGS_MAC_TOGGLE_ALWAYS_SHOW_TYPES, DIALOGS_DIR_CHOOSE, DIALOGS_DIR_CHOOSE_WINDOW_MODAL, diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index bd93a71657..7fb3822864 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -24,6 +24,9 @@ #include "wx/window.h" #endif // WX_PRECOMP +#include "wx/filedlgcustomize.h" +#include "wx/private/filedlgcustomize.h" + extern WXDLLEXPORT_DATA(const char) wxFileDialogNameStr[] = "filedlg"; extern WXDLLEXPORT_DATA(const char) wxFileSelectorPromptStr[] = "Select a file"; extern WXDLLEXPORT_DATA(const char) wxFileSelectorDefaultWildcardStr[] = @@ -34,6 +37,157 @@ extern WXDLLEXPORT_DATA(const char) wxFileSelectorDefaultWildcardStr[] = #endif ; +// ============================================================================ +// File dialog customization support +// ============================================================================ + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomControl and derived classes +// ---------------------------------------------------------------------------- + +// Everything here is trivial, but has to be defined here, where the private +// "Impl" classes are fully declared. + +wxFileDialogCustomControlImpl::~wxFileDialogCustomControlImpl() +{ +} + +void wxFileDialogCustomControl::Show(bool show) +{ + return m_impl->Show(show); +} + +void wxFileDialogCustomControl::Enable(bool enable) +{ + return m_impl->Enable(enable); +} + +wxFileDialogCustomControl::~wxFileDialogCustomControl() +{ + delete m_impl; +} + +wxFileDialogButton::wxFileDialogButton(wxFileDialogButtonImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +wxFileDialogButtonImpl* wxFileDialogButton::GetImpl() const +{ + return static_cast(m_impl); +} + +wxFileDialogCheckBox::wxFileDialogCheckBox(wxFileDialogCheckBoxImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +wxFileDialogCheckBoxImpl* wxFileDialogCheckBox::GetImpl() const +{ + return static_cast(m_impl); +} + +bool wxFileDialogCheckBox::GetValue() const +{ + return GetImpl()->GetValue(); +} + +void wxFileDialogCheckBox::SetValue(bool value) +{ + GetImpl()->SetValue(value); +} + +wxFileDialogTextCtrl::wxFileDialogTextCtrl(wxFileDialogTextCtrlImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +wxFileDialogTextCtrlImpl* wxFileDialogTextCtrl::GetImpl() const +{ + return static_cast(m_impl); +} + +wxString wxFileDialogTextCtrl::GetValue() const +{ + return GetImpl()->GetValue(); +} + +void wxFileDialogTextCtrl::SetValue(const wxString& value) +{ + GetImpl()->SetValue(value); +} + +wxFileDialogStaticText::wxFileDialogStaticText(wxFileDialogStaticTextImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +wxFileDialogStaticTextImpl* wxFileDialogStaticText::GetImpl() const +{ + return static_cast(m_impl); +} + +void wxFileDialogStaticText::SetLabelText(const wxString& text) +{ + GetImpl()->SetLabelText(text); +} + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomize +// ---------------------------------------------------------------------------- + +wxFileDialogCustomizeHook::~wxFileDialogCustomizeHook() +{ +} + +wxFileDialogCustomizeImpl::~wxFileDialogCustomizeImpl() +{ +} + +wxFileDialogCustomize::~wxFileDialogCustomize() +{ + // For consistency with the rest of wx API, we own all the custom controls + // pointers and delete them when we're deleted. + for ( size_t n = 0; n < m_controls.size(); ++n ) + delete m_controls[n]; + + // Do not delete m_impl, the derived classes use this object itself as + // implementation, which allows us to avoid allocating it on the heap in + // the first place. +} + +template +T* +wxFileDialogCustomize::StoreAndReturn(T* control) +{ + m_controls.push_back(control); + return control; +} + +wxFileDialogButton* +wxFileDialogCustomize::AddButton(const wxString& label) +{ + return StoreAndReturn(new wxFileDialogButton(m_impl->AddButton(label))); +} + +wxFileDialogCheckBox* +wxFileDialogCustomize::AddCheckBox(const wxString& label) +{ + return StoreAndReturn(new wxFileDialogCheckBox(m_impl->AddCheckBox(label))); +} + +wxFileDialogTextCtrl* +wxFileDialogCustomize::AddTextCtrl() +{ + return StoreAndReturn(new wxFileDialogTextCtrl(m_impl->AddTextCtrl())); +} + +wxFileDialogStaticText* +wxFileDialogCustomize::AddStaticText(const wxString& label) +{ + return StoreAndReturn(new wxFileDialogStaticText(m_impl->AddStaticText(label))); +} + //---------------------------------------------------------------------------- // wxFileDialogBase //---------------------------------------------------------------------------- @@ -45,6 +199,7 @@ void wxFileDialogBase::Init() m_filterIndex = 0; m_currentlySelectedFilterIndex = wxNOT_FOUND; m_windowStyle = 0; + m_customizeHook = NULL; m_extraControl = NULL; m_extraControlCreator = NULL; } @@ -163,6 +318,18 @@ wxString wxFileDialogBase::AppendExtension(const wxString &filePath, return filePath + ext; } +bool wxFileDialogBase::SetCustomizeHook(wxFileDialogCustomizeHook& customizeHook) +{ + if ( !SupportsExtraControl() ) + return false; + + wxASSERT_MSG( !m_extraControlCreator, + "Call either SetExtraControlCreator() or SetCustomizeHook()" ); + + m_customizeHook = &customizeHook; + return true; +} + bool wxFileDialogBase::SetExtraControlCreator(ExtraControlCreatorFunction creator) { wxCHECK_MSG( !m_extraControlCreator, false, @@ -194,6 +361,9 @@ wxSize wxFileDialogBase::GetExtraControlSize() void wxFileDialogBase::UpdateExtraControlUI() { + if ( m_customizeHook ) + m_customizeHook->UpdateCustomControls(); + if ( m_extraControl ) m_extraControl->UpdateWindowUI(wxUPDATE_UI_RECURSE); } diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index b5c5acd9fe..a742220ada 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -52,7 +52,17 @@ // Note: this must be done after including the header above, as this is where // wxUSE_IFILEOPENDIALOG is defined. #if wxUSE_IFILEOPENDIALOG + #include "wx/filedlgcustomize.h" + #include "wx/private/filedlgcustomize.h" + + #include "wx/button.h" + #include "wx/checkbox.h" + #include "wx/stattext.h" + #include "wx/textctrl.h" + #include "wx/msw/wrapshl.h" + + #include "wx/msw/private/cotaskmemptr.h" #endif // wxUSE_IFILEOPENDIALOG // ---------------------------------------------------------------------------- @@ -200,6 +210,246 @@ UINT FileDialogGetFileTypeIndex(IFileDialog* fileDialog) return nFilterIndex; } +// ---------------------------------------------------------------------------- +// IFileDialogCustomize-related stuff: all classes use FDC suffix +// ---------------------------------------------------------------------------- + +// Base class class used only to avoid template bloat: this contains all the +// type-independent parts of wxFileDialogImplFDC. +class wxFileDialogImplFDCBase +{ +protected: + wxFileDialogImplFDCBase(IFileDialogCustomize* fdc, DWORD id) + : m_fdc(fdc), + m_id(id) + { + } + + void DoUpdateState(CDCONTROLSTATEF stateBit, bool on) + { + CDCONTROLSTATEF state = CDCS_INACTIVE; + HRESULT hr = m_fdc->GetControlState(m_id, &state); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::GetControlState"), hr); + + if ( on ) + state |= stateBit; + else + state &= ~stateBit; + + hr = m_fdc->SetControlState(m_id, state); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::SetControlState"), hr); + } + + IFileDialogCustomize* const m_fdc; + const DWORD m_id; +}; + +// Template base class for the implementation classes below inheriting from the +// specified Impl subclass. +template +class wxFileDialogImplFDC + : public T, + protected wxFileDialogImplFDCBase +{ +public: + wxFileDialogImplFDC(IFileDialogCustomize* fdc, DWORD id) + : wxFileDialogImplFDCBase(fdc, id) + { + } + + virtual void Show(bool show) wxOVERRIDE + { + DoUpdateState(CDCS_VISIBLE, show); + } + + virtual void Enable(bool enable) wxOVERRIDE + { + DoUpdateState(CDCS_ENABLED, enable); + } +}; + +class wxFileDialogCustomControlImplFDC + : public wxFileDialogImplFDC +{ +public: + // All custom controls are identified by their ID in this implementation. + wxFileDialogCustomControlImplFDC(IFileDialogCustomize* fdc, DWORD id) + : wxFileDialogImplFDC(fdc, id) + { + } + + wxDECLARE_NO_COPY_CLASS(wxFileDialogCustomControlImplFDC); +}; + +class wxFileDialogButtonImplFDC + : public wxFileDialogImplFDC +{ +public: + wxFileDialogButtonImplFDC(IFileDialogCustomize* fdc, DWORD id) + : wxFileDialogImplFDC(fdc, id) + { + } +}; + +class wxFileDialogCheckBoxImplFDC + : public wxFileDialogImplFDC +{ +public: + wxFileDialogCheckBoxImplFDC(IFileDialogCustomize* fdc, DWORD id) + : wxFileDialogImplFDC(fdc, id) + { + } + + virtual bool GetValue() wxOVERRIDE + { + BOOL checked = FALSE; + HRESULT hr = m_fdc->GetCheckButtonState(m_id, &checked); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::GetCheckButtonState"), hr); + + return checked != FALSE; + } + + virtual void SetValue(bool value) wxOVERRIDE + { + HRESULT hr = m_fdc->SetCheckButtonState(m_id, value ? TRUE : FALSE); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::SetCheckButtonState"), hr); + } +}; + +class wxFileDialogTextCtrlImplFDC + : public wxFileDialogImplFDC +{ +public: + wxFileDialogTextCtrlImplFDC(IFileDialogCustomize* fdc, DWORD id) + : wxFileDialogImplFDC(fdc, id) + { + } + + virtual wxString GetValue() wxOVERRIDE + { + wxCoTaskMemPtr value; + HRESULT hr = m_fdc->GetEditBoxText(m_id, &value); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::GetEditBoxText"), hr); + + return wxString(value); + } + + virtual void SetValue(const wxString& value) wxOVERRIDE + { + HRESULT hr = m_fdc->SetEditBoxText(m_id, value.wc_str()); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::SetEditBoxText"), hr); + } +}; + +class wxFileDialogStaticTextImplFDC + : public wxFileDialogImplFDC +{ +public: + wxFileDialogStaticTextImplFDC(IFileDialogCustomize* fdc, DWORD id) + : wxFileDialogImplFDC(fdc, id) + { + } + + virtual void SetLabelText(const wxString& text) wxOVERRIDE + { + // Prevent ampersands from being misinterpreted as mnemonics. + const wxString& label = wxControl::EscapeMnemonics(text); + + HRESULT hr = m_fdc->SetControlLabel(m_id, label.wc_str()); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::SetControlLabel"), hr); + } +}; + +// Implementation of wxFileDialogCustomize based on IFileDialogCustomize: to +// simplify things, this class is its own implementation pointer too. +class wxFileDialogCustomizeFDC : public wxFileDialogCustomize, + private wxFileDialogCustomizeImpl +{ +public: + // For convenience, this class has a default ctor, but it can't be really + // used before Initialize() is called. + wxFileDialogCustomizeFDC() + : wxFileDialogCustomize(this) + { + m_lastId = 0; + } + + bool Initialize(IFileDialog* fileDialog) + { + HRESULT hr = fileDialog->QueryInterface + ( + wxIID_PPV_ARGS(IFileDialogCustomize, &m_fdc) + ); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialog::QI(IFileDialogCustomize)"), hr); + + return false; + } + + return true; + } + + wxFileDialogButtonImpl* AddButton(const wxString& label) wxOVERRIDE + { + HRESULT hr = m_fdc->AddPushButton(++m_lastId, label.wc_str()); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddPushButton"), hr); + return NULL; + } + + return new wxFileDialogButtonImplFDC(m_fdc, m_lastId); + } + + wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) wxOVERRIDE + { + HRESULT hr = m_fdc->AddCheckButton(++m_lastId, label.wc_str(), FALSE); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddCheckButton"), hr); + return NULL; + } + + return new wxFileDialogCheckBoxImplFDC(m_fdc, m_lastId); + } + + wxFileDialogTextCtrlImpl* AddTextCtrl() wxOVERRIDE + { + HRESULT hr = m_fdc->AddEditBox(++m_lastId, L""); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddEditBox"), hr); + return NULL; + } + + return new wxFileDialogTextCtrlImplFDC(m_fdc, m_lastId); + } + + wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) wxOVERRIDE + { + HRESULT hr = m_fdc->AddText(++m_lastId, label.wc_str()); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddText"), hr); + return NULL; + } + + return new wxFileDialogStaticTextImplFDC(m_fdc, m_lastId); + } + +private: + wxCOMPtr m_fdc; + DWORD m_lastId; +}; + #endif // wxUSE_IFILEOPENDIALOG } // unnamed namespace @@ -264,7 +514,18 @@ public: // IFileDialogEvents - wxSTDMETHODIMP OnFileOk(IFileDialog*) wxOVERRIDE { return E_NOTIMPL; } + + wxSTDMETHODIMP OnFileOk(IFileDialog*) wxOVERRIDE + { + // Note that we need to call this hook function from here as the + // controls are destroyed later and getting their values wouldn't work + // any more. + if ( m_fileDialog->m_customizeHook ) + m_fileDialog->m_customizeHook->TransferDataFromCustomControls(); + + return S_OK; + } + wxSTDMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) wxOVERRIDE { return E_NOTIMPL; } wxSTDMETHODIMP OnFolderChange(IFileDialog*) wxOVERRIDE { return E_NOTIMPL; } @@ -317,6 +578,8 @@ public: wxFileDialog* const m_fileDialog; + wxFileDialogCustomizeFDC m_customize; + bool m_typeAlreadyChanged; #endif // wxUSE_IFILEOPENDIALOG @@ -945,6 +1208,13 @@ int wxFileDialog::ShowIFileDialog(WXHWND hWndParent) FileDialogEventsRegistrar registerEvents(fileDialog, data); + // Add custom controls, if any. + if ( m_customizeHook ) + { + if ( data.m_customize.Initialize(fileDialog.Get()) ) + m_customizeHook->AddCustomControls(data.m_customize); + } + // Configure the dialog before showing it. fileDialog.SetTitle(m_message);