Merge branch 'custom-file-dialog-controls'

Implement support for custom file dialog controls in new style MSW
dialogs.

See #22476.

Closes #14770.
This commit is contained in:
Vadim Zeitlin
2022-06-05 13:13:28 +01:00
15 changed files with 3084 additions and 329 deletions

View File

@@ -24,6 +24,8 @@
#define wxHAS_MULTIPLE_FILEDLG_FILTERS
#endif
class WXDLLIMPEXP_FWD_CORE wxFileDialogCustomizeHook;
//----------------------------------------------------------------------------
// wxFileDialog data
//----------------------------------------------------------------------------
@@ -127,6 +129,22 @@ public:
virtual int GetCurrentlySelectedFilterIndex () const
{ return m_currentlySelectedFilterIndex; }
// A customize hook methods will be called by wxFileDialog later if this
// function returns true, see its documentation for details.
//
// Note that the customizeHook object must remain alive at least until
// ShowModal() returns.
//
// If this function returns false, it means that customizing the file
// dialog is not supported on this platforms.
virtual bool SetCustomizeHook(wxFileDialogCustomizeHook& customizeHook);
// Extra controls support is deprecated now as it doesn't allow to use the
// contemporary file dialogs under MSW, use wxFileDialogCustomize-based
// API above instead in the new code.
// this function is called with wxFileDialog as parameter and should
// create the window containing the extra controls we want to show in it
typedef wxWindow *(*ExtraControlCreatorFunction)(wxWindow*);
@@ -172,15 +190,17 @@ protected:
// provide a useful implementation of GetCurrentlySelectedFilterIndex().
int m_currentlySelectedFilterIndex;
wxFileDialogCustomizeHook* m_customizeHook;
wxWindow* m_extraControl;
// returns true if control is created (if it already exists returns false)
// create and return the extra control using the given parent
wxWindow* CreateExtraControlWithParent(wxWindow* parent) const;
// returns true if control is created, also sets m_extraControl
bool CreateExtraControl();
// return true if SetExtraControlCreator() was called
bool HasExtraControlCreator() const
{ return m_extraControlCreator != NULL; }
// get the size of the extra control by creating and deleting it
wxSize GetExtraControlSize();
// Helper function for native file dialog usage where no wx events
// are processed.
void UpdateExtraControlUI();

View File

@@ -0,0 +1,222 @@
///////////////////////////////////////////////////////////////////////////////
// Name: wx/filedlgcustomize.h
// Purpose: Classes for wxFileDialog customization.
// Author: Vadim Zeitlin
// Created: 2022-05-26
// Copyright: (c) 2022 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef _WX_FILEDLGCUSTOMIZE_H_
#define _WX_FILEDLGCUSTOMIZE_H_
#include "wx/vector.h"
class wxFileDialogCustomControlImpl;
class wxFileDialogButtonImpl;
class wxFileDialogCheckBoxImpl;
class wxFileDialogRadioButtonImpl;
class wxFileDialogChoiceImpl;
class wxFileDialogTextCtrlImpl;
class wxFileDialogStaticTextImpl;
class wxFileDialogCustomizeImpl;
// ----------------------------------------------------------------------------
// wxFileDialog custom controls
// ----------------------------------------------------------------------------
// All these controls support a very limited set of functions, but use the same
// names for the things that they do support as the corresponding "normal" wx
// classes and also generate some (but not all) of the same events.
// The base class for all wxFileDialog custom controls.
class WXDLLIMPEXP_CORE wxFileDialogCustomControl : public wxEvtHandler
{
public:
void Show(bool show = true);
void Hide() { Show(false); }
void Enable(bool enable = true);
void Disable() { Enable(false); }
~wxFileDialogCustomControl();
protected:
explicit wxFileDialogCustomControl(wxFileDialogCustomControlImpl* impl)
: m_impl(impl)
{
}
// By default custom controls don't generate any events, but some of them
// override this function to allow connecting to the selected events.
virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE;
wxFileDialogCustomControlImpl* const m_impl;
wxDECLARE_NO_COPY_CLASS(wxFileDialogCustomControl);
};
// A class representing a custom button.
class WXDLLIMPEXP_CORE wxFileDialogButton : public wxFileDialogCustomControl
{
public:
// Ctor is only used by wxWidgets itself.
explicit wxFileDialogButton(wxFileDialogButtonImpl* impl);
protected:
virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE;
private:
wxFileDialogButtonImpl* GetImpl() const;
wxDECLARE_NO_COPY_CLASS(wxFileDialogButton);
};
// A class representing a custom checkbox.
class WXDLLIMPEXP_CORE wxFileDialogCheckBox : public wxFileDialogCustomControl
{
public:
bool GetValue() const;
void SetValue(bool value);
// Ctor is only used by wxWidgets itself.
explicit wxFileDialogCheckBox(wxFileDialogCheckBoxImpl* impl);
protected:
virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE;
private:
wxFileDialogCheckBoxImpl* GetImpl() const;
wxDECLARE_NO_COPY_CLASS(wxFileDialogCheckBox);
};
// A class representing a custom radio button.
class WXDLLIMPEXP_CORE wxFileDialogRadioButton : public wxFileDialogCustomControl
{
public:
bool GetValue() const;
void SetValue(bool value);
// Ctor is only used by wxWidgets itself.
explicit wxFileDialogRadioButton(wxFileDialogRadioButtonImpl* impl);
protected:
virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE;
private:
wxFileDialogRadioButtonImpl* GetImpl() const;
wxDECLARE_NO_COPY_CLASS(wxFileDialogRadioButton);
};
// A class representing a custom combobox button.
class WXDLLIMPEXP_CORE wxFileDialogChoice : public wxFileDialogCustomControl
{
public:
int GetSelection() const;
void SetSelection(int n);
// Ctor is only used by wxWidgets itself.
explicit wxFileDialogChoice(wxFileDialogChoiceImpl* impl);
protected:
virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE;
private:
wxFileDialogChoiceImpl* GetImpl() const;
wxDECLARE_NO_COPY_CLASS(wxFileDialogChoice);
};
// A class representing a custom text control.
class WXDLLIMPEXP_CORE wxFileDialogTextCtrl : public wxFileDialogCustomControl
{
public:
wxString GetValue() const;
void SetValue(const wxString& text);
// Ctor is only used by wxWidgets itself.
explicit wxFileDialogTextCtrl(wxFileDialogTextCtrlImpl* impl);
private:
wxFileDialogTextCtrlImpl* GetImpl() const;
wxDECLARE_NO_COPY_CLASS(wxFileDialogTextCtrl);
};
// A class representing a custom static text.
class WXDLLIMPEXP_CORE wxFileDialogStaticText : public wxFileDialogCustomControl
{
public:
void SetLabelText(const wxString& text);
// Ctor is only used by wxWidgets itself.
explicit wxFileDialogStaticText(wxFileDialogStaticTextImpl* impl);
private:
wxFileDialogStaticTextImpl* GetImpl() const;
wxDECLARE_NO_COPY_CLASS(wxFileDialogStaticText);
};
// ----------------------------------------------------------------------------
// wxFileDialogCustomizer is used by wxFileDialogCustomizeHook
// ----------------------------------------------------------------------------
class WXDLLIMPEXP_CORE wxFileDialogCustomize
{
public:
wxFileDialogButton* AddButton(const wxString& label);
wxFileDialogCheckBox* AddCheckBox(const wxString& label);
wxFileDialogRadioButton* AddRadioButton(const wxString& label);
wxFileDialogChoice* AddChoice(size_t n, const wxString* strings);
wxFileDialogTextCtrl* AddTextCtrl(const wxString& label = wxString());
wxFileDialogStaticText* AddStaticText(const wxString& label);
~wxFileDialogCustomize();
protected:
// Ctor is only used by wxWidgets itself.
//
// Note that we don't take ownership of the implementation pointer here,
// see the comment in the dtor for more details.
explicit wxFileDialogCustomize(wxFileDialogCustomizeImpl* impl)
: m_impl(impl)
{
}
wxVector<wxFileDialogCustomControl*> m_controls;
private:
template <typename T> T* StoreAndReturn(T* control);
wxFileDialogCustomizeImpl* const m_impl;
wxDECLARE_NO_COPY_CLASS(wxFileDialogCustomize);
};
// ----------------------------------------------------------------------------
// wxFileDialogCustomizeHook: used by wxFileDialog itself
// ----------------------------------------------------------------------------
class WXDLLIMPEXP_CORE wxFileDialogCustomizeHook
{
public:
// This method must be overridden to add custom controls to the dialog
// using the provided customizer object.
virtual void AddCustomControls(wxFileDialogCustomize& customizer) = 0;
// This method may be overridden to update the custom controls whenever
// something changes in the dialog.
virtual void UpdateCustomControls() { }
// This method should typically be overridden to save the values of the
// custom controls when the dialog is accepted.
virtual void TransferDataFromCustomControls() { }
virtual ~wxFileDialogCustomizeHook();
};
#endif // _WX_FILEDLGCUSTOMIZE_H_

View File

