From f1c114ebc60117920bc86f6029a67a9c35927eba Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 17 Jul 2022 18:34:58 +0100 Subject: [PATCH 1/7] Factor out IShellItem creation code in a reusable function Add InitShellItemFromPath() which is going to be reused in the upcoming commit. No real changes, this is a pure refactoring. --- include/wx/msw/private/filedialog.h | 3 +++ src/msw/dirdlg.cpp | 23 +++++++++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/include/wx/msw/private/filedialog.h b/include/wx/msw/private/filedialog.h index 2f92093f5c..d8ed352ebb 100644 --- a/include/wx/msw/private/filedialog.h +++ b/include/wx/msw/private/filedialog.h @@ -73,6 +73,9 @@ private: wxCOMPtr m_fileDialog; }; +// Initialize an IShellItem object with the given path. +HRESULT InitShellItemFromPath(wxCOMPtr& item, const wxString& path); + // Extract the filesystem path corresponding to the given shell item. HRESULT GetFSPathFromShellItem(const wxCOMPtr& item, wxString& path); diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index eb6f588665..06e56e7108 100644 --- a/src/msw/dirdlg.cpp +++ b/src/msw/dirdlg.cpp @@ -352,7 +352,7 @@ void wxIFileDialog::SetTitle(const wxString& message) } } -void wxIFileDialog::SetInitialPath(const wxString& defaultPath) +HRESULT InitShellItemFromPath(wxCOMPtr& item, const wxString& path) { HRESULT hr; @@ -383,20 +383,27 @@ void wxIFileDialog::SetInitialPath(const wxString& defaultPath) if ( !s_pfnSHCreateItemFromParsingName ) { // There is nothing we can do and the error was already reported. - return; + return E_FAIL; } - wxCOMPtr folder; hr = s_pfnSHCreateItemFromParsingName ( - defaultPath.wc_str(), + path.wc_str(), NULL, - wxIID_PPV_ARGS(IShellItem, &folder) + wxIID_PPV_ARGS(IShellItem, &item) ); - // 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. + return hr; +} + +void wxIFileDialog::SetInitialPath(const wxString& defaultPath) +{ + wxCOMPtr folder; + + HRESULT hr = InitShellItemFromPath(folder, defaultPath); + + // Failing to parse the folder name is not really an error, e.g. it might + // not exist, so we'll just ignore the initial directory in this case. if ( SUCCEEDED(hr) ) { hr = m_fileDialog->SetFolder(folder); From eb0ee6d82f4d9b346de45e92c4d8f7ef1f8ff336 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 17 Jul 2022 19:20:29 +0100 Subject: [PATCH 2/7] Do log SHCreateItemFromParsingName() errors Even if we mostly ignore them when they happen, it can still be useful to see them in the debug logs. --- src/msw/dirdlg.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index 06e56e7108..9f433c830b 100644 --- a/src/msw/dirdlg.cpp +++ b/src/msw/dirdlg.cpp @@ -392,6 +392,14 @@ HRESULT InitShellItemFromPath(wxCOMPtr& item, const wxString& path) NULL, wxIID_PPV_ARGS(IShellItem, &item) ); + if ( FAILED(hr) ) + { + wxLogApiError + ( + wxString::Format(wxS("SHCreateItemFromParsingName(\"%s\")"), path), + hr + ); + } return hr; } From 172ebc6dbcbb58b1aaf1480305b948a0935326b3 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 17 Jul 2022 19:21:18 +0100 Subject: [PATCH 3/7] Fix using slashes in wxFileDialog paths Convert slashes to backslashes when using SHCreateItemFromParsingName() as it doesn't support the former and returns E_INVALIDARG for the strings containing them. This fixes a regression in wxFileDialog::SetDirectory() since the switch to using IFileDialog in wxMSW, as it didn't set the initial directory correctly if it contained slashes, unlike before (because we already replaced slashes with backslashes manually when using common file dialogs). --- src/msw/dirdlg.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index 9f433c830b..3b7064b517 100644 --- a/src/msw/dirdlg.cpp +++ b/src/msw/dirdlg.cpp @@ -386,9 +386,25 @@ HRESULT InitShellItemFromPath(wxCOMPtr& item, const wxString& path) return E_FAIL; } + // SHCreateItemFromParsingName() doesn't support slashes, so if the path + // uses them, replace them with the backslashes. + wxString pathBS; + const wxString* pathWithoutSlashes; + if ( path.find('/') != wxString::npos ) + { + pathBS = path; + pathBS.Replace("/", "\\", true); + + pathWithoutSlashes = &pathBS; + } + else // Just use the original path without copying. + { + pathWithoutSlashes = &path; + } + hr = s_pfnSHCreateItemFromParsingName ( - path.wc_str(), + pathWithoutSlashes->wc_str(), NULL, wxIID_PPV_ARGS(IShellItem, &item) ); @@ -396,7 +412,8 @@ HRESULT InitShellItemFromPath(wxCOMPtr& item, const wxString& path) { wxLogApiError ( - wxString::Format(wxS("SHCreateItemFromParsingName(\"%s\")"), path), + wxString::Format(wxS("SHCreateItemFromParsingName(\"%s\")"), + *pathWithoutSlashes), hr ); } From 5d3ebf94573e23308a68bae0d476021d52a62755 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 17 Jul 2022 19:23:47 +0100 Subject: [PATCH 4/7] Add wxFileDialog::AddShortcut() This allows to add application-specific directories to the file dialog. This commit only contains the implementation for wxMSW and a change showing the new function in the sample. --- include/wx/filedlg.h | 12 +++++++ include/wx/msw/filedlg.h | 1 + include/wx/msw/private/filedialog.h | 3 ++ samples/dialogs/dialogs.cpp | 8 +++++ src/common/fldlgcmn.cpp | 7 ++++ src/msw/dirdlg.cpp | 21 ++++++++++++ src/msw/filedlg.cpp | 53 +++++++++++++++++++++++++++++ 7 files changed, 105 insertions(+) diff --git a/include/wx/filedlg.h b/include/wx/filedlg.h index 06b0259395..318f08bc10 100644 --- a/include/wx/filedlg.h +++ b/include/wx/filedlg.h @@ -56,6 +56,13 @@ enum #define wxFD_DEFAULT_STYLE wxFD_OPEN +// Flags for wxFileDialog::AddShortcut(). +enum +{ + wxFD_SHORTCUT_TOP = 0x0001, + wxFD_SHORTCUT_BOTTOM = 0x0002 +}; + extern WXDLLIMPEXP_DATA_CORE(const char) wxFileDialogNameStr[]; extern WXDLLIMPEXP_DATA_CORE(const char) wxFileSelectorPromptStr[]; extern WXDLLIMPEXP_DATA_CORE(const char) wxFileSelectorDefaultWildcardStr[]; @@ -130,6 +137,11 @@ public: { return m_currentlySelectedFilterIndex; } + // Add a shortcut to the given directory in the sidebar containing such + // shortcuts if supported. + virtual bool AddShortcut(const wxString& directory, int flags = 0); + + // A customize hook methods will be called by wxFileDialog later if this // function returns true, see its documentation for details. // diff --git a/include/wx/msw/filedlg.h b/include/wx/msw/filedlg.h index 8eadbcebd0..b3f798c2b5 100644 --- a/include/wx/msw/filedlg.h +++ b/include/wx/msw/filedlg.h @@ -33,6 +33,7 @@ public: virtual void GetPaths(wxArrayString& paths) const wxOVERRIDE; virtual void GetFilenames(wxArrayString& files) const wxOVERRIDE; + virtual bool AddShortcut(const wxString& directory, int flags = 0) wxOVERRIDE; virtual bool SupportsExtraControl() const wxOVERRIDE { return true; } virtual int ShowModal() wxOVERRIDE; diff --git a/include/wx/msw/private/filedialog.h b/include/wx/msw/private/filedialog.h index d8ed352ebb..72f48a5134 100644 --- a/include/wx/msw/private/filedialog.h +++ b/include/wx/msw/private/filedialog.h @@ -55,6 +55,9 @@ public: // Set the initial path to show in the dialog. void SetInitialPath(const wxString& path); + // Add a shortcut. + void AddPlace(const wxString& path, FDAP fdap); + // Show the file dialog with the given parent window and options. // // Returns the selected path, or paths, in the provided output parameters, diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index 581f9428d3..a723414063 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -1824,6 +1824,14 @@ void MyFrame::FileOpen(wxCommandEvent& WXUNUSED(event) ) ) ); + // For demonstration purposes, add wxWidgets directories to the sidebar. + wxString wxdir; + if ( wxGetEnv("WXWIN", &wxdir) ) + { + dialog.AddShortcut(wxdir + "/src", wxFD_SHORTCUT_BOTTOM); + dialog.AddShortcut(wxdir + "/include", wxFD_SHORTCUT_TOP); + } + // Note: this object must remain alive until ShowModal() returns. MyCustomizeHook myCustomizer(dialog); diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index 173d993dc1..8969b54942 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -851,6 +851,13 @@ wxString wxFileDialogBase::AppendExtension(const wxString &filePath, return filePath + ext; } +bool wxFileDialogBase::AddShortcut(const wxString& WXUNUSED(directory), + int WXUNUSED(flags)) +{ + // Not implemented by default. + return false; +} + bool wxFileDialogBase::SetCustomizeHook(wxFileDialogCustomizeHook& customizeHook) { if ( !SupportsExtraControl() ) diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index 3b7064b517..e307b9160b 100644 --- a/src/msw/dirdlg.cpp +++ b/src/msw/dirdlg.cpp @@ -437,6 +437,27 @@ void wxIFileDialog::SetInitialPath(const wxString& defaultPath) } } +void wxIFileDialog::AddPlace(const wxString& path, FDAP fdap) +{ + wxCOMPtr place; + + HRESULT hr = InitShellItemFromPath(place, path); + + // Don't bother with doing anything else if we couldn't parse the path + // (debug message about failing to do it was already logged). + if ( FAILED(hr) ) + return; + + hr = m_fileDialog->AddPlace(place, fdap); + if ( FAILED(hr) ) + { + wxLogApiError + ( + wxString::Format(wxS("IFileDialog::AddPlace(\"%s\")"), path), hr + ); + } +} + } // namespace wxMSWImpl // ---------------------------------------------------------------------------- diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index b0c2c900a9..446979523e 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -714,6 +714,20 @@ public: #if wxUSE_IFILEOPENDIALOG + // Store the extra shortcut directories and their flags. + struct ShortcutData + { + ShortcutData(const wxString& path_, int flags_) + : path(path_), flags(flags_) + { + } + + wxString path; + int flags; + }; + wxVector m_customShortcuts; + + // IUnknown wxSTDMETHODIMP QueryInterface(REFIID iid, void** ppv) @@ -1181,6 +1195,28 @@ void wxFileDialog::MSWOnInitDialogHook(WXHWND hwnd) CreateExtraControl(); } +bool wxFileDialog::AddShortcut(const wxString& directory, int flags) +{ +#if wxUSE_IFILEOPENDIALOG + if ( !HasExtraControlCreator() ) + { + MSWData().m_customShortcuts.push_back( + wxFileDialogMSWData::ShortcutData(directory, flags) + ); + + return true; + } + else + { + // It could be surprising if AddShortcut() silently didn't work, so + // warn the developer about this incompatibility. + wxFAIL_MSG("Can't use both AddShortcut() and SetExtraControlCreator()"); + } +#endif // wxUSE_IFILEOPENDIALOG + + return false; +} + int wxFileDialog::ShowModal() { WX_HOOK_MODAL_DIALOG(); @@ -1576,6 +1612,23 @@ int wxFileDialog::ShowIFileDialog(WXHWND hWndParent) } + for ( wxVector::const_iterator + it = data.m_customShortcuts.begin(); + it != data.m_customShortcuts.end(); + ++it ) + { + FDAP fdap = FDAP_BOTTOM; + if ( it->flags & wxFD_SHORTCUT_TOP ) + { + wxASSERT_MSG( !(it->flags & wxFD_SHORTCUT_BOTTOM), + wxS("Can't use both wxFD_SHORTCUT_TOP and BOTTOM") ); + + fdap = FDAP_TOP; + } + + fileDialog.AddPlace(it->path, fdap); + } + // We never set the following flags currently: // // - FOS_STRICTFILETYPES From dedd0384e6af540f97d3b509ffc5d848acb4d946 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 17 Jul 2022 20:33:59 +0200 Subject: [PATCH 5/7] Implement wxFileDialog::AddShortcut() for wxGTK too Flags are not supported as GTK doesn't allow defining the order of the shortcuts, at least not without removing all the existing ones, which looks like an overkill. --- include/wx/gtk/filedlg.h | 1 + src/gtk/filedlg.cpp | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/include/wx/gtk/filedlg.h b/include/wx/gtk/filedlg.h index 9e3644493b..04bb92a2e7 100644 --- a/include/wx/gtk/filedlg.h +++ b/include/wx/gtk/filedlg.h @@ -55,6 +55,7 @@ public: virtual int ShowModal() wxOVERRIDE; + virtual bool AddShortcut(const wxString& directory, int flags = 0) wxOVERRIDE; virtual bool SupportsExtraControl() const wxOVERRIDE { return true; } // Implementation only. diff --git a/src/gtk/filedlg.cpp b/src/gtk/filedlg.cpp index 5d4b69f602..20dc80b26a 100644 --- a/src/gtk/filedlg.cpp +++ b/src/gtk/filedlg.cpp @@ -20,6 +20,7 @@ #endif #include "wx/gtk/private.h" +#include "wx/gtk/private/error.h" #include "wx/gtk/private/mnemonics.h" #ifdef __UNIX__ @@ -499,4 +500,21 @@ void wxFileDialog::GTKSelectionChanged(const wxString& filename) UpdateExtraControlUI(); } +bool wxFileDialog::AddShortcut(const wxString& directory, int WXUNUSED(flags)) +{ + wxGtkError error; + + if ( !gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(m_widget), + directory.utf8_str(), + error.Out()) ) + { + wxLogDebug("Failed to add shortcut \"%s\": %s", + directory, error.GetMessage()); + + return false; + } + + return true; +} + #endif // wxUSE_FILEDLG From 854599c0bd1bba891f5c19c11dc1c8234da8624d Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 17 Jul 2022 19:44:19 +0100 Subject: [PATCH 6/7] Add wxFileDialog::AddShortcut() documentation Also add a comment to the sample explaining the default behaviour. --- interface/wx/filedlg.h | 33 +++++++++++++++++++++++++++++++++ samples/dialogs/dialogs.cpp | 6 +++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/interface/wx/filedlg.h b/interface/wx/filedlg.h index 1f3c835e09..0cb159243c 100644 --- a/interface/wx/filedlg.h +++ b/interface/wx/filedlg.h @@ -232,6 +232,39 @@ public: */ virtual ~wxFileDialog(); + /** + Add a directory to the list of shortcuts shown in the dialog. + + File dialogs on many platforms display a fixed list of directories + which can be easily selected by the user. This function allows to add + an application-defined directory to this list, which can be convenient + for the programs that use specific directories for their files instead + of the default user document directory (see wxStandardPaths). + + Currently this function is only implemented in wxMSW and wxGTK and does + nothing under the other platforms. Moreover, in wxMSW this function is + incompatible with the use of SetExtraControlCreator(), if you need to + use this function and customize the dialog contents, please use the + newer SetCustomizeHook() instead. + + The @ref page_samples_dialogs "dialogs sample" shows the use of this + function by adding two custom shortcuts corresponding to the + subdirectories of @c WXWIN environment variable if it is defined. + + @param directory The full path to the directory, which should exist. + @param flags Can be set to @c wxFD_SHORTCUT_BOTTOM (which is also the + default behaviour) to add the shortcut after the existing ones, + or @c wxFD_SHORTCUT_TOP to add it before them. Support for the + latter flag is only available in wxMSW, in wxGTK the shortcuts are + always added to the bottom of the list. + @return @true on success or @false if shortcut couldn't be added, e.g. + because this functionality is not available on the current + platform. + + @since 3.3.0 + */ + bool AddShortcut(const wxString& directory, int flags = 0); + /** Returns the path of the file currently selected in dialog. diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index a723414063..a49d9c89d1 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -1828,7 +1828,11 @@ void MyFrame::FileOpen(wxCommandEvent& WXUNUSED(event) ) wxString wxdir; if ( wxGetEnv("WXWIN", &wxdir) ) { - dialog.AddShortcut(wxdir + "/src", wxFD_SHORTCUT_BOTTOM); + dialog.AddShortcut(wxdir + "/src"); + + // By default shortcuts are added at the bottom, but we can override + // this in the ports that support it (currently only wxMSW) and add a + // shortcut added later at the top instead. dialog.AddShortcut(wxdir + "/include", wxFD_SHORTCUT_TOP); } From 39a226c600be69d7fc0448f5705342b06eb5901a Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 18 Jul 2022 23:57:49 +0100 Subject: [PATCH 7/7] Document customizing shortcuts section title in wxMSW It's quite non obvious that the "FileDescription" field of the version information defined in the resource file is used for this, so explain it. Co-Authored-By: Mark Roszko --- interface/wx/filedlg.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/interface/wx/filedlg.h b/interface/wx/filedlg.h index 0cb159243c..7f3c757f65 100644 --- a/interface/wx/filedlg.h +++ b/interface/wx/filedlg.h @@ -251,6 +251,13 @@ public: function by adding two custom shortcuts corresponding to the subdirectories of @c WXWIN environment variable if it is defined. + @note In wxMSW, the shortcuts appear in a separate section called + "Application Links" by default. To change the title of this + section, the application can specify a value of the @c + FileDescription field of the version information structure in its + resource file -- if present, this string will be used as the + section title. + @param directory The full path to the directory, which should exist. @param flags Can be set to @c wxFD_SHORTCUT_BOTTOM (which is also the default behaviour) to add the shortcut after the existing ones,