From 359ab98cb2159d868aa21b492ae15d075ed495a6 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 27 May 2022 00:52:49 +0100 Subject: [PATCH] Start adding new API for wxFileDialog customization wxFileDialog::SetCustomizeHook() can be implemented in terms of IFileDialogCustomize in wxMSW, which means that it can be used with the new style dialogs shown by IFileDialog, unlike SetExtraControlCreator(), which could only be supported when using older style common dialogs. Add support for a few different controls and wxMSW implementation, more controls, generic implementation and the documentation will be updated later. Also update the sample to show the new API in action and allow toggling between using it and the old API for testing. --- include/wx/filedlg.h | 20 ++ include/wx/filedlgcustomize.h | 169 ++++++++++++++++ include/wx/private/filedlgcustomize.h | 67 +++++++ samples/dialogs/dialogs.cpp | 149 +++++++++++--- samples/dialogs/dialogs.h | 2 + src/common/fldlgcmn.cpp | 170 ++++++++++++++++ src/msw/filedlg.cpp | 272 +++++++++++++++++++++++++- 7 files changed, 826 insertions(+), 23 deletions(-) create mode 100644 include/wx/filedlgcustomize.h create mode 100644 include/wx/private/filedlgcustomize.h 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);