From 4fb9ce574a9cad15e2ee87961b8017eb7ccf1193 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 15 May 2022 17:43:42 +0100 Subject: [PATCH 01/43] Inline ShowCommFileDialog() helper into wxFileDialog::ShowModal() No real changes, just don't use a separate helper function for something that we do only once to avoid an extra level of indirection which doesn't help the comprehensibility of this code. This commit is best viewed with git --color-moved option. --- src/msw/filedlg.cpp | 54 +++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index b5ace56532..7ba7d51ff6 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -389,36 +389,6 @@ 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); @@ -645,8 +615,30 @@ int wxFileDialog::ShowModal() //== Execute FileDialog >>================================================= - if ( !ShowCommFileDialog(&of, m_windowStyle) ) + DWORD errCode; + bool success = DoShowCommFileDialog(&of, m_windowStyle, &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, 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(); From 20430728bcbe2ade5bbdf4a3427197499ca1d913 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 15 May 2022 17:49:11 +0100 Subject: [PATCH 02/43] Add wxFileDialog::ShowCommFileDialog() No changes, just extract this function from ShowModal() before adding an alternative implementation in the upcoming commits. --- include/wx/msw/filedlg.h | 4 ++++ src/msw/filedlg.cpp | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/include/wx/msw/filedlg.h b/include/wx/msw/filedlg.h index 37a9aca01a..398641025d 100644 --- a/include/wx/msw/filedlg.h +++ b/include/wx/msw/filedlg.h @@ -55,6 +55,10 @@ protected: virtual void DoGetPosition( int *x, int *y ) const wxOVERRIDE; private: + // The real implementation of ShowModal() using traditional common dialog + // functions. + int ShowCommFileDialog(WXHWND owner); + wxArrayString m_fileNames; // remember if our SetPosition() or Centre() (which requires special diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 7ba7d51ff6..60636c83c3 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -405,6 +405,11 @@ int wxFileDialog::ShowModal() wxWindowDisabler disableOthers(this, parent); + 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 From 9a17c0983494cf56ff985fdb932e80ebe7168bb6 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 15 May 2022 18:54:59 +0100 Subject: [PATCH 03/43] Pass FOS_xxx options to InitIFileOpenDialog() directly This is more flexible and will allow using this function for the file (and not just directory) dialog too in the future and makes the code more readable and robust already because it removes the use of non-obvious boolean parameters. No real changes. --- src/msw/dirdlg.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index 9b6dd029c9..959c3be1d1 100644 --- a/src/msw/dirdlg.cpp +++ b/src/msw/dirdlg.cpp @@ -90,7 +90,8 @@ wxIMPLEMENT_CLASS(wxDirDialog, wxDialog); // helper functions for wxDirDialog::ShowIFileOpenDialog() bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, - bool multipleSelection, bool showHidden, wxCOMPtr& fileDialog); + int options, + wxCOMPtr& fileDialog); bool GetPathsFromIFileOpenDialog(const wxCOMPtr& fileDialog, bool multipleSelection, wxArrayString& paths); bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path); @@ -256,8 +257,15 @@ int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) HRESULT hr = S_OK; wxCOMPtr fileDialog; - if ( !InitIFileOpenDialog(m_message, m_path, HasFlag(wxDD_MULTIPLE), - HasFlag(wxDD_SHOW_HIDDEN), fileDialog) ) + // 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; + + if ( !InitIFileOpenDialog(m_message, m_path, options, fileDialog) ) { return wxID_NONE; // Failed to initialize the dialog } @@ -296,13 +304,11 @@ int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) // helper function for wxDirDialog::ShowIFileOpenDialog() bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, - bool multipleSelection, bool showHidden, + int options, wxCOMPtr& fileDialog) { 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)); @@ -312,12 +318,8 @@ bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, return false; } - if ( multipleSelection ) - options |= FOS_ALLOWMULTISELECT; - if ( showHidden ) - options |= FOS_FORCESHOWHIDDEN; - - hr = dlg->SetOptions(options); + // allow to select only a file system object + hr = dlg->SetOptions(options | FOS_FORCEFILESYSTEM); if ( FAILED(hr) ) { wxLogApiError(wxS("IFileOpenDialog::SetOptions"), hr); From c05d62f94f38101adff4780b84cccbe7c8e915d2 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 15 May 2022 20:50:56 +0100 Subject: [PATCH 04/43] Put private IFileDialog helpers into an anonymous namespace Don't use extern linkage for these private helpers. No real changes. --- src/msw/dirdlg.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index 959c3be1d1..caa26cc32d 100644 --- a/src/msw/dirdlg.cpp +++ b/src/msw/dirdlg.cpp @@ -88,6 +88,9 @@ wxIMPLEMENT_CLASS(wxDirDialog, wxDialog); #if wxUSE_IFILEOPENDIALOG +namespace +{ + // helper functions for wxDirDialog::ShowIFileOpenDialog() bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, int options, @@ -96,6 +99,8 @@ bool GetPathsFromIFileOpenDialog(const wxCOMPtr& fileDialog, bo wxArrayString& paths); bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path); +} // anonymous namespace + #endif // #if wxUSE_IFILEOPENDIALOG // callback used in wxDirDialog::ShowSHBrowseForFolder() @@ -302,6 +307,9 @@ int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) // private functions // ---------------------------------------------------------------------------- +namespace +{ + // helper function for wxDirDialog::ShowIFileOpenDialog() bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, int options, @@ -475,6 +483,8 @@ bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path) return true; } +} // anonymous namespace + #endif // wxUSE_IFILEOPENDIALOG // callback used in wxDirDialog::ShowSHBrowseForFolder() From a623f34b398d6ba88d0a1fd5e6b43db96314ddd0 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 15 May 2022 20:57:36 +0100 Subject: [PATCH 05/43] Split GetPathsFromIFileOpenDialog() into 2 functions Use different functions for the case of multiple paths and a single path as they are rather different and even use methods of different interfaces, which will become important soon. Also pass just raw pointers to these functions, there is no reason to use wxCOMPtr here as these functions don't take ownership of the pointer but just use it during their execution. No real changes yet. This commit is best viewed ignoring whitespace-only changes. --- src/msw/dirdlg.cpp | 116 ++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index caa26cc32d..f9830d692b 100644 --- a/src/msw/dirdlg.cpp +++ b/src/msw/dirdlg.cpp @@ -95,8 +95,8 @@ namespace bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, int options, wxCOMPtr& fileDialog); -bool GetPathsFromIFileOpenDialog(const wxCOMPtr& fileDialog, bool multipleSelection, - wxArrayString& paths); +bool GetPathsFromIFileOpenDialog(IFileOpenDialog* fileDialog, wxArrayString& paths); +bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path); bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path); } // anonymous namespace @@ -287,15 +287,15 @@ 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) ) - { - m_path = m_paths.Last(); - } - - return wxID_OK; + if ( GetPathsFromIFileOpenDialog(fileDialog, m_paths) ) + return wxID_OK; + } + else // Single selection only, path output parameter must be non-null. + { + if ( GetPathFromIFileDialog(fileDialog, m_path) ) + return wxID_OK; } // Failed to show the dialog or obtain the selected folders(s) @@ -388,72 +388,49 @@ bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, } // 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) ) { - return false; + // again, just fail + tempPaths.clear(); + break; } tempPaths.push_back(path); @@ -466,6 +443,25 @@ bool GetPathsFromIFileOpenDialog(const wxCOMPtr& fileDialog, bo return true; } +bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path) +{ + wxCOMPtr item; + + HRESULT hr = fileDialog->GetResult(&item); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileOpenDialog::GetResult"), hr); + return false; + } + + if ( !ConvertIShellItemToPath(item, path) ) + { + return false; + } + + return true; +} + // helper function for wxDirDialog::ShowIFileOpenDialog() bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path) { From d77a3731181d9307ad362fd2a31eb57b275effc3 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 15 May 2022 21:06:23 +0100 Subject: [PATCH 06/43] Extract wxIFileDialog::Show() from wxDirDialog code Add a trivial class for showing an IFileDialog (and not just IFileOpenDialog) and getting result(s) from it. This class will be also used by wxFileDialog soon, but this commit only updates wxDirDialog to use it. This commit is best viewed with git --color-moved --color-moved-ws=ignore-all-spac options. --- include/wx/msw/private/filedialog.h | 74 ++++++++++++++ src/msw/dirdlg.cpp | 148 ++++++++++++++++------------ 2 files changed, 159 insertions(+), 63 deletions(-) create mode 100644 include/wx/msw/private/filedialog.h diff --git a/include/wx/msw/private/filedialog.h b/include/wx/msw/private/filedialog.h new file mode 100644 index 0000000000..97adaf9415 --- /dev/null +++ b/include/wx/msw/private/filedialog.h @@ -0,0 +1,74 @@ +/////////////////////////////////////////////////////////////////////////////// +// 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 +{ + +class wxIFileDialog +{ +public: + wxIFileDialog() { } + + // 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 or wxID_NONE if creating it failed, in which case + // fileDialog is null -- otherwise it contains the pointer to the dialog which + // can be used to retrieve more information from it. + // + // For historical reasons, this function is defined in src/msw/dirdlg.cpp. + int + Show(HWND owner, + const CLSID& clsid, + int options, + const wxString& message, + const wxString& defaultValue, + wxArrayString* pathsOut, + wxString* pathOut); + + // Behave as IFileDialog. + IFileDialog* operator->() const { return m_fileDialog.Get(); } + +private: + wxCOMPtr m_fileDialog; +}; + +} // namespace wxMSWImpl + +#endif // wxUSE_IFILEOPENDIALOG + +#endif // _WX_MSW_PRIVATE_FILEDIALOG_H_ diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index f9830d692b..082008b6c6 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,36 @@ DEFINE_GUID(IID_IShellItem, #endif // wxUSE_IFILEOPENDIALOG +// ---------------------------------------------------------------------------- +// private functions prototypes +// ---------------------------------------------------------------------------- + +#if wxUSE_IFILEOPENDIALOG + +namespace +{ + +// helper functions for wxDirDialog::ShowIFileOpenDialog() +bool InitIFileDialog(const CLSID& clsid, + const wxString& message, const wxString& defaultPath, + int options, + wxCOMPtr& fileDialog); +bool GetPathsFromIFileOpenDialog(IFileOpenDialog* fileDialog, wxArrayString& paths); +bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path); +bool ConvertIShellItemToPath(const wxCOMPtr& item, 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,32 +96,6 @@ DEFINE_GUID(IID_IShellItem, wxIMPLEMENT_CLASS(wxDirDialog, wxDialog); -// ---------------------------------------------------------------------------- -// private functions prototypes -// ---------------------------------------------------------------------------- - -#if wxUSE_IFILEOPENDIALOG - -namespace -{ - -// helper functions for wxDirDialog::ShowIFileOpenDialog() -bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, - int options, - wxCOMPtr& fileDialog); -bool GetPathsFromIFileOpenDialog(IFileOpenDialog* fileDialog, wxArrayString& paths); -bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path); -bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path); - -} // anonymous namespace - -#endif // #if wxUSE_IFILEOPENDIALOG - -// callback used in wxDirDialog::ShowSHBrowseForFolder() -static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, - LPARAM pData); - - // ============================================================================ // implementation // ============================================================================ @@ -259,9 +247,6 @@ int wxDirDialog::ShowSHBrowseForFolder(WXHWND owner) int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) { - HRESULT hr = S_OK; - wxCOMPtr fileDialog; - // 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; @@ -270,12 +255,37 @@ int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) if ( HasFlag(wxDD_SHOW_HIDDEN) ) options |= FOS_FORCESHOWHIDDEN; - if ( !InitIFileOpenDialog(m_message, m_path, options, fileDialog) ) + wxMSWImpl::wxIFileDialog fileDialog; + return fileDialog.Show(owner, CLSID_FileOpenDialog, options, + m_message, m_path, &m_paths, &m_path); +} + +#endif // wxUSE_IFILEOPENDIALOG + +#endif // wxUSE_DIRDLG + +#if wxUSE_IFILEOPENDIALOG + +// ---------------------------------------------------------------------------- +// Helper functions used by wxDirDialog and wxFileDialog. +// ---------------------------------------------------------------------------- + +namespace wxMSWImpl +{ + +int wxIFileDialog::Show(HWND owner, const CLSID& clsid, int options, + const wxString& message, + const wxString& defaultValue, + wxArrayString* pathsOut, wxString* pathOut) +{ + HRESULT hr = S_OK; + + if ( !InitIFileDialog(clsid, message, defaultValue, options, m_fileDialog) ) { return wxID_NONE; // Failed to initialize the dialog } - hr = fileDialog->Show(owner); + hr = m_fileDialog->Show(owner); if ( FAILED(hr) ) { if ( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) @@ -289,12 +299,21 @@ int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) } else if ( options & FOS_ALLOWMULTISELECT ) { - if ( GetPathsFromIFileOpenDialog(fileDialog, m_paths) ) - return wxID_OK; + wxCOMPtr fileOpenDialog; + hr = m_fileDialog->QueryInterface(wxIID_PPV_ARGS(IFileOpenDialog, &fileOpenDialog)); + if ( SUCCEEDED(hr) ) + { + if ( GetPathsFromIFileOpenDialog(fileOpenDialog, *pathsOut) ) + return wxID_OK; + } + else + { + wxLogApiError(wxS("IFileDialog::QI(IFileOpenDialog)"), hr); + } } else // Single selection only, path output parameter must be non-null. { - if ( GetPathFromIFileDialog(fileDialog, m_path) ) + if ( GetPathFromIFileDialog(m_fileDialog, *pathOut) ) return wxID_OK; } @@ -303,6 +322,8 @@ int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) return wxID_CANCEL; } +} // namespace wxMSWImpl + // ---------------------------------------------------------------------------- // private functions // ---------------------------------------------------------------------------- @@ -311,14 +332,15 @@ namespace { // helper function for wxDirDialog::ShowIFileOpenDialog() -bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, - int options, - wxCOMPtr& fileDialog) +bool InitIFileDialog(const CLSID& clsid, + const wxString& message, const wxString& defaultPath, + int options, + wxCOMPtr& fileDialog) { HRESULT hr = S_OK; - wxCOMPtr dlg; + wxCOMPtr dlg; - hr = ::CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, + hr = ::CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, wxIID_PPV_ARGS(IFileOpenDialog, &dlg)); if ( FAILED(hr) ) { @@ -450,7 +472,7 @@ bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path) HRESULT hr = fileDialog->GetResult(&item); if ( FAILED(hr) ) { - wxLogApiError(wxS("IFileOpenDialog::GetResult"), hr); + wxLogApiError(wxS("IFileDialog::GetResult"), hr); return false; } @@ -483,6 +505,8 @@ bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path) #endif // wxUSE_IFILEOPENDIALOG +#if wxUSE_DIRDLG + // callback used in wxDirDialog::ShowSHBrowseForFolder() static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) @@ -533,6 +557,4 @@ BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) return 0; } -#endif // compiler/platform on which the code here compiles - #endif // wxUSE_DIRDLG From 4bcbff00f3aa65f918b214aa4b4552e80ab9912e Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 16 May 2022 00:58:19 +0100 Subject: [PATCH 07/43] Split wxIFileDialog::Show() in multiple functions No real changes, just refactor the code to have separate ctor, setter functions and Show() instead of doing everything in a single function. This commit is best viewed ignoring whitespace-only changes. --- include/wx/msw/private/filedialog.h | 31 +++-- src/msw/dirdlg.cpp | 177 ++++++++++++++-------------- 2 files changed, 104 insertions(+), 104 deletions(-) diff --git a/include/wx/msw/private/filedialog.h b/include/wx/msw/private/filedialog.h index 97adaf9415..a7adb6ae43 100644 --- a/include/wx/msw/private/filedialog.h +++ b/include/wx/msw/private/filedialog.h @@ -35,10 +35,25 @@ namespace wxMSWImpl { +// For historical reasons, this class is defined in src/msw/dirdlg.cpp. class wxIFileDialog { public: - wxIFileDialog() { } + // 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. // @@ -46,19 +61,9 @@ public: // 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 or wxID_NONE if creating it failed, in which case - // fileDialog is null -- otherwise it contains the pointer to the dialog which - // can be used to retrieve more information from it. - // - // For historical reasons, this function is defined in src/msw/dirdlg.cpp. + // dialog was cancelled. int - Show(HWND owner, - const CLSID& clsid, - int options, - const wxString& message, - const wxString& defaultValue, - wxArrayString* pathsOut, - wxString* pathOut); + Show(HWND owner, int options, wxArrayString* pathsOut, wxString* pathOut); // Behave as IFileDialog. IFileDialog* operator->() const { return m_fileDialog.Get(); } diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index 082008b6c6..4f2f59412f 100644 --- a/src/msw/dirdlg.cpp +++ b/src/msw/dirdlg.cpp @@ -62,10 +62,6 @@ namespace { // helper functions for wxDirDialog::ShowIFileOpenDialog() -bool InitIFileDialog(const CLSID& clsid, - const wxString& message, const wxString& defaultPath, - int options, - wxCOMPtr& fileDialog); bool GetPathsFromIFileOpenDialog(IFileOpenDialog* fileDialog, wxArrayString& paths); bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path); bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path); @@ -247,6 +243,14 @@ int wxDirDialog::ShowSHBrowseForFolder(WXHWND owner) int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) { + wxMSWImpl::wxIFileDialog fileDialog(CLSID_FileOpenDialog); + if ( !fileDialog.IsOk() ) + return wxID_NONE; + + 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; @@ -255,9 +259,7 @@ int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) if ( HasFlag(wxDD_SHOW_HIDDEN) ) options |= FOS_FORCESHOWHIDDEN; - wxMSWImpl::wxIFileDialog fileDialog; - return fileDialog.Show(owner, CLSID_FileOpenDialog, options, - m_message, m_path, &m_paths, &m_path); + return fileDialog.Show(owner, options, &m_paths, &m_path); } #endif // wxUSE_IFILEOPENDIALOG @@ -273,16 +275,34 @@ int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) namespace wxMSWImpl { -int wxIFileDialog::Show(HWND owner, const CLSID& clsid, int options, - const wxString& message, - const wxString& defaultValue, +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) ) + { + wxLogApiError(wxS("CoCreateInstance(CLSID_FileOpenDialog)"), hr); + } +} + +int wxIFileDialog::Show(HWND owner, int options, wxArrayString* pathsOut, wxString* pathOut) { - HRESULT hr = S_OK; + wxCHECK_MSG( m_fileDialog, wxID_NONE, wxS("shouldn't be called") ); - if ( !InitIFileDialog(clsid, message, defaultValue, options, m_fileDialog) ) + HRESULT hr; + + // allow to select only a file system object + hr = m_fileDialog->SetOptions(options | FOS_FORCEFILESYSTEM); + if ( FAILED(hr) ) { - return wxID_NONE; // Failed to initialize the dialog + wxLogApiError(wxS("IFileOpenDialog::SetOptions"), hr); + return false; } hr = m_fileDialog->Show(owner); @@ -322,6 +342,59 @@ int wxIFileDialog::Show(HWND owner, const CLSID& clsid, int options, return wxID_CANCEL; } +void wxIFileDialog::SetTitle(const wxString& message) +{ + 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); + } +} + +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**); + + SHCreateItemFromParsingName_t pfnSHCreateItemFromParsingName = NULL; + wxDynamicLibrary dllShell32; + if ( dllShell32.Load(wxS("shell32.dll"), wxDL_VERBATIM | wxDL_QUIET) ) + { + wxDL_INIT_FUNC(pfn, SHCreateItemFromParsingName, dllShell32); + } + + if ( !pfnSHCreateItemFromParsingName ) + { + wxLogLastError(wxS("SHCreateItemFromParsingName() not found")); + return; + } + + 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 = m_fileDialog->SetFolder(folder); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileOpenDialog::SetFolder"), hr); + } +} + } // namespace wxMSWImpl // ---------------------------------------------------------------------------- @@ -331,84 +404,6 @@ int wxIFileDialog::Show(HWND owner, const CLSID& clsid, int options, namespace { -// helper function for wxDirDialog::ShowIFileOpenDialog() -bool InitIFileDialog(const CLSID& clsid, - const wxString& message, const wxString& defaultPath, - int options, - wxCOMPtr& fileDialog) -{ - HRESULT hr = S_OK; - wxCOMPtr dlg; - - hr = ::CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, - wxIID_PPV_ARGS(IFileOpenDialog, &dlg)); - if ( FAILED(hr) ) - { - wxLogApiError(wxS("CoCreateInstance(CLSID_FileOpenDialog)"), hr); - return false; - } - - // allow to select only a file system object - hr = dlg->SetOptions(options | FOS_FORCEFILESYSTEM); - if ( FAILED(hr) ) - { - wxLogApiError(wxS("IFileOpenDialog::SetOptions"), hr); - return false; - } - - hr = dlg->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() ) - { - // 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); - } - - if ( !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; -} - // helper function for wxDirDialog::ShowIFileOpenDialog() bool GetPathsFromIFileOpenDialog(IFileOpenDialog* fileDialog, wxArrayString& paths) { From d54bf06060c4f087ddb0cda8858772b3acafc1c2 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 16 May 2022 01:09:21 +0100 Subject: [PATCH 08/43] Dynamically load SHCreateItemFromParsingName() only once Don't do it every time the dialog is shown. --- src/msw/dirdlg.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index 4f2f59412f..dae436113b 100644 --- a/src/msw/dirdlg.cpp +++ b/src/msw/dirdlg.cpp @@ -365,24 +365,35 @@ void wxIFileDialog::SetInitialPath(const wxString& defaultPath) REFIID, void**); - SHCreateItemFromParsingName_t pfnSHCreateItemFromParsingName = NULL; - wxDynamicLibrary dllShell32; - if ( dllShell32.Load(wxS("shell32.dll"), wxDL_VERBATIM | wxDL_QUIET) ) + static SHCreateItemFromParsingName_t + s_pfnSHCreateItemFromParsingName = (SHCreateItemFromParsingName_t)-1; + if ( s_pfnSHCreateItemFromParsingName == (SHCreateItemFromParsingName_t)-1 ) { - wxDL_INIT_FUNC(pfn, SHCreateItemFromParsingName, dllShell32); + wxDynamicLibrary dllShell32; + if ( dllShell32.Load(wxS("shell32.dll"), wxDL_VERBATIM | wxDL_QUIET) ) + { + wxDL_INIT_FUNC(s_pfn, SHCreateItemFromParsingName, dllShell32); + } + + if ( !s_pfnSHCreateItemFromParsingName ) + { + wxLogLastError(wxS("SHCreateItemFromParsingName() not found")); + } } - if ( !pfnSHCreateItemFromParsingName ) + if ( !s_pfnSHCreateItemFromParsingName ) { - wxLogLastError(wxS("SHCreateItemFromParsingName() not found")); + // There is nothing we can do and the error was already reported. return; } wxCOMPtr folder; - hr = pfnSHCreateItemFromParsingName(defaultPath.wc_str(), - NULL, - wxIID_PPV_ARGS(IShellItem, - &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 From cdba0ba961fbac9dede03f44bc9185e5a9caf75d Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 16 May 2022 01:30:04 +0100 Subject: [PATCH 09/43] Add IFileDialog-based wxFileDialog implementation If possible, i.e. if none of the features not supported by IFileDialog are needed, prefer to use IFileDialog for wxFileDialog rather than the old common dialog functions. There are no real differences in appearance because the old functions actually already forward to the new IFileDialog-based implementation internally anyhow, if possible, but this provides us with more flexibility and some things that were ignored by the common dialog functions now work, such as setting the initial dialog directory. --- include/wx/msw/filedlg.h | 4 ++ src/msw/filedlg.cpp | 142 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/include/wx/msw/filedlg.h b/include/wx/msw/filedlg.h index 398641025d..51ac463231 100644 --- a/include/wx/msw/filedlg.h +++ b/include/wx/msw/filedlg.h @@ -59,6 +59,10 @@ private: // functions. int ShowCommFileDialog(WXHWND owner); + // And another one using IFileDialog. + int ShowIFileDialog(WXHWND owner); + + wxArrayString m_fileNames; // remember if our SetPosition() or Centre() (which requires special diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 60636c83c3..55fd4d2074 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -45,7 +45,9 @@ #include "wx/scopeguard.h" #include "wx/tokenzr.h" #include "wx/modalhook.h" + #include "wx/msw/private/dpiaware.h" +#include "wx/msw/private/filedialog.h" // ---------------------------------------------------------------------------- // constants @@ -405,6 +407,21 @@ 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 position it or use custom controls in it but, if + possible, we prefer to use the new style one instead. + */ +#if wxUSE_IFILEOPENDIALOG + if ( !m_bMovedWindow && !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); } @@ -699,4 +716,129 @@ int wxFileDialog::ShowCommFileDialog(WXHWND hWndParent) } +#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; + + // 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 ) + { + UINT nFilterIndex; + hr = fileDialog->GetFileTypeIndex(&nFilterIndex); + if ( SUCCEEDED(hr) ) + { + // As with the common dialog, the index is 1-based here. + m_filterIndex = nFilterIndex - 1; + } + else + { + wxLogApiError(wxS("IFileDialog::GetFileTypeIndex"), hr); + + m_filterIndex = 0; + } + + 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 From cf81527cfcf54d54a3860f2363d8b372d8f04847 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 24 May 2022 23:25:19 +0100 Subject: [PATCH 10/43] Move private wxFileDialog members to wxFileDialogMSWData No real changes, just make it possibly to add more fields to the private struct in the future without affecting the ABI. This commit is best viewed with git --color-moved --color-moved-ws=ignore-all-space options. --- include/wx/msw/filedlg.h | 14 ++++++++--- src/msw/filedlg.cpp | 53 ++++++++++++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/include/wx/msw/filedlg.h b/include/wx/msw/filedlg.h index 51ac463231..86952b6c6b 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,6 +29,7 @@ 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; @@ -62,13 +65,16 @@ private: // And another one using IFileDialog. int ShowIFileDialog(WXHWND owner); + // Get the data object, allocating it if necessary. + struct 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/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 55fd4d2074..d6e69363a1 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -145,6 +145,24 @@ void RestoreExceptionPolicy() } // unnamed namespace +// ---------------------------------------------------------------------------- +// wxFileDialogMSWData: private data used by the dialog +// ---------------------------------------------------------------------------- + +struct wxFileDialogMSWData +{ + wxFileDialogMSWData() + { + m_bMovedWindow = false; + m_centreDir = 0; + } + + // remember if our SetPosition() or Centre() (which requires special + // treatment) was called + bool m_bMovedWindow; + int m_centreDir; // nothing to do if 0 +}; + // ---------------------------------------------------------------------------- // hook function for moving the dialog // ---------------------------------------------------------------------------- @@ -232,8 +250,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 @@ -242,6 +259,19 @@ wxFileDialog::wxFileDialog(wxWindow *parent, gs_rectDialog.y = 0; } +wxFileDialog::~wxFileDialog() +{ + delete m_data; +} + +wxFileDialogMSWData& wxFileDialog::MSWData() +{ + if ( !m_data ) + m_data = new wxFileDialogMSWData(); + + return *m_data; +} + void wxFileDialog::GetPaths(wxArrayString& paths) const { paths.Empty(); @@ -297,18 +327,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 @@ -323,7 +356,7 @@ void wxFileDialog::MSWOnInitDone(WXHWND hDlg) // set HWND so that our DoMoveWindow() works correctly TempHWNDSetter set(this, (WXHWND)hFileDlg); - if ( m_centreDir ) + if ( m_data && m_data->m_centreDir ) { // now we have the real dialog size, remember it RECT rect; @@ -332,7 +365,7 @@ void wxFileDialog::MSWOnInitDone(WXHWND hDlg) // 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 { @@ -413,7 +446,7 @@ int wxFileDialog::ShowModal() possible, we prefer to use the new style one instead. */ #if wxUSE_IFILEOPENDIALOG - if ( !m_bMovedWindow && !HasExtraControlCreator() ) + if ( (!m_data || !m_data->m_bMovedWindow) && !HasExtraControlCreator() ) { const int rc = ShowIFileDialog(hWndParent); if ( rc != wxID_NONE ) @@ -451,7 +484,7 @@ int wxFileDialog::ShowCommFileDialog(WXHWND hWndParent) 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()) // we need these flags. { ChangeExceptionPolicy(); msw_flags |= OFN_EXPLORER|OFN_ENABLEHOOK; From 895b4a07106290c0c5ad1f1237b05da89106b6cb Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 May 2022 16:54:30 +0100 Subject: [PATCH 11/43] Make wxFileDialog::MSWOnXXX() private These functions were always marked as being implementation-only, but we can actually do better and make them private, by making the dialog hook function a (static) member of wxFileDialogMSWData and making this class (which is now a class and not a struct any more, as it's not just a collection of fields any longer) a friend of wxFileDialog. No real changes. --- include/wx/msw/filedlg.h | 30 ++++++++++++++++-------------- src/msw/filedlg.cpp | 18 ++++++++++++------ 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/include/wx/msw/filedlg.h b/include/wx/msw/filedlg.h index 86952b6c6b..d2ba87a54f 100644 --- a/include/wx/msw/filedlg.h +++ b/include/wx/msw/filedlg.h @@ -34,22 +34,9 @@ public: 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; @@ -58,6 +45,21 @@ 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 from the hook procedure on CDN_INITDONE reception + 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); + // The real implementation of ShowModal() using traditional common dialog // functions. int ShowCommFileDialog(WXHWND owner); @@ -66,7 +68,7 @@ private: int ShowIFileDialog(WXHWND owner); // Get the data object, allocating it if necessary. - struct wxFileDialogMSWData& MSWData(); + wxFileDialogMSWData& MSWData(); wxArrayString m_fileNames; diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index d6e69363a1..28715b139d 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -149,14 +149,20 @@ void RestoreExceptionPolicy() // wxFileDialogMSWData: private data used by the dialog // ---------------------------------------------------------------------------- -struct wxFileDialogMSWData +class wxFileDialogMSWData { +public: wxFileDialogMSWData() { m_bMovedWindow = false; m_centreDir = 0; } + // 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; @@ -168,10 +174,10 @@ struct wxFileDialogMSWData // ---------------------------------------------------------------------------- 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 ) { @@ -590,7 +596,7 @@ int wxFileDialog::ShowCommFileDialog(WXHWND hWndParent) of.lpstrInitialDir = dir.c_str(); of.Flags = msw_flags; - of.lpfnHook = wxFileDialogHookFunction; + of.lpfnHook = wxFileDialogMSWData::HookFunction; of.lCustData = (LPARAM)this; wxArrayString wildDescriptions, wildFilters; From 05fdf15c54a2f718136203b26b8314ca3fc6c293 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 May 2022 17:31:55 +0100 Subject: [PATCH 12/43] Use NMHDR to refer to the notification code everywhere It was confusing to take the notification code from NMHDR itself in one place and then from OFNOTIFY just below -- use NMHDR in both places, as OFNOTIFY field refers to the same thing. No real changes. --- src/msw/filedlg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 28715b139d..4b831007dc 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -201,7 +201,7 @@ wxFileDialogMSWData::HookFunction(HWND hDlg, pNotifyCode->lpOFN->lCustData ); - switch ( pNotifyCode->hdr.code ) + switch ( pNM->code ) { case CDN_INITDONE: dialog->MSWOnInitDone((WXHWND)hDlg); From db7cf34e30cf4a9c6741326f1754a69d0e9bd775 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 May 2022 17:33:12 +0100 Subject: [PATCH 13/43] Remove unused HWND parameter of MSWOnTypeChange() Also simplify the hook function code a little by using OPENFILENAME local variable directly instead of using OFNOTIFY only to always use its OPENFILENAME member and nothing else. No real changes. --- include/wx/msw/filedlg.h | 2 +- src/msw/filedlg.cpp | 16 +++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/include/wx/msw/filedlg.h b/include/wx/msw/filedlg.h index d2ba87a54f..e516bda634 100644 --- a/include/wx/msw/filedlg.h +++ b/include/wx/msw/filedlg.h @@ -58,7 +58,7 @@ private: void MSWOnSelChange(WXHWND hDlg); // called from the hook procedure on CDN_TYPECHANGE. - void MSWOnTypeChange(WXHWND hDlg, int nFilterIndex); + void MSWOnTypeChange(int nFilterIndex); // The real implementation of ShowModal() using traditional common dialog // functions. diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 4b831007dc..ee7b53e81f 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -194,12 +194,10 @@ wxFileDialogMSWData::HookFunction(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 ( pNM->code ) { @@ -212,11 +210,7 @@ wxFileDialogMSWData::HookFunction(HWND hDlg, break; case CDN_TYPECHANGE: - dialog->MSWOnTypeChange - ( - (WXHWND)hDlg, - pNotifyCode->lpOFN->nFilterIndex - ); + dialog->MSWOnTypeChange(ofn.nFilterIndex); break; } } @@ -397,7 +391,7 @@ void wxFileDialog::MSWOnSelChange(WXHWND hDlg) 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 From a341efec346b0b7a984eac897e1120629e5de834 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 May 2022 18:05:45 +0100 Subject: [PATCH 14/43] Add helper FileDialogGetFileTypeIndex() This is just a refactoring to allow reusing this function later. No real changes. --- include/wx/msw/private/filedialog.h | 1 + src/msw/filedlg.cpp | 43 ++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/include/wx/msw/private/filedialog.h b/include/wx/msw/private/filedialog.h index a7adb6ae43..fe02dc34ef 100644 --- a/include/wx/msw/private/filedialog.h +++ b/include/wx/msw/private/filedialog.h @@ -66,6 +66,7 @@ public: 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: diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index ee7b53e81f..698ebf4fe8 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -143,6 +143,31 @@ 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 +// ---------------------------------------------------------------------------- + +// 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; +} + +#endif // wxUSE_IFILEOPENDIALOG + } // unnamed namespace // ---------------------------------------------------------------------------- @@ -833,19 +858,11 @@ int wxFileDialog::ShowIFileDialog(WXHWND hWndParent) const int rc = fileDialog.Show(hWndParent, options, &m_fileNames, &m_path); if ( rc == wxID_OK ) { - UINT nFilterIndex; - hr = fileDialog->GetFileTypeIndex(&nFilterIndex); - if ( SUCCEEDED(hr) ) - { - // As with the common dialog, the index is 1-based here. - m_filterIndex = nFilterIndex - 1; - } - else - { - wxLogApiError(wxS("IFileDialog::GetFileTypeIndex"), hr); - - m_filterIndex = 0; - } + // 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) ) { From 0a1714b709ffa8ea18b3bcfc1632fb44c8b32350 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 May 2022 18:06:53 +0100 Subject: [PATCH 15/43] Start using IFileDialogEvents to get notifications about changes For now only update the currently selected file type using this interface, but it will be used for more notifications later. --- include/wx/msw/filedlg.h | 2 +- src/msw/filedlg.cpp | 96 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/include/wx/msw/filedlg.h b/include/wx/msw/filedlg.h index e516bda634..b22eaf9a3d 100644 --- a/include/wx/msw/filedlg.h +++ b/include/wx/msw/filedlg.h @@ -57,7 +57,7 @@ private: // called from the hook procedure on CDN_SELCHANGE. void MSWOnSelChange(WXHWND hDlg); - // called from the hook procedure on CDN_TYPECHANGE. + // 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 diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 698ebf4fe8..c665d37da2 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -49,6 +49,14 @@ #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/msw/wrapshl.h" + + #include "wx/msw/ole/comimpl.h" +#endif // wxUSE_IFILEOPENDIALOG + // ---------------------------------------------------------------------------- // constants // ---------------------------------------------------------------------------- @@ -151,6 +159,34 @@ void RestoreExceptionPolicy() // 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) { @@ -175,9 +211,15 @@ UINT FileDialogGetFileTypeIndex(IFileDialog* fileDialog) // ---------------------------------------------------------------------------- class wxFileDialogMSWData +#if wxUSE_IFILEOPENDIALOG + : public IFileDialogEvents +#endif // wxUSE_IFILEOPENDIALOG { public: - wxFileDialogMSWData() + explicit wxFileDialogMSWData(wxFileDialog* fileDialog) +#if wxUSE_IFILEOPENDIALOG + : m_fileDialog(fileDialog) +#endif // wxUSE_IFILEOPENDIALOG { m_bMovedWindow = false; m_centreDir = 0; @@ -192,8 +234,41 @@ public: // treatment) was called bool m_bMovedWindow; int m_centreDir; // nothing to do if 0 + + +#if wxUSE_IFILEOPENDIALOG + // IFileDialogEvents + wxSTDMETHODIMP OnFileOk(IFileDialog*) wxOVERRIDE { return E_NOTIMPL; } + wxSTDMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) wxOVERRIDE { return E_NOTIMPL; } + wxSTDMETHODIMP OnFolderChange(IFileDialog*) wxOVERRIDE { return E_NOTIMPL; } + wxSTDMETHODIMP OnSelectionChange(IFileDialog*) wxOVERRIDE { return E_NOTIMPL; } + wxSTDMETHODIMP OnShareViolation(IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) wxOVERRIDE { return E_NOTIMPL; } + + wxSTDMETHODIMP OnTypeChange(IFileDialog* pfd) wxOVERRIDE + { + m_fileDialog->MSWOnTypeChange(FileDialogGetFileTypeIndex(pfd)); + + return S_OK; + } + + wxSTDMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) wxOVERRIDE { return E_NOTIMPL; } + + + wxFileDialog* const m_fileDialog; + + DECLARE_IUNKNOWN_METHODS; +#endif // wxUSE_IFILEOPENDIALOG + + wxDECLARE_NO_COPY_CLASS(wxFileDialogMSWData); }; +BEGIN_IID_TABLE(wxFileDialogMSWData) + ADD_IID(Unknown) + ADD_IID(FileDialogEvents) +END_IID_TABLE; + +IMPLEMENT_IUNKNOWN_METHODS(wxFileDialogMSWData) + // ---------------------------------------------------------------------------- // hook function for moving the dialog // ---------------------------------------------------------------------------- @@ -286,13 +361,25 @@ wxFileDialog::wxFileDialog(wxWindow *parent, wxFileDialog::~wxFileDialog() { +#if wxUSE_IFILEOPENDIALOG + if ( m_data ) + m_data->Release(); +#else // !wxUSE_IFILEOPENDIALOG delete m_data; +#endif // wxUSE_IFILEOPENDIALOG } wxFileDialogMSWData& wxFileDialog::MSWData() { if ( !m_data ) - m_data = new wxFileDialogMSWData(); + { + m_data = new wxFileDialogMSWData(this); + +#if wxUSE_IFILEOPENDIALOG + // Make sure it stays alive while we are. + m_data->AddRef(); +#endif // wxUSE_IFILEOPENDIALOG + } return *m_data; } @@ -786,6 +873,11 @@ int wxFileDialog::ShowIFileDialog(WXHWND hWndParent) if ( !fileDialog.IsOk() ) return wxID_NONE; + // Register our event handler with the dialog. + wxFileDialogMSWData& data = MSWData(); + + FileDialogEventsRegistrar registerEvents(fileDialog, data); + // Configure the dialog before showing it. fileDialog.SetTitle(m_message); From 5bd765355b03120954e796a3ce3946a41a1906a9 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 May 2022 18:45:10 +0100 Subject: [PATCH 16/43] Make ConvertIShellItemToPath() public and rename Extract a private function from src/msw/dirdlg.cpp, change its return type to avoid losing information about the error and rename it to a more clear name now that it's used outside of this file. No real changes. --- include/wx/msw/private/filedialog.h | 3 +++ src/msw/dirdlg.cpp | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/include/wx/msw/private/filedialog.h b/include/wx/msw/private/filedialog.h index fe02dc34ef..2f92093f5c 100644 --- a/include/wx/msw/private/filedialog.h +++ b/include/wx/msw/private/filedialog.h @@ -73,6 +73,9 @@ 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 diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index dae436113b..c3317402f0 100644 --- a/src/msw/dirdlg.cpp +++ b/src/msw/dirdlg.cpp @@ -64,7 +64,6 @@ namespace // helper functions for wxDirDialog::ShowIFileOpenDialog() bool GetPathsFromIFileOpenDialog(IFileOpenDialog* fileDialog, wxArrayString& paths); bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path); -bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path); } // anonymous namespace @@ -454,7 +453,8 @@ bool GetPathsFromIFileOpenDialog(IFileOpenDialog* fileDialog, wxArrayString& pat break; } - if ( !ConvertIShellItemToPath(item, path) ) + hr = wxMSWImpl::GetFSPathFromShellItem(item, path); + if ( FAILED(hr) ) { // again, just fail tempPaths.clear(); @@ -482,7 +482,8 @@ bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path) return false; } - if ( !ConvertIShellItemToPath(item, path) ) + hr = wxMSWImpl::GetFSPathFromShellItem(item, path); + if ( FAILED(hr) ) { return false; } @@ -490,8 +491,10 @@ bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path) return true; } -// helper function for wxDirDialog::ShowIFileOpenDialog() -bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path) +} // anonymous namespace + +HRESULT +wxMSWImpl::GetFSPathFromShellItem(const wxCOMPtr& item, wxString& path) { wxCoTaskMemPtr pOLEPath; const HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &pOLEPath); @@ -499,16 +502,14 @@ 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; } -} // anonymous namespace - #endif // wxUSE_IFILEOPENDIALOG #if wxUSE_DIRDLG From a137145a44f67ace4e2a6999715b41b75ea67630 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 May 2022 18:46:32 +0100 Subject: [PATCH 17/43] Update the currently selected file when using IFileDialog too Implement IFileDialogEvents::OnSelectionChange() to react to the changes in the dialog selection. --- include/wx/msw/filedlg.h | 4 ++-- src/msw/filedlg.cpp | 48 ++++++++++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/include/wx/msw/filedlg.h b/include/wx/msw/filedlg.h index b22eaf9a3d..74a708e9f7 100644 --- a/include/wx/msw/filedlg.h +++ b/include/wx/msw/filedlg.h @@ -54,8 +54,8 @@ private: // called from the hook procedure on CDN_INITDONE reception void MSWOnInitDone(WXHWND hDlg); - // called from the hook procedure on CDN_SELCHANGE. - void MSWOnSelChange(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); diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index c665d37da2..35d12da18f 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -241,7 +241,24 @@ public: wxSTDMETHODIMP OnFileOk(IFileDialog*) wxOVERRIDE { return E_NOTIMPL; } wxSTDMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) wxOVERRIDE { return E_NOTIMPL; } wxSTDMETHODIMP OnFolderChange(IFileDialog*) wxOVERRIDE { return E_NOTIMPL; } - wxSTDMETHODIMP OnSelectionChange(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 @@ -306,7 +323,21 @@ wxFileDialogMSWData::HookFunction(HWND hDlg, 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: @@ -486,19 +517,12 @@ void wxFileDialog::MSWOnInitDone(WXHWND hDlg) // Call selection change handler so that update handler will be // called once with no selection. - MSWOnSelChange(hDlg); + MSWOnSelChange(wxString()); } -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(); } From 92534cf270cc4f3953d1f30d94bb7724e00c7f9e Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 May 2022 19:06:45 +0100 Subject: [PATCH 18/43] Check if we need to do something in wxFileDialog::MSWOnInitDone() And don't do anything at all if we don't. No real changes. --- src/msw/filedlg.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 35d12da18f..b859d30f59 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -492,6 +492,12 @@ void wxFileDialog::DoCentre(int dir) void wxFileDialog::MSWOnInitDone(WXHWND hDlg) { + if ( !m_data || !m_data->m_bMovedWindow ) + { + // We only use this to position the dialog, so nothing to do. + return; + } + // note the dialog is the parent window: hDlg is a child of it when // OFN_EXPLORER is used HWND hFileDlg = ::GetParent((HWND)hDlg); @@ -499,7 +505,7 @@ void wxFileDialog::MSWOnInitDone(WXHWND hDlg) // set HWND so that our DoMoveWindow() works correctly TempHWNDSetter set(this, (WXHWND)hFileDlg); - if ( m_data && m_data->m_centreDir ) + if ( m_data->m_centreDir ) { // now we have the real dialog size, remember it RECT rect; From 56bb9c2420c700564600daf25f6911b33ecd487c Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 May 2022 19:07:53 +0100 Subject: [PATCH 19/43] Don't call MSWOnSelChange() from MSWOnInitDone() itself Call it from the hook function when it gets CDN_INITDONE notification instead. This doesn't change anything yet, but will when MSWOnInitDone() is called from elsewhere. --- src/msw/filedlg.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index b859d30f59..4abd25ec1a 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -320,6 +320,10 @@ wxFileDialogMSWData::HookFunction(HWND hDlg, { case CDN_INITDONE: dialog->MSWOnInitDone((WXHWND)hDlg); + + // Call selection change handler so that update + // handler will be called once with no selection. + dialog->MSWOnSelChange(wxString()); break; case CDN_SELCHANGE: @@ -520,10 +524,6 @@ void wxFileDialog::MSWOnInitDone(WXHWND hDlg) { SetPosition(gs_rectDialog.GetPosition()); } - - // Call selection change handler so that update handler will be - // called once with no selection. - MSWOnSelChange(wxString()); } void wxFileDialog::MSWOnSelChange(const wxString& selectedFilename) From 1de1c62872bf4fa080df2251d4b269dba5d6c541 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 May 2022 19:10:31 +0100 Subject: [PATCH 20/43] Pass the correct HWND to MSWOnInitDone() Instead of requiring the function itself to use GetParent(), call it in the hook function and pass the actual dialog HWND to MSWOnInitDone(). No real changes, just refactoring to prepare for further changes. --- src/msw/filedlg.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 4abd25ec1a..f7e45c4d41 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -319,7 +319,9 @@ wxFileDialogMSWData::HookFunction(HWND hDlg, 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. @@ -502,18 +504,14 @@ void wxFileDialog::MSWOnInitDone(WXHWND hDlg) return; } - // note the dialog is the parent window: hDlg is a child of it when - // OFN_EXPLORER is used - HWND hFileDlg = ::GetParent((HWND)hDlg); - // set HWND so that our DoMoveWindow() works correctly - TempHWNDSetter set(this, (WXHWND)hFileDlg); + TempHWNDSetter set(this, hDlg); 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 From 63f44b5bfafa6f6b4e3966c700f2a373d63c39a5 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 May 2022 19:11:35 +0100 Subject: [PATCH 21/43] Call MSWOnInitDone() when using IFileDialog too This allows to use the new style dialog for the file dialogs for which Centre() or SetSize() had been called, as we can now position the window even when not using a hook function. As there is no IFileDialogEvents function corresponding to CDN_INITDONE notification, use the first call to OnTypeChange() instead. --- include/wx/msw/filedlg.h | 2 +- samples/dialogs/dialogs.cpp | 2 ++ src/msw/filedlg.cpp | 29 +++++++++++++++++++++++++---- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/include/wx/msw/filedlg.h b/include/wx/msw/filedlg.h index 74a708e9f7..0c9ba46228 100644 --- a/include/wx/msw/filedlg.h +++ b/include/wx/msw/filedlg.h @@ -51,7 +51,7 @@ private: // called when the dialog is created void MSWOnInitDialogHook(WXHWND hwnd); - // called from the hook procedure on CDN_INITDONE reception + // called when the dialog initialization is fully done void MSWOnInitDone(WXHWND hDlg); // called when the currently selected file changes in the dialog diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index c12adb2731..297c0d1eef 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -1741,6 +1741,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; diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index f7e45c4d41..66ed09ab53 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -218,7 +218,8 @@ class wxFileDialogMSWData public: explicit wxFileDialogMSWData(wxFileDialog* fileDialog) #if wxUSE_IFILEOPENDIALOG - : m_fileDialog(fileDialog) + : m_fileDialog(fileDialog), + m_typeAlreadyChanged(false) #endif // wxUSE_IFILEOPENDIALOG { m_bMovedWindow = false; @@ -263,6 +264,24 @@ public: 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; @@ -273,6 +292,8 @@ public: wxFileDialog* const m_fileDialog; + bool m_typeAlreadyChanged; + DECLARE_IUNKNOWN_METHODS; #endif // wxUSE_IFILEOPENDIALOG @@ -582,11 +603,11 @@ int wxFileDialog::ShowModal() /* We need to use the old style dialog in order to use a hook function - which allows us to position it or use custom controls in it but, if - possible, we prefer to use the new style one instead. + 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 ( (!m_data || !m_data->m_bMovedWindow) && !HasExtraControlCreator() ) + if ( !HasExtraControlCreator() ) { const int rc = ShowIFileDialog(hWndParent); if ( rc != wxID_NONE ) From c93f5350a394665015368b41d529cac368fe3ac7 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 25 May 2022 19:22:30 +0100 Subject: [PATCH 22/43] Fix wxFileDialog repositioning The calls to SetSize() were doing nothing because our own overridden DoGet{Position,Size}() fooled it into thinking that the window already had the correct geometry. Return the actual position and size during MSWOnInitDone(), i.e. when we have a correct HWND, to prevent this from happening. --- src/msw/filedlg.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 66ed09ab53..a804b5eff6 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -467,6 +467,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 ) @@ -475,6 +482,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 ) From 0caddb4472ecea3612eb08e0a37e87f986d98e06 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 27 May 2022 00:29:20 +0100 Subject: [PATCH 23/43] Document important COM implementation macro limitation These macros can't be used with multiple inheritance, as they don't do anything to cast the pointer to the correct type in this case. --- include/wx/msw/ole/comimpl.h | 3 +++ 1 file changed, 3 insertions(+) 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. From f8976c32470516c11a919105a4ae68cdda4fb37b Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 27 May 2022 00:37:43 +0100 Subject: [PATCH 24/43] Implement IUnknown methods manually in wxFileDialogMSWData Using the helper macros won't work with multiple inheritance that is going to be used in the upcoming commits. We also don't need real reference counting here, so simplify the code by not using it. --- src/msw/filedlg.cpp | 50 ++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index a804b5eff6..b5c5acd9fe 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -53,8 +53,6 @@ // wxUSE_IFILEOPENDIALOG is defined. #if wxUSE_IFILEOPENDIALOG #include "wx/msw/wrapshl.h" - - #include "wx/msw/ole/comimpl.h" #endif // wxUSE_IFILEOPENDIALOG // ---------------------------------------------------------------------------- @@ -238,6 +236,33 @@ public: #if wxUSE_IFILEOPENDIALOG + // IUnknown + + wxSTDMETHODIMP QueryInterface(REFIID iid, void** ppv) + { + if ( iid == IID_IUnknown || iid == IID_IFileDialogEvents ) + { + *ppv = 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 { return E_NOTIMPL; } wxSTDMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) wxOVERRIDE { return E_NOTIMPL; } @@ -293,20 +318,11 @@ public: wxFileDialog* const m_fileDialog; bool m_typeAlreadyChanged; - - DECLARE_IUNKNOWN_METHODS; #endif // wxUSE_IFILEOPENDIALOG wxDECLARE_NO_COPY_CLASS(wxFileDialogMSWData); }; -BEGIN_IID_TABLE(wxFileDialogMSWData) - ADD_IID(Unknown) - ADD_IID(FileDialogEvents) -END_IID_TABLE; - -IMPLEMENT_IUNKNOWN_METHODS(wxFileDialogMSWData) - // ---------------------------------------------------------------------------- // hook function for moving the dialog // ---------------------------------------------------------------------------- @@ -419,26 +435,14 @@ wxFileDialog::wxFileDialog(wxWindow *parent, wxFileDialog::~wxFileDialog() { -#if wxUSE_IFILEOPENDIALOG - if ( m_data ) - m_data->Release(); -#else // !wxUSE_IFILEOPENDIALOG delete m_data; -#endif // wxUSE_IFILEOPENDIALOG } wxFileDialogMSWData& wxFileDialog::MSWData() { if ( !m_data ) - { m_data = new wxFileDialogMSWData(this); -#if wxUSE_IFILEOPENDIALOG - // Make sure it stays alive while we are. - m_data->AddRef(); -#endif // wxUSE_IFILEOPENDIALOG - } - return *m_data; } From 359ab98cb2159d868aa21b492ae15d075ed495a6 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 27 May 2022 00:52:49 +0100 Subject: [PATCH 25/43] Start adding new API for wxFileDialog customization wxFileDialog::SetCustomizeHook() can be implemented in terms of IFileDialogCustomize in wxMSW, which means that it can be used with the new style dialogs shown by IFileDialog, unlike SetExtraControlCreator(), which could only be supported when using older style common dialogs. Add support for a few different controls and wxMSW implementation, more controls, generic implementation and the documentation will be updated later. Also update the sample to show the new API in action and allow toggling between using it and the old API for testing. --- include/wx/filedlg.h | 20 ++ include/wx/filedlgcustomize.h | 169 ++++++++++++++++ include/wx/private/filedlgcustomize.h | 67 +++++++ samples/dialogs/dialogs.cpp | 149 +++++++++++--- samples/dialogs/dialogs.h | 2 + src/common/fldlgcmn.cpp | 170 ++++++++++++++++ src/msw/filedlg.cpp | 272 +++++++++++++++++++++++++- 7 files changed, 826 insertions(+), 23 deletions(-) create mode 100644 include/wx/filedlgcustomize.h create mode 100644 include/wx/private/filedlgcustomize.h diff --git a/include/wx/filedlg.h b/include/wx/filedlg.h index 230caf4163..65731b722b 100644 --- a/include/wx/filedlg.h +++ b/include/wx/filedlg.h @@ -24,6 +24,8 @@ #define wxHAS_MULTIPLE_FILEDLG_FILTERS #endif +class WXDLLIMPEXP_FWD_CORE wxFileDialogCustomizeHook; + //---------------------------------------------------------------------------- // wxFileDialog data //---------------------------------------------------------------------------- @@ -127,6 +129,22 @@ public: virtual int GetCurrentlySelectedFilterIndex () const { return m_currentlySelectedFilterIndex; } + + // A customize hook methods will be called by wxFileDialog later if this + // function returns true, see its documentation for details. + // + // Note that the customizeHook object must remain alive at least until + // ShowModal() returns. + // + // If this function returns false, it means that customizing the file + // dialog is not supported on this platforms. + virtual bool SetCustomizeHook(wxFileDialogCustomizeHook& customizeHook); + + + // Extra controls support is deprecated now as it doesn't allow to use the + // contemporary file dialogs under MSW, use wxFileDialogCustomize-based + // API above instead in the new code. + // this function is called with wxFileDialog as parameter and should // create the window containing the extra controls we want to show in it typedef wxWindow *(*ExtraControlCreatorFunction)(wxWindow*); @@ -172,6 +190,8 @@ protected: // provide a useful implementation of GetCurrentlySelectedFilterIndex(). int m_currentlySelectedFilterIndex; + wxFileDialogCustomizeHook* m_customizeHook; + wxWindow* m_extraControl; // returns true if control is created (if it already exists returns false) diff --git a/include/wx/filedlgcustomize.h b/include/wx/filedlgcustomize.h new file mode 100644 index 0000000000..10ff297a9f --- /dev/null +++ b/include/wx/filedlgcustomize.h @@ -0,0 +1,169 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/filedlgcustomize.h +// Purpose: Classes for wxFileDialog customization. +// Author: Vadim Zeitlin +// Created: 2022-05-26 +// Copyright: (c) 2022 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_FILEDLGCUSTOMIZE_H_ +#define _WX_FILEDLGCUSTOMIZE_H_ + +#include "wx/vector.h" + +class wxFileDialogCustomControlImpl; +class wxFileDialogButtonImpl; +class wxFileDialogCheckBoxImpl; +class wxFileDialogTextCtrlImpl; +class wxFileDialogStaticTextImpl; +class wxFileDialogCustomizeImpl; + +// ---------------------------------------------------------------------------- +// wxFileDialog custom controls +// ---------------------------------------------------------------------------- + +// All these controls support a very limited set of functions, but use the same +// names for the things that they do support as the corresponding "normal" wx +// classes. + +// The base class for all wxFileDialog custom controls. +class wxFileDialogCustomControl +{ +public: + void Show(bool show = true); + void Hide() { Show(false); } + + void Enable(bool enable = true); + void Disable() { Enable(false); } + + ~wxFileDialogCustomControl(); + +protected: + explicit wxFileDialogCustomControl(wxFileDialogCustomControlImpl* impl) + : m_impl(impl) + { + } + + wxFileDialogCustomControlImpl* const m_impl; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogCustomControl); +}; + +// A class representing a custom button. +class wxFileDialogButton : public wxFileDialogCustomControl +{ +public: + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogButton(wxFileDialogButtonImpl* impl); + +private: + wxFileDialogButtonImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogButton); +}; + +// A class representing a custom checkbox. +class wxFileDialogCheckBox : public wxFileDialogCustomControl +{ +public: + bool GetValue() const; + void SetValue(bool value); + + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogCheckBox(wxFileDialogCheckBoxImpl* impl); + +private: + wxFileDialogCheckBoxImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogCheckBox); +}; + +// A class representing a custom text control. +class wxFileDialogTextCtrl : public wxFileDialogCustomControl +{ +public: + wxString GetValue() const; + void SetValue(const wxString& text); + + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogTextCtrl(wxFileDialogTextCtrlImpl* impl); + +private: + wxFileDialogTextCtrlImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogTextCtrl); +}; + +// A class representing a custom static text. +class wxFileDialogStaticText : public wxFileDialogCustomControl +{ +public: + void SetLabelText(const wxString& text); + + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogStaticText(wxFileDialogStaticTextImpl* impl); + +private: + wxFileDialogStaticTextImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogStaticText); +}; + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomizer is used by wxFileDialogCustomizeHook +// ---------------------------------------------------------------------------- + +class wxFileDialogCustomize +{ +public: + wxFileDialogButton* AddButton(const wxString& label); + wxFileDialogCheckBox* AddCheckBox(const wxString& label); + wxFileDialogTextCtrl* AddTextCtrl(); + wxFileDialogStaticText* AddStaticText(const wxString& label); + + ~wxFileDialogCustomize(); + +protected: + // Ctor is only used by wxWidgets itself. + // + // Note that we don't take ownership of the implementation pointer here, + // see the comment in the dtor for more details. + explicit wxFileDialogCustomize(wxFileDialogCustomizeImpl* impl) + : m_impl(impl) + { + } + +private: + template T* StoreAndReturn(T* control); + + wxFileDialogCustomizeImpl* const m_impl; + + wxVector m_controls; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogCustomize); +}; + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomizeHook: used by wxFileDialog itself +// ---------------------------------------------------------------------------- + +class wxFileDialogCustomizeHook +{ +public: + // This method must be overridden to add custom controls to the dialog + // using the provided customizer object. + virtual void AddCustomControls(wxFileDialogCustomize& customizer) = 0; + + // This method may be overridden to update the custom controls whenever + // something changes in the dialog. + virtual void UpdateCustomControls() { } + + // This method should typically be overridden to save the values of the + // custom controls when the dialog is accepted. + virtual void TransferDataFromCustomControls() { } + + virtual ~wxFileDialogCustomizeHook(); +}; + +#endif // _WX_FILEDLGCUSTOMIZE_H_ diff --git a/include/wx/private/filedlgcustomize.h b/include/wx/private/filedlgcustomize.h new file mode 100644 index 0000000000..d7c222951d --- /dev/null +++ b/include/wx/private/filedlgcustomize.h @@ -0,0 +1,67 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/private/filedlgcustomize.h +// Purpose: Private helpers used for wxFileDialog customization +// Author: Vadim Zeitlin +// Created: 2022-05-26 +// Copyright: (c) 2022 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_PRIVATE_FILEDLGCUSTOMIZE_H_ +#define _WX_PRIVATE_FILEDLGCUSTOMIZE_H_ + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomControlImpl: interface for all custom controls +// ---------------------------------------------------------------------------- + +class wxFileDialogCustomControlImpl +{ +public: + virtual void Show(bool show) = 0; + virtual void Enable(bool enable) = 0; + + virtual ~wxFileDialogCustomControlImpl(); +}; + +// This class is defined for symmetry with the other ones, but there are no +// button-specific methods so far. +class wxFileDialogButtonImpl : public wxFileDialogCustomControlImpl +{ +}; + +class wxFileDialogCheckBoxImpl : public wxFileDialogCustomControlImpl +{ +public: + virtual bool GetValue() = 0; + virtual void SetValue(bool value) = 0; +}; + +class wxFileDialogTextCtrlImpl : public wxFileDialogCustomControlImpl +{ +public: + virtual wxString GetValue() = 0; + virtual void SetValue(const wxString& value) = 0; +}; + +class wxFileDialogStaticTextImpl : public wxFileDialogCustomControlImpl +{ +public: + virtual void SetLabelText(const wxString& text) = 0; +}; + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomizeImpl: interface for actual customizers +// ---------------------------------------------------------------------------- + +class wxFileDialogCustomizeImpl +{ +public: + virtual wxFileDialogButtonImpl* AddButton(const wxString& label) = 0; + virtual wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) = 0; + virtual wxFileDialogTextCtrlImpl* AddTextCtrl() = 0; + virtual wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) = 0; + + virtual ~wxFileDialogCustomizeImpl(); +}; + +#endif // _WX_PRIVATE_FILEDLGCUSTOMIZE_H_ diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index 297c0d1eef..cbab4f178a 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -87,6 +87,7 @@ #if wxUSE_FILEDLG #include "wx/filedlg.h" + #include "wx/filedlgcustomize.h" #endif // wxUSE_FILEDLG #if wxUSE_DIRDLG @@ -507,6 +508,17 @@ bool MyApp::OnInit() filedlg_menu->Append(DIALOGS_FILE_SAVE_GENERIC, "Sa&ve file (generic)"); #endif // USE_FILEDLG_GENERIC + filedlg_menu->AppendSeparator(); + filedlg_menu->AppendRadioItem( + DIALOGS_FILE_USE_CUSTOMIZER, + "Use new customization API", + "Use wxFileDialog::SetCustomizeHook() for file dialog customization" + ); + filedlg_menu->AppendRadioItem( + DIALOGS_FILE_USE_EXTRA_CONTROL_CREATOR, + "Use old customization API", + "Use wxFileDialog::SetExtraControlCreator() for file dialog customization" + ); #ifdef __WXOSX_COCOA__ filedlg_menu->AppendSeparator(); filedlg_menu->AppendCheckItem(DIALOGS_MAC_TOGGLE_ALWAYS_SHOW_TYPES, @@ -1562,6 +1574,30 @@ void MyFrame::AddRemove(wxCommandEvent& WXUNUSED(event)) #if wxUSE_FILEDLG +// Simple function showing the current wxFileDialog state. +wxString GetFileDialogStateDescription(wxFileDialogBase* dialog) +{ + const wxString fn = dialog->GetCurrentlySelectedFilename(); + + wxString msg; + if ( fn.empty() ) + msg = "Nothing"; + else if ( wxFileName::FileExists(fn) ) + msg = "File"; + else if ( wxFileName::DirExists(fn) ) + msg = "Directory"; + else + msg = "Something else"; + + msg += " selected"; + + const int filter = dialog->GetCurrentlySelectedFilterIndex(); + if ( filter != wxNOT_FOUND ) + msg += wxString::Format(" (filter=%d)", filter); + + return msg; +} + // panel with custom controls for file dialog class MyExtraPanel : public wxPanel { @@ -1590,25 +1626,8 @@ private: // wxGenericFileDialog, so we need to cast to the base class. In a // typical application, we would cast to just wxFileDialog instead. wxFileDialogBase* const dialog = wxStaticCast(GetParent(), wxFileDialogBase); - const wxString fn = dialog->GetCurrentlySelectedFilename(); - wxString msg; - if ( fn.empty() ) - msg = "Nothing"; - else if ( wxFileName::FileExists(fn) ) - msg = "File"; - else if ( wxFileName::DirExists(fn) ) - msg = "Directory"; - else - msg = "Something else"; - - msg += " selected"; - - const int filter = dialog->GetCurrentlySelectedFilterIndex(); - if ( filter != wxNOT_FOUND ) - msg += wxString::Format(" (filter=%d)", filter); - - event.SetText(msg); + event.SetText(GetFileDialogStateDescription(dialog)); } wxString m_str; @@ -1656,6 +1675,64 @@ static wxWindow* createMyExtraPanel(wxWindow *parent) return new MyExtraPanel(parent); } +// This class does the same thing as MyExtraPanel above, but uses newer API for +// wxFileDialog customization. +class MyCustomizeHook : public wxFileDialogCustomizeHook +{ +public: + explicit MyCustomizeHook(wxFileDialog& dialog) + : m_dialog(&dialog) + { + } + + // Override pure virtual base class method to add our custom controls. + virtual void AddCustomControls(wxFileDialogCustomize& customizer) wxOVERRIDE + { + // Note: all the pointers created here cease to be valid once + // ShowModal() returns, TransferDataFromCustomControls() is the latest + // moment when they can still be used. + customizer.AddStaticText("Just some extra text:"); + m_text = customizer.AddTextCtrl(); + m_cb = customizer.AddCheckBox("Enable Custom Button"); + m_btn = customizer.AddButton("Custom Button"); + m_label = customizer.AddStaticText("Nothing selected"); + } + + // Override another method called whenever something changes in the dialog. + virtual void UpdateCustomControls() wxOVERRIDE + { + // Enable the button if and only if the checkbox is checked. + m_btn->Enable(m_cb->GetValue()); + + // Also show the current dialog state. + m_label->SetLabelText(GetFileDialogStateDescription(m_dialog)); + } + + // And another one called when the dialog is accepted. + virtual void TransferDataFromCustomControls() wxOVERRIDE + { + m_info.Printf("checkbox=%d, text=\"%s\"", + m_cb->GetValue(), m_text->GetValue()); + } + + + // This is just a helper function allowing to show the values of the custom + // controls. + wxString GetInfo() const { return m_info; } + +private: + wxFileDialog* const m_dialog; + + wxFileDialogButton* m_btn; + wxFileDialogCheckBox* m_cb; + wxFileDialogTextCtrl* m_text; + wxFileDialogStaticText* m_label; + + wxString m_info; + + wxDECLARE_NO_COPY_CLASS(MyCustomizeHook); +}; + void MyFrame::FileOpen(wxCommandEvent& WXUNUSED(event) ) { wxFileDialog dialog @@ -1672,14 +1749,43 @@ void MyFrame::FileOpen(wxCommandEvent& WXUNUSED(event) ) ) ); - dialog.SetExtraControlCreator(&createMyExtraPanel); + // Note: this object must remain alive until ShowModal() returns. + MyCustomizeHook myCustomizer(dialog); + + // Normal programs would use either SetCustomizeHook() (preferred) or + // SetExtraControlCreator() (if its extra flexibility is really required), + // but, for demonstration purposes, this sample allows either one or the + // other. + const bool useExtra = + GetMenuBar()->IsChecked(DIALOGS_FILE_USE_EXTRA_CONTROL_CREATOR); + const bool hasExtra = + useExtra ? dialog.SetExtraControlCreator(&createMyExtraPanel) + : dialog.SetCustomizeHook(myCustomizer); + dialog.CentreOnParent(); dialog.SetDirectory(wxGetHomeDir()); if (dialog.ShowModal() == wxID_OK) { + wxString extraInfo; + if ( hasExtra ) + { + if ( useExtra ) + { + wxWindow * const extra = dialog.GetExtraControl(); + extraInfo = static_cast(extra)->GetInfo(); + } + else + { + extraInfo = myCustomizer.GetInfo(); + } + } + else + { + extraInfo = ""; + } + wxString info; - wxWindow * const extra = dialog.GetExtraControl(); info.Printf("Full file name: %s\n" "Path: %s\n" "Name: %s\n" @@ -1687,8 +1793,7 @@ void MyFrame::FileOpen(wxCommandEvent& WXUNUSED(event) ) dialog.GetPath(), dialog.GetDirectory(), dialog.GetFilename(), - extra ? static_cast(extra)->GetInfo() - : wxString("None")); + extraInfo); wxMessageDialog dialog2(this, info, "Selected file"); dialog2.ShowModal(); } diff --git a/samples/dialogs/dialogs.h b/samples/dialogs/dialogs.h index a026b0a157..2c8cd705d5 100644 --- a/samples/dialogs/dialogs.h +++ b/samples/dialogs/dialogs.h @@ -620,6 +620,8 @@ enum DIALOGS_FILE_OPEN_GENERIC, DIALOGS_FILES_OPEN_GENERIC, DIALOGS_FILE_SAVE_GENERIC, + DIALOGS_FILE_USE_CUSTOMIZER, + DIALOGS_FILE_USE_EXTRA_CONTROL_CREATOR, DIALOGS_MAC_TOGGLE_ALWAYS_SHOW_TYPES, DIALOGS_DIR_CHOOSE, DIALOGS_DIR_CHOOSE_WINDOW_MODAL, diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index bd93a71657..7fb3822864 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -24,6 +24,9 @@ #include "wx/window.h" #endif // WX_PRECOMP +#include "wx/filedlgcustomize.h" +#include "wx/private/filedlgcustomize.h" + extern WXDLLEXPORT_DATA(const char) wxFileDialogNameStr[] = "filedlg"; extern WXDLLEXPORT_DATA(const char) wxFileSelectorPromptStr[] = "Select a file"; extern WXDLLEXPORT_DATA(const char) wxFileSelectorDefaultWildcardStr[] = @@ -34,6 +37,157 @@ extern WXDLLEXPORT_DATA(const char) wxFileSelectorDefaultWildcardStr[] = #endif ; +// ============================================================================ +// File dialog customization support +// ============================================================================ + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomControl and derived classes +// ---------------------------------------------------------------------------- + +// Everything here is trivial, but has to be defined here, where the private +// "Impl" classes are fully declared. + +wxFileDialogCustomControlImpl::~wxFileDialogCustomControlImpl() +{ +} + +void wxFileDialogCustomControl::Show(bool show) +{ + return m_impl->Show(show); +} + +void wxFileDialogCustomControl::Enable(bool enable) +{ + return m_impl->Enable(enable); +} + +wxFileDialogCustomControl::~wxFileDialogCustomControl() +{ + delete m_impl; +} + +wxFileDialogButton::wxFileDialogButton(wxFileDialogButtonImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +wxFileDialogButtonImpl* wxFileDialogButton::GetImpl() const +{ + return static_cast(m_impl); +} + +wxFileDialogCheckBox::wxFileDialogCheckBox(wxFileDialogCheckBoxImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +wxFileDialogCheckBoxImpl* wxFileDialogCheckBox::GetImpl() const +{ + return static_cast(m_impl); +} + +bool wxFileDialogCheckBox::GetValue() const +{ + return GetImpl()->GetValue(); +} + +void wxFileDialogCheckBox::SetValue(bool value) +{ + GetImpl()->SetValue(value); +} + +wxFileDialogTextCtrl::wxFileDialogTextCtrl(wxFileDialogTextCtrlImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +wxFileDialogTextCtrlImpl* wxFileDialogTextCtrl::GetImpl() const +{ + return static_cast(m_impl); +} + +wxString wxFileDialogTextCtrl::GetValue() const +{ + return GetImpl()->GetValue(); +} + +void wxFileDialogTextCtrl::SetValue(const wxString& value) +{ + GetImpl()->SetValue(value); +} + +wxFileDialogStaticText::wxFileDialogStaticText(wxFileDialogStaticTextImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +wxFileDialogStaticTextImpl* wxFileDialogStaticText::GetImpl() const +{ + return static_cast(m_impl); +} + +void wxFileDialogStaticText::SetLabelText(const wxString& text) +{ + GetImpl()->SetLabelText(text); +} + +// ---------------------------------------------------------------------------- +// wxFileDialogCustomize +// ---------------------------------------------------------------------------- + +wxFileDialogCustomizeHook::~wxFileDialogCustomizeHook() +{ +} + +wxFileDialogCustomizeImpl::~wxFileDialogCustomizeImpl() +{ +} + +wxFileDialogCustomize::~wxFileDialogCustomize() +{ + // For consistency with the rest of wx API, we own all the custom controls + // pointers and delete them when we're deleted. + for ( size_t n = 0; n < m_controls.size(); ++n ) + delete m_controls[n]; + + // Do not delete m_impl, the derived classes use this object itself as + // implementation, which allows us to avoid allocating it on the heap in + // the first place. +} + +template +T* +wxFileDialogCustomize::StoreAndReturn(T* control) +{ + m_controls.push_back(control); + return control; +} + +wxFileDialogButton* +wxFileDialogCustomize::AddButton(const wxString& label) +{ + return StoreAndReturn(new wxFileDialogButton(m_impl->AddButton(label))); +} + +wxFileDialogCheckBox* +wxFileDialogCustomize::AddCheckBox(const wxString& label) +{ + return StoreAndReturn(new wxFileDialogCheckBox(m_impl->AddCheckBox(label))); +} + +wxFileDialogTextCtrl* +wxFileDialogCustomize::AddTextCtrl() +{ + return StoreAndReturn(new wxFileDialogTextCtrl(m_impl->AddTextCtrl())); +} + +wxFileDialogStaticText* +wxFileDialogCustomize::AddStaticText(const wxString& label) +{ + return StoreAndReturn(new wxFileDialogStaticText(m_impl->AddStaticText(label))); +} + //---------------------------------------------------------------------------- // wxFileDialogBase //---------------------------------------------------------------------------- @@ -45,6 +199,7 @@ void wxFileDialogBase::Init() m_filterIndex = 0; m_currentlySelectedFilterIndex = wxNOT_FOUND; m_windowStyle = 0; + m_customizeHook = NULL; m_extraControl = NULL; m_extraControlCreator = NULL; } @@ -163,6 +318,18 @@ wxString wxFileDialogBase::AppendExtension(const wxString &filePath, return filePath + ext; } +bool wxFileDialogBase::SetCustomizeHook(wxFileDialogCustomizeHook& customizeHook) +{ + if ( !SupportsExtraControl() ) + return false; + + wxASSERT_MSG( !m_extraControlCreator, + "Call either SetExtraControlCreator() or SetCustomizeHook()" ); + + m_customizeHook = &customizeHook; + return true; +} + bool wxFileDialogBase::SetExtraControlCreator(ExtraControlCreatorFunction creator) { wxCHECK_MSG( !m_extraControlCreator, false, @@ -194,6 +361,9 @@ wxSize wxFileDialogBase::GetExtraControlSize() void wxFileDialogBase::UpdateExtraControlUI() { + if ( m_customizeHook ) + m_customizeHook->UpdateCustomControls(); + if ( m_extraControl ) m_extraControl->UpdateWindowUI(wxUPDATE_UI_RECURSE); } diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index b5c5acd9fe..a742220ada 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -52,7 +52,17 @@ // Note: this must be done after including the header above, as this is where // wxUSE_IFILEOPENDIALOG is defined. #if wxUSE_IFILEOPENDIALOG + #include "wx/filedlgcustomize.h" + #include "wx/private/filedlgcustomize.h" + + #include "wx/button.h" + #include "wx/checkbox.h" + #include "wx/stattext.h" + #include "wx/textctrl.h" + #include "wx/msw/wrapshl.h" + + #include "wx/msw/private/cotaskmemptr.h" #endif // wxUSE_IFILEOPENDIALOG // ---------------------------------------------------------------------------- @@ -200,6 +210,246 @@ UINT FileDialogGetFileTypeIndex(IFileDialog* fileDialog) return nFilterIndex; } +// ---------------------------------------------------------------------------- +// IFileDialogCustomize-related stuff: all classes use FDC suffix +// ---------------------------------------------------------------------------- + +// Base class class used only to avoid template bloat: this contains all the +// type-independent parts of wxFileDialogImplFDC. +class wxFileDialogImplFDCBase +{ +protected: + wxFileDialogImplFDCBase(IFileDialogCustomize* fdc, DWORD id) + : m_fdc(fdc), + m_id(id) + { + } + + void DoUpdateState(CDCONTROLSTATEF stateBit, bool on) + { + CDCONTROLSTATEF state = CDCS_INACTIVE; + HRESULT hr = m_fdc->GetControlState(m_id, &state); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::GetControlState"), hr); + + if ( on ) + state |= stateBit; + else + state &= ~stateBit; + + hr = m_fdc->SetControlState(m_id, state); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::SetControlState"), hr); + } + + IFileDialogCustomize* const m_fdc; + const DWORD m_id; +}; + +// Template base class for the implementation classes below inheriting from the +// specified Impl subclass. +template +class wxFileDialogImplFDC + : public T, + protected wxFileDialogImplFDCBase +{ +public: + wxFileDialogImplFDC(IFileDialogCustomize* fdc, DWORD id) + : wxFileDialogImplFDCBase(fdc, id) + { + } + + virtual void Show(bool show) wxOVERRIDE + { + DoUpdateState(CDCS_VISIBLE, show); + } + + virtual void Enable(bool enable) wxOVERRIDE + { + DoUpdateState(CDCS_ENABLED, enable); + } +}; + +class wxFileDialogCustomControlImplFDC + : public wxFileDialogImplFDC +{ +public: + // All custom controls are identified by their ID in this implementation. + wxFileDialogCustomControlImplFDC(IFileDialogCustomize* fdc, DWORD id) + : wxFileDialogImplFDC(fdc, id) + { + } + + wxDECLARE_NO_COPY_CLASS(wxFileDialogCustomControlImplFDC); +}; + +class wxFileDialogButtonImplFDC + : public wxFileDialogImplFDC +{ +public: + wxFileDialogButtonImplFDC(IFileDialogCustomize* fdc, DWORD id) + : wxFileDialogImplFDC(fdc, id) + { + } +}; + +class wxFileDialogCheckBoxImplFDC + : public wxFileDialogImplFDC +{ +public: + wxFileDialogCheckBoxImplFDC(IFileDialogCustomize* fdc, DWORD id) + : wxFileDialogImplFDC(fdc, id) + { + } + + virtual bool GetValue() wxOVERRIDE + { + BOOL checked = FALSE; + HRESULT hr = m_fdc->GetCheckButtonState(m_id, &checked); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::GetCheckButtonState"), hr); + + return checked != FALSE; + } + + virtual void SetValue(bool value) wxOVERRIDE + { + HRESULT hr = m_fdc->SetCheckButtonState(m_id, value ? TRUE : FALSE); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::SetCheckButtonState"), hr); + } +}; + +class wxFileDialogTextCtrlImplFDC + : public wxFileDialogImplFDC +{ +public: + wxFileDialogTextCtrlImplFDC(IFileDialogCustomize* fdc, DWORD id) + : wxFileDialogImplFDC(fdc, id) + { + } + + virtual wxString GetValue() wxOVERRIDE + { + wxCoTaskMemPtr value; + HRESULT hr = m_fdc->GetEditBoxText(m_id, &value); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::GetEditBoxText"), hr); + + return wxString(value); + } + + virtual void SetValue(const wxString& value) wxOVERRIDE + { + HRESULT hr = m_fdc->SetEditBoxText(m_id, value.wc_str()); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::SetEditBoxText"), hr); + } +}; + +class wxFileDialogStaticTextImplFDC + : public wxFileDialogImplFDC +{ +public: + wxFileDialogStaticTextImplFDC(IFileDialogCustomize* fdc, DWORD id) + : wxFileDialogImplFDC(fdc, id) + { + } + + virtual void SetLabelText(const wxString& text) wxOVERRIDE + { + // Prevent ampersands from being misinterpreted as mnemonics. + const wxString& label = wxControl::EscapeMnemonics(text); + + HRESULT hr = m_fdc->SetControlLabel(m_id, label.wc_str()); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::SetControlLabel"), hr); + } +}; + +// Implementation of wxFileDialogCustomize based on IFileDialogCustomize: to +// simplify things, this class is its own implementation pointer too. +class wxFileDialogCustomizeFDC : public wxFileDialogCustomize, + private wxFileDialogCustomizeImpl +{ +public: + // For convenience, this class has a default ctor, but it can't be really + // used before Initialize() is called. + wxFileDialogCustomizeFDC() + : wxFileDialogCustomize(this) + { + m_lastId = 0; + } + + bool Initialize(IFileDialog* fileDialog) + { + HRESULT hr = fileDialog->QueryInterface + ( + wxIID_PPV_ARGS(IFileDialogCustomize, &m_fdc) + ); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialog::QI(IFileDialogCustomize)"), hr); + + return false; + } + + return true; + } + + wxFileDialogButtonImpl* AddButton(const wxString& label) wxOVERRIDE + { + HRESULT hr = m_fdc->AddPushButton(++m_lastId, label.wc_str()); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddPushButton"), hr); + return NULL; + } + + return new wxFileDialogButtonImplFDC(m_fdc, m_lastId); + } + + wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) wxOVERRIDE + { + HRESULT hr = m_fdc->AddCheckButton(++m_lastId, label.wc_str(), FALSE); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddCheckButton"), hr); + return NULL; + } + + return new wxFileDialogCheckBoxImplFDC(m_fdc, m_lastId); + } + + wxFileDialogTextCtrlImpl* AddTextCtrl() wxOVERRIDE + { + HRESULT hr = m_fdc->AddEditBox(++m_lastId, L""); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddEditBox"), hr); + return NULL; + } + + return new wxFileDialogTextCtrlImplFDC(m_fdc, m_lastId); + } + + wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) wxOVERRIDE + { + HRESULT hr = m_fdc->AddText(++m_lastId, label.wc_str()); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddText"), hr); + return NULL; + } + + return new wxFileDialogStaticTextImplFDC(m_fdc, m_lastId); + } + +private: + wxCOMPtr m_fdc; + DWORD m_lastId; +}; + #endif // wxUSE_IFILEOPENDIALOG } // unnamed namespace @@ -264,7 +514,18 @@ public: // IFileDialogEvents - wxSTDMETHODIMP OnFileOk(IFileDialog*) wxOVERRIDE { return E_NOTIMPL; } + + wxSTDMETHODIMP OnFileOk(IFileDialog*) wxOVERRIDE + { + // Note that we need to call this hook function from here as the + // controls are destroyed later and getting their values wouldn't work + // any more. + if ( m_fileDialog->m_customizeHook ) + m_fileDialog->m_customizeHook->TransferDataFromCustomControls(); + + return S_OK; + } + wxSTDMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) wxOVERRIDE { return E_NOTIMPL; } wxSTDMETHODIMP OnFolderChange(IFileDialog*) wxOVERRIDE { return E_NOTIMPL; } @@ -317,6 +578,8 @@ public: wxFileDialog* const m_fileDialog; + wxFileDialogCustomizeFDC m_customize; + bool m_typeAlreadyChanged; #endif // wxUSE_IFILEOPENDIALOG @@ -945,6 +1208,13 @@ int wxFileDialog::ShowIFileDialog(WXHWND hWndParent) FileDialogEventsRegistrar registerEvents(fileDialog, data); + // Add custom controls, if any. + if ( m_customizeHook ) + { + if ( data.m_customize.Initialize(fileDialog.Get()) ) + m_customizeHook->AddCustomControls(data.m_customize); + } + // Configure the dialog before showing it. fileDialog.SetTitle(m_message); From 095c4dfc94edf25cbe7d5b76feda7c6f70bac899 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 28 May 2022 01:12:10 +0100 Subject: [PATCH 26/43] Add event generation to MSW IFileDialog-based implementation Inherit wxFileDialogCustomControl from wxEvtHandler to allow Bind()ing to events on it and use this to handle wxEVT_BUTTON and wxEVT_CHECKBOX in the sample. And inherit from IFileDialogControlEvents in wxMSW code to actually generate these events when they happen. --- include/wx/filedlgcustomize.h | 19 +++++++-- samples/dialogs/dialogs.cpp | 13 ++++++ src/common/fldlgcmn.cpp | 29 ++++++++++++++ src/msw/filedlg.cpp | 75 ++++++++++++++++++++++++++++++++++- 4 files changed, 130 insertions(+), 6 deletions(-) diff --git a/include/wx/filedlgcustomize.h b/include/wx/filedlgcustomize.h index 10ff297a9f..b088cce6a1 100644 --- a/include/wx/filedlgcustomize.h +++ b/include/wx/filedlgcustomize.h @@ -25,10 +25,10 @@ class wxFileDialogCustomizeImpl; // 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. +// classes and also generate some (but not all) of the same events. // The base class for all wxFileDialog custom controls. -class wxFileDialogCustomControl +class wxFileDialogCustomControl : public wxEvtHandler { public: void Show(bool show = true); @@ -45,6 +45,11 @@ protected: { } + // 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); @@ -57,6 +62,9 @@ public: // Ctor is only used by wxWidgets itself. explicit wxFileDialogButton(wxFileDialogButtonImpl* impl); +protected: + virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE; + private: wxFileDialogButtonImpl* GetImpl() const; @@ -73,6 +81,9 @@ public: // Ctor is only used by wxWidgets itself. explicit wxFileDialogCheckBox(wxFileDialogCheckBoxImpl* impl); +protected: + virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE; + private: wxFileDialogCheckBoxImpl* GetImpl() const; @@ -134,13 +145,13 @@ protected: { } + wxVector m_controls; + private: template T* StoreAndReturn(T* control); wxFileDialogCustomizeImpl* const m_impl; - wxVector m_controls; - wxDECLARE_NO_COPY_CLASS(wxFileDialogCustomize); }; diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index cbab4f178a..e7d0f25011 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -1694,7 +1694,9 @@ public: customizer.AddStaticText("Just some extra text:"); m_text = customizer.AddTextCtrl(); 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"); } @@ -1721,6 +1723,17 @@ public: 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); + } + wxFileDialog* const m_dialog; wxFileDialogButton* m_btn; diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index 7fb3822864..6606ea8b8e 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -52,6 +52,19 @@ wxFileDialogCustomControlImpl::~wxFileDialogCustomControlImpl() { } +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); @@ -72,6 +85,14 @@ wxFileDialogButton::wxFileDialogButton(wxFileDialogButtonImpl* impl) { } +bool wxFileDialogButton::OnDynamicBind(wxDynamicEventTableEntry& entry) +{ + if ( entry.m_eventType == wxEVT_BUTTON ) + return true; + + return wxFileDialogCustomControl::OnDynamicBind(entry); +} + wxFileDialogButtonImpl* wxFileDialogButton::GetImpl() const { return static_cast(m_impl); @@ -82,6 +103,14 @@ wxFileDialogCheckBox::wxFileDialogCheckBox(wxFileDialogCheckBoxImpl* impl) { } +bool wxFileDialogCheckBox::OnDynamicBind(wxDynamicEventTableEntry& entry) +{ + if ( entry.m_eventType == wxEVT_CHECKBOX ) + return true; + + return wxFileDialogCustomControl::OnDynamicBind(entry); +} + wxFileDialogCheckBoxImpl* wxFileDialogCheckBox::GetImpl() const { return static_cast(m_impl); diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index a742220ada..57bbdc21f6 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -397,6 +397,18 @@ public: 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 { HRESULT hr = m_fdc->AddPushButton(++m_lastId, label.wc_str()); @@ -460,7 +472,8 @@ private: class wxFileDialogMSWData #if wxUSE_IFILEOPENDIALOG - : public IFileDialogEvents + : public IFileDialogEvents, + public IFileDialogControlEvents #endif // wxUSE_IFILEOPENDIALOG { public: @@ -492,7 +505,14 @@ public: { if ( iid == IID_IUnknown || iid == IID_IFileDialogEvents ) { - *ppv = this; + // 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 { @@ -576,6 +596,57 @@ public: wxSTDMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) wxOVERRIDE { return E_NOTIMPL; } + // IFileDialogControlEvents + + wxSTDMETHODIMP + OnItemSelected(IFileDialogCustomize*, + DWORD WXUNUSED(dwIDCtl), + DWORD WXUNUSED(dwIDItem)) wxOVERRIDE + { + 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; From 751a73a2caea136e38efb45cdbaf6e820eacec55 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 28 May 2022 01:26:39 +0100 Subject: [PATCH 27/43] Add optional label to wxFileDialogCustomize::AddTextCtrl() Creating a text with a label is a common operation and can be implemented to look slightly better than AddStaticText() followed by AddTextCtrl() without a label when using IFileDialog. --- include/wx/filedlgcustomize.h | 2 +- include/wx/private/filedlgcustomize.h | 2 +- samples/dialogs/dialogs.cpp | 3 +-- src/common/fldlgcmn.cpp | 4 ++-- src/msw/filedlg.cpp | 31 ++++++++++++++++++++++++--- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/include/wx/filedlgcustomize.h b/include/wx/filedlgcustomize.h index b088cce6a1..7898b3dcc2 100644 --- a/include/wx/filedlgcustomize.h +++ b/include/wx/filedlgcustomize.h @@ -130,7 +130,7 @@ class wxFileDialogCustomize public: wxFileDialogButton* AddButton(const wxString& label); wxFileDialogCheckBox* AddCheckBox(const wxString& label); - wxFileDialogTextCtrl* AddTextCtrl(); + wxFileDialogTextCtrl* AddTextCtrl(const wxString& label = wxString()); wxFileDialogStaticText* AddStaticText(const wxString& label); ~wxFileDialogCustomize(); diff --git a/include/wx/private/filedlgcustomize.h b/include/wx/private/filedlgcustomize.h index d7c222951d..4523941842 100644 --- a/include/wx/private/filedlgcustomize.h +++ b/include/wx/private/filedlgcustomize.h @@ -58,7 +58,7 @@ class wxFileDialogCustomizeImpl public: virtual wxFileDialogButtonImpl* AddButton(const wxString& label) = 0; virtual wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) = 0; - virtual wxFileDialogTextCtrlImpl* AddTextCtrl() = 0; + virtual wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) = 0; virtual wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) = 0; virtual ~wxFileDialogCustomizeImpl(); diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index e7d0f25011..8886eb0ae9 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -1691,8 +1691,7 @@ public: // Note: all the pointers created here cease to be valid once // ShowModal() returns, TransferDataFromCustomControls() is the latest // moment when they can still be used. - customizer.AddStaticText("Just some extra text:"); - m_text = customizer.AddTextCtrl(); + m_text = customizer.AddTextCtrl("Just some extra text:"); m_cb = customizer.AddCheckBox("Enable Custom Button"); m_cb->Bind(wxEVT_CHECKBOX, &MyCustomizeHook::OnCheckBox, this); m_btn = customizer.AddButton("Custom Button"); diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index 6606ea8b8e..5dfc24f0d8 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -206,9 +206,9 @@ wxFileDialogCustomize::AddCheckBox(const wxString& label) } wxFileDialogTextCtrl* -wxFileDialogCustomize::AddTextCtrl() +wxFileDialogCustomize::AddTextCtrl(const wxString& label) { - return StoreAndReturn(new wxFileDialogTextCtrl(m_impl->AddTextCtrl())); + return StoreAndReturn(new wxFileDialogTextCtrl(m_impl->AddTextCtrl(label))); } wxFileDialogStaticText* diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 57bbdc21f6..94edf19a05 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -378,7 +378,8 @@ public: wxFileDialogCustomizeFDC() : wxFileDialogCustomize(this) { - m_lastId = 0; + m_lastId = + m_lastAuxId = 0; } bool Initialize(IFileDialog* fileDialog) @@ -433,15 +434,31 @@ public: return new wxFileDialogCheckBoxImplFDC(m_fdc, m_lastId); } - wxFileDialogTextCtrlImpl* AddTextCtrl() wxOVERRIDE + wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) wxOVERRIDE { - HRESULT hr = m_fdc->AddEditBox(++m_lastId, L""); + 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); } @@ -459,7 +476,15 @@ public: 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). + DWORD m_lastAuxId; }; #endif // wxUSE_IFILEOPENDIALOG From 096c4bf19541830bdba36fc96b8f1cab50008f9c Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 28 May 2022 01:33:05 +0100 Subject: [PATCH 28/43] Remove unnecessary HasExtraControlCreator() check from wxOSX There is no need to call this function, just call CreateExtraControl() directly -- it will do nothing if there are no extra controls to create. No real changes. --- src/osx/cocoa/filedlg.mm | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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; From f6a261468ed26b5be99e5e716f7ee73597646c16 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 28 May 2022 01:35:42 +0100 Subject: [PATCH 29/43] Slightly change the logic of CreateExtraControl() This will make it simpler to extend in the upcoming commits. No real changes yet. --- src/common/fldlgcmn.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index 5dfc24f0d8..d57b4b8110 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -370,10 +370,21 @@ bool wxFileDialogBase::SetExtraControlCreator(ExtraControlCreatorFunction creato bool wxFileDialogBase::CreateExtraControl() { - if (!m_extraControlCreator || m_extraControl) - return false; - m_extraControl = (*m_extraControlCreator)(this); - return true; + // 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 ) + return true; + + if ( m_extraControlCreator ) + { + m_extraControl = (*m_extraControlCreator)(this); + + return true; + } + + // It's not an error to call this function if there are no extra controls + // to create, just do nothing in this case. + return false; } wxSize wxFileDialogBase::GetExtraControlSize() From fdcaeb050f92c5ea5168a9deeacb65dbbef0c01c Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 28 May 2022 19:01:11 +0100 Subject: [PATCH 30/43] Move GetExtraControlSize() hack from wxFileDialogBase to MSW code The hack with creating a dummy dialog just to get the size of the extra controls is only used in wxMSW, so move it to MSW-specific file from the common code. To allow doing this there, add CreateExtraControlWithParent() helper, which is still not really used anywhere else than in wxMSW, but at least doesn't do anything particularly ugly and doesn't really penalize the common code for wxMSW sins. No real changes. --- include/wx/filedlg.h | 6 +++--- src/common/fldlgcmn.cpp | 37 +++++++++++++------------------------ src/msw/filedlg.cpp | 7 ++++++- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/include/wx/filedlg.h b/include/wx/filedlg.h index 65731b722b..fa53e25620 100644 --- a/include/wx/filedlg.h +++ b/include/wx/filedlg.h @@ -194,13 +194,13 @@ protected: 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/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index d57b4b8110..09e0edfefc 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -368,35 +368,24 @@ bool wxFileDialogBase::SetExtraControlCreator(ExtraControlCreatorFunction creato return SupportsExtraControl(); } +wxWindow* wxFileDialogBase::CreateExtraControlWithParent(wxWindow* parent) const +{ + 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; +} + bool wxFileDialogBase::CreateExtraControl() { // 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 ) - return true; + if ( !m_extraControl ) + m_extraControl = CreateExtraControlWithParent(this); - if ( m_extraControlCreator ) - { - m_extraControl = (*m_extraControlCreator)(this); - - return true; - } - - // It's not an error to call this function if there are no extra controls - // to create, just do nothing in this case. - return false; -} - -wxSize wxFileDialogBase::GetExtraControlSize() -{ - if ( !m_extraControlCreator ) - return wxDefaultSize; - - // 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() diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 94edf19a05..3f29a50584 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -1075,8 +1075,13 @@ int wxFileDialog::ShowCommFileDialog(WXHWND hWndParent) 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)); From ae1665742673ec411d799c55c8c4ac6dd9e34968 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 28 May 2022 19:05:03 +0100 Subject: [PATCH 31/43] Add generic implementation of wxFileDialogCustomize API Add implementation of the new file dialog customization API in terms of the old one for the non-MSW ports (or even wxMSW if IFileDialog can't be used for whatever reasons). Just map wxFileDialogFoo to the corresponding wxFoo. --- include/wx/filedlgcustomize.h | 14 +- src/common/fldlgcmn.cpp | 233 ++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+), 7 deletions(-) diff --git a/include/wx/filedlgcustomize.h b/include/wx/filedlgcustomize.h index 7898b3dcc2..ba13d07850 100644 --- a/include/wx/filedlgcustomize.h +++ b/include/wx/filedlgcustomize.h @@ -28,7 +28,7 @@ class wxFileDialogCustomizeImpl; // classes and also generate some (but not all) of the same events. // The base class for all wxFileDialog custom controls. -class wxFileDialogCustomControl : public wxEvtHandler +class WXDLLIMPEXP_CORE wxFileDialogCustomControl : public wxEvtHandler { public: void Show(bool show = true); @@ -56,7 +56,7 @@ protected: }; // A class representing a custom button. -class wxFileDialogButton : public wxFileDialogCustomControl +class WXDLLIMPEXP_CORE wxFileDialogButton : public wxFileDialogCustomControl { public: // Ctor is only used by wxWidgets itself. @@ -72,7 +72,7 @@ private: }; // A class representing a custom checkbox. -class wxFileDialogCheckBox : public wxFileDialogCustomControl +class WXDLLIMPEXP_CORE wxFileDialogCheckBox : public wxFileDialogCustomControl { public: bool GetValue() const; @@ -91,7 +91,7 @@ private: }; // A class representing a custom text control. -class wxFileDialogTextCtrl : public wxFileDialogCustomControl +class WXDLLIMPEXP_CORE wxFileDialogTextCtrl : public wxFileDialogCustomControl { public: wxString GetValue() const; @@ -107,7 +107,7 @@ private: }; // A class representing a custom static text. -class wxFileDialogStaticText : public wxFileDialogCustomControl +class WXDLLIMPEXP_CORE wxFileDialogStaticText : public wxFileDialogCustomControl { public: void SetLabelText(const wxString& text); @@ -125,7 +125,7 @@ private: // wxFileDialogCustomizer is used by wxFileDialogCustomizeHook // ---------------------------------------------------------------------------- -class wxFileDialogCustomize +class WXDLLIMPEXP_CORE wxFileDialogCustomize { public: wxFileDialogButton* AddButton(const wxString& label); @@ -159,7 +159,7 @@ private: // wxFileDialogCustomizeHook: used by wxFileDialog itself // ---------------------------------------------------------------------------- -class wxFileDialogCustomizeHook +class WXDLLIMPEXP_CORE wxFileDialogCustomizeHook { public: // This method must be overridden to add custom controls to the dialog diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index 09e0edfefc..116e9eeb24 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -21,7 +21,14 @@ #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/stattext.h" + #include "wx/textctrl.h" #endif // WX_PRECOMP #include "wx/filedlgcustomize.h" @@ -217,6 +224,229 @@ 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) + ) + { + } +}; + +class CheckBoxImpl : public ControlImplBase +{ +public: + CheckBoxImpl(wxWindow* parent, const wxString& label) + : ControlImplBase + ( + new wxCheckBox(parent, wxID_ANY, label) + ) + { + } + + virtual bool GetValue() wxOVERRIDE + { + return GetCheckBox()->GetValue(); + } + + virtual void SetValue(bool value) wxOVERRIDE + { + GetCheckBox()->SetValue(value); + } + +private: + wxCheckBox* GetCheckBox() const + { + return static_cast(m_win); + } +}; + +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); + } + +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) + { + // 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 + { + return AddToLayoutAndReturn(label); + } + + wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) wxOVERRIDE + { + return AddToLayoutAndReturn(label); + } + + wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) wxOVERRIDE + { + if ( !label.empty() ) + { + AddToLayout(new wxStaticText(this, wxID_ANY, label)); + } + + return AddToLayoutAndReturn(); + } + + wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) wxOVERRIDE + { + 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; + + wxDECLARE_NO_COPY_CLASS(Panel); +}; + +} // namespace wxGenericCustomizer + //---------------------------------------------------------------------------- // wxFileDialogBase //---------------------------------------------------------------------------- @@ -370,6 +600,9 @@ bool wxFileDialogBase::SetExtraControlCreator(ExtraControlCreatorFunction creato wxWindow* wxFileDialogBase::CreateExtraControlWithParent(wxWindow* parent) const { + if ( m_customizeHook ) + return new wxGenericCustomizer::Panel(parent, *m_customizeHook); + if ( m_extraControlCreator ) return (*m_extraControlCreator)(parent); From 96bcd545a90dc251ad3b3287c866e68504bf7adb Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 28 May 2022 19:06:35 +0100 Subject: [PATCH 32/43] Support wxFileDialogCustomize when not using IFileDialog in wxMSW This should be rarely needed, but it doesn't cost anything to support the new customization API if IFileDialog is not available at either compile- or run-time, as this just reuses the existing generic implementation. --- src/msw/filedlg.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 3f29a50584..f5660d9934 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -1022,7 +1022,9 @@ int wxFileDialog::ShowCommFileDialog(WXHWND hWndParent) in the upper left of the frame, it does not center automatically. */ - if ((m_data && m_data->m_bMovedWindow) || HasExtraControlCreator()) // we need these flags. + if ((m_data && m_data->m_bMovedWindow) || + HasExtraControlCreator() || + m_customizeHook) { ChangeExceptionPolicy(); msw_flags |= OFN_EXPLORER|OFN_ENABLEHOOK; @@ -1060,7 +1062,7 @@ int wxFileDialog::ShowCommFileDialog(WXHWND hWndParent) of.nMaxFileTitle = wxMAXFILE + 1 + wxMAXEXT; GlobalPtr hgbl; - if ( HasExtraControlCreator() ) + if ( HasExtraControlCreator() || m_customizeHook ) { msw_flags |= OFN_ENABLETEMPLATEHANDLE; From 8c87d90844aa64e7c1a54d9734005feb2e244db5 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 29 May 2022 00:14:42 +0100 Subject: [PATCH 33/43] Fix using wxFileDialog geometry in custom controls event handlers Keep wxFileDialog HWND valid until it's dismissed in order to allow any operations involving the dialog geometry to work correctly while it's shown, so that e.g. resizing windows from the custom controls event handlers works. --- src/msw/filedlg.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index f5660d9934..54333252a1 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -964,7 +964,9 @@ static bool DoShowCommFileDialog(OPENFILENAME *of, long style, DWORD *err) 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(); } @@ -1218,6 +1220,12 @@ int wxFileDialog::ShowCommFileDialog(WXHWND hWndParent) 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] ) From 64fb2b1202556a969d640351c159e5d59ae798d9 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 29 May 2022 00:16:33 +0100 Subject: [PATCH 34/43] Adjust size of custom wxStaticText in wxFileDialog to contents This is more useful than just truncating the contents if the label is set to a value longer than its initial one. --- src/common/fldlgcmn.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index 116e9eeb24..cc151c5c02 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -355,6 +355,9 @@ public: virtual void SetLabelText(const wxString& text) wxOVERRIDE { GetStaticText()->SetLabelText(text); + + wxWindow* const parent = m_win->GetParent(); + parent->GetSizer()->Fit(parent); } private: From 44b6ce48265580173cb8bfb0c48f31a0e4668d19 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 29 May 2022 00:46:17 +0100 Subject: [PATCH 35/43] Implement sending events for generic custom wxFileDialog controls Forward the events from the actual wxControls used in the generic implementation of wxFileDialogCustomize to wxFileDialogCustomControl so that they could be handled by the application code binding to them. --- include/wx/private/filedlgcustomize.h | 2 + src/common/fldlgcmn.cpp | 68 ++++++++++++++++++++++++++- src/msw/filedlg.cpp | 12 +++++ 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/include/wx/private/filedlgcustomize.h b/include/wx/private/filedlgcustomize.h index 4523941842..5fd2aadfc4 100644 --- a/include/wx/private/filedlgcustomize.h +++ b/include/wx/private/filedlgcustomize.h @@ -20,6 +20,8 @@ public: virtual void Show(bool show) = 0; virtual void Enable(bool enable) = 0; + virtual bool DoBind(wxEvtHandler* handler); + virtual ~wxFileDialogCustomControlImpl(); }; diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index cc151c5c02..9db33db0f8 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -59,6 +59,16 @@ 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. @@ -95,7 +105,7 @@ wxFileDialogButton::wxFileDialogButton(wxFileDialogButtonImpl* impl) bool wxFileDialogButton::OnDynamicBind(wxDynamicEventTableEntry& entry) { if ( entry.m_eventType == wxEVT_BUTTON ) - return true; + return GetImpl()->DoBind(this); return wxFileDialogCustomControl::OnDynamicBind(entry); } @@ -113,7 +123,7 @@ wxFileDialogCheckBox::wxFileDialogCheckBox(wxFileDialogCheckBoxImpl* impl) bool wxFileDialogCheckBox::OnDynamicBind(wxDynamicEventTableEntry& entry) { if ( entry.m_eventType == wxEVT_CHECKBOX ) - return true; + return GetImpl()->DoBind(this); return wxFileDialogCustomControl::OnDynamicBind(entry); } @@ -279,7 +289,37 @@ public: 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 @@ -291,6 +331,7 @@ public: new wxCheckBox(parent, wxID_ANY, label) ) { + m_handler = NULL; } virtual bool GetValue() wxOVERRIDE @@ -303,11 +344,34 @@ public: 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 TextCtrlImpl : public ControlImplBase diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 54333252a1..bb16f82352 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -291,6 +291,12 @@ public: : 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 @@ -318,6 +324,12 @@ public: 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 wxFileDialogTextCtrlImplFDC From bf5ddc200b1efae583ea0a86715c99ccb718d54b Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 29 May 2022 01:06:47 +0100 Subject: [PATCH 36/43] Improve extra controls support in wxGenericFileDialog Update the extra controls whenever anything changes in the dialog to make the example in the dialogs sample actually work with the generic dialog (previously nothing was updated at all). Also delete the extra controls earlier to make the behaviour more consistent with the native dialogs. Use the new customization API for the generic dialogs in the sample too. --- samples/dialogs/dialogs.cpp | 16 +++++++++++----- src/generic/filedlgg.cpp | 14 +++++++++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index 8886eb0ae9..5f43f3c362 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -1680,7 +1680,10 @@ static wxWindow* createMyExtraPanel(wxWindow *parent) class MyCustomizeHook : public wxFileDialogCustomizeHook { public: - explicit MyCustomizeHook(wxFileDialog& dialog) + // 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) { } @@ -1733,7 +1736,7 @@ private: wxOK | wxICON_INFORMATION, m_dialog); } - wxFileDialog* const m_dialog; + wxFileDialogBase* const m_dialog; wxFileDialogButton* m_btn; wxFileDialogCheckBox* m_cb; @@ -2005,7 +2008,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) @@ -2013,10 +2017,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/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 From 07d7dd19f80347c3a4cebe796da27225f22d2be2 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 30 May 2022 23:38:17 +0100 Subject: [PATCH 37/43] Add support for custom radio button controls in wxFileDialog Update the dialogs sample to show using them too. --- include/wx/filedlgcustomize.h | 21 +++++ include/wx/private/filedlgcustomize.h | 8 ++ samples/dialogs/dialogs.cpp | 30 ++++++- src/common/fldlgcmn.cpp | 114 +++++++++++++++++++++++++- src/msw/filedlg.cpp | 106 +++++++++++++++++++++++- 5 files changed, 274 insertions(+), 5 deletions(-) diff --git a/include/wx/filedlgcustomize.h b/include/wx/filedlgcustomize.h index ba13d07850..20b7149632 100644 --- a/include/wx/filedlgcustomize.h +++ b/include/wx/filedlgcustomize.h @@ -15,6 +15,7 @@ class wxFileDialogCustomControlImpl; class wxFileDialogButtonImpl; class wxFileDialogCheckBoxImpl; +class wxFileDialogRadioButtonImpl; class wxFileDialogTextCtrlImpl; class wxFileDialogStaticTextImpl; class wxFileDialogCustomizeImpl; @@ -90,6 +91,25 @@ private: 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 text control. class WXDLLIMPEXP_CORE wxFileDialogTextCtrl : public wxFileDialogCustomControl { @@ -130,6 +150,7 @@ class WXDLLIMPEXP_CORE wxFileDialogCustomize public: wxFileDialogButton* AddButton(const wxString& label); wxFileDialogCheckBox* AddCheckBox(const wxString& label); + wxFileDialogRadioButton* AddRadioButton(const wxString& label); wxFileDialogTextCtrl* AddTextCtrl(const wxString& label = wxString()); wxFileDialogStaticText* AddStaticText(const wxString& label); diff --git a/include/wx/private/filedlgcustomize.h b/include/wx/private/filedlgcustomize.h index 5fd2aadfc4..fbd59ccc4a 100644 --- a/include/wx/private/filedlgcustomize.h +++ b/include/wx/private/filedlgcustomize.h @@ -38,6 +38,13 @@ public: virtual void SetValue(bool value) = 0; }; +class wxFileDialogRadioButtonImpl : public wxFileDialogCustomControlImpl +{ +public: + virtual bool GetValue() = 0; + virtual void SetValue(bool value) = 0; +}; + class wxFileDialogTextCtrlImpl : public wxFileDialogCustomControlImpl { public: @@ -60,6 +67,7 @@ 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 wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) = 0; virtual wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) = 0; diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index 5f43f3c362..25b8014d3f 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -1605,7 +1605,8 @@ public: MyExtraPanel(wxWindow *parent); wxString GetInfo() const { - return wxString::Format("checkbox=%d, text=\"%s\"", m_checked, m_str); + return wxString::Format("paper=%s, enabled=%d, text=\"%s\"", + m_paperSize, m_checked, m_str); } private: @@ -1615,6 +1616,16 @@ private: m_btn->Enable(m_checked); } + void OnRadioButton(wxCommandEvent& event) + { + if ( event.GetEventObject() == m_radioA4 ) + m_paperSize = "A4"; + else if ( event.GetEventObject() == m_radioLetter ) + m_paperSize = "Letter"; + else + m_paperSize = "Unknown"; + } + void OnText(wxCommandEvent& event) { m_str = event.GetString(); @@ -1632,9 +1643,12 @@ private: wxString m_str; bool m_checked; + wxString m_paperSize; wxButton *m_btn; wxCheckBox *m_cb; + wxRadioButton *m_radioA4; + wxRadioButton *m_radioLetter; wxStaticText *m_label; wxTextCtrl *m_text; }; @@ -1648,6 +1662,11 @@ 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); + m_radioA4 = new wxRadioButton(this, wxID_ANY, "A4", + wxDefaultPosition, wxDefaultSize, wxRB_GROUP); + m_radioA4->Bind(wxEVT_RADIOBUTTON, &MyExtraPanel::OnRadioButton, this); + m_radioLetter = new wxRadioButton(this, wxID_ANY, "Letter"); + m_radioLetter->Bind(wxEVT_RADIOBUTTON, &MyExtraPanel::OnRadioButton, this); m_label = new wxStaticText(this, wxID_ANY, "Nothing selected"); m_label->Bind(wxEVT_UPDATE_UI, &MyExtraPanel::OnUpdateLabelUI, this); @@ -1660,6 +1679,8 @@ MyExtraPanel::MyExtraPanel(wxWindow *parent) wxSizerFlags().Centre().Border()); sizerTop->Add(m_text, wxSizerFlags(1).Centre().Border()); sizerTop->AddSpacer(10); + sizerTop->Add(m_radioA4, wxSizerFlags().Centre().Border()); + sizerTop->Add(m_radioLetter, wxSizerFlags().Centre().Border()); sizerTop->Add(m_cb, wxSizerFlags().Centre().Border()); sizerTop->AddSpacer(5); sizerTop->Add(m_btn, wxSizerFlags().Centre().Border()); @@ -1695,6 +1716,8 @@ public: // ShowModal() returns, TransferDataFromCustomControls() is the latest // moment when they can still be used. m_text = customizer.AddTextCtrl("Just some extra text:"); + m_radioA4 = customizer.AddRadioButton("A4"); + m_radioLetter = customizer.AddRadioButton("Letter"); m_cb = customizer.AddCheckBox("Enable Custom Button"); m_cb->Bind(wxEVT_CHECKBOX, &MyCustomizeHook::OnCheckBox, this); m_btn = customizer.AddButton("Custom Button"); @@ -1715,7 +1738,8 @@ public: // And another one called when the dialog is accepted. virtual void TransferDataFromCustomControls() wxOVERRIDE { - m_info.Printf("checkbox=%d, text=\"%s\"", + m_info.Printf("paper=%s, enabled=%d, text=\"%s\"", + m_radioA4->GetValue() ? "A4" : "Letter", m_cb->GetValue(), m_text->GetValue()); } @@ -1740,6 +1764,8 @@ private: wxFileDialogButton* m_btn; wxFileDialogCheckBox* m_cb; + wxFileDialogRadioButton* m_radioA4; + wxFileDialogRadioButton* m_radioLetter; wxFileDialogTextCtrl* m_text; wxFileDialogStaticText* m_label; diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index 9db33db0f8..496c409da8 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -27,6 +27,7 @@ #include "wx/button.h" #include "wx/checkbox.h" + #include "wx/radiobut.h" #include "wx/stattext.h" #include "wx/textctrl.h" #endif // WX_PRECOMP @@ -143,6 +144,34 @@ 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); +} + wxFileDialogTextCtrl::wxFileDialogTextCtrl(wxFileDialogTextCtrlImpl* impl) : wxFileDialogCustomControl(impl) { @@ -222,6 +251,12 @@ 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))); +} + wxFileDialogTextCtrl* wxFileDialogCustomize::AddTextCtrl(const wxString& label) { @@ -374,6 +409,58 @@ private: 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 TextCtrlImpl : public ControlImplBase { public: @@ -441,7 +528,8 @@ public: Panel(wxWindow* parent, wxFileDialogCustomizeHook& customizeHook) : wxPanel(parent), wxFileDialogCustomize(this), - m_customizeHook(customizeHook) + m_customizeHook(customizeHook), + m_lastWasRadio(false) { // Use a simple horizontal sizer to layout all the controls for now. wxBoxSizer* const sizer = new wxBoxSizer(wxHORIZONTAL); @@ -467,16 +555,36 @@ public: // 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; + } + wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) wxOVERRIDE { + m_lastWasRadio = false; + if ( !label.empty() ) { AddToLayout(new wxStaticText(this, wxID_ANY, label)); @@ -487,6 +595,8 @@ public: wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) wxOVERRIDE { + m_lastWasRadio = false; + return AddToLayoutAndReturn(label); } @@ -509,6 +619,8 @@ private: wxFileDialogCustomizeHook& m_customizeHook; + bool m_lastWasRadio; + wxDECLARE_NO_COPY_CLASS(Panel); }; diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index bb16f82352..8872c1d4ae 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -57,6 +57,7 @@ #include "wx/button.h" #include "wx/checkbox.h" + #include "wx/radiobut.h" #include "wx/stattext.h" #include "wx/textctrl.h" @@ -332,6 +333,47 @@ public: } }; +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 wxFileDialogTextCtrlImplFDC : public wxFileDialogImplFDC { @@ -391,7 +433,8 @@ public: : wxFileDialogCustomize(this) { m_lastId = - m_lastAuxId = 0; + m_lastAuxId = + m_radioListId = 0; } bool Initialize(IFileDialog* fileDialog) @@ -424,6 +467,8 @@ public: // 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) ) { @@ -436,6 +481,8 @@ public: wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) wxOVERRIDE { + m_radioListId = 0; + HRESULT hr = m_fdc->AddCheckButton(++m_lastId, label.wc_str(), FALSE); if ( FAILED(hr) ) { @@ -446,8 +493,45 @@ public: 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; + } + wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) wxOVERRIDE { + m_radioListId = 0; + HRESULT hr; if ( !label.empty() ) @@ -476,6 +560,8 @@ public: wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) wxOVERRIDE { + m_radioListId = 0; + HRESULT hr = m_fdc->AddText(++m_lastId, label.wc_str()); if ( FAILED(hr) ) { @@ -497,6 +583,10 @@ private: // IDs used for any other controls, they're negative (which means they // decrement from USHORT_MAX down). 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 @@ -638,8 +728,20 @@ public: wxSTDMETHODIMP OnItemSelected(IFileDialogCustomize*, DWORD WXUNUSED(dwIDCtl), - DWORD WXUNUSED(dwIDItem)) wxOVERRIDE + 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; } From 23e9ac9d938c3d2808a3ea23fcbf9c78cae2d17d Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 31 May 2022 13:26:18 +0100 Subject: [PATCH 38/43] Fix spurious gcc warning about wxFileDialogMSWData dtor Add unnecessary virtual dtor to this class just to avoid -Wdelete-non-virtual-dtor from gcc (which is wrong here as this class is never used polymorphically, i.e. never deleted via a pointer to its base class). --- src/msw/filedlg.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 8872c1d4ae..564244cad9 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -614,6 +614,10 @@ public: 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 From 06232cd2757aec1f5f7b15866ee3db75ab537dc7 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 3 Jun 2022 01:39:55 +0100 Subject: [PATCH 39/43] Make filters discussion a section in wxFileDialog documentation Using a section looks better and prepares for adding another one in the upcoming commit. Also remove a note about Motif file dialog limitation, nobody cares about it any more anyhow. --- interface/wx/filedlg.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/interface/wx/filedlg.h b/interface/wx/filedlg.h index cd53f73e22..6c2eee9f8e 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: From 153a024492a7951c540c5218830828d36a04102e Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 3 Jun 2022 03:14:29 +0100 Subject: [PATCH 40/43] Document the new wxFileDialog customization API Explain its relationship to the old API and document the new classes. --- interface/wx/filedlg.h | 53 +++++ interface/wx/filedlgcustomize.h | 356 ++++++++++++++++++++++++++++++++ 2 files changed, 409 insertions(+) create mode 100644 interface/wx/filedlgcustomize.h diff --git a/interface/wx/filedlg.h b/interface/wx/filedlg.h index 6c2eee9f8e..1f3c835e09 100644 --- a/interface/wx/filedlg.h +++ b/interface/wx/filedlg.h @@ -119,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. @@ -320,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. */ @@ -341,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..56e819cb34 --- /dev/null +++ b/interface/wx/filedlgcustomize.h @@ -0,0 +1,356 @@ +/////////////////////////////////////////////////////////////////////////////// +// 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 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 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(); +}; From 76ff441bf2029564ef0d0c1818863d6c10eda81e Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 3 Jun 2022 23:00:23 +0100 Subject: [PATCH 41/43] Demonstrate disabling radio buttons in the sample Radio buttons are different from the other controls internally in IFileDialogCustomize-based implementation, so check that disabling them also works correctly. --- samples/dialogs/dialogs.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index 25b8014d3f..437e45e933 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -1731,6 +1731,13 @@ public: // 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_radioA4->Enable(hasFile); + m_radioLetter->Enable(hasFile); + // Also show the current dialog state. m_label->SetLabelText(GetFileDialogStateDescription(m_dialog)); } From 5e7b1042482f7181438c045db62f61441f504f9d Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 3 Jun 2022 23:07:47 +0100 Subject: [PATCH 42/43] Change the meaning of the radio buttons in the file dialog No real changes, just rename the buttons to use paper size for demonstrating something else. --- samples/dialogs/dialogs.cpp | 44 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index 437e45e933..e6e3044e66 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -1606,7 +1606,7 @@ public: wxString GetInfo() const { return wxString::Format("paper=%s, enabled=%d, text=\"%s\"", - m_paperSize, m_checked, m_str); + m_paperOrient, m_checked, m_str); } private: @@ -1618,12 +1618,12 @@ private: void OnRadioButton(wxCommandEvent& event) { - if ( event.GetEventObject() == m_radioA4 ) - m_paperSize = "A4"; - else if ( event.GetEventObject() == m_radioLetter ) - m_paperSize = "Letter"; + if ( event.GetEventObject() == m_radioPortrait ) + m_paperOrient = "portrait"; + else if ( event.GetEventObject() == m_radioLandscape ) + m_paperOrient = "landscape"; else - m_paperSize = "Unknown"; + m_paperOrient = "unknown"; } void OnText(wxCommandEvent& event) @@ -1643,12 +1643,12 @@ private: wxString m_str; bool m_checked; - wxString m_paperSize; + wxString m_paperOrient; wxButton *m_btn; wxCheckBox *m_cb; - wxRadioButton *m_radioA4; - wxRadioButton *m_radioLetter; + wxRadioButton *m_radioPortrait; + wxRadioButton *m_radioLandscape; wxStaticText *m_label; wxTextCtrl *m_text; }; @@ -1662,11 +1662,11 @@ 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); - m_radioA4 = new wxRadioButton(this, wxID_ANY, "A4", + m_radioPortrait = new wxRadioButton(this, wxID_ANY, "&Portrait", wxDefaultPosition, wxDefaultSize, wxRB_GROUP); - m_radioA4->Bind(wxEVT_RADIOBUTTON, &MyExtraPanel::OnRadioButton, this); - m_radioLetter = new wxRadioButton(this, wxID_ANY, "Letter"); - m_radioLetter->Bind(wxEVT_RADIOBUTTON, &MyExtraPanel::OnRadioButton, this); + 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); @@ -1679,8 +1679,8 @@ MyExtraPanel::MyExtraPanel(wxWindow *parent) wxSizerFlags().Centre().Border()); sizerTop->Add(m_text, wxSizerFlags(1).Centre().Border()); sizerTop->AddSpacer(10); - sizerTop->Add(m_radioA4, wxSizerFlags().Centre().Border()); - sizerTop->Add(m_radioLetter, wxSizerFlags().Centre().Border()); + 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()); @@ -1716,8 +1716,8 @@ public: // ShowModal() returns, TransferDataFromCustomControls() is the latest // moment when they can still be used. m_text = customizer.AddTextCtrl("Just some extra text:"); - m_radioA4 = customizer.AddRadioButton("A4"); - m_radioLetter = customizer.AddRadioButton("Letter"); + 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"); @@ -1735,8 +1735,8 @@ public: bool hasFile = wxFileName::FileExists( m_dialog->GetCurrentlySelectedFilename() ); - m_radioA4->Enable(hasFile); - m_radioLetter->Enable(hasFile); + m_radioPortrait->Enable(hasFile); + m_radioLandscape->Enable(hasFile); // Also show the current dialog state. m_label->SetLabelText(GetFileDialogStateDescription(m_dialog)); @@ -1746,7 +1746,7 @@ public: virtual void TransferDataFromCustomControls() wxOVERRIDE { m_info.Printf("paper=%s, enabled=%d, text=\"%s\"", - m_radioA4->GetValue() ? "A4" : "Letter", + m_radioPortrait->GetValue() ? "portrait" : "landscape", m_cb->GetValue(), m_text->GetValue()); } @@ -1771,8 +1771,8 @@ private: wxFileDialogButton* m_btn; wxFileDialogCheckBox* m_cb; - wxFileDialogRadioButton* m_radioA4; - wxFileDialogRadioButton* m_radioLetter; + wxFileDialogRadioButton* m_radioPortrait; + wxFileDialogRadioButton* m_radioLandscape; wxFileDialogTextCtrl* m_text; wxFileDialogStaticText* m_label; From c18486e81ff765a743b3b536cce59cd650da46ba Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 4 Jun 2022 00:50:54 +0100 Subject: [PATCH 43/43] Add support for custom comboboxes in wxFileDialogCustomize Allow using simple (i.e. not editable) comboboxes, known as wxChoice in wx API, in the dialog too. Demonstrate their use in the dialogs sample. --- include/wx/filedlgcustomize.h | 21 ++++++ include/wx/private/filedlgcustomize.h | 8 ++ interface/wx/filedlgcustomize.h | 41 ++++++++++ samples/dialogs/dialogs.cpp | 33 ++++++++- src/common/fldlgcmn.cpp | 103 ++++++++++++++++++++++++++ src/msw/filedlg.cpp | 82 ++++++++++++++++++++ 6 files changed, 285 insertions(+), 3 deletions(-) diff --git a/include/wx/filedlgcustomize.h b/include/wx/filedlgcustomize.h index 20b7149632..4821e5ed8e 100644 --- a/include/wx/filedlgcustomize.h +++ b/include/wx/filedlgcustomize.h @@ -16,6 +16,7 @@ class wxFileDialogCustomControlImpl; class wxFileDialogButtonImpl; class wxFileDialogCheckBoxImpl; class wxFileDialogRadioButtonImpl; +class wxFileDialogChoiceImpl; class wxFileDialogTextCtrlImpl; class wxFileDialogStaticTextImpl; class wxFileDialogCustomizeImpl; @@ -110,6 +111,25 @@ private: wxDECLARE_NO_COPY_CLASS(wxFileDialogRadioButton); }; +// A class representing a custom combobox button. +class WXDLLIMPEXP_CORE wxFileDialogChoice : public wxFileDialogCustomControl +{ +public: + int GetSelection() const; + void SetSelection(int n); + + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogChoice(wxFileDialogChoiceImpl* impl); + +protected: + virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE; + +private: + wxFileDialogChoiceImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogChoice); +}; + // A class representing a custom text control. class WXDLLIMPEXP_CORE wxFileDialogTextCtrl : public wxFileDialogCustomControl { @@ -151,6 +171,7 @@ public: wxFileDialogButton* AddButton(const wxString& label); wxFileDialogCheckBox* AddCheckBox(const wxString& label); wxFileDialogRadioButton* AddRadioButton(const wxString& label); + wxFileDialogChoice* AddChoice(size_t n, const wxString* strings); wxFileDialogTextCtrl* AddTextCtrl(const wxString& label = wxString()); wxFileDialogStaticText* AddStaticText(const wxString& label); diff --git a/include/wx/private/filedlgcustomize.h b/include/wx/private/filedlgcustomize.h index fbd59ccc4a..ffa8ec3192 100644 --- a/include/wx/private/filedlgcustomize.h +++ b/include/wx/private/filedlgcustomize.h @@ -45,6 +45,13 @@ public: virtual void SetValue(bool value) = 0; }; +class wxFileDialogChoiceImpl : public wxFileDialogCustomControlImpl +{ +public: + virtual int GetSelection() = 0; + virtual void SetSelection(int n) = 0; +}; + class wxFileDialogTextCtrlImpl : public wxFileDialogCustomControlImpl { public: @@ -68,6 +75,7 @@ public: virtual wxFileDialogButtonImpl* AddButton(const wxString& label) = 0; virtual wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) = 0; virtual wxFileDialogRadioButtonImpl* AddRadioButton(const wxString& label) = 0; + virtual wxFileDialogChoiceImpl* AddChoice(size_t n, const wxString* strings) = 0; virtual wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) = 0; virtual wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) = 0; diff --git a/interface/wx/filedlgcustomize.h b/interface/wx/filedlgcustomize.h index 56e819cb34..e49bbbbf44 100644 --- a/interface/wx/filedlgcustomize.h +++ b/interface/wx/filedlgcustomize.h @@ -109,6 +109,32 @@ public: void SetValue(bool value); }; +/** + Represents a custom read-only combobox inside wxFileDialog. + + Objects of this class can only be created by + wxFileDialogCustomize::AddChoice(). + + It is possible to bind to wxEVT_CHOICE events on this object, which + will be generated when the selection in the combobox changes. + + See wxFileDialogCustomControl for more information. + + @since 3.1.7 + */ +class wxFileDialogChoice : public wxFileDialogCustomControl +{ +public: + /// Return the index of the selected item, possibly wxNOT_FOUND. + int GetSelection() const; + + /// Set the selection to the item with the given index. + /// + /// Using @c wxNOT_FOUND for @a n is not supported, once a selection is + /// made it cannot be undone. + void SetSelection(int n); +}; + /** Represents a custom text control inside wxFileDialog. @@ -203,6 +229,21 @@ public: */ wxFileDialogRadioButton* AddRadioButton(const wxString& label); + /** + Add a read-only combobox with the specified contents. + + The combobox doesn't have any initial selection, i.e. + wxFileDialogChoice::GetSelection() returns @c wxNOT_FOUND, if some item + must be selected, use wxFileDialogChoice::SetSelection() explicitly to + do it. + + @param n The number of strings, must be positive, as there is no way to + add more strings later and creating an empty combobox is not very + useful. + @param strings A non-@NULL pointer to an array of @a n strings. + */ + wxFileDialogChoice* AddChoice(size_t n, const wxString* strings); + /** Add a text control with an optional label preceding it. diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index e6e3044e66..81458a6d06 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -1598,6 +1598,18 @@ wxString GetFileDialogStateDescription(wxFileDialogBase* dialog) return msg; } +// Another helper translating demo combobox selection. +wxString GetFileDialogPaperSize(int selection) +{ + switch ( selection ) + { + case -1: return ""; + case 0: return "A4"; + case 1: return "Letter"; + default: return "INVALID"; + } +} + // panel with custom controls for file dialog class MyExtraPanel : public wxPanel { @@ -1605,8 +1617,8 @@ public: MyExtraPanel(wxWindow *parent); wxString GetInfo() const { - return wxString::Format("paper=%s, enabled=%d, text=\"%s\"", - m_paperOrient, m_checked, m_str); + return wxString::Format("paper=%s (%s), enabled=%d, text=\"%s\"", + m_paperSize, m_paperOrient, m_checked, m_str); } private: @@ -1626,6 +1638,11 @@ private: m_paperOrient = "unknown"; } + void OnChoice(wxCommandEvent& event) + { + m_paperSize = GetFileDialogPaperSize(event.GetSelection()); + } + void OnText(wxCommandEvent& event) { m_str = event.GetString(); @@ -1643,6 +1660,7 @@ private: wxString m_str; bool m_checked; + wxString m_paperSize; wxString m_paperOrient; wxButton *m_btn; @@ -1662,6 +1680,10 @@ MyExtraPanel::MyExtraPanel(wxWindow *parent) m_btn->Enable(false); m_cb = new wxCheckBox(this, -1, "Enable Custom Button"); m_cb->Bind(wxEVT_CHECKBOX, &MyExtraPanel::OnCheckBox, this); + wxChoice* choiceSize = new wxChoice(this, wxID_ANY); + choiceSize->Append("A4"); + choiceSize->Append("Letter"); + choiceSize->Bind(wxEVT_CHOICE, &MyExtraPanel::OnChoice, this); m_radioPortrait = new wxRadioButton(this, wxID_ANY, "&Portrait", wxDefaultPosition, wxDefaultSize, wxRB_GROUP); m_radioPortrait->Bind(wxEVT_RADIOBUTTON, &MyExtraPanel::OnRadioButton, this); @@ -1679,6 +1701,7 @@ MyExtraPanel::MyExtraPanel(wxWindow *parent) wxSizerFlags().Centre().Border()); sizerTop->Add(m_text, wxSizerFlags(1).Centre().Border()); sizerTop->AddSpacer(10); + sizerTop->Add(choiceSize, wxSizerFlags().Centre().Border(wxRIGHT)); sizerTop->Add(m_radioPortrait, wxSizerFlags().Centre().Border()); sizerTop->Add(m_radioLandscape, wxSizerFlags().Centre().Border()); sizerTop->Add(m_cb, wxSizerFlags().Centre().Border()); @@ -1716,6 +1739,8 @@ public: // ShowModal() returns, TransferDataFromCustomControls() is the latest // moment when they can still be used. m_text = customizer.AddTextCtrl("Just some extra text:"); + const wxString sizes[] = { "A4", "Letter" }; + m_choiceSize = customizer.AddChoice(WXSIZEOF(sizes), sizes); m_radioPortrait = customizer.AddRadioButton("&Portrait"); m_radioLandscape = customizer.AddRadioButton("&Landscape"); m_cb = customizer.AddCheckBox("Enable Custom Button"); @@ -1745,7 +1770,8 @@ public: // And another one called when the dialog is accepted. virtual void TransferDataFromCustomControls() wxOVERRIDE { - m_info.Printf("paper=%s, enabled=%d, text=\"%s\"", + m_info.Printf("paper=%s (%s), enabled=%d, text=\"%s\"", + GetFileDialogPaperSize(m_choiceSize->GetSelection()), m_radioPortrait->GetValue() ? "portrait" : "landscape", m_cb->GetValue(), m_text->GetValue()); } @@ -1771,6 +1797,7 @@ private: wxFileDialogButton* m_btn; wxFileDialogCheckBox* m_cb; + wxFileDialogChoice* m_choiceSize; wxFileDialogRadioButton* m_radioPortrait; wxFileDialogRadioButton* m_radioLandscape; wxFileDialogTextCtrl* m_text; diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index 496c409da8..71370891ad 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -27,6 +27,7 @@ #include "wx/button.h" #include "wx/checkbox.h" + #include "wx/choice.h" #include "wx/radiobut.h" #include "wx/stattext.h" #include "wx/textctrl.h" @@ -172,6 +173,34 @@ void wxFileDialogRadioButton::SetValue(bool value) GetImpl()->SetValue(value); } +wxFileDialogChoice::wxFileDialogChoice(wxFileDialogChoiceImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +bool wxFileDialogChoice::OnDynamicBind(wxDynamicEventTableEntry& entry) +{ + if ( entry.m_eventType == wxEVT_CHOICE ) + return GetImpl()->DoBind(this); + + return wxFileDialogCustomControl::OnDynamicBind(entry); +} + +wxFileDialogChoiceImpl* wxFileDialogChoice::GetImpl() const +{ + return static_cast(m_impl); +} + +int wxFileDialogChoice::GetSelection() const +{ + return GetImpl()->GetSelection(); +} + +void wxFileDialogChoice::SetSelection(int n) +{ + GetImpl()->SetSelection(n); +} + wxFileDialogTextCtrl::wxFileDialogTextCtrl(wxFileDialogTextCtrlImpl* impl) : wxFileDialogCustomControl(impl) { @@ -257,6 +286,12 @@ wxFileDialogCustomize::AddRadioButton(const wxString& label) return StoreAndReturn(new wxFileDialogRadioButton(m_impl->AddRadioButton(label))); } +wxFileDialogChoice* +wxFileDialogCustomize::AddChoice(size_t n, const wxString* strings) +{ + return StoreAndReturn(new wxFileDialogChoice(m_impl->AddChoice(n, strings))); +} + wxFileDialogTextCtrl* wxFileDialogCustomize::AddTextCtrl(const wxString& label) { @@ -461,6 +496,60 @@ private: wxEvtHandler* m_handler; }; +class ChoiceImpl : public ControlImplBase +{ +public: + ChoiceImpl(wxWindow* parent, size_t n, const wxString* strings) + : ControlImplBase + ( + new wxChoice(parent, wxID_ANY, + wxDefaultPosition, wxDefaultSize, + n, strings) + ) + { + m_handler = NULL; + } + + virtual int GetSelection() wxOVERRIDE + { + return GetChoice()->GetSelection(); + } + + virtual void SetSelection(int selection) wxOVERRIDE + { + GetChoice()->SetSelection(selection); + } + + virtual bool DoBind(wxEvtHandler* handler) wxOVERRIDE + { + if ( !m_handler ) + { + m_handler = handler; + m_win->Bind(wxEVT_CHOICE, &ChoiceImpl::OnChoice, this); + } + + return true; + } + +private: + wxChoice* GetChoice() const + { + return static_cast(m_win); + } + + void OnChoice(wxCommandEvent& event) + { + // See comments in OnButton() above, they also apply here. + + wxCommandEvent eventCopy(event); + eventCopy.SetEventObject(m_handler); + + m_handler->ProcessEvent(eventCopy); + } + + wxEvtHandler* m_handler; +}; + class TextCtrlImpl : public ControlImplBase { public: @@ -581,6 +670,20 @@ public: return impl; } + wxFileDialogChoiceImpl* AddChoice(size_t n, const wxString* strings) wxOVERRIDE + { + m_lastWasRadio = false; + + // TODO-C++11: Can't use AddToLayoutAndReturn() here easily without + // variadic templates. + ChoiceImpl* const impl = new ChoiceImpl(this, n, strings); + + AddToLayout(impl->m_win); + + return impl; + } + + wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) wxOVERRIDE { m_lastWasRadio = false; diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index 564244cad9..936d2f50b9 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -57,6 +57,7 @@ #include "wx/button.h" #include "wx/checkbox.h" + #include "wx/choice.h" #include "wx/radiobut.h" #include "wx/stattext.h" #include "wx/textctrl.h" @@ -374,6 +375,54 @@ private: const DWORD m_item; }; +class wxFileDialogChoiceImplFDC + : public wxFileDialogImplFDC +{ +public: + wxFileDialogChoiceImplFDC(IFileDialogCustomize* fdc, DWORD id, DWORD item) + : wxFileDialogImplFDC(fdc, id), + m_firstItem(item) + { + } + + virtual int GetSelection() wxOVERRIDE + { + DWORD selected = 0; + HRESULT hr = m_fdc->GetSelectedControlItem(m_id, &selected); + if ( hr == E_FAIL ) + { + // This seems to be returned if there is no selection. + return -1; + } + + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::GetSelectedControlItem"), hr); + + // See m_firstItem comment for the explanation of subtraction order. + return m_firstItem - selected; + } + + virtual void SetSelection(int n) wxOVERRIDE + { + // As above, see m_firstItem comment. + HRESULT hr = m_fdc->SetSelectedControlItem(m_id, m_firstItem - n); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::SetSelectedControlItem"), hr); + } + + virtual bool DoBind(wxEvtHandler* WXUNUSED(handler)) wxOVERRIDE + { + // We don't need to do anything special to get the events here. + return true; + } + +private: + // The ID of the first item of the combobox. The subsequent items are + // consecutive numbers _smaller_ than this one, because auxiliary IDs are + // assigned in decreasing order by decrementing them. + const DWORD m_firstItem; +}; + class wxFileDialogTextCtrlImplFDC : public wxFileDialogImplFDC { @@ -528,6 +577,33 @@ public: return impl; } + wxFileDialogChoiceImpl* AddChoice(size_t n, const wxString* strings) wxOVERRIDE + { + HRESULT hr = m_fdc->AddComboBox(++m_lastId); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddComboBox"), hr); + return NULL; + } + + // We pass the ID of the first control that will be added to the + // combobox as the ctor argument. + wxScopedPtr + impl(new wxFileDialogChoiceImplFDC(m_fdc, m_lastId, m_lastAuxId - 1)); + + for ( size_t i = 0; i < n; ++i ) + { + hr = m_fdc->AddControlItem(m_lastId, --m_lastAuxId, strings[i].wc_str()); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddControlItem"), hr); + return NULL; + } + } + + return impl.release(); + } + wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) wxOVERRIDE { m_radioListId = 0; @@ -582,6 +658,12 @@ private: // IDs used for any other controls, they're negative (which means they // decrement from USHORT_MAX down). + // + // Note that auxiliary IDs are sometimes used for the main control, at + // native level, as with the radio buttons, that are represented by + // separate controls at wx level, and sometimes for the control elements, + // such as for the combobox, which itself uses a normal ID, as it + // corresponds to the wx level control. DWORD m_lastAuxId; // ID of the current radio button list, i.e. the one to which the next call