@@ -11,6 +11,8 @@
#ifndef _WX_FILEDLG_H_
#define _WX_FILEDLG_H_
class wxFileDialogMSWData;
//-------------------------------------------------------------------------
// wxFileDialog
//-------------------------------------------------------------------------
@@ -27,26 +29,14 @@ public:
const wxPoint& pos = wxDefaultPosition,
const wxSize& sz = wxDefaultSize,
const wxString& name = wxASCII_STR(wxFileDialogNameStr));
virtual ~wxFileDialog();
virtual void GetPaths(wxArrayString& paths) const wxOVERRIDE;
virtual void GetFilenames(wxArrayString& files) const wxOVERRIDE;
virtual bool SupportsExtraControl() const wxOVERRIDE { return true; }
void MSWOnInitDialogHook(WXHWND hwnd);
virtual int ShowModal() wxOVERRIDE;
// wxMSW-specific implementation from now on
// -----------------------------------------
// called from the hook procedure on CDN_INITDONE reception
virtual void MSWOnInitDone(WXHWND hDlg);
// called from the hook procedure on CDN_SELCHANGE.
void MSWOnSelChange(WXHWND hDlg);
// called from the hook procedure on CDN_TYPECHANGE.
void MSWOnTypeChange(WXHWND hDlg, int nFilterIndex);
protected:
virtual void DoMoveWindow(int x, int y, int width, int height) wxOVERRIDE;
@@ -55,12 +45,38 @@ protected:
virtual void DoGetPosition( int *x, int *y ) const wxOVERRIDE;
private:
// Allow it to call MSWOnXXX() functions below.
friend class wxFileDialogMSWData;
// called when the dialog is created
void MSWOnInitDialogHook(WXHWND hwnd);
// called when the dialog initialization is fully done
void MSWOnInitDone(WXHWND hDlg);
// called when the currently selected file changes in the dialog
void MSWOnSelChange(const wxString& selectedFilename);
// called when the currently selected type of files changes in the dialog
void MSWOnTypeChange(int nFilterIndex);
// The real implementation of ShowModal() using traditional common dialog
// functions.
int ShowCommFileDialog(WXHWND owner);
// And another one using IFileDialog.
int ShowIFileDialog(WXHWND owner);
// Get the data object, allocating it if necessary.
wxFileDialogMSWData& MSWData();
wxArrayString m_fileNames;
// remember if our SetPosition() or Centre() (which requires special
// treatment) was called
bool m_bMovedWindow;
int m_centreDir; // nothing to do if 0
// Extra data, possibly null if not needed, use MSWData() to access it if
// it should be created on demand.
wxFileDialogMSWData* m_data;
wxDECLARE_DYNAMIC_CLASS(wxFileDialog);
wxDECLARE_NO_COPY_CLASS(wxFileDialog);

View File

@@ -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.

View File

@@ -0,0 +1,83 @@
///////////////////////////////////////////////////////////////////////////////
// Name: wx/msw/private/filedialog.h
// Purpose: IFileDialog-related functions
// Author: Vadim Zeitlin
// Created: 2022-05-15
// Copyright: (c) 2022 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef _WX_MSW_PRIVATE_FILEDIALOG_H_
#define _WX_MSW_PRIVATE_FILEDIALOG_H_
#include "wx/msw/private.h"
#include "wx/msw/wrapshl.h"
// We want to use IFileDialog if either wxDirDialog or wxFileDialog are used.
//
// IFileOpenDialog implementation needs wxDynamicLibrary for
// run-time linking SHCreateItemFromParsingName(), available
// only under Windows Vista and newer.
// It also needs a compiler providing declarations and definitions
// of interfaces available in Windows Vista.
// And it needs OLE support to actually use these interfaces.
#if (wxUSE_DIRDLG || wxUSE_FILEDLG) && wxUSE_DYNLIB_CLASS && wxUSE_OLE && \
defined(__IFileOpenDialog_INTERFACE_DEFINED__)
#define wxUSE_IFILEOPENDIALOG 1
#else
#define wxUSE_IFILEOPENDIALOG 0
#endif
#if wxUSE_IFILEOPENDIALOG
#include "wx/msw/private/comptr.h"
namespace wxMSWImpl
{
// For historical reasons, this class is defined in src/msw/dirdlg.cpp.
class wxIFileDialog
{
public:
// Create the dialog of the specified type.
//
// CLSID must be either CLSID_FileOpenDialog or CLSID_FileSaveDialog.
//
// Use IsOk() to check if the dialog was created successfully.
explicit wxIFileDialog(const CLSID& clsid);
// If this returns false, the dialog can't be used at all.
bool IsOk() const { return m_fileDialog.Get() != NULL; }
// Set the dialog title.
void SetTitle(const wxString& title);
// Set the initial path to show in the dialog.
void SetInitialPath(const wxString& path);
// Show the file dialog with the given parent window and options.
//
// Returns the selected path, or paths, in the provided output parameters,
// depending on whether FOS_ALLOWMULTISELECT is part of the options.
//
// The return value is wxID_OK if any paths were returned, wxID_CANCEL if the
// dialog was cancelled.
int
Show(HWND owner, int options, wxArrayString* pathsOut, wxString* pathOut);
// Behave as IFileDialog.
IFileDialog* Get() const { return m_fileDialog.Get(); }
IFileDialog* operator->() const { return m_fileDialog.Get(); }
private:
wxCOMPtr<IFileDialog> m_fileDialog;
};
// Extract the filesystem path corresponding to the given shell item.
HRESULT GetFSPathFromShellItem(const wxCOMPtr<IShellItem>& item, wxString& path);
} // namespace wxMSWImpl
#endif // wxUSE_IFILEOPENDIALOG
#endif // _WX_MSW_PRIVATE_FILEDIALOG_H_

View File

@@ -0,0 +1,85 @@
///////////////////////////////////////////////////////////////////////////////
// Name: wx/private/filedlgcustomize.h
// Purpose: Private helpers used for wxFileDialog customization
// Author: Vadim Zeitlin
// Created: 2022-05-26
// Copyright: (c) 2022 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef _WX_PRIVATE_FILEDLGCUSTOMIZE_H_
#define _WX_PRIVATE_FILEDLGCUSTOMIZE_H_
// ----------------------------------------------------------------------------
// wxFileDialogCustomControlImpl: interface for all custom controls
// ----------------------------------------------------------------------------
class wxFileDialogCustomControlImpl
{
public:
virtual void Show(bool show) = 0;
virtual void Enable(bool enable) = 0;
virtual bool DoBind(wxEvtHandler* handler);
virtual ~wxFileDialogCustomControlImpl();
};
// This class is defined for symmetry with the other ones, but there are no
// button-specific methods so far.
class wxFileDialogButtonImpl : public wxFileDialogCustomControlImpl
{
};
class wxFileDialogCheckBoxImpl : public wxFileDialogCustomControlImpl
{
public:
virtual bool GetValue() = 0;
virtual void SetValue(bool value) = 0;
};
class wxFileDialogRadioButtonImpl : public wxFileDialogCustomControlImpl
{
public:
virtual bool GetValue() = 0;
virtual void SetValue(bool value) = 0;
};
class wxFileDialogChoiceImpl : public wxFileDialogCustomControlImpl
{
public:
virtual int GetSelection() = 0;
virtual void SetSelection(int n) = 0;
};
class wxFileDialogTextCtrlImpl : public wxFileDialogCustomControlImpl
{
public:
virtual wxString GetValue() = 0;
virtual void SetValue(const wxString& value) = 0;
};
class wxFileDialogStaticTextImpl : public wxFileDialogCustomControlImpl
{
public:
virtual void SetLabelText(const wxString& text) = 0;
};
// ----------------------------------------------------------------------------
// wxFileDialogCustomizeImpl: interface for actual customizers
// ----------------------------------------------------------------------------
class wxFileDialogCustomizeImpl
{
public:
virtual wxFileDialogButtonImpl* AddButton(const wxString& label) = 0;
virtual wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) = 0;
virtual wxFileDialogRadioButtonImpl* AddRadioButton(const wxString& label) = 0;
virtual wxFileDialogChoiceImpl* AddChoice(size_t n, const wxString* strings) = 0;
virtual wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) = 0;
virtual wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) = 0;
virtual ~wxFileDialogCustomizeImpl();
};
#endif // _WX_PRIVATE_FILEDLGCUSTOMIZE_H_

View File

