diff --git a/include/wx/dirdlg.h b/include/wx/dirdlg.h index dfdbffc737..fba5f4d6e4 100644 --- a/include/wx/dirdlg.h +++ b/include/wx/dirdlg.h @@ -25,8 +25,22 @@ extern WXDLLIMPEXP_DATA_CORE(const char) wxDirDialogNameStr[]; extern WXDLLIMPEXP_DATA_CORE(const char) wxDirDialogDefaultFolderStr[]; extern WXDLLIMPEXP_DATA_CORE(const char) wxDirSelectorPromptStr[]; + +/* + The flags below must coexist with the following flags in m_windowStyle + #define wxCAPTION 0x20000000 + #define wxMAXIMIZE 0x00002000 + #define wxCLOSE_BOX 0x00001000 + #define wxSYSTEM_MENU 0x00000800 + wxBORDER_NONE = 0x00200000 + #define wxRESIZE_BORDER 0x00000040 + #define wxDIALOG_NO_PARENT 0x00000020 +*/ + #define wxDD_CHANGE_DIR 0x0100 #define wxDD_DIR_MUST_EXIST 0x0200 +#define wxDD_MULTIPLE 0x0400 +#define wxDD_SHOW_HIDDEN 0x0001 // deprecated, on by default now, use wxDD_DIR_MUST_EXIST to disable it #define wxDD_NEW_DIR_BUTTON 0 @@ -74,11 +88,22 @@ public: virtual void SetPath(const wxString& path) { m_path = path; } virtual wxString GetMessage() const { return m_message; } - virtual wxString GetPath() const { return m_path; } + virtual wxString GetPath() const + { + wxCHECK_MSG( !HasFlag(wxDD_MULTIPLE), wxString(), + "When using wxDD_MULTIPLE, must call GetPaths() instead" ); + return m_path; + } + + virtual void GetPaths(wxArrayString& paths) const + { + paths = m_paths; + } protected: wxString m_message; wxString m_path; + wxArrayString m_paths; }; diff --git a/include/wx/gtk/dirdlg.h b/include/wx/gtk/dirdlg.h index 1e4473ee66..e8029a21c7 100644 --- a/include/wx/gtk/dirdlg.h +++ b/include/wx/gtk/dirdlg.h @@ -37,7 +37,6 @@ public: public: // overrides from wxGenericDirDialog - wxString GetPath() const wxOVERRIDE; void SetPath(const wxString& path) wxOVERRIDE; @@ -55,8 +54,6 @@ protected: private: - wxString m_selectedDirectory; - wxDECLARE_DYNAMIC_CLASS(wxDirDialog); }; diff --git a/include/wx/msw/dirdlg.h b/include/wx/msw/dirdlg.h index b840ee7629..b99ff5b4f8 100644 --- a/include/wx/msw/dirdlg.h +++ b/include/wx/msw/dirdlg.h @@ -30,7 +30,7 @@ private: // The real implementations of ShowModal(), used for Windows versions // before and since Vista. int ShowSHBrowseForFolder(WXHWND owner); - int ShowIFileDialog(WXHWND owner); + int ShowIFileOpenDialog(WXHWND owner); wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxDirDialog); }; diff --git a/include/wx/osx/dirdlg.h b/include/wx/osx/dirdlg.h index a77cd0f880..376905b029 100644 --- a/include/wx/osx/dirdlg.h +++ b/include/wx/osx/dirdlg.h @@ -47,6 +47,10 @@ public: virtual int ShowModal() wxOVERRIDE; + // MacOS 10.11 has removed the titlebar from the dialog, so this is provided + // only for compatibility with older versions + virtual void SetTitle(const wxString& title) wxOVERRIDE; + #if wxOSX_USE_COCOA virtual void ShowWindowModal() wxOVERRIDE; virtual void ModalFinishedCallback(void* panel, int returnCode) wxOVERRIDE; @@ -64,6 +68,8 @@ private: // Common part of all ctors. void Init(); + wxString m_title; + wxDECLARE_DYNAMIC_CLASS(wxDirDialog); }; diff --git a/interface/wx/dirdlg.h b/interface/wx/dirdlg.h index 19d9827a9b..bd46febbec 100644 --- a/interface/wx/dirdlg.h +++ b/interface/wx/dirdlg.h @@ -5,8 +5,12 @@ // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// + #define wxDD_CHANGE_DIR 0x0100 #define wxDD_DIR_MUST_EXIST 0x0200 +#define wxDD_MULTIPLE 0x0400 +#define wxDD_SHOW_HIDDEN 0x0001 + #define wxDD_NEW_DIR_BUTTON 0 // deprecated, on by default now, #define wxDD_DEFAULT_STYLE (wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) @@ -43,6 +47,13 @@ const char wxDirDialogNameStr[] = "wxDirCtrl"; @style{wxDD_CHANGE_DIR} Change the current working directory to the directory chosen by the user. + @note This flag cannot be used with the @c wxDD_MULTIPLE style. + @style{wxDD_MULTIPLE} + Allow the user to select multiple directories. + This flag is only available since wxWidgets 3.1.4 + @style{wxDD_SHOW_HIDDEN} + Show hidden and system folders. + This flag is only available since wxWidgets 3.1.4 @endStyleTable Notice that @c wxRESIZE_BORDER has special side effect under Windows @@ -60,6 +71,11 @@ const char wxDirDialogNameStr[] = "wxDirCtrl"; @endcode instead of just using @c wxDD_DIR_MUST_EXIST style alone. + @remarks MacOS 10.11+ does not display a title bar on the dialog. Use SetMessage() + to change the string displayed to the user at the top of the dialog after creation. + The SetTitle() method is provided for compatibility with pre-10.11 MacOS versions + that do still support displaying the title bar. + @library{wxcore} @category{cmndlg} @@ -106,9 +122,22 @@ public: /** Returns the default or user-selected path. + + @note This function can't be used with dialogs which have the @c wxDD_MULTIPLE style, + use GetPaths() instead. */ virtual wxString GetPath() const; + /** + Fills the array @a paths with the full paths of the chosen directories. + + @note This function should only be used with the dialogs which have @c wxDD_MULTIPLE style, + use GetPath() for the others. + + @since 3.1.4 + */ + virtual void GetPaths(wxArrayString& paths) const; + /** Sets the message that will be displayed on the dialog. */ diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index c5c1cc1da7..9a69411e62 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -202,6 +202,7 @@ wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) #if wxUSE_DIRDLG EVT_MENU(DIALOGS_DIR_CHOOSE, MyFrame::DirChoose) EVT_MENU(DIALOGS_DIRNEW_CHOOSE, MyFrame::DirChooseNew) + EVT_MENU(DIALOGS_DIRMULTIPLE_CHOOSE, MyFrame::DirChooseMultiple) #endif // wxUSE_DIRDLG #if USE_MODAL_PRESENTATION @@ -488,6 +489,7 @@ bool MyApp::OnInit() dir_menu->Append(DIALOGS_DIR_CHOOSE, "&Choose a directory\tCtrl-D"); dir_menu->Append(DIALOGS_DIRNEW_CHOOSE, "Choose a directory (with \"Ne&w\" button)\tShift-Ctrl-D"); + dir_menu->Append(DIALOGS_DIRMULTIPLE_CHOOSE, "Choose multiple and hidden directories\tAlt-Ctrl-D"); menuDlg->Append(wxID_ANY,"&Directory operations",dir_menu); #if USE_DIRDLG_GENERIC @@ -1803,6 +1805,36 @@ void MyFrame::DirChooseNew(wxCommandEvent& WXUNUSED(event) ) { DoDirChoose(wxDD_DEFAULT_STYLE & ~wxDD_DIR_MUST_EXIST); } + +void MyFrame::DirChooseMultiple(wxCommandEvent& WXUNUSED(event)) +{ + // pass some initial dir and the style to wxDirDialog + int style = wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST | wxDD_MULTIPLE | wxDD_SHOW_HIDDEN; + wxString dirHome; + wxGetHomeDir(&dirHome); + + wxDirDialog dialog(this, "Testing multiple directory picker", dirHome, style); + + if ( dialog.ShowModal() == wxID_OK ) + { + wxArrayString paths; + + dialog.GetPaths(paths); + + wxString msg, s; + size_t count = paths.GetCount(); + for ( size_t n = 0; n < count; n++ ) + { + s.Printf("Directory %d: %s\n", + (int)n, paths[n]); + + msg += s; + } + + wxMessageDialog dialog2(this, msg, "Selected directories"); + dialog2.ShowModal(); + } +} #endif // wxUSE_DIRDLG #if USE_DIRDLG_GENERIC diff --git a/samples/dialogs/dialogs.h b/samples/dialogs/dialogs.h index 085e9eaeba..4bde46e3c6 100644 --- a/samples/dialogs/dialogs.h +++ b/samples/dialogs/dialogs.h @@ -434,6 +434,7 @@ public: #if wxUSE_DIRDLG void DirChoose(wxCommandEvent& event); void DirChooseNew(wxCommandEvent& event); + void DirChooseMultiple(wxCommandEvent& event); #endif // wxUSE_DIRDLG #if USE_DIRDLG_GENERIC @@ -594,6 +595,7 @@ enum DIALOGS_FILE_SAVE_GENERIC, DIALOGS_DIR_CHOOSE, DIALOGS_DIRNEW_CHOOSE, + DIALOGS_DIRMULTIPLE_CHOOSE, DIALOGS_GENERIC_DIR_CHOOSE, DIALOGS_TIP, DIALOGS_NUM_ENTRY, diff --git a/src/gtk/dirdlg.cpp b/src/gtk/dirdlg.cpp index 005771a54e..317acab13e 100644 --- a/src/gtk/dirdlg.cpp +++ b/src/gtk/dirdlg.cpp @@ -71,6 +71,9 @@ bool wxDirDialog::Create(wxWindow* parent, { m_message = title; + wxASSERT_MSG( !( (style & wxDD_MULTIPLE) && (style & wxDD_CHANGE_DIR) ), + "wxDD_CHANGE_DIR can't be used together with wxDD_MULTIPLE" ); + parent = GetParentForModalDialog(parent, style); if (!PreCreation(parent, pos, wxDefaultSize) || @@ -110,10 +113,18 @@ bool wxDirDialog::Create(wxWindow* parent, if (wx_is_at_least_gtk2(18)) { gtk_file_chooser_set_create_folders( - GTK_FILE_CHOOSER(m_widget), (style & wxDD_DIR_MUST_EXIST) == 0); + GTK_FILE_CHOOSER(m_widget), !HasFlag(wxDD_DIR_MUST_EXIST) ); } #endif + // Enable multiple selection if desired + gtk_file_chooser_set_select_multiple( + GTK_FILE_CHOOSER(m_widget), HasFlag(wxDD_MULTIPLE) ); + + // Enable show hidden folders if desired + gtk_file_chooser_set_show_hidden( + GTK_FILE_CHOOSER(m_widget), HasFlag(wxDD_SHOW_HIDDEN) ); + // local-only property could be set to false to allow non-local files to be loaded. // In that case get/set_uri(s) should be used instead of get/set_filename(s) everywhere // and the GtkFileChooserDialog should probably also be created with a backend, @@ -132,13 +143,29 @@ bool wxDirDialog::Create(wxWindow* parent, void wxDirDialog::GTKOnAccept() { - wxGtkString str(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(m_widget))); - m_selectedDirectory = wxString::FromUTF8(str); + GSList *fnamesi = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(m_widget)); + GSList *fnames = fnamesi; + + while ( fnamesi ) + { + wxString dir(wxString::FromUTF8(static_cast(fnamesi->data))); + m_paths.Add(dir); + + g_free(fnamesi->data); + fnamesi = fnamesi->next; + } + + g_slist_free(fnames); // change to the directory where the user went if asked if (HasFlag(wxDD_CHANGE_DIR)) { - wxSetWorkingDirectory(m_selectedDirectory); + wxSetWorkingDirectory(m_paths.Last()); + } + + if (!HasFlag(wxDD_MULTIPLE)) + { + m_path = m_paths.Last(); } EndDialog(wxID_OK); @@ -166,9 +193,4 @@ void wxDirDialog::SetPath(const wxString& dir) } } -wxString wxDirDialog::GetPath() const -{ - return m_selectedDirectory; -} - #endif // wxUSE_DIRDLG diff --git a/src/msw/dirdlg.cpp b/src/msw/dirdlg.cpp index dbd567889b..21e0dc8224 100644 --- a/src/msw/dirdlg.cpp +++ b/src/msw/dirdlg.cpp @@ -40,111 +40,36 @@ #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 -// We can only use IFileDialog under desktop Windows and we need -// wxDynamicLibrary for it. -#if wxUSE_DYNLIB_CLASS - #define wxUSE_IFILEDIALOG 1 +// 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_IFILEDIALOG 0 + #define wxUSE_IFILEOPENDIALOG 0 #endif -#if wxUSE_IFILEDIALOG +#if wxUSE_IFILEOPENDIALOG // IFileDialog related declarations missing from some compilers headers. -// IShellItem -#ifndef __IShellItem_INTERFACE_DEFINED__ - -#ifndef SIGDN_FILESYSPATH - #define SIGDN_FILESYSPATH 0x80058000 -#endif - -struct IShellItem : public IUnknown -{ - virtual HRESULT wxSTDCALL BindToHandler(IBindCtx*, REFGUID, REFIID, void**) = 0; - virtual HRESULT wxSTDCALL GetParent(IShellItem**) = 0; - virtual HRESULT wxSTDCALL GetDisplayName(DWORD, LPWSTR*) = 0; - virtual HRESULT wxSTDCALL GetAttributes(ULONG, ULONG*) = 0; - virtual HRESULT wxSTDCALL Compare(IShellItem*, DWORD, int*) = 0; -}; - -#endif // #ifndef __IShellItem_INTERFACE_DEFINED__ - -#if defined(__VISUALC__) || !defined(__IShellItem_INTERFACE_DEFINED__) -// Define this GUID in any case, even when __IShellItem_INTERFACE_DEFINED__ is -// defined in the headers we might still not have it in the actual uuid.lib, -// this happens with at least VC7 used with its original (i.e. not updated) SDK. +#if defined(__VISUALC__) +// Always define this GUID, we might still not have it in the actual uuid.lib, +// even when IShellItem interface is defined in the headers. +// This happens with at least VC7 used with its original (i.e. not updated) SDK. // clang complains about multiple definitions, so only define it unconditionally // when using a Visual C compiler. DEFINE_GUID(IID_IShellItem, 0x43826D1E, 0xE718, 0x42EE, 0xBC, 0x55, 0xA1, 0xE2, 0x61, 0xC3, 0x7B, 0xFE); #endif -struct IShellItemFilter; -struct IFileDialogEvents; - -// IModalWindow -#ifndef __IModalWindow_INTERFACE_DEFINED__ - -struct IModalWindow : public IUnknown -{ - virtual HRESULT wxSTDCALL Show(HWND) = 0; -}; - -#endif // #ifndef __IModalWindow_INTERFACE_DEFINED__ - -// IFileDialog -#ifndef __IFileDialog_INTERFACE_DEFINED__ - -#ifndef FOS_PICKFOLDERS - #define FOS_PICKFOLDERS 0x20 -#endif - -#ifndef FOS_FORCEFILESYSTEM - #define FOS_FORCEFILESYSTEM 0x40 -#endif - -struct _COMDLG_FILTERSPEC; - -struct IFileDialog : public IModalWindow -{ - virtual HRESULT wxSTDCALL SetFileTypes(UINT, const _COMDLG_FILTERSPEC*) = 0; - virtual HRESULT wxSTDCALL SetFileTypeIndex(UINT) = 0; - virtual HRESULT wxSTDCALL GetFileTypeIndex(UINT*) = 0; - virtual HRESULT wxSTDCALL Advise(IFileDialogEvents*, DWORD*) = 0; - virtual HRESULT wxSTDCALL Unadvise(DWORD) = 0; - virtual HRESULT wxSTDCALL SetOptions(DWORD) = 0; - virtual HRESULT wxSTDCALL GetOptions(DWORD*) = 0; - virtual HRESULT wxSTDCALL SetDefaultFolder(IShellItem*) = 0; - virtual HRESULT wxSTDCALL SetFolder(IShellItem*) = 0; - virtual HRESULT wxSTDCALL GetFolder(IShellItem**) = 0; - virtual HRESULT wxSTDCALL GetCurrentSelection(IShellItem**) = 0; - virtual HRESULT wxSTDCALL SetFileName(LPCWSTR) = 0; - virtual HRESULT wxSTDCALL GetFileName(LPWSTR*) = 0; - virtual HRESULT wxSTDCALL SetTitle(LPCWSTR) = 0; - virtual HRESULT wxSTDCALL SetOkButtonLabel(LPCWSTR) = 0; - virtual HRESULT wxSTDCALL SetFileNameLabel(LPCWSTR) = 0; - virtual HRESULT wxSTDCALL GetResult(IShellItem**) = 0; - virtual HRESULT wxSTDCALL AddPlace(IShellItem*, DWORD) = 0; - virtual HRESULT wxSTDCALL SetDefaultExtension(LPCWSTR) = 0; - virtual HRESULT wxSTDCALL Close(HRESULT) = 0; - virtual HRESULT wxSTDCALL SetClientGuid(REFGUID) = 0; - virtual HRESULT wxSTDCALL ClearClientData() = 0; - virtual HRESULT wxSTDCALL SetFilter(IShellItemFilter*) = 0; -}; - -DEFINE_GUID(CLSID_FileOpenDialog, - 0xDC1C5A9C, 0xE88A, 0x4dde, 0xA5, 0xA1, 0x60, 0xF8, 0x2A, 0x20, 0xAE, 0xF7); - -DEFINE_GUID(IID_IFileDialog, - 0x42F85136, 0xDB7E, 0x439C, 0x85, 0xF1, 0xE4, 0x07, 0x5D, 0x13, 0x5F, 0xC8); - -#endif // #ifndef __IFileDialog_INTERFACE_DEFINED__ - -#endif // wxUSE_IFILEDIALOG +#endif // wxUSE_IFILEOPENDIALOG // ---------------------------------------------------------------------------- // constants @@ -164,7 +89,18 @@ wxIMPLEMENT_CLASS(wxDirDialog, wxDialog); // private functions prototypes // ---------------------------------------------------------------------------- -// the callback proc for the dir dlg +#if wxUSE_IFILEOPENDIALOG + +// helper functions for wxDirDialog::ShowIFileOpenDialog() +bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, + bool multipleSelection, bool showHidden, wxCOMPtr& fileDialog); +bool GetPathsFromIFileOpenDialog(const wxCOMPtr& fileDialog, bool multipleSelection, + wxArrayString& paths); +bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path); + +#endif // #if wxUSE_IFILEOPENDIALOG + +// callback used in wxDirDialog::ShowSHBrowseForFolder() static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData); @@ -188,6 +124,9 @@ wxDirDialog::wxDirDialog(wxWindow *parent, m_message = message; m_parent = parent; + wxASSERT_MSG( !( (style & wxDD_MULTIPLE) && (style & wxDD_CHANGE_DIR) ), + "wxDD_CHANGE_DIR can't be used together with wxDD_MULTIPLE" ); + SetWindowStyle(style); SetPath(defaultPath); } @@ -219,9 +158,11 @@ int wxDirDialog::ShowModal() wxWindow* const parent = GetParentForModalDialog(); WXHWND hWndParent = parent ? GetHwndOf(parent) : NULL; + m_paths.clear(); + // Use IFileDialog under new enough Windows, it's more user-friendly. int rc; -#if wxUSE_IFILEDIALOG +#if wxUSE_IFILEOPENDIALOG // While the new dialog is available under Vista, it may return a wrong // path there (see http://support.microsoft.com/kb/969885/en-us), so we // don't use it there by default. We could improve the version test to @@ -229,7 +170,7 @@ int wxDirDialog::ShowModal() // as this means that the hotfix correcting this bug is installed. if ( wxGetWinVersion() > wxWinVersion_Vista ) { - rc = ShowIFileDialog(hWndParent); + rc = ShowIFileOpenDialog(hWndParent); } else { @@ -237,7 +178,7 @@ int wxDirDialog::ShowModal() } if ( rc == wxID_NONE ) -#endif // wxUSE_IFILEDIALOG +#endif // wxUSE_IFILEOPENDIALOG { rc = ShowSHBrowseForFolder(hWndParent); } @@ -309,39 +250,91 @@ int wxDirDialog::ShowSHBrowseForFolder(WXHWND owner) // // Returns wxID_OK on success, wxID_CANCEL if cancelled by user or wxID_NONE if // an error occurred and we should fall back onto the old dialog. -#if wxUSE_IFILEDIALOG +#if wxUSE_IFILEOPENDIALOG -int wxDirDialog::ShowIFileDialog(WXHWND owner) +int wxDirDialog::ShowIFileOpenDialog(WXHWND owner) { - HRESULT hr; - wxCOMPtr fileDialog; + HRESULT hr = S_OK; + wxCOMPtr fileDialog; + + if ( !InitIFileOpenDialog(m_message, m_path, HasFlag(wxDD_MULTIPLE), + HasFlag(wxDD_SHOW_HIDDEN), fileDialog) ) + { + return wxID_NONE; // Failed to initialize the dialog + } + + hr = fileDialog->Show(owner); + if ( FAILED(hr) ) + { + if ( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) + { + return wxID_CANCEL; // the user cancelled the dialog + } + else + { + wxLogApiError(wxS("IFileDialog::Show"), hr); + } + } + else if ( GetPathsFromIFileOpenDialog(fileDialog, HasFlag(wxDD_MULTIPLE), + m_paths) ) + { + if ( !HasFlag(wxDD_MULTIPLE) ) + { + m_path = m_paths.Last(); + } + + return wxID_OK; + } + + // Failed to show the dialog or obtain the selected folders(s) + wxLogSysError(_("Couldn't obtain folder name"), hr); + return wxID_CANCEL; +} + +// ---------------------------------------------------------------------------- +// private functions +// ---------------------------------------------------------------------------- + +// helper function for wxDirDialog::ShowIFileOpenDialog() +bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath, + bool multipleSelection, bool showHidden, + 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(IFileDialog, &fileDialog)); + wxIID_PPV_ARGS(IFileOpenDialog, &dlg)); if ( FAILED(hr) ) { wxLogApiError(wxS("CoCreateInstance(CLSID_FileOpenDialog)"), hr); - return wxID_NONE; + return false; } - // allow user to select only a file system folder - hr = fileDialog->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM); + if ( multipleSelection ) + options |= FOS_ALLOWMULTISELECT; + if ( showHidden ) + options |= FOS_FORCESHOWHIDDEN; + + hr = dlg->SetOptions(options); if ( FAILED(hr) ) { - wxLogApiError(wxS("IFileDialog::SetOptions"), hr); - return wxID_NONE; + wxLogApiError(wxS("IFileOpenDialog::SetOptions"), hr); + return false; } - hr = fileDialog->SetTitle(m_message.wc_str()); + 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("IFileDialog::SetTitle"), hr); + wxLogApiError(wxS("IFileOpenDialog::SetTitle"), hr); } // set the initial path - if ( !m_path.empty() ) + if ( !defaultPath.empty() ) { // We need to link SHCreateItemFromParsingName() dynamically as it's // not available on pre-Vista systems. @@ -361,14 +354,14 @@ int wxDirDialog::ShowIFileDialog(WXHWND owner) if ( !pfnSHCreateItemFromParsingName ) { wxLogLastError(wxS("SHCreateItemFromParsingName() not found")); - return wxID_NONE; + return false; } wxCOMPtr folder; - hr = pfnSHCreateItemFromParsingName(m_path.wc_str(), - NULL, - wxIID_PPV_ARGS(IShellItem, - &folder)); + hr = pfnSHCreateItemFromParsingName(defaultPath.wc_str(), + NULL, + wxIID_PPV_ARGS(IShellItem, + &folder)); // Failing to parse the folder name is not really an error, we'll just // ignore the initial directory in this case, but we should still show @@ -378,77 +371,123 @@ int wxDirDialog::ShowIFileDialog(WXHWND owner) if ( hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ) { wxLogApiError(wxS("SHCreateItemFromParsingName"), hr); - return wxID_NONE; + return false; } } else // The folder was parsed correctly. { - hr = fileDialog->SetFolder(folder); + hr = dlg->SetFolder(folder); if ( FAILED(hr) ) { - wxLogApiError(wxS("IFileDialog::SetFolder"), hr); - return wxID_NONE; + wxLogApiError(wxS("IFileOpenDialog::SetFolder"), hr); + return false; } } } - - wxString path; - - hr = fileDialog->Show(owner); - if ( SUCCEEDED(hr) ) - { - wxCOMPtr folder; - - hr = fileDialog->GetResult(&folder); - if ( SUCCEEDED(hr) ) - { - LPOLESTR pathOLE = NULL; - - hr = folder->GetDisplayName(SIGDN_FILESYSPATH, &pathOLE); - if ( SUCCEEDED(hr) ) - { - path = pathOLE; - CoTaskMemFree(pathOLE); - } - else - { - wxLogApiError(wxS("IShellItem::GetDisplayName"), hr); - } - } - else - { - wxLogApiError(wxS("IFileDialog::GetResult"), hr); - } - } - else if ( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) ) - { - return wxID_CANCEL; // the user cancelled the dialog - } - else - { - wxLogApiError(wxS("IFileDialog::Show"), hr); - } - - if ( path.empty() ) - { - // the user didn't cancel the dialog and yet the path is empty - // it means there was an error, already logged by wxLogApiError() - // now report the error to the user and return - wxLogSysError(_("Couldn't obtain folder name"), hr); - return wxID_CANCEL; - } - - m_path = path; - return wxID_OK; + fileDialog = dlg; + return true; } -#endif // wxUSE_IFILEDIALOG +// helper function for wxDirDialog::ShowIFileOpenDialog() +bool GetPathsFromIFileOpenDialog(const wxCOMPtr& fileDialog, bool multipleSelection, + wxArrayString& paths) +{ + HRESULT hr = S_OK; + wxString path; + wxArrayString tempPaths; -// ---------------------------------------------------------------------------- -// private functions -// ---------------------------------------------------------------------------- + if ( multipleSelection ) + { + 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); + } + + } + else // single selection + { + wxCOMPtr item; + + hr = fileDialog->GetResult(&item); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileOpenDialog::GetResult"), hr); + return false; + } + + if ( !ConvertIShellItemToPath(item, path) ) + { + return false; + } + + tempPaths.push_back(path); + } + + if ( tempPaths.empty() ) + return false; // there was en error + + paths = tempPaths; + return true; +} + +// helper function for wxDirDialog::ShowIFileOpenDialog() +bool ConvertIShellItemToPath(const wxCOMPtr& item, wxString& path) +{ + wxCoTaskMemPtr pOLEPath; + const HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &pOLEPath); + + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IShellItem::GetDisplayName"), hr); + return false; + } + + path = pOLEPath; + + return true; +} + +#endif // wxUSE_IFILEOPENDIALOG + +// callback used in wxDirDialog::ShowSHBrowseForFolder() static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) { diff --git a/src/osx/cocoa/dirdlg.mm b/src/osx/cocoa/dirdlg.mm index c46c515a58..373e1e8a94 100644 --- a/src/osx/cocoa/dirdlg.mm +++ b/src/osx/cocoa/dirdlg.mm @@ -48,6 +48,9 @@ void wxDirDialog::Create(wxWindow *parent, const wxString& message, { m_parent = parent; + wxASSERT_MSG( !( (style & wxDD_MULTIPLE) && (style & wxDD_CHANGE_DIR) ), + "wxDD_CHANGE_DIR can't be used together with wxDD_MULTIPLE" ); + SetMessage( message ); SetWindowStyle(style); SetPath(defaultPath); @@ -70,18 +73,32 @@ WX_NSOpenPanel wxDirDialog::OSXCreatePanel() const wxCFStringRef cf( m_message ); [oPanel setMessage:cf.AsNSString()]; + if ( !m_title.empty() ) + { + wxCFStringRef cfTitle(m_title); + [oPanel setTitle:cfTitle.AsNSString()]; + } + if ( !HasFlag(wxDD_DIR_MUST_EXIST) ) [oPanel setCanCreateDirectories:YES]; + if ( HasFlag(wxDD_MULTIPLE) ) + [oPanel setAllowsMultipleSelection:YES]; + + if ( HasFlag(wxDD_SHOW_HIDDEN) ) + [oPanel setShowsHiddenFiles:YES]; + + // Set the directory to use + if ( !m_path.IsEmpty() ) + { + wxCFStringRef dir(m_path); + NSURL* dirUrl = [NSURL fileURLWithPath: dir.AsNSString() isDirectory: YES]; + [oPanel setDirectoryURL: dirUrl]; + } + return oPanel; } -// We use several deprecated methods of NSOpenPanel in the code below, we -// should replace them with newer equivalents now that we don't support OS X -// versions which didn't have them (pre 10.6), but until then, get rid of -// the warning. -wxGCC_WARNING_SUPPRESS(deprecated-declarations) - void wxDirDialog::ShowWindowModal() { wxNonOwnedWindow* parentWindow = NULL; @@ -96,11 +113,11 @@ void wxDirDialog::ShowWindowModal() NSOpenPanel *oPanel = OSXCreatePanel(); NSWindow* nativeParent = parentWindow->GetWXWindow(); - wxCFStringRef dir( m_path ); - [oPanel beginSheetForDirectory:dir.AsNSString() file:nil types: nil - modalForWindow: nativeParent modalDelegate: m_sheetDelegate - didEndSelector: @selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo: nil]; + + // Create the window and have it call the ModalFinishedCallback on completion + [oPanel beginSheetModalForWindow: nativeParent completionHandler: ^(NSModalResponse returnCode){ + [(ModalDialogDelegate*)m_sheetDelegate sheetDidEnd: oPanel returnCode: returnCode contextInfo: nil]; + }]; } int wxDirDialog::ShowModal() @@ -111,15 +128,12 @@ int wxDirDialog::ShowModal() NSOpenPanel *oPanel = OSXCreatePanel(); - wxCFStringRef dir( m_path ); - - m_path.clear(); - int returnCode = -1; OSXBeginModalDialog(); - returnCode = (NSInteger)[oPanel runModalForDirectory:dir.AsNSString() file:nil types:nil]; + // Display the panel and process the result on completion + returnCode = (NSInteger)[oPanel runModal]; ModalFinishedCallback(oPanel, returnCode); OSXEndModalDialog(); @@ -135,7 +149,19 @@ void wxDirDialog::ModalFinishedCallback(void* panel, int returnCode) if (returnCode == NSOKButton ) { NSOpenPanel* oPanel = (NSOpenPanel*)panel; - SetPath( wxCFStringRef::AsStringWithNormalizationFormC([[oPanel filenames] objectAtIndex:0])); + + NSArray* selectedURL = [oPanel URLs]; + + for ( NSURL* url in selectedURL ) + { + m_paths.Add([url fileSystemRepresentation]); + } + + if ( !HasFlag(wxDD_MULTIPLE) ) + { + m_path = m_paths.Last(); + } + result = wxID_OK; } SetReturnCode(result); @@ -144,6 +170,10 @@ void wxDirDialog::ModalFinishedCallback(void* panel, int returnCode) SendWindowModalDialogEvent ( wxEVT_WINDOW_MODAL_DIALOG_CLOSED ); } -wxGCC_WARNING_RESTORE(deprecated-declarations) +void wxDirDialog::SetTitle(const wxString &title) +{ + m_title = title; + wxDialog::SetTitle(title); +} #endif // wxUSE_DIRDLG