diff --git a/include/wx/filedlg.h b/include/wx/filedlg.h index 230caf4163..fa53e25620 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,15 +190,17 @@ 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) + // create and return the extra control using the given parent + wxWindow* CreateExtraControlWithParent(wxWindow* parent) const; + // returns true if control is created, also sets m_extraControl bool CreateExtraControl(); // return true if SetExtraControlCreator() was called bool HasExtraControlCreator() const { return m_extraControlCreator != NULL; } - // get the size of the extra control by creating and deleting it - wxSize GetExtraControlSize(); // Helper function for native file dialog usage where no wx events // are processed. void UpdateExtraControlUI(); diff --git a/include/wx/filedlgcustomize.h b/include/wx/filedlgcustomize.h new file mode 100644 index 0000000000..4821e5ed8e --- /dev/null +++ b/include/wx/filedlgcustomize.h @@ -0,0 +1,222 @@ +/////////////////////////////////////////////////////////////////////////////// +// 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 wxFileDialogRadioButtonImpl; +class wxFileDialogChoiceImpl; +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 and also generate some (but not all) of the same events. + +// The base class for all wxFileDialog custom controls. +class WXDLLIMPEXP_CORE wxFileDialogCustomControl : public wxEvtHandler +{ +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) + { + } + + // By default custom controls don't generate any events, but some of them + // override this function to allow connecting to the selected events. + virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE; + + + wxFileDialogCustomControlImpl* const m_impl; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogCustomControl); +}; + +// A class representing a custom button. +class WXDLLIMPEXP_CORE wxFileDialogButton : public wxFileDialogCustomControl +{ +public: + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogButton(wxFileDialogButtonImpl* impl); + +protected: + virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE; + +private: + wxFileDialogButtonImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogButton); +}; + +// A class representing a custom checkbox. +class WXDLLIMPEXP_CORE wxFileDialogCheckBox : public wxFileDialogCustomControl +{ +public: + bool GetValue() const; + void SetValue(bool value); + + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogCheckBox(wxFileDialogCheckBoxImpl* impl); + +protected: + virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE; + +private: + wxFileDialogCheckBoxImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogCheckBox); +}; + +// A class representing a custom radio button. +class WXDLLIMPEXP_CORE wxFileDialogRadioButton : public wxFileDialogCustomControl +{ +public: + bool GetValue() const; + void SetValue(bool value); + + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogRadioButton(wxFileDialogRadioButtonImpl* impl); + +protected: + virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE; + +private: + wxFileDialogRadioButtonImpl* GetImpl() const; + + 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 +{ +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 WXDLLIMPEXP_CORE 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 WXDLLIMPEXP_CORE wxFileDialogCustomize +{ +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); + + ~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) + { + } + + wxVector m_controls; + +private: + template T* StoreAndReturn(T* control); + + wxFileDialogCustomizeImpl* const m_impl; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogCustomize); +}; + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomizeHook: used by wxFileDialog itself +// ---------------------------------------------------------------------------- + +class WXDLLIMPEXP_CORE 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/msw/filedlg.h b/include/wx/msw/filedlg.h index 37a9aca01a..0c9ba46228 100644 --- a/include/wx/msw/filedlg.h +++ b/include/wx/msw/filedlg.h @@ -11,6 +11,8 @@ #ifndef _WX_FILEDLG_H_ #define _WX_FILEDLG_H_ +class wxFileDialogMSWData; + //------------------------------------------------------------------------- // wxFileDialog //------------------------------------------------------------------------- @@ -27,26 +29,14 @@ public: const wxPoint& pos = wxDefaultPosition, const wxSize& sz = wxDefaultSize, const wxString& name = wxASCII_STR(wxFileDialogNameStr)); + virtual ~wxFileDialog(); virtual void GetPaths(wxArrayString& paths) const wxOVERRIDE; virtual void GetFilenames(wxArrayString& files) const wxOVERRIDE; virtual bool SupportsExtraControl() const wxOVERRIDE { return true; } - void MSWOnInitDialogHook(WXHWND hwnd); virtual int ShowModal() wxOVERRIDE; - // wxMSW-specific implementation from now on - // ----------------------------------------- - - // called from the hook procedure on CDN_INITDONE reception - virtual void MSWOnInitDone(WXHWND hDlg); - - // called from the hook procedure on CDN_SELCHANGE. - void MSWOnSelChange(WXHWND hDlg); - - // called from the hook procedure on CDN_TYPECHANGE. - void MSWOnTypeChange(WXHWND hDlg, int nFilterIndex); - protected: virtual void DoMoveWindow(int x, int y, int width, int height) wxOVERRIDE; @@ -55,12 +45,38 @@ protected: virtual void DoGetPosition( int *x, int *y ) const wxOVERRIDE; private: + // Allow it to call MSWOnXXX() functions below. + friend class wxFileDialogMSWData; + + // called when the dialog is created + void MSWOnInitDialogHook(WXHWND hwnd); + + // called when the dialog initialization is fully done + void MSWOnInitDone(WXHWND hDlg); + + // called when the currently selected file changes in the dialog + void MSWOnSelChange(const wxString& selectedFilename); + + // called when the currently selected type of files changes in the dialog + void MSWOnTypeChange(int nFilterIndex); + + // The real implementation of ShowModal() using traditional common dialog + // functions. + int ShowCommFileDialog(WXHWND owner); + + // And another one using IFileDialog. + int ShowIFileDialog(WXHWND owner); + + // Get the data object, allocating it if necessary. + wxFileDialogMSWData& MSWData(); + + wxArrayString m_fileNames; - // remember if our SetPosition() or Centre() (which requires special - // treatment) was called - bool m_bMovedWindow; - int m_centreDir; // nothing to do if 0 + // Extra data, possibly null if not needed, use MSWData() to access it if + // it should be created on demand. + wxFileDialogMSWData* m_data; + wxDECLARE_DYNAMIC_CLASS(wxFileDialog); wxDECLARE_NO_COPY_CLASS(wxFileDialog); diff --git a/include/wx/msw/ole/comimpl.h b/include/wx/msw/ole/comimpl.h index 3d21bff916..3f8b1c7996 100644 --- a/include/wx/msw/ole/comimpl.h +++ b/include/wx/msw/ole/comimpl.h @@ -39,6 +39,9 @@ extern WXDLLIMPEXP_CORE bool IsIidFromList(REFIID riid, const IID *aIids[], size // ============================================================================ /* + WARNING: This does NOT work with multiple inheritance, so multiple + interfaces can only be supported when they inherit from each other. + The most dumb implementation of IUnknown methods. We don't support aggregation nor containment, but for 99% of cases this simple implementation is quite enough. diff --git a/include/wx/msw/private/filedialog.h b/include/wx/msw/private/filedialog.h new file mode 100644 index 0000000000..2f92093f5c --- /dev/null +++ b/include/wx/msw/private/filedialog.h @@ -0,0 +1,83 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/msw/private/filedialog.h +// Purpose: IFileDialog-related functions +// Author: Vadim Zeitlin +// Created: 2022-05-15 +// Copyright: (c) 2022 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_MSW_PRIVATE_FILEDIALOG_H_ +#define _WX_MSW_PRIVATE_FILEDIALOG_H_ + +#include "wx/msw/private.h" +#include "wx/msw/wrapshl.h" + +// We want to use IFileDialog if either wxDirDialog or wxFileDialog are used. +// +// IFileOpenDialog implementation needs wxDynamicLibrary for +// run-time linking SHCreateItemFromParsingName(), available +// only under Windows Vista and newer. +// It also needs a compiler providing declarations and definitions +// of interfaces available in Windows Vista. +// And it needs OLE support to actually use these interfaces. +#if (wxUSE_DIRDLG || wxUSE_FILEDLG) && wxUSE_DYNLIB_CLASS && wxUSE_OLE && \ + defined(__IFileOpenDialog_INTERFACE_DEFINED__) + #define wxUSE_IFILEOPENDIALOG 1 +#else + #define wxUSE_IFILEOPENDIALOG 0 +#endif + +#if wxUSE_IFILEOPENDIALOG + +#include "wx/msw/private/comptr.h" + +namespace wxMSWImpl +{ + +// For historical reasons, this class is defined in src/msw/dirdlg.cpp. +class wxIFileDialog +{ +public: + // Create the dialog of the specified type. + // + // CLSID must be either CLSID_FileOpenDialog or CLSID_FileSaveDialog. + // + // Use IsOk() to check if the dialog was created successfully. + explicit wxIFileDialog(const CLSID& clsid); + + // If this returns false, the dialog can't be used at all. + bool IsOk() const { return m_fileDialog.Get() != NULL; } + + // Set the dialog title. + void SetTitle(const wxString& title); + + // Set the initial path to show in the dialog. + void SetInitialPath(const wxString& path); + + // Show the file dialog with the given parent window and options. + // + // Returns the selected path, or paths, in the provided output parameters, + // depending on whether FOS_ALLOWMULTISELECT is part of the options. + // + // The return value is wxID_OK if any paths were returned, wxID_CANCEL if the + // dialog was cancelled. + int + Show(HWND owner, int options, wxArrayString* pathsOut, wxString* pathOut); + + // Behave as IFileDialog. + IFileDialog* Get() const { return m_fileDialog.Get(); } + IFileDialog* operator->() const { return m_fileDialog.Get(); } + +private: + wxCOMPtr m_fileDialog; +}; + +// Extract the filesystem path corresponding to the given shell item. +HRESULT GetFSPathFromShellItem(const wxCOMPtr& item, wxString& path); + +} // namespace wxMSWImpl + +#endif // wxUSE_IFILEOPENDIALOG + +#endif // _WX_MSW_PRIVATE_FILEDIALOG_H_ diff --git a/include/wx/private/filedlgcustomize.h b/include/wx/private/filedlgcustomize.h new file mode 100644 index 0000000000..ffa8ec3192 --- /dev/null +++ b/include/wx/private/filedlgcustomize.h @@ -0,0 +1,85 @@ +/////////////////////////////////////////////////////////////////////////////// +// 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 bool DoBind(wxEvtHandler* handler); + + 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 wxFileDialogRadioButtonImpl : public wxFileDialogCustomControlImpl +{ +public: + virtual bool GetValue() = 0; + 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: + 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 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; + + virtual ~wxFileDialogCustomizeImpl(); +}; + +#endif // _WX_PRIVATE_FILEDLGCUSTOMIZE_H_ diff --git a/interface/wx/filedlg.h b/interface/wx/filedlg.h index cd53f73e22..1f3c835e09 100644 --- a/interface/wx/filedlg.h +++ b/interface/wx/filedlg.h @@ -94,7 +94,9 @@ const char wxFileSelectorDefaultWildcardStr[]; } @endcode - @remarks + + @section filedialog_filters Wildcard Filters + All implementations of the wxFileDialog provide a wildcard filter. Typing a filename containing wildcards (*, ?) in the filename text item, and clicking on Ok, will result in only those files matching the pattern being displayed. @@ -103,13 +105,9 @@ const char wxFileSelectorDefaultWildcardStr[]; @code "BMP and GIF files (*.bmp;*.gif)|*.bmp;*.gif|PNG files (*.png)|*.png" @endcode - It must be noted that wildcard support in the native Motif file dialog is quite - limited: only one file type is supported, and it is displayed without the - descriptive test; "BMP files (*.bmp)|*.bmp" is displayed as "*.bmp", and both - "BMP files (*.bmp)|*.bmp|GIF files (*.gif)|*.gif" and "Image files|*.bmp;*.gif" - are errors. + On Mac macOS in the open file dialog the filter choice box is not shown by default. - Instead all given wildcards are appplied at the same time: So in the above + Instead all given wildcards are applied at the same time: So in the above example all bmp, gif and png files are displayed. To enforce the display of the filter choice set the corresponding wxSystemOptions before calling the file open dialog: @@ -121,6 +119,33 @@ const char wxFileSelectorDefaultWildcardStr[]; matching all file types. The files which does not match the currently selected file type are greyed out and are not selectable. + + @section filedialog_customize Dialog Customization + + Uniquely among the other standard dialogs, wxFileDialog can be customized + by adding extra controls to it. Moreover, there are two ways to do it: the + first one is to define a callback function and use SetExtraControlCreator() + to tell the dialog to call it, while the second one requires defining a + class inheriting from wxFileDialogCustomizeHook and implementing its + virtual functions, notably wxFileDialogCustomizeHook::AddCustomControls() + where the extra controls have to be created, and finally calling + SetCustomizeHook() with this custom hook object. + + The first approach is somewhat simpler and more flexible, as it allows to + create any kind of custom controls, but is not supported by the "new style" + (where "new" means used since Windows Vista, i.e. circa 2007) file dialogs + under MSW. Because of this, calling SetExtraControlCreator() in wxMSW + forces the use of old style (Windows XP) dialogs, that may look out of + place. The second approach is implemented by the MSW dialogs natively and + doesn't suffer from this limitation, so its use is recommended, especially + if the few simple control types supported by it (see wxFileDialogCustomize + for more information about the supported controls) are sufficient for your + needs. + + Both of the approaches to the dialog customization are demonstrated in the + @ref page_samples_dialogs, please check it for more details. + + @beginStyleTable @style{wxFD_DEFAULT_STYLE} Equivalent to @c wxFD_OPEN. @@ -322,6 +347,27 @@ public: */ virtual wxString GetWildcard() const; + /** + Set the hook to be used for customizing the dialog contents. + + This function can be called before calling ShowModal() to specify that + the dialog contents should be customized using the provided hook. See + wxFileDialogCustomizeHook documentation and @ref page_samples_dialogs + for the examples of using it. + + @note In order to define a custom hook object, @c wx/filedlgcustomize.h + must be included in addition to the usual @c wx/filedlg.h header. + + @param customizeHook The hook object that will be used by the dialog. + This object must remain valid at least until ShowModal() returns. + + @return @true if the hook was successfully set or @false if customizing + the file dialog is not supported by the current platform. + + @since 3.1.7 + */ + bool SetCustomizeHook(wxFileDialogCustomizeHook& customizeHook); + /** Sets the default directory. */ @@ -343,6 +389,11 @@ public: The @c creator function should take pointer to parent window (file dialog) and should return a window allocated with operator new. + @note Using SetExtraControlCreator() in wxMSW forces the use of "old + style" (Windows XP-like) file dialogs, instead of the newer + (Vista-like) ones and is not recommended for this reason. Prefer to + use SetCustomizeHook() instead. + @since 2.9.0 */ bool SetExtraControlCreator(ExtraControlCreatorFunction creator); diff --git a/interface/wx/filedlgcustomize.h b/interface/wx/filedlgcustomize.h new file mode 100644 index 0000000000..e49bbbbf44 --- /dev/null +++ b/interface/wx/filedlgcustomize.h @@ -0,0 +1,397 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: interface/wx/filedlgcustomize.h +// Purpose: Documentation of classes used for wxFileDialog customization. +// Author: Vadim Zeitlin +// Created: 2022-06-03 +// Copyright: (c) 2022 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +/** + The base class for all wxFileDialog custom controls. + + Unlike normal controls, inheriting from wxWindow, custom controls in + wxFileDialog are not actual windows, but they do provide some window-like + operations, and can be disabled or hidden, just as the windows are. + + Also, similarly to the normal windows, objects of this and derived classes + belong to wxWidgets and must @e not be deleted by the application code. + + Unlike windows, custom controls cannot be created directly, but can only be + returned from wxFileDialogCustomize functions that are specifically + provided for creating them. + + @since 3.1.7 + */ +class wxFileDialogCustomControl : public wxEvtHandler +{ +public: + /// Show or hide this control. + void Show(bool show = true); + + /// Hide this control. + /// + /// This is equivalent to @c Show(false). + void Hide(); + + /// Enable or disable this control. + void Enable(bool enable = true); + + /// Disable this control. + /// + /// This is equivalent to @c Enable(false). + void Disable(); +}; + +/** + Represents a custom button inside wxFileDialog. + + Objects of this class can only be created by + wxFileDialogCustomize::AddButton(). + + It is possible to bind to wxEVT_BUTTON events on this object, which will be + generated when the button is clicked. + + See wxFileDialogCustomControl for more information. + + @since 3.1.7 + */ +class wxFileDialogButton : public wxFileDialogCustomControl +{ +}; + +/** + Represents a custom checkbox inside wxFileDialog. + + Objects of this class can only be created by + wxFileDialogCustomize::AddCheckBox(). + + It is possible to bind to wxEVT_CHECKBOX events on this object, which will + be generated when the checkbox is clicked. + + See wxFileDialogCustomControl for more information. + + @since 3.1.7 + */ +class wxFileDialogCheckBox : public wxFileDialogCustomControl +{ +public: + /// Return @true if the checkbox is checked. + bool GetValue() const; + + /// Check or uncheck the checkbox. + void SetValue(bool value); +}; + +/** + Represents a custom radio button inside wxFileDialog. + + Objects of this class can only be created by + wxFileDialogCustomize::AddRadioButton(). + + It is possible to bind to wxEVT_RADIOBUTTON events on this object, which + will be generated when the radio button is selected. + + See wxFileDialogCustomControl for more information. + + @since 3.1.7 + */ +class wxFileDialogRadioButton : public wxFileDialogCustomControl +{ +public: + /// Return @true if the radio button is selected. + bool GetValue() const; + + /// Select the value of the radio button. + /// + /// Using @false for @a value is not supported, this argument only exists + /// for consistency with wxRadioButton::SetValue(). + 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. + + Objects of this class can only be created by + wxFileDialogCustomize::AddTextCtrl(). + + Objects of this class don't generate any events currently. + + See wxFileDialogCustomControl for more information. + + @since 3.1.7 + */ +class wxFileDialogTextCtrl : public wxFileDialogCustomControl +{ +public: + /// Get the current value entered into the control. + wxString GetValue() const; + + /// Set the current control value. + void SetValue(const wxString& text); +}; + +/** + Represents a custom static text inside wxFileDialog. + + Objects of this class can only be created by + wxFileDialogCustomize::AddStaticText(). + + Objects of this class don't generate any events. + + See wxFileDialogCustomControl for more information. + + @since 3.1.7 + */ +class wxFileDialogStaticText : public wxFileDialogCustomControl +{ +public: + /** + Set the text shown in the label. + + Any ampersands in the @a text will be escaped, there is no need to do + it manually, e.g. using wxControl::EscapeMnemonics(). + */ + void SetLabelText(const wxString& text); +}; + + +/** + Used with wxFileDialogCustomizeHook to add custom controls to wxFileDialog. + + An object of this class is passed to wxFileDialogCustomizeHook::AddCustomControls() + to allow it to actually add controls to the dialog. + + The pointers returned by the functions of this class belong to wxWidgets + and should @e not be deleted by the application, just as wxWindow-derived + objects (even if these controls do not inherit from wxWindow). These + pointers become invalid when wxFileDialog::ShowModal() returns, and the + dialog containing them is destroyed, and the latest point at which they can + be still used is when wxFileDialogCustomizeHook::TransferDataFromCustomControls() + is called. + + @library{wxcore} + @category{cmndlg} + + @see wxFileDialog + + @since 3.1.7 + */ +class wxFileDialogCustomize +{ +public: + /** + Add a button with the specified label. + */ + wxFileDialogButton* AddButton(const wxString& label); + + /** + Add a checkbox with the specified label. + */ + wxFileDialogCheckBox* AddCheckBox(const wxString& label); + + /** + Add a radio button with the specified label. + + The first radio button added will be initially checked. All the radio + buttons added immediately after it will become part of the same radio + group and will not be checked, but checking any one of them later will + uncheck the first button and all the other ones. + + If two consecutive but distinct radio groups are required, + AddStaticText() with an empty label can be used to separate them. + */ + 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. + + Unlike all the other functions for adding controls, the @a label + parameter here doesn't specify the contents of the text control itself, + but rather the label appearing before it. Unlike static controls added + by AddStaticText(), this label is guaranteed to be immediately adjacent + to it. + + If @a label is empty, no label is created. + */ + wxFileDialogTextCtrl* AddTextCtrl(const wxString& label = wxString()); + + /** + Add a static text with the given contents. + + The contents of the static text can be updated later, i.e. it doesn't + need to be actually static. + */ + wxFileDialogStaticText* AddStaticText(const wxString& label); +}; + +/** + Base class for customization hooks used with wxFileDialog. + + wxFileDialogCustomizeHook is an abstract base class, i.e. in order to use a + concrete class inheriting from it and implementing its pure virtual + AddCustomControls() function must be defined. Then an object of this class + should be passed to wxFileDialog::SetCustomizeHook(), which will result in + its AddCustomControls() being called before the dialog is shown, + UpdateCustomControls() being called whenever something changes in the + dialog while it is shown and, finally, TransferDataFromCustomControls() + being called when the user accepts their choice in the dialog. + + Putting all this together, here is an example of customizing the file + dialog using this class: + @code + class EncryptHook : public wxFileDialogCustomizeHook + { + public: + // Override to add custom controls using the provided customizer object. + void AddCustomControls(wxFileDialogCustomize& customizer) override + { + // Suppose we can encrypt files when saving them. + m_checkbox = customizer.AddCheckBox("Encrypt"); + + // While m_checkbox is not a wxCheckBox, it looks almost like one + // and, in particular, we can bind to custom control events as usual. + m_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { + // We can also call wxWindow-like functions on them. + m_button->Enable(event.IsChecked()); + }); + + // The encryption parameters can be edited in a dedicated dialog. + m_button = customizer.AddButton("Parameters..."); + m_button->Bind(wxEVT_BUTTON, [](wxCommandEvent&) { + ... show the encryption parameters dialog here ... + }); + } + + // Override to save the values of the custom controls. + void TransferDataFromCustomControls() override + { + // Save the checkbox value, as we won't be able to use it any more + // once this function returns. + m_encrypt = m_checkbox->GetValue(); + } + + // Just a simple accessor to get the results. + bool Encrypt() const { return m_encrypt; } + + private: + wxFileDialogButton* m_button; + wxFileDialogCheckBox* m_checkbox; + + bool m_encrypt = false; + }; + + void SomeFunc() + { + wxFileDialog dialog(NULL, "Save document", wxString(), "file.my", + "My files (*.my)|*.my", + wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + // This object may be destroyed before the dialog, but must remain + // alive until ShowModal() returns. + EncryptHook customizeHook; + + if ( dialog.ShowModal() == wxID_OK ) + { + if ( customizeHook.Encrypt() ) + ... save with encryption ... + else + ... save without encryption ... + } + } + @endcode + + @library{wxcore} + @category{cmndlg} + + @see wxFileDialog + + @since 3.1.7 +*/ +class wxFileDialogCustomizeHook +{ +public: + /** + Must be overridden to add custom controls to the dialog using the + provided customizer object. + + Call wxFileDialogCustomize functions to add controls and possibly bind + to their events. + + Note that there is no possibility to define the custom controls layout, + they will appear more or less consecutively, but the exact layout is + determined by the current platform. + */ + virtual void AddCustomControls(wxFileDialogCustomize& customizer) = 0; + + /** + May be overridden to update the custom controls whenever something + changes in the dialog. + + This function is called when the user selects a file, changes the + directory or changes the current filter in the dialog, for example. + It can be used to update the custom controls state depending on the + currently selected file, for example. + + Note that it is @e not necessarily called when the value of a custom + control changes. + + Base class version does nothing. + */ + virtual void UpdateCustomControls(); + + /** + Should typically be overridden to save the values of the custom + controls when the dialog is accepted. + + Custom controls are destroyed and cannot be used any longer once + wxFileDialog::ShowModal() returns, so their values must be retrieved in + this function, which is called just before this happens. + + This function is @e not called if the user cancels the dialog. + + Base class version does nothing. + */ + virtual void TransferDataFromCustomControls(); +}; diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index c12adb2731..81458a6d06 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,42 @@ 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; +} + +// 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 { @@ -1569,7 +1617,8 @@ public: MyExtraPanel(wxWindow *parent); wxString GetInfo() const { - return wxString::Format("checkbox=%d, text=\"%s\"", m_checked, m_str); + return wxString::Format("paper=%s (%s), enabled=%d, text=\"%s\"", + m_paperSize, m_paperOrient, m_checked, m_str); } private: @@ -1579,6 +1628,21 @@ private: m_btn->Enable(m_checked); } + void OnRadioButton(wxCommandEvent& event) + { + if ( event.GetEventObject() == m_radioPortrait ) + m_paperOrient = "portrait"; + else if ( event.GetEventObject() == m_radioLandscape ) + m_paperOrient = "landscape"; + else + m_paperOrient = "unknown"; + } + + void OnChoice(wxCommandEvent& event) + { + m_paperSize = GetFileDialogPaperSize(event.GetSelection()); + } + void OnText(wxCommandEvent& event) { m_str = event.GetString(); @@ -1590,32 +1654,19 @@ 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; bool m_checked; + wxString m_paperSize; + wxString m_paperOrient; wxButton *m_btn; wxCheckBox *m_cb; + wxRadioButton *m_radioPortrait; + wxRadioButton *m_radioLandscape; wxStaticText *m_label; wxTextCtrl *m_text; }; @@ -1629,6 +1680,15 @@ 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); + m_radioLandscape = new wxRadioButton(this, wxID_ANY, "&Landscape"); + m_radioLandscape->Bind(wxEVT_RADIOBUTTON, &MyExtraPanel::OnRadioButton, this); m_label = new wxStaticText(this, wxID_ANY, "Nothing selected"); m_label->Bind(wxEVT_UPDATE_UI, &MyExtraPanel::OnUpdateLabelUI, this); @@ -1641,6 +1701,9 @@ 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()); sizerTop->AddSpacer(5); sizerTop->Add(m_btn, wxSizerFlags().Centre().Border()); @@ -1656,6 +1719,95 @@ 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: + // Normally we would just use wxFileDialog, but this sample allows using + // both the real wxFileDialog and wxGenericFileDialog, so allow passing + // either of them here. + explicit MyCustomizeHook(wxFileDialogBase& 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. + 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"); + m_cb->Bind(wxEVT_CHECKBOX, &MyCustomizeHook::OnCheckBox, this); + m_btn = customizer.AddButton("Custom Button"); + m_btn->Bind(wxEVT_BUTTON, &MyCustomizeHook::OnButton, this); + 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()); + + // Enable radio buttons only if a file is selected. + bool hasFile = wxFileName::FileExists( + m_dialog->GetCurrentlySelectedFilename() + ); + m_radioPortrait->Enable(hasFile); + m_radioLandscape->Enable(hasFile); + + // 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("paper=%s (%s), enabled=%d, text=\"%s\"", + GetFileDialogPaperSize(m_choiceSize->GetSelection()), + m_radioPortrait->GetValue() ? "portrait" : "landscape", + 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: + void OnCheckBox(wxCommandEvent& event) + { + m_btn->Enable(event.IsChecked()); + } + + void OnButton(wxCommandEvent& WXUNUSED(event)) + { + wxMessageBox("Custom button pressed", "wxWidgets dialogs sample", + wxOK | wxICON_INFORMATION, m_dialog); + } + + wxFileDialogBase* const m_dialog; + + wxFileDialogButton* m_btn; + wxFileDialogCheckBox* m_cb; + wxFileDialogChoice* m_choiceSize; + wxFileDialogRadioButton* m_radioPortrait; + wxFileDialogRadioButton* m_radioLandscape; + wxFileDialogTextCtrl* m_text; + wxFileDialogStaticText* m_label; + + wxString m_info; + + wxDECLARE_NO_COPY_CLASS(MyCustomizeHook); +}; + void MyFrame::FileOpen(wxCommandEvent& WXUNUSED(event) ) { wxFileDialog dialog @@ -1672,14 +1824,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 +1868,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(); } @@ -1741,6 +1921,8 @@ void MyFrame::FilesOpen(wxCommandEvent& WXUNUSED(event) ) wxEmptyString, wxEmptyString, wildcards, wxFD_OPEN|wxFD_MULTIPLE); + dialog.Centre(wxCENTER_ON_SCREEN); + if (dialog.ShowModal() == wxID_OK) { wxArrayString paths, filenames; @@ -1886,7 +2068,8 @@ void MyFrame::FileOpenGeneric(wxCommandEvent& WXUNUSED(event) ) "C++ files (*.cpp;*.h)|*.cpp;*.h" ); - dialog.SetExtraControlCreator(&createMyExtraPanel); + MyCustomizeHook myCustomizer(dialog); + dialog.SetCustomizeHook(myCustomizer); dialog.SetDirectory(wxGetHomeDir()); if (dialog.ShowModal() == wxID_OK) @@ -1894,10 +2077,12 @@ void MyFrame::FileOpenGeneric(wxCommandEvent& WXUNUSED(event) ) wxString info; info.Printf("Full file name: %s\n" "Path: %s\n" - "Name: %s", + "Name: %s\n" + "Custom window: %s", dialog.GetPath(), dialog.GetDirectory(), - dialog.GetFilename()); + dialog.GetFilename(), + myCustomizer.GetInfo()); 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..71370891ad 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -21,9 +21,21 @@ #ifndef WX_PRECOMP #include "wx/string.h" #include "wx/intl.h" + #include "wx/panel.h" + #include "wx/sizer.h" #include "wx/window.h" + + #include "wx/button.h" + #include "wx/checkbox.h" + #include "wx/choice.h" + #include "wx/radiobut.h" + #include "wx/stattext.h" + #include "wx/textctrl.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 +46,689 @@ 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() +{ +} + +bool +wxFileDialogCustomControlImpl::DoBind(wxEvtHandler* WXUNUSED(handler)) +{ + // Do nothing here by default, some controls don't generate any events at + // all and so this function will never be called for them. + wxFAIL_MSG(wxS("Should be overridden if called")); + + return false; +} + +bool wxFileDialogCustomControl::OnDynamicBind(wxDynamicEventTableEntry& entry) +{ + wxUnusedVar(entry); // Needed when debug is disabled. + + wxFAIL_MSG(wxString::Format + ( + "This custom control doesn't generate the event %d.", + entry.m_eventType + )); + + return false; +} + +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) +{ +} + +bool wxFileDialogButton::OnDynamicBind(wxDynamicEventTableEntry& entry) +{ + if ( entry.m_eventType == wxEVT_BUTTON ) + return GetImpl()->DoBind(this); + + return wxFileDialogCustomControl::OnDynamicBind(entry); +} + +wxFileDialogButtonImpl* wxFileDialogButton::GetImpl() const +{ + return static_cast(m_impl); +} + +wxFileDialogCheckBox::wxFileDialogCheckBox(wxFileDialogCheckBoxImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +bool wxFileDialogCheckBox::OnDynamicBind(wxDynamicEventTableEntry& entry) +{ + if ( entry.m_eventType == wxEVT_CHECKBOX ) + return GetImpl()->DoBind(this); + + return wxFileDialogCustomControl::OnDynamicBind(entry); +} + +wxFileDialogCheckBoxImpl* wxFileDialogCheckBox::GetImpl() const +{ + return static_cast(m_impl); +} + +bool wxFileDialogCheckBox::GetValue() const +{ + return GetImpl()->GetValue(); +} + +void wxFileDialogCheckBox::SetValue(bool value) +{ + GetImpl()->SetValue(value); +} + +wxFileDialogRadioButton::wxFileDialogRadioButton(wxFileDialogRadioButtonImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +bool wxFileDialogRadioButton::OnDynamicBind(wxDynamicEventTableEntry& entry) +{ + if ( entry.m_eventType == wxEVT_RADIOBUTTON ) + return GetImpl()->DoBind(this); + + return wxFileDialogCustomControl::OnDynamicBind(entry); +} + +wxFileDialogRadioButtonImpl* wxFileDialogRadioButton::GetImpl() const +{ + return static_cast(m_impl); +} + +bool wxFileDialogRadioButton::GetValue() const +{ + return GetImpl()->GetValue(); +} + +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) +{ +} + +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))); +} + +wxFileDialogRadioButton* +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) +{ + return StoreAndReturn(new wxFileDialogTextCtrl(m_impl->AddTextCtrl(label))); +} + +wxFileDialogStaticText* +wxFileDialogCustomize::AddStaticText(const wxString& label) +{ + return StoreAndReturn(new wxFileDialogStaticText(m_impl->AddStaticText(label))); +} + +// ---------------------------------------------------------------------------- +// Generic implementation of wxFileDialogCustomize and related classes +// ---------------------------------------------------------------------------- + +namespace wxGenericCustomizer +{ + +// Template base class for the implementation classes below inheriting from the +// specified Impl subclass. +template +class ControlImplBase : public T +{ +public: + explicit ControlImplBase(wxWindow* win) + : m_win(win) + { + } + + virtual void Show(bool show) wxOVERRIDE + { + m_win->Show(show); + } + + virtual void Enable(bool enable) wxOVERRIDE + { + m_win->Enable(enable); + } + + // Leave it public for Panel to access. + wxWindow* const m_win; + + wxDECLARE_NO_COPY_TEMPLATE_CLASS(ControlImplBase, T); +}; + +class CustomControlImpl : public ControlImplBase +{ +public: + // All custom controls are identified by their ID in this implementation. + explicit CustomControlImpl(wxWindow* win) + : ControlImplBase(win) + { + } + + wxDECLARE_NO_COPY_CLASS(CustomControlImpl); +}; + +class ButtonImpl : public ControlImplBase +{ +public: + ButtonImpl(wxWindow* parent, const wxString& label) + : ControlImplBase + ( + new wxButton(parent, wxID_ANY, label) + ) + { + m_handler = NULL; + } + + virtual bool DoBind(wxEvtHandler* handler) wxOVERRIDE + { + if ( !m_handler ) + { + m_handler = handler; + m_win->Bind(wxEVT_BUTTON, &ButtonImpl::OnButton, this); + } + + return true; + } + +private: + void OnButton(wxCommandEvent& event) + { + // Pretend that the event is coming from the custom control and not the + // actual wx control implementing it. + + // Make a copy of the event to set the event object correctly. + wxCommandEvent eventCopy(event); + eventCopy.SetEventObject(m_handler); + + m_handler->ProcessEvent(eventCopy); + + // We don't need to do anything about skipping, vetoing etc as all this + // is not used anyhow for simple command events. + } + + wxEvtHandler* m_handler; +}; + +class CheckBoxImpl : public ControlImplBase +{ +public: + CheckBoxImpl(wxWindow* parent, const wxString& label) + : ControlImplBase + ( + new wxCheckBox(parent, wxID_ANY, label) + ) + { + m_handler = NULL; + } + + virtual bool GetValue() wxOVERRIDE + { + return GetCheckBox()->GetValue(); + } + + virtual void SetValue(bool value) wxOVERRIDE + { + GetCheckBox()->SetValue(value); + } + + virtual bool DoBind(wxEvtHandler* handler) wxOVERRIDE + { + if ( !m_handler ) + { + m_handler = handler; + m_win->Bind(wxEVT_CHECKBOX, &CheckBoxImpl::OnCheckBox, this); + } + + return true; + } + +private: + wxCheckBox* GetCheckBox() const + { + return static_cast(m_win); + } + + void OnCheckBox(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 RadioButtonImpl : public ControlImplBase +{ +public: + RadioButtonImpl(wxWindow* parent, const wxString& label) + : ControlImplBase + ( + new wxRadioButton(parent, wxID_ANY, label) + ) + { + m_handler = NULL; + } + + virtual bool GetValue() wxOVERRIDE + { + return GetRadioButton()->GetValue(); + } + + virtual void SetValue(bool value) wxOVERRIDE + { + GetRadioButton()->SetValue(value); + } + + virtual bool DoBind(wxEvtHandler* handler) wxOVERRIDE + { + if ( !m_handler ) + { + m_handler = handler; + m_win->Bind(wxEVT_RADIOBUTTON, &RadioButtonImpl::OnRadioButton, this); + } + + return true; + } + +private: + wxRadioButton* GetRadioButton() const + { + return static_cast(m_win); + } + + void OnRadioButton(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 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: + // The dummy argument is there just for consistency with the other classes + // and allows to keep the code simple even without vararg templates support. + explicit TextCtrlImpl(wxWindow* parent, const wxString& WXUNUSED(dummy)) + : ControlImplBase + ( + new wxTextCtrl(parent, wxID_ANY) + ) + { + } + + virtual wxString GetValue() wxOVERRIDE + { + return GetText()->GetValue(); + } + + virtual void SetValue(const wxString& value) wxOVERRIDE + { + // Don't use SetValue(), we don't need any extra events here. + return GetText()->ChangeValue(value); + } + +private: + wxTextCtrl* GetText() const + { + return static_cast(m_win); + } +}; + +class StaticTextImpl : public ControlImplBase +{ +public: + StaticTextImpl(wxWindow* parent, const wxString& label) + : ControlImplBase + ( + new wxStaticText(parent, wxID_ANY, wxControl::EscapeMnemonics(label)) + ) + { + } + + virtual void SetLabelText(const wxString& text) wxOVERRIDE + { + GetStaticText()->SetLabelText(text); + + wxWindow* const parent = m_win->GetParent(); + parent->GetSizer()->Fit(parent); + } + +private: + wxStaticText* GetStaticText() const + { + return static_cast(m_win); + } +}; + +// Generic implementation of wxFileDialogCustomize which is also a window that +// can be used as part of the dialog. +class Panel : public wxPanel, + public wxFileDialogCustomize, + private wxFileDialogCustomizeImpl +{ +public: + Panel(wxWindow* parent, wxFileDialogCustomizeHook& customizeHook) + : wxPanel(parent), + wxFileDialogCustomize(this), + m_customizeHook(customizeHook), + m_lastWasRadio(false) + { + // Use a simple horizontal sizer to layout all the controls for now. + wxBoxSizer* const sizer = new wxBoxSizer(wxHORIZONTAL); + SetSizer(sizer); + + // Leave a margin before the first item. + sizer->AddSpacer(wxSizerFlags::GetDefaultBorder()); + + // This will call our own AddXXX(). + m_customizeHook.AddCustomControls(*this); + + // Now that everything was created, resize and layout. + SetClientSize(sizer->ComputeFittingClientSize(this)); + sizer->Layout(); + } + + virtual ~Panel() + { + m_customizeHook.TransferDataFromCustomControls(); + } + + + // Implement wxFileDialogCustomizeImpl pure virtual methods. + wxFileDialogButtonImpl* AddButton(const wxString& label) wxOVERRIDE + { + m_lastWasRadio = false; + + return AddToLayoutAndReturn(label); + } + + wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) wxOVERRIDE + { + m_lastWasRadio = false; + + return AddToLayoutAndReturn(label); + } + + wxFileDialogRadioButtonImpl* AddRadioButton(const wxString& label) wxOVERRIDE + { + RadioButtonImpl* const impl = AddToLayoutAndReturn(label); + if ( !m_lastWasRadio ) + { + // Select the first button of a new radio group. + impl->SetValue(true); + + m_lastWasRadio = true; + } + + 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; + + if ( !label.empty() ) + { + AddToLayout(new wxStaticText(this, wxID_ANY, label)); + } + + return AddToLayoutAndReturn(); + } + + wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) wxOVERRIDE + { + m_lastWasRadio = false; + + return AddToLayoutAndReturn(label); + } + +private: + void AddToLayout(wxWindow* win) + { + GetSizer()->Add(win, wxSizerFlags().Center().Border(wxRIGHT)); + } + + template + T* AddToLayoutAndReturn(const wxString& label = wxString()) + { + T* const controlImpl = new T(this, label); + + AddToLayout(controlImpl->m_win); + + return controlImpl; + } + + + wxFileDialogCustomizeHook& m_customizeHook; + + bool m_lastWasRadio; + + wxDECLARE_NO_COPY_CLASS(Panel); +}; + +} // namespace wxGenericCustomizer + //---------------------------------------------------------------------------- // wxFileDialogBase //---------------------------------------------------------------------------- @@ -45,6 +740,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 +859,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, @@ -172,28 +880,34 @@ bool wxFileDialogBase::SetExtraControlCreator(ExtraControlCreatorFunction creato return SupportsExtraControl(); } -bool wxFileDialogBase::CreateExtraControl() +wxWindow* wxFileDialogBase::CreateExtraControlWithParent(wxWindow* parent) const { - if (!m_extraControlCreator || m_extraControl) - return false; - m_extraControl = (*m_extraControlCreator)(this); - return true; + if ( m_customizeHook ) + return new wxGenericCustomizer::Panel(parent, *m_customizeHook); + + if ( m_extraControlCreator ) + return (*m_extraControlCreator)(parent); + + // It's not an error to call this function if there are no extra controls + // to create, just do nothing in this case. + return NULL; } -wxSize wxFileDialogBase::GetExtraControlSize() +bool wxFileDialogBase::CreateExtraControl() { - if ( !m_extraControlCreator ) - return wxDefaultSize; + // We're not supposed to be called more than once normally, but just do + // nothing if we had already created the custom controls somehow. + if ( !m_extraControl ) + m_extraControl = CreateExtraControlWithParent(this); - // create the extra control in an empty dialog just to find its size: this - // is not terribly efficient but we do need to know the size before - // creating the native dialog and this seems to be the only way - wxDialog dlg(NULL, wxID_ANY, wxString()); - return (*m_extraControlCreator)(&dlg)->GetSize(); + return m_extraControl != NULL; } void wxFileDialogBase::UpdateExtraControlUI() { + if ( m_customizeHook ) + m_customizeHook->UpdateCustomControls(); + if ( m_extraControl ) m_extraControl->UpdateWindowUI(wxUPDATE_UI_RECURSE); } diff --git a/src/generic/filedlgg.cpp b/src/generic/filedlgg.cpp index 904c55ad8d..c32509dfb0 100644 --- a/src/generic/filedlgg.cpp +++ b/src/generic/filedlgg.cpp @@ -301,7 +301,17 @@ int wxGenericFileDialog::ShowModal() m_filectrl->SetDirectory(m_dir); - return wxDialog::ShowModal(); + const int rc = wxDialog::ShowModal(); + + // Destroy the extra controls before ShowModal() returns for consistency + // with the native implementations. + if (m_extraControl) + { + m_extraControl->Destroy(); + m_extraControl = NULL; + } + + return rc; } bool wxGenericFileDialog::Show( bool show ) @@ -402,6 +412,8 @@ void wxGenericFileDialog::OnUpdateButtonsUI(wxUpdateUIEvent& event) // wxFileCtrl ctor itself can generate idle events, so we need this test if ( m_filectrl ) event.Enable( !IsTopMostDir(m_filectrl->GetShownDirectory()) ); + + UpdateExtraControlUI(); } #ifdef wxHAS_GENERIC_FILEDIALOG diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index 9b6dd029c9..c3317402f0 100644 --- a/src/msw/dirdlg.cpp +++ b/src/msw/dirdlg.cpp @@ -19,11 +19,6 @@ // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" - -#if wxUSE_DIRDLG - -#if wxUSE_OLE - #include "wx/dirdlg.h" #include "wx/modalhook.h" @@ -34,26 +29,15 @@ #include "wx/app.h" // for GetComCtl32Version() #endif -#include "wx/msw/private.h" -#include "wx/msw/wrapshl.h" -#include "wx/msw/private/comptr.h" -#include "wx/msw/private/cotaskmemptr.h" -#include "wx/dynlib.h" +#include "wx/msw/private/filedialog.h" + +#if wxUSE_IFILEOPENDIALOG #include -// IFileOpenDialog implementation needs wxDynamicLibrary for -// run-time linking SHCreateItemFromParsingName(), available -// only under Windows Vista and newer. -// It also needs a compiler providing declarations and definitions -// of interfaces available in Windows Vista. -#if wxUSE_DYNLIB_CLASS && defined(__IFileOpenDialog_INTERFACE_DEFINED__) - #define wxUSE_IFILEOPENDIALOG 1 -#else - #define wxUSE_IFILEOPENDIALOG 0 -#endif +#include "wx/msw/private/cotaskmemptr.h" +#include "wx/dynlib.h" -#if wxUSE_IFILEOPENDIALOG // IFileDialog related declarations missing from some compilers headers. #if defined(__VISUALC__) @@ -68,6 +52,31 @@ DEFINE_GUID(IID_IShellItem, #endif // wxUSE_IFILEOPENDIALOG +// ---------------------------------------------------------------------------- +// private functions prototypes +// ---------------------------------------------------------------------------- + +#if wxUSE_IFILEOPENDIALOG + +namespace +{ + +// helper functions for wxDirDialog::ShowIFileOpenDialog() +bool GetPathsFromIFileOpenDialog(IFileOpenDialog* fileDialog, wxArrayString& paths); +bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path); + +} // anonymous namespace + +#endif // #if wxUSE_IFILEOPENDIALOG + +// Note that parts of this file related to IFileDialog are still compiled even +// when wxUSE_DIRDLG == 0 because they're used by wxUSE_FILEDLG too. +#if wxUSE_DIRDLG + +// callback used in wxDirDialog::ShowSHBrowseForFolder() +static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, + LPARAM pData); + // ---------------------------------------------------------------------------- // constants // ---------------------------------------------------------------------------- @@ -82,26 +91,6 @@ DEFINE_GUID(IID_IShellItem, wxIMPLEMENT_CLASS(wxDirDialog, wxDialog); -// ---------------------------------------------------------------------------- -// private functions prototypes -// ---------------------------------------------------------------------------- - -#if wxUSE_IFILEOPENDIALOG - -// helper functions for wxDirDialog::ShowIFileOpenDialog() -bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, - bool multipleSelection, bool showHidden, wxCOMPtr& fileDialog); -bool GetPathsFromIFileOpenDialog(const wxCOMPtr& fileDialog, bool multipleSelection, - wxArrayString& paths); -bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path); - -#endif // #if wxUSE_IFILEOPENDIALOG - -// callback used in wxDirDialog::ShowSHBrowseForFolder() -static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, - LPARAM pData); - - // ============================================================================ // implementation // ============================================================================ @@ -253,16 +242,69 @@ int wxDirDialog::ShowSHBrowseForFolder(WXHWND owner) int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) { - HRESULT hr = S_OK; - wxCOMPtr fileDialog; + wxMSWImpl::wxIFileDialog fileDialog(CLSID_FileOpenDialog); + if ( !fileDialog.IsOk() ) + return wxID_NONE; - if ( !InitIFileOpenDialog(m_message, m_path, HasFlag(wxDD_MULTIPLE), - HasFlag(wxDD_SHOW_HIDDEN), fileDialog) ) + fileDialog.SetTitle(m_message); + if ( !m_path.empty() ) + fileDialog.SetInitialPath(m_path); + + // We currently always use FOS_NOCHANGEDIR even if wxDD_CHANGE_DIR was + // specified because we change the directory ourselves in this case. + int options = FOS_PICKFOLDERS | FOS_NOCHANGEDIR; + if ( HasFlag(wxDD_MULTIPLE) ) + options |= FOS_ALLOWMULTISELECT; + if ( HasFlag(wxDD_SHOW_HIDDEN) ) + options |= FOS_FORCESHOWHIDDEN; + + return fileDialog.Show(owner, options, &m_paths, &m_path); +} + +#endif // wxUSE_IFILEOPENDIALOG + +#endif // wxUSE_DIRDLG + +#if wxUSE_IFILEOPENDIALOG + +// ---------------------------------------------------------------------------- +// Helper functions used by wxDirDialog and wxFileDialog. +// ---------------------------------------------------------------------------- + +namespace wxMSWImpl +{ + +wxIFileDialog::wxIFileDialog(const CLSID& clsid) +{ + HRESULT hr = ::CoCreateInstance + ( + clsid, + NULL, // no outer IUnknown + CLSCTX_INPROC_SERVER, + wxIID_PPV_ARGS(IFileOpenDialog, &m_fileDialog) + ); + if ( FAILED(hr) ) { - return wxID_NONE; // Failed to initialize the dialog + wxLogApiError(wxS("CoCreateInstance(CLSID_FileOpenDialog)"), hr); + } +} + +int wxIFileDialog::Show(HWND owner, int options, + wxArrayString* pathsOut, wxString* pathOut) +{ + wxCHECK_MSG( m_fileDialog, wxID_NONE, wxS("shouldn't be called") ); + + HRESULT hr; + + // allow to select only a file system object + hr = m_fileDialog->SetOptions(options | FOS_FORCEFILESYSTEM); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileOpenDialog::SetOptions"), hr); + return false; } - hr = fileDialog->Show(owner); + hr = m_fileDialog->Show(owner); if ( FAILED(hr) ) { if ( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) @@ -274,15 +316,24 @@ int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) wxLogApiError(wxS("IFileDialog::Show"), hr); } } - else if ( GetPathsFromIFileOpenDialog(fileDialog, HasFlag(wxDD_MULTIPLE), - m_paths) ) + else if ( options & FOS_ALLOWMULTISELECT ) { - if ( !HasFlag(wxDD_MULTIPLE) ) + wxCOMPtr fileOpenDialog; + hr = m_fileDialog->QueryInterface(wxIID_PPV_ARGS(IFileOpenDialog, &fileOpenDialog)); + if ( SUCCEEDED(hr) ) { - m_path = m_paths.Last(); + if ( GetPathsFromIFileOpenDialog(fileOpenDialog, *pathsOut) ) + return wxID_OK; } - - return wxID_OK; + else + { + wxLogApiError(wxS("IFileDialog::QI(IFileOpenDialog)"), hr); + } + } + else // Single selection only, path output parameter must be non-null. + { + if ( GetPathFromIFileDialog(m_fileDialog, *pathOut) ) + return wxID_OK; } // Failed to show the dialog or obtain the selected folders(s) @@ -290,160 +341,124 @@ int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) return wxID_CANCEL; } -// ---------------------------------------------------------------------------- -// private functions -// ---------------------------------------------------------------------------- - -// helper function for wxDirDialog::ShowIFileOpenDialog() -bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, - bool multipleSelection, bool showHidden, - wxCOMPtr& fileDialog) +void wxIFileDialog::SetTitle(const wxString& message) { - HRESULT hr = S_OK; - wxCOMPtr dlg; - // allow to select only a file system folder, do not change the CWD - long options = FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_NOCHANGEDIR; - - hr = ::CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, - wxIID_PPV_ARGS(IFileOpenDialog, &dlg)); - if ( FAILED(hr) ) - { - wxLogApiError(wxS("CoCreateInstance(CLSID_FileOpenDialog)"), hr); - return false; - } - - if ( multipleSelection ) - options |= FOS_ALLOWMULTISELECT; - if ( showHidden ) - options |= FOS_FORCESHOWHIDDEN; - - hr = dlg->SetOptions(options); - if ( FAILED(hr) ) - { - wxLogApiError(wxS("IFileOpenDialog::SetOptions"), hr); - return false; - } - - hr = dlg->SetTitle(message.wc_str()); + HRESULT hr = m_fileDialog->SetTitle(message.wc_str()); if ( FAILED(hr) ) { // This error is not serious, let's just log it and continue even // without the title set. wxLogApiError(wxS("IFileOpenDialog::SetTitle"), hr); } +} - // set the initial path - if ( !defaultPath.empty() ) +void wxIFileDialog::SetInitialPath(const wxString& defaultPath) +{ + HRESULT hr; + + // We need to link SHCreateItemFromParsingName() dynamically as it's + // not available on pre-Vista systems. + typedef HRESULT + (WINAPI *SHCreateItemFromParsingName_t)(PCWSTR, + IBindCtx*, + REFIID, + void**); + + static SHCreateItemFromParsingName_t + s_pfnSHCreateItemFromParsingName = (SHCreateItemFromParsingName_t)-1; + if ( s_pfnSHCreateItemFromParsingName == (SHCreateItemFromParsingName_t)-1 ) { - // We need to link SHCreateItemFromParsingName() dynamically as it's - // not available on pre-Vista systems. - typedef HRESULT - (WINAPI *SHCreateItemFromParsingName_t)(PCWSTR, - IBindCtx*, - REFIID, - void**); - - SHCreateItemFromParsingName_t pfnSHCreateItemFromParsingName = NULL; wxDynamicLibrary dllShell32; if ( dllShell32.Load(wxS("shell32.dll"), wxDL_VERBATIM | wxDL_QUIET) ) { - wxDL_INIT_FUNC(pfn, SHCreateItemFromParsingName, dllShell32); + wxDL_INIT_FUNC(s_pfn, SHCreateItemFromParsingName, dllShell32); } - if ( !pfnSHCreateItemFromParsingName ) + if ( !s_pfnSHCreateItemFromParsingName ) { wxLogLastError(wxS("SHCreateItemFromParsingName() not found")); - return false; - } - - wxCOMPtr folder; - hr = pfnSHCreateItemFromParsingName(defaultPath.wc_str(), - NULL, - wxIID_PPV_ARGS(IShellItem, - &folder)); - - // Failing to parse the folder name or set it is not really an error, - // we'll just ignore the initial directory in this case, but we should - // still show the dialog. - if ( SUCCEEDED(hr) ) - { - hr = dlg->SetFolder(folder); - if ( FAILED(hr) ) - wxLogApiError(wxS("IFileOpenDialog::SetFolder"), hr); } } - fileDialog = dlg; - return true; + if ( !s_pfnSHCreateItemFromParsingName ) + { + // There is nothing we can do and the error was already reported. + return; + } + + wxCOMPtr folder; + hr = s_pfnSHCreateItemFromParsingName + ( + defaultPath.wc_str(), + NULL, + wxIID_PPV_ARGS(IShellItem, &folder) + ); + + // Failing to parse the folder name or set it is not really an error, + // we'll just ignore the initial directory in this case, but we should + // still show the dialog. + if ( SUCCEEDED(hr) ) + { + hr = m_fileDialog->SetFolder(folder); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileOpenDialog::SetFolder"), hr); + } } +} // namespace wxMSWImpl + +// ---------------------------------------------------------------------------- +// private functions +// ---------------------------------------------------------------------------- + +namespace +{ + // helper function for wxDirDialog::ShowIFileOpenDialog() -bool GetPathsFromIFileOpenDialog(const wxCOMPtr& fileDialog, bool multipleSelection, - wxArrayString& paths) +bool GetPathsFromIFileOpenDialog(IFileOpenDialog* fileDialog, wxArrayString& paths) { HRESULT hr = S_OK; wxString path; wxArrayString tempPaths; - if ( multipleSelection ) + wxCOMPtr itemArray; + + hr = fileDialog->GetResults(&itemArray); + if ( FAILED(hr) ) { - wxCOMPtr itemArray; - - hr = fileDialog->GetResults(&itemArray); - if ( FAILED(hr) ) - { - wxLogApiError(wxS("IShellItemArray::GetResults"), hr); - return false; - } - - DWORD count = 0; - - hr = itemArray->GetCount(&count); - if ( FAILED(hr) ) - { - wxLogApiError(wxS("IShellItemArray::GetCount"), hr); - return false; - } - - for ( DWORD i = 0; i < count; ++i ) - { - wxCOMPtr item; - - hr = itemArray->GetItemAt(i, &item); - if ( FAILED(hr) ) - { - // do not attempt to retrieve any other items - // and just fail - wxLogApiError(wxS("IShellItemArray::GetItem"), hr); - tempPaths.clear(); - break; - } - - if ( !ConvertIShellItemToPath(item, path) ) - { - // again, just fail - tempPaths.clear(); - break; - } - - tempPaths.push_back(path); - } - + wxLogApiError(wxS("IShellItemArray::GetResults"), hr); + return false; } - else // single selection + + DWORD count = 0; + + hr = itemArray->GetCount(&count); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IShellItemArray::GetCount"), hr); + return false; + } + + for ( DWORD i = 0; i < count; ++i ) { wxCOMPtr item; - hr = fileDialog->GetResult(&item); + hr = itemArray->GetItemAt(i, &item); if ( FAILED(hr) ) { - wxLogApiError(wxS("IFileOpenDialog::GetResult"), hr); - return false; + // do not attempt to retrieve any other items + // and just fail + wxLogApiError(wxS("IShellItemArray::GetItem"), hr); + tempPaths.clear(); + break; } - if ( !ConvertIShellItemToPath(item, path) ) + hr = wxMSWImpl::GetFSPathFromShellItem(item, path); + if ( FAILED(hr) ) { - return false; + // again, just fail + tempPaths.clear(); + break; } tempPaths.push_back(path); @@ -456,8 +471,30 @@ bool GetPathsFromIFileOpenDialog(const wxCOMPtr& fileDialog, bo return true; } -// helper function for wxDirDialog::ShowIFileOpenDialog() -bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path) +bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path) +{ + wxCOMPtr item; + + HRESULT hr = fileDialog->GetResult(&item); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialog::GetResult"), hr); + return false; + } + + hr = wxMSWImpl::GetFSPathFromShellItem(item, path); + if ( FAILED(hr) ) + { + return false; + } + + return true; +} + +} // anonymous namespace + +HRESULT +wxMSWImpl::GetFSPathFromShellItem(const wxCOMPtr& item, wxString& path) { wxCoTaskMemPtr pOLEPath; const HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &pOLEPath); @@ -465,16 +502,18 @@ bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path) if ( FAILED(hr) ) { wxLogApiError(wxS("IShellItem::GetDisplayName"), hr); - return false; + return hr; } path = pOLEPath; - return true; + return S_OK; } #endif // wxUSE_IFILEOPENDIALOG +#if wxUSE_DIRDLG + // callback used in wxDirDialog::ShowSHBrowseForFolder() static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) @@ -525,6 +564,4 @@ BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) return 0; } -#endif // compiler/platform on which the code here compiles - #endif // wxUSE_DIRDLG diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index b5ace56532..936d2f50b9 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -45,7 +45,27 @@ #include "wx/scopeguard.h" #include "wx/tokenzr.h" #include "wx/modalhook.h" + #include "wx/msw/private/dpiaware.h" +#include "wx/msw/private/filedialog.h" + +// 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/choice.h" + #include "wx/radiobut.h" + #include "wx/stattext.h" + #include "wx/textctrl.h" + + #include "wx/msw/wrapshl.h" + + #include "wx/msw/private/cotaskmemptr.h" +#endif // wxUSE_IFILEOPENDIALOG // ---------------------------------------------------------------------------- // constants @@ -141,17 +161,736 @@ void RestoreExceptionPolicy() #endif // wxUSE_DYNLIB_CLASS } +#if wxUSE_IFILEOPENDIALOG + +// ---------------------------------------------------------------------------- +// Various IFileDialog-related helpers: they're only used here for now, but if +// they're ever needed in wxDirDialog too, we should put move them to +// wx/msw/private/filedialog.h +// ---------------------------------------------------------------------------- + +// Register the given event handler with the dialog during its life-time. +class FileDialogEventsRegistrar +{ +public: + // The file dialog scope must be greater than that of this object. + FileDialogEventsRegistrar(wxMSWImpl::wxIFileDialog& fileDialog, + IFileDialogEvents& eventsHandler) + : m_fileDialog(fileDialog) + { + HRESULT hr = m_fileDialog->Advise(&eventsHandler, &m_cookie); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialog::Advise"), hr); + } + + ~FileDialogEventsRegistrar() + { + HRESULT hr = m_fileDialog->Unadvise(m_cookie); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialog::Unadvise"), hr); + } + +private: + wxMSWImpl::wxIFileDialog& m_fileDialog; + DWORD m_cookie; + + wxDECLARE_NO_COPY_CLASS(FileDialogEventsRegistrar); +}; + +// Return 1-based index of the currently selected file type. +UINT FileDialogGetFileTypeIndex(IFileDialog* fileDialog) +{ + UINT nFilterIndex; + HRESULT hr = fileDialog->GetFileTypeIndex(&nFilterIndex); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialog::GetFileTypeIndex"), hr); + + nFilterIndex = 0; + } + + 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) + { + } + + virtual bool DoBind(wxEvtHandler* WXUNUSED(handler)) wxOVERRIDE + { + // We don't need to do anything special to get the events here. + return true; + } +}; + +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); + } + + virtual bool DoBind(wxEvtHandler* WXUNUSED(handler)) wxOVERRIDE + { + // We don't need to do anything special to get the events here. + return true; + } +}; + +class wxFileDialogRadioButtonImplFDC + : public wxFileDialogImplFDC +{ +public: + wxFileDialogRadioButtonImplFDC(IFileDialogCustomize* fdc, DWORD id, DWORD item) + : wxFileDialogImplFDC(fdc, id), + m_item(item) + { + } + + virtual bool GetValue() wxOVERRIDE + { + DWORD selected = 0; + HRESULT hr = m_fdc->GetSelectedControlItem(m_id, &selected); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::GetSelectedControlItem"), hr); + + return selected == m_item; + } + + virtual void SetValue(bool value) wxOVERRIDE + { + // We can't implement it using the available API and this shouldn't be + // ever needed anyhow. + wxCHECK_RET( value, wxS("clearing radio buttons not supported") ); + + HRESULT hr = m_fdc->SetSelectedControlItem(m_id, m_item); + 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: + 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 +{ +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 = + m_lastAuxId = + m_radioListId = 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; + } + + wxFileDialogCustomControl* FindControl(DWORD id) const + { + // Currently there is 1-to-1 correspondence between IDs and the + // controls we create, except that we start assigning IDs with 1. + if ( id < 1 || id > m_controls.size() ) + return NULL; + + return m_controls[id - 1]; + } + + + // Implement wxFileDialogCustomizeImpl pure virtual methods. + wxFileDialogButtonImpl* AddButton(const wxString& label) wxOVERRIDE + { + m_radioListId = 0; + + 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 + { + m_radioListId = 0; + + 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); + } + + wxFileDialogRadioButtonImpl* AddRadioButton(const wxString& label) wxOVERRIDE + { + HRESULT hr; + + bool firstButton = false; + if ( !m_radioListId ) + { + hr = m_fdc->AddRadioButtonList(--m_lastAuxId); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddRadioButtonList"), hr); + return NULL; + } + + m_radioListId = m_lastAuxId; + firstButton = true; + } + + hr = m_fdc->AddControlItem(m_radioListId, ++m_lastId, label.wc_str()); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddControlItem"), hr); + return NULL; + } + + wxFileDialogRadioButtonImplFDC* const + impl = new wxFileDialogRadioButtonImplFDC(m_fdc, m_radioListId, m_lastId); + + // Select the first button of a new radio group. + if ( firstButton ) + impl->SetValue(true); + + 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; + + HRESULT hr; + + if ( !label.empty() ) + { + hr = m_fdc->StartVisualGroup(--m_lastAuxId, label.wc_str()); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::StartVisualGroup"), hr); + } + + hr = m_fdc->AddEditBox(++m_lastId, L""); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddEditBox"), hr); + return NULL; + } + + if ( !label.empty() ) + { + hr = m_fdc->EndVisualGroup(); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::EndVisualGroup"), hr); + } + + return new wxFileDialogTextCtrlImplFDC(m_fdc, m_lastId); + } + + wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) wxOVERRIDE + { + m_radioListId = 0; + + 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; + + // IDs used for the custom controls returned from the public AddXXX() + // functions: they are positive and must be consecutive in order to allow + // accessing the correspond element of m_controls later, see FindControl(). + DWORD m_lastId; + + // 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 + // to AddRadioButton() would add a radio button. 0 if none. + DWORD m_radioListId; +}; + +#endif // wxUSE_IFILEOPENDIALOG + } // unnamed namespace +// ---------------------------------------------------------------------------- +// wxFileDialogMSWData: private data used by the dialog +// ---------------------------------------------------------------------------- + +class wxFileDialogMSWData +#if wxUSE_IFILEOPENDIALOG + : public IFileDialogEvents, + public IFileDialogControlEvents +#endif // wxUSE_IFILEOPENDIALOG +{ +public: + explicit wxFileDialogMSWData(wxFileDialog* fileDialog) +#if wxUSE_IFILEOPENDIALOG + : m_fileDialog(fileDialog), + m_typeAlreadyChanged(false) +#endif // wxUSE_IFILEOPENDIALOG + { + m_bMovedWindow = false; + m_centreDir = 0; + } + + // This class is not really used polymorphically, but gcc still complains + // if it doesn't have a virtual dtor, so pacify it by adding one. + virtual ~wxFileDialogMSWData() { } + + // Hook function used by the common dialogs: it's a member of this class + // just to allow it to call the private functions of wxFileDialog. + static UINT_PTR APIENTRY + HookFunction(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam); + + // remember if our SetPosition() or Centre() (which requires special + // treatment) was called + bool m_bMovedWindow; + int m_centreDir; // nothing to do if 0 + + +#if wxUSE_IFILEOPENDIALOG + // IUnknown + + wxSTDMETHODIMP QueryInterface(REFIID iid, void** ppv) + { + if ( iid == IID_IUnknown || iid == IID_IFileDialogEvents ) + { + // The cast is unnecessary, but do it for clarity and symmetry. + *ppv = static_cast(this); + } + else if ( iid == IID_IFileDialogControlEvents ) + { + // Here the case is necessary to return the pointer of correct + // type. + *ppv = static_cast(this); + } + else + { + *ppv = NULL; + + return E_NOINTERFACE; + } + + // No need for AddRef(), we're not really reference-counted as our + // lifetime is determined by wxFileDialog and there should be no + // outside references to this object once Unadvise() is called. + + return S_OK; + } + + // Dummy implementations because we're not really ref-counted. + STDMETHODIMP_(ULONG) AddRef() { return 1; } + STDMETHODIMP_(ULONG) Release() { return 1; } + + + // IFileDialogEvents + + 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; } + + wxSTDMETHODIMP OnSelectionChange(IFileDialog* pfd) wxOVERRIDE + { + wxCOMPtr item; + HRESULT hr = pfd->GetCurrentSelection(&item); + if ( FAILED(hr) ) + return hr; + + wxString path; + hr = wxMSWImpl::GetFSPathFromShellItem(item, path); + if ( FAILED(hr) ) + return hr; + + m_fileDialog->MSWOnSelChange(path); + + return S_OK; + } + + wxSTDMETHODIMP OnShareViolation(IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) wxOVERRIDE { return E_NOTIMPL; } + + wxSTDMETHODIMP OnTypeChange(IFileDialog* pfd) wxOVERRIDE + { + // There is no special notification for the dialog initialization, but + // this function is always called when it's shown, so use it for + // generating this notification as well. + if ( !m_typeAlreadyChanged ) + { + m_typeAlreadyChanged = true; + + wxCOMPtr window; + HRESULT hr = pfd->QueryInterface(wxIID_PPV_ARGS(IOleWindow, &window)); + if ( SUCCEEDED(hr) ) + { + HWND hwnd; + hr = window->GetWindow(&hwnd); + if ( SUCCEEDED(hr) ) + m_fileDialog->MSWOnInitDone(hwnd); + } + } + + m_fileDialog->MSWOnTypeChange(FileDialogGetFileTypeIndex(pfd)); + + return S_OK; + } + + wxSTDMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) wxOVERRIDE { return E_NOTIMPL; } + + + // IFileDialogControlEvents + + wxSTDMETHODIMP + OnItemSelected(IFileDialogCustomize*, + DWORD WXUNUSED(dwIDCtl), + DWORD dwIDItem) wxOVERRIDE + { + // Note that we don't use dwIDCtl here because we use unique item IDs + // for all controls. + if ( wxFileDialogCustomControl* const + control = m_customize.FindControl(dwIDItem) ) + { + wxCommandEvent event(wxEVT_RADIOBUTTON, dwIDItem); + event.SetEventObject(control); + event.SetInt(true); // Ensure IsChecked() returns true. + + control->SafelyProcessEvent(event); + } + + return S_OK; + } + + wxSTDMETHODIMP + OnButtonClicked(IFileDialogCustomize*, DWORD dwIDCtl) wxOVERRIDE + { + if ( wxFileDialogCustomControl* const + control = m_customize.FindControl(dwIDCtl) ) + { + wxCommandEvent event(wxEVT_BUTTON, dwIDCtl); + event.SetEventObject(control); + + control->SafelyProcessEvent(event); + } + + return S_OK; + } + + wxSTDMETHODIMP + OnCheckButtonToggled(IFileDialogCustomize*, + DWORD dwIDCtl, + BOOL bChecked) wxOVERRIDE + { + if ( wxFileDialogCustomControl* const + control = m_customize.FindControl(dwIDCtl) ) + { + wxCommandEvent event(wxEVT_CHECKBOX, dwIDCtl); + event.SetEventObject(control); + event.SetInt(bChecked); // This is for wxCommandEvent::IsChecked(). + + control->SafelyProcessEvent(event); + } + + return S_OK; + } + + wxSTDMETHODIMP + OnControlActivating(IFileDialogCustomize*, + DWORD WXUNUSED(dwIDCtl)) wxOVERRIDE + { + return S_OK; + } + + + wxFileDialog* const m_fileDialog; + + wxFileDialogCustomizeFDC m_customize; + + bool m_typeAlreadyChanged; +#endif // wxUSE_IFILEOPENDIALOG + + wxDECLARE_NO_COPY_CLASS(wxFileDialogMSWData); +}; + // ---------------------------------------------------------------------------- // hook function for moving the dialog // ---------------------------------------------------------------------------- UINT_PTR APIENTRY -wxFileDialogHookFunction(HWND hDlg, - UINT iMsg, - WPARAM WXUNUSED(wParam), - LPARAM lParam) +wxFileDialogMSWData::HookFunction(HWND hDlg, + UINT iMsg, + WPARAM WXUNUSED(wParam), + LPARAM lParam) { switch ( iMsg ) { @@ -168,29 +907,43 @@ wxFileDialogHookFunction(HWND hDlg, NMHDR* const pNM = reinterpret_cast(lParam); if ( pNM->code > CDN_LAST && pNM->code <= CDN_FIRST ) { - OFNOTIFY* const - pNotifyCode = reinterpret_cast(lParam); + const OPENFILENAME& + ofn = *reinterpret_cast(lParam)->lpOFN; wxFileDialog* const - dialog = reinterpret_cast( - pNotifyCode->lpOFN->lCustData - ); + dialog = reinterpret_cast(ofn.lCustData); - switch ( pNotifyCode->hdr.code ) + switch ( pNM->code ) { case CDN_INITDONE: - dialog->MSWOnInitDone((WXHWND)hDlg); + // Note the dialog is the parent window: hDlg is a + // child of it when OFN_EXPLORER is used + dialog->MSWOnInitDone((WXHWND)::GetParent(hDlg)); + + // Call selection change handler so that update + // handler will be called once with no selection. + dialog->MSWOnSelChange(wxString()); break; case CDN_SELCHANGE: - dialog->MSWOnSelChange((WXHWND)hDlg); + { + TCHAR buf[MAX_PATH]; + LRESULT len = SendMessage + ( + ::GetParent(hDlg), + CDM_GETFILEPATH, + MAX_PATH, + reinterpret_cast(buf) + ); + + wxString str; + if ( len ) + str = buf; + dialog->MSWOnSelChange(str); + } break; case CDN_TYPECHANGE: - dialog->MSWOnTypeChange - ( - (WXHWND)hDlg, - pNotifyCode->lpOFN->nFilterIndex - ); + dialog->MSWOnTypeChange(ofn.nFilterIndex); break; } } @@ -230,8 +983,7 @@ wxFileDialog::wxFileDialog(wxWindow *parent, { // NB: all style checks are done by wxFileDialogBase::Create - m_bMovedWindow = false; - m_centreDir = 0; + m_data = NULL; // Must set to zero, otherwise the wx routines won't size the window // the second time you call the file dialog, because it thinks it is @@ -240,6 +992,19 @@ wxFileDialog::wxFileDialog(wxWindow *parent, gs_rectDialog.y = 0; } +wxFileDialog::~wxFileDialog() +{ + delete m_data; +} + +wxFileDialogMSWData& wxFileDialog::MSWData() +{ + if ( !m_data ) + m_data = new wxFileDialogMSWData(this); + + return *m_data; +} + void wxFileDialog::GetPaths(wxArrayString& paths) const { paths.Empty(); @@ -265,6 +1030,13 @@ void wxFileDialog::GetFilenames(wxArrayString& files) const void wxFileDialog::DoGetPosition(int *x, int *y) const { + // Return the actual HWND position if we have it. + if ( GetHwnd() ) + { + wxFileDialogBase::DoGetPosition(x, y); + return; + } + if ( x ) *x = gs_rectDialog.x; if ( y ) @@ -273,6 +1045,13 @@ void wxFileDialog::DoGetPosition(int *x, int *y) const void wxFileDialog::DoGetSize(int *width, int *height) const { + // Return the actual HWND size if we have it. + if ( GetHwnd() ) + { + wxFileDialogBase::DoGetSize(width, height); + return; + } + if ( width ) *width = gs_rectDialog.width; if ( height ) @@ -295,18 +1074,21 @@ void wxFileDialog::DoMoveWindow(int x, int y, int WXUNUSED(w), int WXUNUSED(h)) } else // just remember that we were requested to move the window { - m_bMovedWindow = true; + wxFileDialogMSWData& data = MSWData(); + data.m_bMovedWindow = true; // if Centre() had been called before, it shouldn't be taken into // account now - m_centreDir = 0; + data.m_centreDir = 0; } } void wxFileDialog::DoCentre(int dir) { - m_centreDir = dir; - m_bMovedWindow = true; + wxFileDialogMSWData& data = MSWData(); + + data.m_centreDir = dir; + data.m_bMovedWindow = true; // it's unnecessary to do anything else at this stage as we'll redo it in // MSWOnInitDone() anyhow @@ -314,49 +1096,40 @@ void wxFileDialog::DoCentre(int dir) void wxFileDialog::MSWOnInitDone(WXHWND hDlg) { - // note the dialog is the parent window: hDlg is a child of it when - // OFN_EXPLORER is used - HWND hFileDlg = ::GetParent((HWND)hDlg); + if ( !m_data || !m_data->m_bMovedWindow ) + { + // We only use this to position the dialog, so nothing to do. + return; + } // set HWND so that our DoMoveWindow() works correctly - TempHWNDSetter set(this, (WXHWND)hFileDlg); + TempHWNDSetter set(this, hDlg); - if ( m_centreDir ) + if ( m_data->m_centreDir ) { // now we have the real dialog size, remember it RECT rect; - GetWindowRect(hFileDlg, &rect); + GetWindowRect(hDlg, &rect); gs_rectDialog = wxRectFromRECT(rect); // and position the window correctly: notice that we must use the base // class version as our own doesn't do anything except setting flags - wxFileDialogBase::DoCentre(m_centreDir); + wxFileDialogBase::DoCentre(m_data->m_centreDir); } else // need to just move it to the correct place { SetPosition(gs_rectDialog.GetPosition()); } - - // Call selection change handler so that update handler will be - // called once with no selection. - MSWOnSelChange(hDlg); } -void wxFileDialog::MSWOnSelChange(WXHWND hDlg) +void wxFileDialog::MSWOnSelChange(const wxString& selectedFilename) { - TCHAR buf[MAX_PATH]; - LRESULT len = SendMessage(::GetParent(hDlg), CDM_GETFILEPATH, - MAX_PATH, reinterpret_cast(buf)); - - if ( len > 0 ) - m_currentlySelectedFilename = buf; - else - m_currentlySelectedFilename.clear(); + m_currentlySelectedFilename = selectedFilename; UpdateExtraControlUI(); } -void wxFileDialog::MSWOnTypeChange(WXHWND WXUNUSED(hDlg), int nFilterIndex) +void wxFileDialog::MSWOnTypeChange(int nFilterIndex) { // Filter indices are 1-based, while we want to use 0-based index, as // usual. However the input index can apparently also be 0 in some @@ -389,39 +1162,11 @@ static bool DoShowCommFileDialog(OPENFILENAME *of, long style, DWORD *err) return false; } -static bool ShowCommFileDialog(OPENFILENAME *of, long style) -{ - DWORD errCode; - bool success = DoShowCommFileDialog(of, style, &errCode); - - if ( !success && - errCode == FNERR_INVALIDFILENAME && - of->lpstrFile[0] ) - { - // this can happen if the default file name is invalid, try without it - // now - of->lpstrFile[0] = wxT('\0'); - success = DoShowCommFileDialog(of, style, &errCode); - } - - if ( !success ) - { - // common dialog failed - why? - if ( errCode != 0 ) - { - wxLogError(_("File dialog failed with error code %0lx."), errCode); - } - //else: it was just cancelled - - return false; - } - - return true; -} - void wxFileDialog::MSWOnInitDialogHook(WXHWND hwnd) { - TempHWNDSetter set(this, hwnd); + // Remember the HWND so that various operations using the dialog geometry + // work correctly while it's shown. + SetHWND(hwnd); CreateExtraControl(); } @@ -435,6 +1180,26 @@ int wxFileDialog::ShowModal() wxWindowDisabler disableOthers(this, parent); + /* + We need to use the old style dialog in order to use a hook function + which allows us to use custom controls in it but, if possible, we + prefer to use the new style one instead. + */ +#if wxUSE_IFILEOPENDIALOG + if ( !HasExtraControlCreator() ) + { + const int rc = ShowIFileDialog(hWndParent); + if ( rc != wxID_NONE ) + return rc; + //else: Failed to use IFileDialog, fall back to the traditional one. + } +#endif // wxUSE_IFILEOPENDIALOG + + return ShowCommFileDialog(hWndParent); +} + +int wxFileDialog::ShowCommFileDialog(WXHWND hWndParent) +{ static wxChar fileNameBuffer [ wxMAXPATH ]; // the file-name wxChar titleBuffer [ wxMAXFILE+1+wxMAXEXT ]; // the file-name, without path @@ -459,7 +1224,9 @@ int wxFileDialog::ShowModal() in the upper left of the frame, it does not center automatically. */ - if (m_bMovedWindow || HasExtraControlCreator()) // we need these flags. + if ((m_data && m_data->m_bMovedWindow) || + HasExtraControlCreator() || + m_customizeHook) { ChangeExceptionPolicy(); msw_flags |= OFN_EXPLORER|OFN_ENABLEHOOK; @@ -497,7 +1264,7 @@ int wxFileDialog::ShowModal() of.nMaxFileTitle = wxMAXFILE + 1 + wxMAXEXT; GlobalPtr hgbl; - if ( HasExtraControlCreator() ) + if ( HasExtraControlCreator() || m_customizeHook ) { msw_flags |= OFN_ENABLETEMPLATEHANDLE; @@ -512,8 +1279,13 @@ int wxFileDialog::ShowModal() lpdt->x = 0; lpdt->y = 0; + // create the extra control in an empty dialog just to find its size: this + // is not terribly efficient but we do need to know the size before + // creating the native dialog and this seems to be the only way + wxDialog dlg(NULL, wxID_ANY, wxString()); + const wxSize extraSize = CreateExtraControlWithParent(&dlg)->GetSize(); + // convert the size of the extra controls to the dialog units - const wxSize extraSize = GetExtraControlSize(); const LONG baseUnits = ::GetDialogBaseUnits(); lpdt->cx = ::MulDiv(extraSize.x, 4, LOWORD(baseUnits)); lpdt->cy = ::MulDiv(extraSize.y, 8, HIWORD(baseUnits)); @@ -565,7 +1337,7 @@ int wxFileDialog::ShowModal() of.lpstrInitialDir = dir.c_str(); of.Flags = msw_flags; - of.lpfnHook = wxFileDialogHookFunction; + of.lpfnHook = wxFileDialogMSWData::HookFunction; of.lCustData = (LPARAM)this; wxArrayString wildDescriptions, wildFilters; @@ -645,8 +1417,36 @@ int wxFileDialog::ShowModal() //== Execute FileDialog >>================================================= - if ( !ShowCommFileDialog(&of, m_windowStyle) ) + DWORD errCode; + bool success = DoShowCommFileDialog(&of, m_windowStyle, &errCode); + + // When using a hook, our HWND was set from MSWOnInitDialogHook() called + // above, but it's not valid any longer once the dialog was destroyed, so + // reset it now. + if ( msw_flags & OFN_ENABLEHOOK ) + SetHWND(0); + + if ( !success && + errCode == FNERR_INVALIDFILENAME && + of.lpstrFile[0] ) + { + // this can happen if the default file name is invalid, try without it + // now + of.lpstrFile[0] = wxT('\0'); + success = DoShowCommFileDialog(&of, m_windowStyle, &errCode); + } + + if ( !success ) + { + // common dialog failed - why? + if ( errCode != 0 ) + { + wxLogError(_("File dialog failed with error code %0lx."), errCode); + } + //else: it was just cancelled + return wxID_CANCEL; + } m_fileNames.Empty(); @@ -702,4 +1502,133 @@ int wxFileDialog::ShowModal() } +#if wxUSE_IFILEOPENDIALOG + +int wxFileDialog::ShowIFileDialog(WXHWND hWndParent) +{ + // Create the dialog. + wxMSWImpl::wxIFileDialog + fileDialog(HasFlag(wxFD_SAVE) ? CLSID_FileSaveDialog + : CLSID_FileOpenDialog); + + if ( !fileDialog.IsOk() ) + return wxID_NONE; + + // Register our event handler with the dialog. + wxFileDialogMSWData& data = MSWData(); + + 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); + + HRESULT hr; + + wxArrayString wildDescriptions, wildFilters; + const UINT nWildcards = wxParseCommonDialogsFilter(m_wildCard, + wildDescriptions, + wildFilters); + if ( nWildcards ) + { + wxVector filterSpecs(nWildcards); + for ( UINT n = 0; n < nWildcards; ++n ) + { + filterSpecs[n].pszName = wildDescriptions[n].wc_str(); + filterSpecs[n].pszSpec = wildFilters[n].wc_str(); + } + + hr = fileDialog->SetFileTypes(nWildcards, &filterSpecs[0]); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialog::SetFileTypes"), hr); + + hr = fileDialog->SetFileTypeIndex(m_filterIndex + 1); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialog::SetFileTypeIndex"), hr); + } + + if ( !m_dir.empty() ) + { + fileDialog.SetInitialPath(m_dir); + } + + if ( !m_fileName.empty() ) + { + hr = fileDialog->SetFileName(m_fileName.wc_str()); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialog::SetDefaultExtension"), hr); + } + + + // We never set the following flags currently: + // + // - FOS_STRICTFILETYPES + // - FOS_NOVALIDATE + // - FOS_CREATEPROMPT + // - FOS_SHAREAWARE + // - FOS_NOREADONLYRETURN + // - FOS_NOTESTFILECREATE + // - FOS_OKBUTTONNEEDSINTERACTION + // - FOS_DONTADDTORECENT + // - FOS_DEFAULTNOMINIMODE + // - FOS_FORCEPREVIEWPANEON + // + // We might want to add wxFD_XXX equivalents for some of them in the future. + int options = 0; + if ( HasFlag(wxFD_OVERWRITE_PROMPT) ) + options |= FOS_OVERWRITEPROMPT; + if ( !HasFlag(wxFD_CHANGE_DIR) ) + options |= FOS_NOCHANGEDIR; + if ( HasFlag(wxFD_FILE_MUST_EXIST) ) + options |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST; + if ( HasFlag(wxFD_MULTIPLE) ) + options |= FOS_ALLOWMULTISELECT; + if ( HasFlag(wxFD_SHOW_HIDDEN) ) + options |= FOS_FORCESHOWHIDDEN; + if ( HasFlag(wxFD_NO_FOLLOW) ) + options |= FOS_NODEREFERENCELINKS; + + // Finally do show the dialog. + const int rc = fileDialog.Show(hWndParent, options, &m_fileNames, &m_path); + if ( rc == wxID_OK ) + { + // As with the common dialog, the index is 1-based here, but don't make + // it negative if we somehow failed to retrieve it at all. + m_filterIndex = FileDialogGetFileTypeIndex(fileDialog.Get()); + if ( m_filterIndex > 0 ) + m_filterIndex--; + + if ( HasFlag(wxFD_MULTIPLE) ) + { + // This shouldn't the case, but check to be absolutely sure. + if ( !m_fileNames.empty() ) + m_dir = wxFileName(m_fileNames[0]).GetPath(); + } + else // Single selected file is in m_path. + { + // Append the extension if necessary. + m_path = AppendExtension(m_path, wildFilters[m_filterIndex]); + + const wxFileName fn(m_path); + m_dir = fn.GetPath(); + m_fileName = fn.GetFullName(); + + // For compatibility, our GetFilenames() must also return the same + // file, so put it into the array too. + m_fileNames.Clear(); + m_fileNames.Add(m_fileName); + } + } + + return rc; +} + +#endif // wxUSE_IFILEOPENDIALOG + #endif // wxUSE_FILEDLG diff --git a/src/osx/cocoa/filedlg.mm b/src/osx/cocoa/filedlg.mm index 91cb448131..759f08f967 100644 --- a/src/osx/cocoa/filedlg.mm +++ b/src/osx/cocoa/filedlg.mm @@ -411,12 +411,11 @@ void wxFileDialog::SetupExtraControls(WXWindow nativeWindow) return; wxNonOwnedWindow::Create( GetParent(), nativeWindow ); - wxWindow* extracontrol = NULL; - if ( HasExtraControlCreator() ) - { - CreateExtraControl(); - extracontrol = GetExtraControl(); - } + + // This won't do anything if there are no extra controls to create and + // extracontrol will be NULL in this case. + CreateExtraControl(); + wxWindow* const extracontrol = GetExtraControl(); NSView* accView = nil;