@@ -94,7 +94,9 @@ const char wxFileSelectorDefaultWildcardStr[];
}
@endcode
@remarks
@section filedialog_filters Wildcard Filters
All implementations of the wxFileDialog provide a wildcard filter. Typing a filename
containing wildcards (*, ?) in the filename text item, and clicking on Ok, will
result in only those files matching the pattern being displayed.
@@ -103,13 +105,9 @@ const char wxFileSelectorDefaultWildcardStr[];
@code
"BMP and GIF files (*.bmp;*.gif)|*.bmp;*.gif|PNG files (*.png)|*.png"
@endcode
It must be noted that wildcard support in the native Motif file dialog is quite
limited: only one file type is supported, and it is displayed without the
descriptive test; "BMP files (*.bmp)|*.bmp" is displayed as "*.bmp", and both
"BMP files (*.bmp)|*.bmp|GIF files (*.gif)|*.gif" and "Image files|*.bmp;*.gif"
are errors.
On Mac macOS in the open file dialog the filter choice box is not shown by default.
Instead all given wildcards are appplied at the same time: So in the above
Instead all given wildcards are applied at the same time: So in the above
example all bmp, gif and png files are displayed. To enforce the
display of the filter choice set the corresponding wxSystemOptions before calling
the file open dialog:
@@ -121,6 +119,33 @@ const char wxFileSelectorDefaultWildcardStr[];
matching all file types. The files which does not match the currently selected
file type are greyed out and are not selectable.
@section filedialog_customize Dialog Customization
Uniquely among the other standard dialogs, wxFileDialog can be customized
by adding extra controls to it. Moreover, there are two ways to do it: the
first one is to define a callback function and use SetExtraControlCreator()
to tell the dialog to call it, while the second one requires defining a
class inheriting from wxFileDialogCustomizeHook and implementing its
virtual functions, notably wxFileDialogCustomizeHook::AddCustomControls()
where the extra controls have to be created, and finally calling
SetCustomizeHook() with this custom hook object.
The first approach is somewhat simpler and more flexible, as it allows to
create any kind of custom controls, but is not supported by the "new style"
(where "new" means used since Windows Vista, i.e. circa 2007) file dialogs
under MSW. Because of this, calling SetExtraControlCreator() in wxMSW
forces the use of old style (Windows XP) dialogs, that may look out of
place. The second approach is implemented by the MSW dialogs natively and
doesn't suffer from this limitation, so its use is recommended, especially
if the few simple control types supported by it (see wxFileDialogCustomize
for more information about the supported controls) are sufficient for your
needs.
Both of the approaches to the dialog customization are demonstrated in the
@ref page_samples_dialogs, please check it for more details.
@beginStyleTable
@style{wxFD_DEFAULT_STYLE}
Equivalent to @c wxFD_OPEN.
@@ -322,6 +347,27 @@ public:
*/
virtual wxString GetWildcard() const;
/**
Set the hook to be used for customizing the dialog contents.
This function can be called before calling ShowModal() to specify that
the dialog contents should be customized using the provided hook. See
wxFileDialogCustomizeHook documentation and @ref page_samples_dialogs
for the examples of using it.
@note In order to define a custom hook object, @c wx/filedlgcustomize.h
must be included in addition to the usual @c wx/filedlg.h header.
@param customizeHook The hook object that will be used by the dialog.
This object must remain valid at least until ShowModal() returns.
@return @true if the hook was successfully set or @false if customizing
the file dialog is not supported by the current platform.
@since 3.1.7
*/
bool SetCustomizeHook(wxFileDialogCustomizeHook& customizeHook);
/**
Sets the default directory.
*/
@@ -343,6 +389,11 @@ public:
The @c creator function should take pointer to parent window (file dialog)
and should return a window allocated with operator new.
@note Using SetExtraControlCreator() in wxMSW forces the use of "old
style" (Windows XP-like) file dialogs, instead of the newer
(Vista-like) ones and is not recommended for this reason. Prefer to
use SetCustomizeHook() instead.
@since 2.9.0
*/
bool SetExtraControlCreator(ExtraControlCreatorFunction creator);

View File

@@ -0,0 +1,397 @@
///////////////////////////////////////////////////////////////////////////////
// Name: interface/wx/filedlgcustomize.h
// Purpose: Documentation of classes used for wxFileDialog customization.
// Author: Vadim Zeitlin
// Created: 2022-06-03
// Copyright: (c) 2022 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
/**
The base class for all wxFileDialog custom controls.
Unlike normal controls, inheriting from wxWindow, custom controls in
wxFileDialog are not actual windows, but they do provide some window-like
operations, and can be disabled or hidden, just as the windows are.
Also, similarly to the normal windows, objects of this and derived classes
belong to wxWidgets and must @e not be deleted by the application code.
Unlike windows, custom controls cannot be created directly, but can only be
returned from wxFileDialogCustomize functions that are specifically
provided for creating them.
@since 3.1.7
*/
class wxFileDialogCustomControl : public wxEvtHandler
{
public:
/// Show or hide this control.
void Show(bool show = true);
/// Hide this control.
///
/// This is equivalent to @c Show(false).
void Hide();
/// Enable or disable this control.
void Enable(bool enable = true);
/// Disable this control.
///
/// This is equivalent to @c Enable(false).
void Disable();
};
/**
Represents a custom button inside wxFileDialog.
Objects of this class can only be created by
wxFileDialogCustomize::AddButton().
It is possible to bind to wxEVT_BUTTON events on this object, which will be
generated when the button is clicked.
See wxFileDialogCustomControl for more information.
@since 3.1.7
*/
class wxFileDialogButton : public wxFileDialogCustomControl
{
};
/**
Represents a custom checkbox inside wxFileDialog.
Objects of this class can only be created by
wxFileDialogCustomize::AddCheckBox().
It is possible to bind to wxEVT_CHECKBOX events on this object, which will
be generated when the checkbox is clicked.
See wxFileDialogCustomControl for more information.
@since 3.1.7
*/
class wxFileDialogCheckBox : public wxFileDialogCustomControl
{
public:
/// Return @true if the checkbox is checked.
bool GetValue() const;
/// Check or uncheck the checkbox.
void SetValue(bool value);
};
/**
Represents a custom radio button inside wxFileDialog.
Objects of this class can only be created by
wxFileDialogCustomize::AddRadioButton().
It is possible to bind to wxEVT_RADIOBUTTON events on this object, which
will be generated when the radio button is selected.
See wxFileDialogCustomControl for more information.
@since 3.1.7
*/
class wxFileDialogRadioButton : public wxFileDialogCustomControl
{
public:
/// Return @true if the radio button is selected.
bool GetValue() const;
/// Select the value of the radio button.
///
/// Using @false for @a value is not supported, this argument only exists
/// for consistency with wxRadioButton::SetValue().
void SetValue(bool value);
};
/**
Represents a custom read-only combobox inside wxFileDialog.
Objects of this class can only be created by
wxFileDialogCustomize::AddChoice().
It is possible to bind to wxEVT_CHOICE events on this object, which
will be generated when the selection in the combobox changes.
See wxFileDialogCustomControl for more information.
@since 3.1.7
*/
class wxFileDialogChoice : public wxFileDialogCustomControl
{
public:
/// Return the index of the selected item, possibly wxNOT_FOUND.
int GetSelection() const;
/// Set the selection to the item with the given index.
///
/// Using @c wxNOT_FOUND for @a n is not supported, once a selection is
/// made it cannot be undone.
void SetSelection(int n);
};
/**
Represents a custom text control inside wxFileDialog.
Objects of this class can only be created by
wxFileDialogCustomize::AddTextCtrl().
Objects of this class don't generate any events currently.
See wxFileDialogCustomControl for more information.
@since 3.1.7
*/
class wxFileDialogTextCtrl : public wxFileDialogCustomControl
{
public:
/// Get the current value entered into the control.
wxString GetValue() const;
/// Set the current control value.
void SetValue(const wxString& text);
};
/**
Represents a custom static text inside wxFileDialog.
Objects of this class can only be created by
wxFileDialogCustomize::AddStaticText().
Objects of this class don't generate any events.
See wxFileDialogCustomControl for more information.
@since 3.1.7
*/
class wxFileDialogStaticText : public wxFileDialogCustomControl
{
public:
/**
Set the text shown in the label.
Any ampersands in the @a text will be escaped, there is no need to do
it manually, e.g. using wxControl::EscapeMnemonics().
*/
void SetLabelText(const wxString& text);
};
/**
Used with wxFileDialogCustomizeHook to add custom controls to wxFileDialog.
An object of this class is passed to wxFileDialogCustomizeHook::AddCustomControls()
to allow it to actually add controls to the dialog.
The pointers returned by the functions of this class belong to wxWidgets
and should @e not be deleted by the application, just as wxWindow-derived
objects (even if these controls do not inherit from wxWindow). These
pointers become invalid when wxFileDialog::ShowModal() returns, and the
dialog containing them is destroyed, and the latest point at which they can
be still used is when wxFileDialogCustomizeHook::TransferDataFromCustomControls()
is called.
@library{wxcore}
@category{cmndlg}
@see wxFileDialog
@since 3.1.7
*/
class wxFileDialogCustomize
{
public:
/**
Add a button with the specified label.
*/
wxFileDialogButton* AddButton(const wxString& label);
/**
Add a checkbox with the specified label.
*/
wxFileDialogCheckBox* AddCheckBox(const wxString& label);
/**
Add a radio button with the specified label.
The first radio button added will be initially checked. All the radio
buttons added immediately after it will become part of the same radio
group and will not be checked, but checking any one of them later will
uncheck the first button and all the other ones.
If two consecutive but distinct radio groups are required,
AddStaticText() with an empty label can be used to separate them.
*/
wxFileDialogRadioButton* AddRadioButton(const wxString& label);
/**
Add a read-only combobox with the specified contents.
The combobox doesn't have any initial selection, i.e.
wxFileDialogChoice::GetSelection() returns @c wxNOT_FOUND, if some item
must be selected, use wxFileDialogChoice::SetSelection() explicitly to
do it.
@param n The number of strings, must be positive, as there is no way to
add more strings later and creating an empty combobox is not very
useful.
@param strings A non-@NULL pointer to an array of @a n strings.
*/
wxFileDialogChoice* AddChoice(size_t n, const wxString* strings);
/**
Add a text control with an optional label preceding it.
Unlike all the other functions for adding controls, the @a label
parameter here doesn't specify the contents of the text control itself,
but rather the label appearing before it. Unlike static controls added
by AddStaticText(), this label is guaranteed to be immediately adjacent
to it.
If @a label is empty, no label is created.
*/
wxFileDialogTextCtrl* AddTextCtrl(const wxString& label = wxString());
/**
Add a static text with the given contents.
The contents of the static text can be updated later, i.e. it doesn't
need to be actually static.
*/
wxFileDialogStaticText* AddStaticText(const wxString& label);
};
/**
Base class for customization hooks used with wxFileDialog.
wxFileDialogCustomizeHook is an abstract base class, i.e. in order to use a
concrete class inheriting from it and implementing its pure virtual
AddCustomControls() function must be defined. Then an object of this class
should be passed to wxFileDialog::SetCustomizeHook(), which will result in
its AddCustomControls() being called before the dialog is shown,
UpdateCustomControls() being called whenever something changes in the
dialog while it is shown and, finally, TransferDataFromCustomControls()
being called when the user accepts their choice in the dialog.
Putting all this together, here is an example of customizing the file
dialog using this class:
@code
class EncryptHook : public wxFileDialogCustomizeHook
{
public:
// Override to add custom controls using the provided customizer object.
void AddCustomControls(wxFileDialogCustomize& customizer) override
{
// Suppose we can encrypt files when saving them.
m_checkbox = customizer.AddCheckBox("Encrypt");
// While m_checkbox is not a wxCheckBox, it looks almost like one
// and, in particular, we can bind to custom control events as usual.
m_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) {
// We can also call wxWindow-like functions on them.
m_button->Enable(event.IsChecked());
});
// The encryption parameters can be edited in a dedicated dialog.
m_button = customizer.AddButton("Parameters...");
m_button->Bind(wxEVT_BUTTON, [](wxCommandEvent&) {
... show the encryption parameters dialog here ...
});
}
// Override to save the values of the custom controls.
void TransferDataFromCustomControls() override
{
// Save the checkbox value, as we won't be able to use it any more
// once this function returns.
m_encrypt = m_checkbox->GetValue();
}
// Just a simple accessor to get the results.
bool Encrypt() const { return m_encrypt; }
private:
wxFileDialogButton* m_button;
wxFileDialogCheckBox* m_checkbox;
bool m_encrypt = false;
};
void SomeFunc()
{
wxFileDialog dialog(NULL, "Save document", wxString(), "file.my",
"My files (*.my)|*.my",
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
// This object may be destroyed before the dialog, but must remain
// alive until ShowModal() returns.
EncryptHook customizeHook;
if ( dialog.ShowModal() == wxID_OK )
{
if ( customizeHook.Encrypt() )
... save with encryption ...
else
... save without encryption ...
}
}
@endcode
@library{wxcore}
@category{cmndlg}
@see wxFileDialog
@since 3.1.7
*/
class wxFileDialogCustomizeHook
{
public:
/**
Must be overridden to add custom controls to the dialog using the
provided customizer object.
Call wxFileDialogCustomize functions to add controls and possibly bind
to their events.
Note that there is no possibility to define the custom controls layout,
they will appear more or less consecutively, but the exact layout is
determined by the current platform.
*/
virtual void AddCustomControls(wxFileDialogCustomize& customizer) = 0;
/**
May be overridden to update the custom controls whenever something
changes in the dialog.
This function is called when the user selects a file, changes the
directory or changes the current filter in the dialog, for example.
It can be used to update the custom controls state depending on the
currently selected file, for example.
Note that it is @e not necessarily called when the value of a custom
control changes.
Base class version does nothing.
*/
virtual void UpdateCustomControls();
/**
Should typically be overridden to save the values of the custom
controls when the dialog is accepted.
Custom controls are destroyed and cannot be used any longer once
wxFileDialog::ShowModal() returns, so their values must be retrieved in
this function, which is called just before this happens.
This function is @e not called if the user cancels the dialog.
Base class version does nothing.
*/
virtual void TransferDataFromCustomControls();
};

View File

@@ -87,6 +87,7 @@
#if wxUSE_FILEDLG
#include "wx/filedlg.h"
#include "wx/filedlgcustomize.h"
#endif // wxUSE_FILEDLG
#if wxUSE_DIRDLG
@@ -507,6 +508,17 @@ bool MyApp::OnInit()
filedlg_menu->Append(DIALOGS_FILE_SAVE_GENERIC, "Sa&ve file (generic)");
#endif // USE_FILEDLG_GENERIC
filedlg_menu->AppendSeparator();
filedlg_menu->AppendRadioItem(
DIALOGS_FILE_USE_CUSTOMIZER,
"Use new customization API",
"Use wxFileDialog::SetCustomizeHook() for file dialog customization"
);
filedlg_menu->AppendRadioItem(
DIALOGS_FILE_USE_EXTRA_CONTROL_CREATOR,
"Use old customization API",
"Use wxFileDialog::SetExtraControlCreator() for file dialog customization"
);
#ifdef __WXOSX_COCOA__
filedlg_menu->AppendSeparator();
filedlg_menu->AppendCheckItem(DIALOGS_MAC_TOGGLE_ALWAYS_SHOW_TYPES,
@@ -1562,6 +1574,42 @@ void MyFrame::AddRemove(wxCommandEvent& WXUNUSED(event))
#if wxUSE_FILEDLG
// Simple function showing the current wxFileDialog state.
wxString GetFileDialogStateDescription(wxFileDialogBase* dialog)
{
const wxString fn = dialog->GetCurrentlySelectedFilename();
wxString msg;
if ( fn.empty() )
msg = "Nothing";
else if ( wxFileName::FileExists(fn) )
msg = "File";
else if ( wxFileName::DirExists(fn) )
msg = "Directory";
else
msg = "Something else";
msg += " selected";
const int filter = dialog->GetCurrentlySelectedFilterIndex();
if ( filter != wxNOT_FOUND )
msg += wxString::Format(" (filter=%d)", filter);
return msg;
}
// Another helper translating demo combobox selection.
wxString GetFileDialogPaperSize(int selection)
{
switch ( selection )
{
case -1: return "<none>";
case 0: return "A4";
case 1: return "Letter";
default: return "INVALID";
}
}
// panel with custom controls for file dialog
class MyExtraPanel : public wxPanel
{
@@ -1569,7 +1617,8 @@ public:
MyExtraPanel(wxWindow *parent);
wxString GetInfo() const
{
return wxString::Format("checkbox=%d, text=\"%s\"", m_checked, m_str);
return wxString::Format("paper=%s (%s), enabled=%d, text=\"%s\"",
m_paperSize, m_paperOrient, m_checked, m_str);
}
private:
@@ -1579,6 +1628,21 @@ private:
m_btn->Enable(m_checked);
}
void OnRadioButton(wxCommandEvent& event)
{
if ( event.GetEventObject() == m_radioPortrait )
m_paperOrient = "portrait";
else if ( event.GetEventObject() == m_radioLandscape )
m_paperOrient = "landscape";
else
m_paperOrient = "unknown";
}
void OnChoice(wxCommandEvent& event)
{
m_paperSize = GetFileDialogPaperSize(event.GetSelection());
}
void OnText(wxCommandEvent& event)
{
m_str = event.GetString();
@@ -1590,32 +1654,19 @@ private:
// wxGenericFileDialog, so we need to cast to the base class. In a
// typical application, we would cast to just wxFileDialog instead.
wxFileDialogBase* const dialog = wxStaticCast(GetParent(), wxFileDialogBase);
const wxString fn = dialog->GetCurrentlySelectedFilename();
wxString msg;
if ( fn.empty() )
msg = "Nothing";
else if ( wxFileName::FileExists(fn) )
msg = "File";
else if ( wxFileName::DirExists(fn) )
msg = "Directory";
else
msg = "Something else";
msg += " selected";
const int filter = dialog->GetCurrentlySelectedFilterIndex();
if ( filter != wxNOT_FOUND )
msg += wxString::Format(" (filter=%d)", filter);
event.SetText(msg);
event.SetText(GetFileDialogStateDescription(dialog));
}
wxString m_str;
bool m_checked;
wxString m_paperSize;
wxString m_paperOrient;
wxButton *m_btn;
wxCheckBox *m_cb;
wxRadioButton *m_radioPortrait;
wxRadioButton *m_radioLandscape;
wxStaticText *m_label;
wxTextCtrl *m_text;
};
@@ -1629,6 +1680,15 @@ MyExtraPanel::MyExtraPanel(wxWindow *parent)
m_btn->Enable(false);
m_cb = new wxCheckBox(this, -1, "Enable Custom Button");
m_cb->Bind(wxEVT_CHECKBOX, &MyExtraPanel::OnCheckBox, this);
wxChoice* choiceSize = new wxChoice(this, wxID_ANY);
choiceSize->Append("A4");
choiceSize->Append("Letter");
choiceSize->Bind(wxEVT_CHOICE, &MyExtraPanel::OnChoice, this);
m_radioPortrait = new wxRadioButton(this, wxID_ANY, "&Portrait",
wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
m_radioPortrait->Bind(wxEVT_RADIOBUTTON, &MyExtraPanel::OnRadioButton, this);
m_radioLandscape = new wxRadioButton(this, wxID_ANY, "&Landscape");
m_radioLandscape->Bind(wxEVT_RADIOBUTTON, &MyExtraPanel::OnRadioButton, this);
m_label = new wxStaticText(this, wxID_ANY, "Nothing selected");
m_label->Bind(wxEVT_UPDATE_UI, &MyExtraPanel::OnUpdateLabelUI, this);
@@ -1641,6 +1701,9 @@ MyExtraPanel::MyExtraPanel(wxWindow *parent)
wxSizerFlags().Centre().Border());
sizerTop->Add(m_text, wxSizerFlags(1).Centre().Border());
sizerTop->AddSpacer(10);
sizerTop->Add(choiceSize, wxSizerFlags().Centre().Border(wxRIGHT));
sizerTop->Add(m_radioPortrait, wxSizerFlags().Centre().Border());
sizerTop->Add(m_radioLandscape, wxSizerFlags().Centre().Border());
sizerTop->Add(m_cb, wxSizerFlags().Centre().Border());
sizerTop->AddSpacer(5);
sizerTop->Add(m_btn, wxSizerFlags().Centre().Border());
@@ -1656,6 +1719,95 @@ static wxWindow* createMyExtraPanel(wxWindow *parent)
return new MyExtraPanel(parent);
}
// This class does the same thing as MyExtraPanel above, but uses newer API for
// wxFileDialog customization.
class MyCustomizeHook : public wxFileDialogCustomizeHook
{
public:
// Normally we would just use wxFileDialog, but this sample allows using
// both the real wxFileDialog and wxGenericFileDialog, so allow passing
// either of them here.
explicit MyCustomizeHook(wxFileDialogBase& dialog)
: m_dialog(&dialog)
{
}
// Override pure virtual base class method to add our custom controls.
virtual void AddCustomControls(wxFileDialogCustomize& customizer) wxOVERRIDE
{
// Note: all the pointers created here cease to be valid once
// ShowModal() returns, TransferDataFromCustomControls() is the latest
// moment when they can still be used.
m_text = customizer.AddTextCtrl("Just some extra text:");
const wxString sizes[] = { "A4", "Letter" };
m_choiceSize = customizer.AddChoice(WXSIZEOF(sizes), sizes);
m_radioPortrait = customizer.AddRadioButton("&Portrait");
m_radioLandscape = customizer.AddRadioButton("&Landscape");
m_cb = customizer.AddCheckBox("Enable Custom Button");
m_cb->Bind(wxEVT_CHECKBOX, &MyCustomizeHook::OnCheckBox, this);
m_btn = customizer.AddButton("Custom Button");
m_btn->Bind(wxEVT_BUTTON, &MyCustomizeHook::OnButton, this);
m_label = customizer.AddStaticText("Nothing selected");
}
// Override another method called whenever something changes in the dialog.
virtual void UpdateCustomControls() wxOVERRIDE
{
// Enable the button if and only if the checkbox is checked.
m_btn->Enable(m_cb->GetValue());
// Enable radio buttons only if a file is selected.
bool hasFile = wxFileName::FileExists(
m_dialog->GetCurrentlySelectedFilename()
);
m_radioPortrait->Enable(hasFile);
m_radioLandscape->Enable(hasFile);
// Also show the current dialog state.
m_label->SetLabelText(GetFileDialogStateDescription(m_dialog));
}
// And another one called when the dialog is accepted.
virtual void TransferDataFromCustomControls() wxOVERRIDE
{
m_info.Printf("paper=%s (%s), enabled=%d, text=\"%s\"",
GetFileDialogPaperSize(m_choiceSize->GetSelection()),
m_radioPortrait->GetValue() ? "portrait" : "landscape",
m_cb->GetValue(), m_text->GetValue());
}
// This is just a helper function allowing to show the values of the custom
// controls.
wxString GetInfo() const { return m_info; }
private:
void OnCheckBox(wxCommandEvent& event)
{
m_btn->Enable(event.IsChecked());
}
void OnButton(wxCommandEvent& WXUNUSED(event))
{
wxMessageBox("Custom button pressed", "wxWidgets dialogs sample",
wxOK | wxICON_INFORMATION, m_dialog);
}
wxFileDialogBase* const m_dialog;
wxFileDialogButton* m_btn;
wxFileDialogCheckBox* m_cb;
wxFileDialogChoice* m_choiceSize;
wxFileDialogRadioButton* m_radioPortrait;
wxFileDialogRadioButton* m_radioLandscape;
wxFileDialogTextCtrl* m_text;
wxFileDialogStaticText* m_label;
wxString m_info;
wxDECLARE_NO_COPY_CLASS(MyCustomizeHook);
};
void MyFrame::FileOpen(wxCommandEvent& WXUNUSED(event) )
{
wxFileDialog dialog
@@ -1672,14 +1824,43 @@ void MyFrame::FileOpen(wxCommandEvent& WXUNUSED(event) )
)
);
dialog.SetExtraControlCreator(&createMyExtraPanel);
// Note: this object must remain alive until ShowModal() returns.
MyCustomizeHook myCustomizer(dialog);
// Normal programs would use either SetCustomizeHook() (preferred) or
// SetExtraControlCreator() (if its extra flexibility is really required),
// but, for demonstration purposes, this sample allows either one or the
// other.
const bool useExtra =
GetMenuBar()->IsChecked(DIALOGS_FILE_USE_EXTRA_CONTROL_CREATOR);
const bool hasExtra =
useExtra ? dialog.SetExtraControlCreator(&createMyExtraPanel)
: dialog.SetCustomizeHook(myCustomizer);
dialog.CentreOnParent();
dialog.SetDirectory(wxGetHomeDir());
if (dialog.ShowModal() == wxID_OK)
{
wxString extraInfo;
if ( hasExtra )
{
if ( useExtra )
{
wxWindow * const extra = dialog.GetExtraControl();
extraInfo = static_cast<MyExtraPanel*>(extra)->GetInfo();
}
else
{
extraInfo = myCustomizer.GetInfo();
}
}
else
{
extraInfo = "<not supported>";
}
wxString info;
wxWindow * const extra = dialog.GetExtraControl();
info.Printf("Full file name: %s\n"
"Path: %s\n"
"Name: %s\n"
@@ -1687,8 +1868,7 @@ void MyFrame::FileOpen(wxCommandEvent& WXUNUSED(event) )
dialog.GetPath(),
dialog.GetDirectory(),
dialog.GetFilename(),
extra ? static_cast<MyExtraPanel*>(extra)->GetInfo()
: wxString("None"));
extraInfo);
wxMessageDialog dialog2(this, info, "Selected file");
dialog2.ShowModal();
}
@@ -1741,6 +1921,8 @@ void MyFrame::FilesOpen(wxCommandEvent& WXUNUSED(event) )
wxEmptyString, wxEmptyString, wildcards,
wxFD_OPEN|wxFD_MULTIPLE);
dialog.Centre(wxCENTER_ON_SCREEN);
if (dialog.ShowModal() == wxID_OK)
{
wxArrayString paths, filenames;
@@ -1886,7 +2068,8 @@ void MyFrame::FileOpenGeneric(wxCommandEvent& WXUNUSED(event) )
"C++ files (*.cpp;*.h)|*.cpp;*.h"
);
dialog.SetExtraControlCreator(&createMyExtraPanel);
MyCustomizeHook myCustomizer(dialog);
dialog.SetCustomizeHook(myCustomizer);
dialog.SetDirectory(wxGetHomeDir());
if (dialog.ShowModal() == wxID_OK)
@@ -1894,10 +2077,12 @@ void MyFrame::FileOpenGeneric(wxCommandEvent& WXUNUSED(event) )
wxString info;
info.Printf("Full file name: %s\n"
"Path: %s\n"
"Name: %s",
"Name: %s\n"
"Custom window: %s",
dialog.GetPath(),
dialog.GetDirectory(),
dialog.GetFilename());
dialog.GetFilename(),
myCustomizer.GetInfo());
wxMessageDialog dialog2(this, info, "Selected file");
dialog2.ShowModal();
}

View File

@@ -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,

View File

@@ -21,9 +21,21 @@
#ifndef WX_PRECOMP
#include "wx/string.h"
#include "wx/intl.h"
#include "wx/panel.h"
#include "wx/sizer.h"
#include "wx/window.h"
#include "wx/button.h"
#include "wx/checkbox.h"
#include "wx/choice.h"
#include "wx/radiobut.h"
#include "wx/stattext.h"
#include "wx/textctrl.h"
#endif // WX_PRECOMP
#include "wx/filedlgcustomize.h"
#include "wx/private/filedlgcustomize.h"
extern WXDLLEXPORT_DATA(const char) wxFileDialogNameStr[] = "filedlg";
extern WXDLLEXPORT_DATA(const char) wxFileSelectorPromptStr[] = "Select a file";
extern WXDLLEXPORT_DATA(const char) wxFileSelectorDefaultWildcardStr[] =
@@ -34,6 +46,689 @@ extern WXDLLEXPORT_DATA(const char) wxFileSelectorDefaultWildcardStr[] =
#endif
;
// ============================================================================
// File dialog customization support
// ============================================================================
// ----------------------------------------------------------------------------
// wxFileDialogCustomControl and derived classes
// ----------------------------------------------------------------------------
// Everything here is trivial, but has to be defined here, where the private
// "Impl" classes are fully declared.
wxFileDialogCustomControlImpl::~wxFileDialogCustomControlImpl()
{
}
bool
wxFileDialogCustomControlImpl::DoBind(wxEvtHandler* WXUNUSED(handler))
{
// Do nothing here by default, some controls don't generate any events at
// all and so this function will never be called for them.
wxFAIL_MSG(wxS("Should be overridden if called"));
return false;
}
bool wxFileDialogCustomControl::OnDynamicBind(wxDynamicEventTableEntry& entry)
{
wxUnusedVar(entry); // Needed when debug is disabled.
wxFAIL_MSG(wxString::Format
(
"This custom control doesn't generate the event %d.",
entry.m_eventType
));
return false;
}
void wxFileDialogCustomControl::Show(bool show)
{
return m_impl->Show(show);
}
void wxFileDialogCustomControl::Enable(bool enable)
{
return m_impl->Enable(enable);
}
wxFileDialogCustomControl::~wxFileDialogCustomControl()
{
delete m_impl;
}
wxFileDialogButton::wxFileDialogButton(wxFileDialogButtonImpl* impl)
: wxFileDialogCustomControl(impl)
{
}
bool wxFileDialogButton::OnDynamicBind(wxDynamicEventTableEntry& entry)
{
if ( entry.m_eventType == wxEVT_BUTTON )
return GetImpl()->DoBind(this);
return wxFileDialogCustomControl::OnDynamicBind(entry);
}
wxFileDialogButtonImpl* wxFileDialogButton::GetImpl() const
{
return static_cast<wxFileDialogButtonImpl*>(m_impl);
}
wxFileDialogCheckBox::wxFileDialogCheckBox(wxFileDialogCheckBoxImpl* impl)
: wxFileDialogCustomControl(impl)
{
}
bool wxFileDialogCheckBox::OnDynamicBind(wxDynamicEventTableEntry& entry)
{
if ( entry.m_eventType == wxEVT_CHECKBOX )
return GetImpl()->DoBind(this);
return wxFileDialogCustomControl::OnDynamicBind(entry);
}
wxFileDialogCheckBoxImpl* wxFileDialogCheckBox::GetImpl() const
{
return static_cast<wxFileDialogCheckBoxImpl*>(m_impl);
}
bool wxFileDialogCheckBox::GetValue() const
{
return GetImpl()->GetValue();
}
void wxFileDialogCheckBox::SetValue(bool value)
{
GetImpl()->SetValue(value);
}
wxFileDialogRadioButton::wxFileDialogRadioButton(wxFileDialogRadioButtonImpl* impl)
: wxFileDialogCustomControl(impl)
{
}
bool wxFileDialogRadioButton::OnDynamicBind(wxDynamicEventTableEntry& entry)
{
if ( entry.m_eventType == wxEVT_RADIOBUTTON )
return GetImpl()->DoBind(this);
return wxFileDialogCustomControl::OnDynamicBind(entry);
}
wxFileDialogRadioButtonImpl* wxFileDialogRadioButton::GetImpl() const
{
return static_cast<wxFileDialogRadioButtonImpl*>(m_impl);
}
bool wxFileDialogRadioButton::GetValue() const
{
return GetImpl()->GetValue();
}
void wxFileDialogRadioButton::SetValue(bool value)
{
GetImpl()->SetValue(value);
}
wxFileDialogChoice::wxFileDialogChoice(wxFileDialogChoiceImpl* impl)
: wxFileDialogCustomControl(impl)
{
}
bool wxFileDialogChoice::OnDynamicBind(wxDynamicEventTableEntry& entry)
{
if ( entry.m_eventType == wxEVT_CHOICE )
return GetImpl()->DoBind(this);
return wxFileDialogCustomControl::OnDynamicBind(entry);
}
wxFileDialogChoiceImpl* wxFileDialogChoice::GetImpl() const
{
return static_cast<wxFileDialogChoiceImpl*>(m_impl);
}
int wxFileDialogChoice::GetSelection() const
{
return GetImpl()->GetSelection();
}
void wxFileDialogChoice::SetSelection(int n)
{
GetImpl()->SetSelection(n);
}
wxFileDialogTextCtrl::wxFileDialogTextCtrl(wxFileDialogTextCtrlImpl* impl)
: wxFileDialogCustomControl(impl)
{
}
wxFileDialogTextCtrlImpl* wxFileDialogTextCtrl::GetImpl() const
{
return static_cast<wxFileDialogTextCtrlImpl*>(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<wxFileDialogStaticTextImpl*>(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 <typename T>
T*
wxFileDialogCustomize::StoreAndReturn(T* control)
{
m_controls.push_back(control);
return control;
}
wxFileDialogButton*
wxFileDialogCustomize::AddButton(const wxString& label)
{
return StoreAndReturn(new wxFileDialogButton(m_impl->AddButton(label)));
}
wxFileDialogCheckBox*
wxFileDialogCustomize::AddCheckBox(const wxString& label)
{
return StoreAndReturn(new wxFileDialogCheckBox(m_impl->AddCheckBox(label)));
}
wxFileDialogRadioButton*
wxFileDialogCustomize::AddRadioButton(const wxString& label)
{
return StoreAndReturn(new wxFileDialogRadioButton(m_impl->AddRadioButton(label)));
}
wxFileDialogChoice*
wxFileDialogCustomize::AddChoice(size_t n, const wxString* strings)
{
return StoreAndReturn(new wxFileDialogChoice(m_impl->AddChoice(n, strings)));
}
wxFileDialogTextCtrl*
wxFileDialogCustomize::AddTextCtrl(const wxString& label)
{
return StoreAndReturn(new wxFileDialogTextCtrl(m_impl->AddTextCtrl(label)));
}
wxFileDialogStaticText*
wxFileDialogCustomize::AddStaticText(const wxString& label)
{
return StoreAndReturn(new wxFileDialogStaticText(m_impl->AddStaticText(label)));
}
// ----------------------------------------------------------------------------
// Generic implementation of wxFileDialogCustomize and related classes
// ----------------------------------------------------------------------------
namespace wxGenericCustomizer
{
// Template base class for the implementation classes below inheriting from the
// specified Impl subclass.
template <typename T>
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<wxFileDialogCustomControlImpl>
{
public:
// All custom controls are identified by their ID in this implementation.
explicit CustomControlImpl(wxWindow* win)
: ControlImplBase<wxFileDialogCustomControlImpl>(win)
{
}
wxDECLARE_NO_COPY_CLASS(CustomControlImpl);
};
class ButtonImpl : public ControlImplBase<wxFileDialogButtonImpl>
{
public:
ButtonImpl(wxWindow* parent, const wxString& label)
: ControlImplBase<wxFileDialogButtonImpl>
(
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<wxFileDialogCheckBoxImpl>
{
public:
CheckBoxImpl(wxWindow* parent, const wxString& label)
: ControlImplBase<wxFileDialogCheckBoxImpl>
(
new wxCheckBox(parent, wxID_ANY, label)
)
{
m_handler = NULL;
}
virtual bool GetValue() wxOVERRIDE
{
return GetCheckBox()->GetValue();
}
virtual void SetValue(bool value) wxOVERRIDE
{
GetCheckBox()->SetValue(value);
}
virtual bool DoBind(wxEvtHandler* handler) wxOVERRIDE
{
if ( !m_handler )
{
m_handler = handler;
m_win->Bind(wxEVT_CHECKBOX, &CheckBoxImpl::OnCheckBox, this);
}
return true;
}
private:
wxCheckBox* GetCheckBox() const
{
return static_cast<wxCheckBox*>(m_win);
}
void OnCheckBox(wxCommandEvent& event)
{
// See comments in OnButton() above, they also apply here.
wxCommandEvent eventCopy(event);
eventCopy.SetEventObject(m_handler);
m_handler->ProcessEvent(eventCopy);
}
wxEvtHandler* m_handler;
};
class RadioButtonImpl : public ControlImplBase<wxFileDialogRadioButtonImpl>
{
public:
RadioButtonImpl(wxWindow* parent, const wxString& label)
: ControlImplBase<wxFileDialogRadioButtonImpl>
(
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<wxRadioButton*>(m_win);
}
void OnRadioButton(wxCommandEvent& event)
{
// See comments in OnButton() above, they also apply here.
wxCommandEvent eventCopy(event);
eventCopy.SetEventObject(m_handler);
m_handler->ProcessEvent(eventCopy);
}
wxEvtHandler* m_handler;
};
class ChoiceImpl : public ControlImplBase<wxFileDialogChoiceImpl>
{
public:
ChoiceImpl(wxWindow* parent, size_t n, const wxString* strings)
: ControlImplBase<wxFileDialogChoiceImpl>
(
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<wxChoice*>(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<wxFileDialogTextCtrlImpl>
{
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<wxFileDialogTextCtrlImpl>
(
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<wxTextCtrl*>(m_win);
}
};
class StaticTextImpl : public ControlImplBase<wxFileDialogStaticTextImpl>
{
public:
StaticTextImpl(wxWindow* parent, const wxString& label)
: ControlImplBase<wxFileDialogStaticTextImpl>
(
new wxStaticText(parent, wxID_ANY, wxControl::EscapeMnemonics(label))
)
{
}
virtual void SetLabelText(const wxString& text) wxOVERRIDE
{
GetStaticText()->SetLabelText(text);
wxWindow* const parent = m_win->GetParent();
parent->GetSizer()->Fit(parent);
}
private:
wxStaticText* GetStaticText() const
{
return static_cast<wxStaticText*>(m_win);
}
};
// Generic implementation of wxFileDialogCustomize which is also a window that
// can be used as part of the dialog.
class Panel : public wxPanel,
public wxFileDialogCustomize,
private wxFileDialogCustomizeImpl
{
public:
Panel(wxWindow* parent, wxFileDialogCustomizeHook& customizeHook)
: wxPanel(parent),
wxFileDialogCustomize(this),
m_customizeHook(customizeHook),
m_lastWasRadio(false)
{
// Use a simple horizontal sizer to layout all the controls for now.
wxBoxSizer* const sizer = new wxBoxSizer(wxHORIZONTAL);
SetSizer(sizer);
// Leave a margin before the first item.
sizer->AddSpacer(wxSizerFlags::GetDefaultBorder());
// This will call our own AddXXX().
m_customizeHook.AddCustomControls(*this);
// Now that everything was created, resize and layout.
SetClientSize(sizer->ComputeFittingClientSize(this));
sizer->Layout();
}
virtual ~Panel()
{
m_customizeHook.TransferDataFromCustomControls();
}
// Implement wxFileDialogCustomizeImpl pure virtual methods.
wxFileDialogButtonImpl* AddButton(const wxString& label) wxOVERRIDE
{
m_lastWasRadio = false;
return AddToLayoutAndReturn<ButtonImpl>(label);
}
wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) wxOVERRIDE
{
m_lastWasRadio = false;
return AddToLayoutAndReturn<CheckBoxImpl>(label);
}
wxFileDialogRadioButtonImpl* AddRadioButton(const wxString& label) wxOVERRIDE
{
RadioButtonImpl* const impl = AddToLayoutAndReturn<RadioButtonImpl>(label);
if ( !m_lastWasRadio )
{
// Select the first button of a new radio group.
impl->SetValue(true);
m_lastWasRadio = true;
}
return impl;
}
wxFileDialogChoiceImpl* AddChoice(size_t n, const wxString* strings) wxOVERRIDE
{
m_lastWasRadio = false;
// TODO-C++11: Can't use AddToLayoutAndReturn() here easily without
// variadic templates.
ChoiceImpl* const impl = new ChoiceImpl(this, n, strings);
AddToLayout(impl->m_win);
return impl;
}
wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) wxOVERRIDE
{
m_lastWasRadio = false;
if ( !label.empty() )
{
AddToLayout(new wxStaticText(this, wxID_ANY, label));
}
return AddToLayoutAndReturn<TextCtrlImpl>();
}
wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) wxOVERRIDE
{
m_lastWasRadio = false;
return AddToLayoutAndReturn<StaticTextImpl>(label);
}
private:
void AddToLayout(wxWindow* win)
{
GetSizer()->Add(win, wxSizerFlags().Center().Border(wxRIGHT));
}
template <typename T>
T* AddToLayoutAndReturn(const wxString& label = wxString())
{
T* const controlImpl = new T(this, label);
AddToLayout(controlImpl->m_win);
return controlImpl;
}
wxFileDialogCustomizeHook& m_customizeHook;
bool m_lastWasRadio;
wxDECLARE_NO_COPY_CLASS(Panel);
};
} // namespace wxGenericCustomizer
//----------------------------------------------------------------------------
// wxFileDialogBase
//----------------------------------------------------------------------------
@@ -45,6 +740,7 @@ void wxFileDialogBase::Init()
m_filterIndex = 0;
m_currentlySelectedFilterIndex = wxNOT_FOUND;
m_windowStyle = 0;
m_customizeHook = NULL;
m_extraControl = NULL;
m_extraControlCreator = NULL;
}
@@ -163,6 +859,18 @@ wxString wxFileDialogBase::AppendExtension(const wxString &filePath,
return filePath + ext;
}
bool wxFileDialogBase::SetCustomizeHook(wxFileDialogCustomizeHook& customizeHook)
{
if ( !SupportsExtraControl() )
return false;
wxASSERT_MSG( !m_extraControlCreator,
"Call either SetExtraControlCreator() or SetCustomizeHook()" );
m_customizeHook = &customizeHook;
return true;
}
bool wxFileDialogBase::SetExtraControlCreator(ExtraControlCreatorFunction creator)
{
wxCHECK_MSG( !m_extraControlCreator, false,
@@ -172,28 +880,34 @@ bool wxFileDialogBase::SetExtraControlCreator(ExtraControlCreatorFunction creato
return SupportsExtraControl();
}
bool wxFileDialogBase::CreateExtraControl()
wxWindow* wxFileDialogBase::CreateExtraControlWithParent(wxWindow* parent) const
{
if (!m_extraControlCreator || m_extraControl)
return false;
m_extraControl = (*m_extraControlCreator)(this);
return true;
if ( m_customizeHook )
return new wxGenericCustomizer::Panel(parent, *m_customizeHook);
if ( m_extraControlCreator )
return (*m_extraControlCreator)(parent);
// It's not an error to call this function if there are no extra controls
// to create, just do nothing in this case.
return NULL;
}
wxSize wxFileDialogBase::GetExtraControlSize()
bool wxFileDialogBase::CreateExtraControl()
{
if ( !m_extraControlCreator )
return wxDefaultSize;
// We're not supposed to be called more than once normally, but just do
// nothing if we had already created the custom controls somehow.
if ( !m_extraControl )
m_extraControl = CreateExtraControlWithParent(this);
// create the extra control in an empty dialog just to find its size: this
// is not terribly efficient but we do need to know the size before
// creating the native dialog and this seems to be the only way
wxDialog dlg(NULL, wxID_ANY, wxString());
return (*m_extraControlCreator)(&dlg)->GetSize();
return m_extraControl != NULL;
}
void wxFileDialogBase::UpdateExtraControlUI()
{
if ( m_customizeHook )
m_customizeHook->UpdateCustomControls();
if ( m_extraControl )
m_extraControl->UpdateWindowUI(wxUPDATE_UI_RECURSE);
}

View File

@@ -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

View File

@@ -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 <initguid.h>
// IFileOpenDialog implementation needs wxDynamicLibrary for
// run-time linking SHCreateItemFromParsingName(), available
// only under Windows Vista and newer.
// It also needs a compiler providing declarations and definitions
// of interfaces available in Windows Vista.
#if wxUSE_DYNLIB_CLASS && defined(__IFileOpenDialog_INTERFACE_DEFINED__)
#define wxUSE_IFILEOPENDIALOG 1
#else
#define wxUSE_IFILEOPENDIALOG 0
#endif
#include "wx/msw/private/cotaskmemptr.h"
#include "wx/dynlib.h"
#if wxUSE_IFILEOPENDIALOG
// IFileDialog related declarations missing from some compilers headers.
#if defined(__VISUALC__)
@@ -68,6 +52,31 @@ DEFINE_GUID(IID_IShellItem,
#endif // wxUSE_IFILEOPENDIALOG
// ----------------------------------------------------------------------------
// private functions prototypes
// ----------------------------------------------------------------------------
#if wxUSE_IFILEOPENDIALOG
namespace
{
// helper functions for wxDirDialog::ShowIFileOpenDialog()
bool GetPathsFromIFileOpenDialog(IFileOpenDialog* fileDialog, wxArrayString& paths);
bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path);
} // anonymous namespace
#endif // #if wxUSE_IFILEOPENDIALOG
// Note that parts of this file related to IFileDialog are still compiled even
// when wxUSE_DIRDLG == 0 because they're used by wxUSE_FILEDLG too.
#if wxUSE_DIRDLG
// callback used in wxDirDialog::ShowSHBrowseForFolder()
static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp,
LPARAM pData);
// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------
@@ -82,26 +91,6 @@ DEFINE_GUID(IID_IShellItem,
wxIMPLEMENT_CLASS(wxDirDialog, wxDialog);
// ----------------------------------------------------------------------------
// private functions prototypes
// ----------------------------------------------------------------------------
#if wxUSE_IFILEOPENDIALOG
// helper functions for wxDirDialog::ShowIFileOpenDialog()
bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath,
bool multipleSelection, bool showHidden, wxCOMPtr<IFileOpenDialog>& fileDialog);
bool GetPathsFromIFileOpenDialog(const wxCOMPtr<IFileOpenDialog>& fileDialog, bool multipleSelection,
wxArrayString& paths);
bool ConvertIShellItemToPath(const wxCOMPtr<IShellItem>& item, wxString& path);
#endif // #if wxUSE_IFILEOPENDIALOG
// callback used in wxDirDialog::ShowSHBrowseForFolder()
static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp,
LPARAM pData);
// ============================================================================
// implementation
// ============================================================================
@@ -253,16 +242,69 @@ int wxDirDialog::ShowSHBrowseForFolder(WXHWND owner)
int wxDirDialog::ShowIFileOpenDialog(WXHWND owner)
{
HRESULT hr = S_OK;
wxCOMPtr<IFileOpenDialog> fileDialog;
wxMSWImpl::wxIFileDialog fileDialog(CLSID_FileOpenDialog);
if ( !fileDialog.IsOk() )
return wxID_NONE;
if ( !InitIFileOpenDialog(m_message, m_path, HasFlag(wxDD_MULTIPLE),
HasFlag(wxDD_SHOW_HIDDEN), fileDialog) )
fileDialog.SetTitle(m_message);
if ( !m_path.empty() )
fileDialog.SetInitialPath(m_path);
// We currently always use FOS_NOCHANGEDIR even if wxDD_CHANGE_DIR was
// specified because we change the directory ourselves in this case.
int options = FOS_PICKFOLDERS | FOS_NOCHANGEDIR;
if ( HasFlag(wxDD_MULTIPLE) )
options |= FOS_ALLOWMULTISELECT;
if ( HasFlag(wxDD_SHOW_HIDDEN) )
options |= FOS_FORCESHOWHIDDEN;
return fileDialog.Show(owner, options, &m_paths, &m_path);
}
#endif // wxUSE_IFILEOPENDIALOG
#endif // wxUSE_DIRDLG
#if wxUSE_IFILEOPENDIALOG
// ----------------------------------------------------------------------------
// Helper functions used by wxDirDialog and wxFileDialog.
// ----------------------------------------------------------------------------
namespace wxMSWImpl
{
wxIFileDialog::wxIFileDialog(const CLSID& clsid)
{
HRESULT hr = ::CoCreateInstance
(
clsid,
NULL, // no outer IUnknown
CLSCTX_INPROC_SERVER,
wxIID_PPV_ARGS(IFileOpenDialog, &m_fileDialog)
);
if ( FAILED(hr) )
{
return wxID_NONE; // Failed to initialize the dialog
wxLogApiError(wxS("CoCreateInstance(CLSID_FileOpenDialog)"), hr);
}
}
int wxIFileDialog::Show(HWND owner, int options,
wxArrayString* pathsOut, wxString* pathOut)
{
wxCHECK_MSG( m_fileDialog, wxID_NONE, wxS("shouldn't be called") );
HRESULT hr;
// allow to select only a file system object
hr = m_fileDialog->SetOptions(options | FOS_FORCEFILESYSTEM);
if ( FAILED(hr) )
{
wxLogApiError(wxS("IFileOpenDialog::SetOptions"), hr);
return false;
}
hr = fileDialog->Show(owner);
hr = m_fileDialog->Show(owner);
if ( FAILED(hr) )
{
if ( hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) )
@@ -274,15 +316,24 @@ int wxDirDialog::ShowIFileOpenDialog(WXHWND owner)
wxLogApiError(wxS("IFileDialog::Show"), hr);
}
}
else if ( GetPathsFromIFileOpenDialog(fileDialog, HasFlag(wxDD_MULTIPLE),
m_paths) )
else if ( options & FOS_ALLOWMULTISELECT )
{
if ( !HasFlag(wxDD_MULTIPLE) )
wxCOMPtr<IFileOpenDialog> fileOpenDialog;
hr = m_fileDialog->QueryInterface(wxIID_PPV_ARGS(IFileOpenDialog, &fileOpenDialog));
if ( SUCCEEDED(hr) )
{
m_path = m_paths.Last();
if ( GetPathsFromIFileOpenDialog(fileOpenDialog, *pathsOut) )
return wxID_OK;
}
return wxID_OK;
else
{
wxLogApiError(wxS("IFileDialog::QI(IFileOpenDialog)"), hr);
}
}
else // Single selection only, path output parameter must be non-null.
{
if ( GetPathFromIFileDialog(m_fileDialog, *pathOut) )
return wxID_OK;
}
// Failed to show the dialog or obtain the selected folders(s)
@@ -290,160 +341,124 @@ int wxDirDialog::ShowIFileOpenDialog(WXHWND owner)
return wxID_CANCEL;
}
// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------
// helper function for wxDirDialog::ShowIFileOpenDialog()
bool InitIFileOpenDialog(const wxString& message, const wxString& defaultPath,
bool multipleSelection, bool showHidden,
wxCOMPtr<IFileOpenDialog>& fileDialog)
void wxIFileDialog::SetTitle(const wxString& message)
{
HRESULT hr = S_OK;
wxCOMPtr<IFileOpenDialog> dlg;
// allow to select only a file system folder, do not change the CWD
long options = FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_NOCHANGEDIR;
hr = ::CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,
wxIID_PPV_ARGS(IFileOpenDialog, &dlg));
if ( FAILED(hr) )
{
wxLogApiError(wxS("CoCreateInstance(CLSID_FileOpenDialog)"), hr);
return false;
}
if ( multipleSelection )
options |= FOS_ALLOWMULTISELECT;
if ( showHidden )
options |= FOS_FORCESHOWHIDDEN;
hr = dlg->SetOptions(options);
if ( FAILED(hr) )
{
wxLogApiError(wxS("IFileOpenDialog::SetOptions"), hr);
return false;
}
hr = dlg->SetTitle(message.wc_str());
HRESULT hr = m_fileDialog->SetTitle(message.wc_str());
if ( FAILED(hr) )
{
// This error is not serious, let's just log it and continue even
// without the title set.
wxLogApiError(wxS("IFileOpenDialog::SetTitle"), hr);
}
}
// set the initial path
if ( !defaultPath.empty() )
void wxIFileDialog::SetInitialPath(const wxString& defaultPath)
{
HRESULT hr;
// We need to link SHCreateItemFromParsingName() dynamically as it's
// not available on pre-Vista systems.
typedef HRESULT
(WINAPI *SHCreateItemFromParsingName_t)(PCWSTR,
IBindCtx*,
REFIID,
void**);
static SHCreateItemFromParsingName_t
s_pfnSHCreateItemFromParsingName = (SHCreateItemFromParsingName_t)-1;
if ( s_pfnSHCreateItemFromParsingName == (SHCreateItemFromParsingName_t)-1 )
{
// We need to link SHCreateItemFromParsingName() dynamically as it's
// not available on pre-Vista systems.
typedef HRESULT
(WINAPI *SHCreateItemFromParsingName_t)(PCWSTR,
IBindCtx*,
REFIID,
void**);
SHCreateItemFromParsingName_t pfnSHCreateItemFromParsingName = NULL;
wxDynamicLibrary dllShell32;
if ( dllShell32.Load(wxS("shell32.dll"), wxDL_VERBATIM | wxDL_QUIET) )
{
wxDL_INIT_FUNC(pfn, SHCreateItemFromParsingName, dllShell32);
wxDL_INIT_FUNC(s_pfn, SHCreateItemFromParsingName, dllShell32);
}
if ( !pfnSHCreateItemFromParsingName )
if ( !s_pfnSHCreateItemFromParsingName )
{
wxLogLastError(wxS("SHCreateItemFromParsingName() not found"));
return false;
}
wxCOMPtr<IShellItem> folder;
hr = pfnSHCreateItemFromParsingName(defaultPath.wc_str(),
NULL,
wxIID_PPV_ARGS(IShellItem,
&folder));
// Failing to parse the folder name or set it is not really an error,
// we'll just ignore the initial directory in this case, but we should
// still show the dialog.
if ( SUCCEEDED(hr) )
{
hr = dlg->SetFolder(folder);
if ( FAILED(hr) )
wxLogApiError(wxS("IFileOpenDialog::SetFolder"), hr);
}
}
fileDialog = dlg;
return true;
if ( !s_pfnSHCreateItemFromParsingName )
{
// There is nothing we can do and the error was already reported.
return;
}
wxCOMPtr<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
// still show the dialog.
if ( SUCCEEDED(hr) )
{
hr = m_fileDialog->SetFolder(folder);
if ( FAILED(hr) )
wxLogApiError(wxS("IFileOpenDialog::SetFolder"), hr);
}
}
} // namespace wxMSWImpl
// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------
namespace
{
// helper function for wxDirDialog::ShowIFileOpenDialog()
bool GetPathsFromIFileOpenDialog(const wxCOMPtr<IFileOpenDialog>& fileDialog, bool multipleSelection,
wxArrayString& paths)
bool GetPathsFromIFileOpenDialog(IFileOpenDialog* fileDialog, wxArrayString& paths)
{
HRESULT hr = S_OK;
wxString path;
wxArrayString tempPaths;
if ( multipleSelection )
wxCOMPtr<IShellItemArray> itemArray;
hr = fileDialog->GetResults(&itemArray);
if ( FAILED(hr) )
{
wxCOMPtr<IShellItemArray> 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<IShellItem> 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<IShellItem> item;
hr = fileDialog->GetResult(&item);
hr = itemArray->GetItemAt(i, &item);
if ( FAILED(hr) )
{
wxLogApiError(wxS("IFileOpenDialog::GetResult"), hr);
return false;
// do not attempt to retrieve any other items
// and just fail
wxLogApiError(wxS("IShellItemArray::GetItem"), hr);
tempPaths.clear();
break;
}
if ( !ConvertIShellItemToPath(item, path) )
hr = wxMSWImpl::GetFSPathFromShellItem(item, path);
if ( FAILED(hr) )
{
return false;
// again, just fail
tempPaths.clear();
break;
}
tempPaths.push_back(path);
@@ -456,8 +471,30 @@ bool GetPathsFromIFileOpenDialog(const wxCOMPtr<IFileOpenDialog>& fileDialog, bo
return true;
}
// helper function for wxDirDialog::ShowIFileOpenDialog()
bool ConvertIShellItemToPath(const wxCOMPtr<IShellItem>& item, wxString& path)
bool GetPathFromIFileDialog(IFileDialog* fileDialog, wxString& path)
{
wxCOMPtr<IShellItem> item;
HRESULT hr = fileDialog->GetResult(&item);
if ( FAILED(hr) )
{
wxLogApiError(wxS("IFileDialog::GetResult"), hr);
return false;
}
hr = wxMSWImpl::GetFSPathFromShellItem(item, path);
if ( FAILED(hr) )
{
return false;
}
return true;
}
} // anonymous namespace
HRESULT
wxMSWImpl::GetFSPathFromShellItem(const wxCOMPtr<IShellItem>& item, wxString& path)
{
wxCoTaskMemPtr<WCHAR> pOLEPath;
const HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &pOLEPath);
@@ -465,16 +502,18 @@ bool ConvertIShellItemToPath(const wxCOMPtr<IShellItem>& item, wxString& path)
if ( FAILED(hr) )
{
wxLogApiError(wxS("IShellItem::GetDisplayName"), hr);
return false;
return hr;
}
path = pOLEPath;
return true;
return S_OK;
}
#endif // wxUSE_IFILEOPENDIALOG
#if wxUSE_DIRDLG
// callback used in wxDirDialog::ShowSHBrowseForFolder()
static int CALLBACK
BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData)
@@ -525,6 +564,4 @@ BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData)
return 0;
}
#endif // compiler/platform on which the code here compiles
#endif // wxUSE_DIRDLG

File diff suppressed because it is too large Load Diff

View File

@@ -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;