//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1992, 1996 by Borland International, All Rights Reserved
//
/// \file
/// Implementation of OpenSave abstract, FileOpen, FileSave Common Dialog
/// classes
//----------------------------------------------------------------------------
#include <owl/pch.h>
#include <owl/opensave.h>
#include <owl/commctrl.h>
#include <utility>
namespace owl {
OWL_DIAGINFO;
//
/// Constructs a TOpenSaveDialog::TData structure.
///
/// \note See TData::SetFilter for more information about the formatting of the \p filter
/// parameter. It is very important that this parameter is formatted correctly, otherwise buffer
/// overrun may ensue.
///
/// \sa TOpenSaveDialog::TData for a description of the fields corresponding to the parameters.
//
TOpenSaveDialog::TData::TData(
uint32 flags,
LPCTSTR filter,
LPTSTR, // customFilter, // Dummy for backward compatibility.
LPCTSTR initialDir,
LPCTSTR defExt,
int maxPath,
int filterIndex,
uint32 flagsEx
)
: Flags(flags),
FlagsEx(flagsEx),
Error(0),
FileName(0),
Filter(0),
FilterIndex(filterIndex),
InitialDir(initialDir),
DefExt(defExt),
MaxPath(maxPath ? maxPath : _MAX_PATH)
{
FileName = new tchar[MaxPath];
*FileName = 0;
SetFilter(filter);
// OFN_EXPLORER implies long names, but we include OFN_LONGNAMES in case OFN_EXPLORER is turned
// off by user code later.
//
Flags |= OFN_EXPLORER | OFN_LONGNAMES;
}
//
/// String-aware overload
/// \todo Add string support for all parameters.
//
TOpenSaveDialog::TData::TData(
uint32 flags,
const tstring& filter,
LPTSTR, // customFilter, // Dummy for backward compatibility.
LPCTSTR initialDir,
LPCTSTR defExt,
int maxPath,
int filterIndex,
uint32 flagsEx
)
: Flags(flags),
FlagsEx(flagsEx),
Error(0),
FileName(0),
Filter(0),
FilterIndex(filterIndex),
InitialDir(initialDir),
DefExt(defExt),
MaxPath(maxPath ? maxPath : _MAX_PATH)
{
FileName = new tchar[MaxPath];
*FileName = 0;
SetFilter(filter);
Flags |= OFN_EXPLORER | OFN_LONGNAMES;
}
//
/// Performs a deep copy of the given object.
//
TOpenSaveDialog::TData::TData(const TData& src)
: Flags(src.Flags),
FlagsEx(src.FlagsEx),
Error(src.Error),
FileName(0),
Filter(0),
FilterIndex(src.FilterIndex),
InitialDir(src.InitialDir),
DefExt(src.DefExt),
MaxPath(src.MaxPath)
{
FileName = strnewdup(src.FileName, MaxPath);
SetFilter(src.Filter);
}
//
/// Frees memory for allocated buffers.
//
TOpenSaveDialog::TData::~TData()
{
delete[] FileName;
delete[] Filter;
}
//
/// Performs a deep copy of the given object.
//
auto TOpenSaveDialog::TData::operator =(const TData& src) -> TData&
{
if (&src == this) return *this;
// Creating copies may fail, so do this first for strong exception safety.
//
auto c = TData{src};
using namespace std;
swap(Flags, c.Flags);
swap(FlagsEx, c.FlagsEx);
swap(Error, c.Error);
swap(FileName, c.FileName);
swap(Filter, c.Filter);
swap(FilterIndex, c.FilterIndex);
swap(InitialDir, c.InitialDir);
swap(DefExt, c.DefExt);
swap(MaxPath, c.MaxPath);
return *this;
}
//
/// Sets the file type filter strings.
///
/// The given string should contain a list of filter specifications as pairs of strings
/// separated by the vertical line character '|'. The first string in the pair specifies the filter
/// description, which is shown in the file type selection field in the dialog box, and the second
/// string is the actual filter. Multiple filter specifications can be specified, each separated by
/// a vertical line character. A trailing vertical line can be included, but this is optional and
/// has no effect.
///
/// The use of the vertical line separator allows the given string to be stored in a resource file
/// as a single string including all the filter specifications, for example:
///
/// \code
/// "Text Files (*.txt)|*.txt|All Files (*.*)|*.*"
/// \endcode
///
/// A copy is taken of the passed string, and the orginal is left unchanged.
///
/// Internally, the vertical line character is replaced by null-characters in the internal buffer,
/// as per Windows API specifications for the corresponding field in the OPENFILENAME structure.
/// The internal buffer is properly null-terminated by a sequence of two null-characters, in
/// accordance with the specifications, whether or not a trailing vertical line is present.
///
/// \note You can also pass a pointer to the first of a contiguous sequence of null-terminated
/// strings, the last of which terminated by two null-characters. The function assumes this is the
/// case, if it cannot find a vertical line character in the string given. Unless you want this
/// interpretation, it is hence very important that you ensure that you provide a proper filter
/// specification with at least one vertical line character. If you incorrectly pass a single
/// string without a vertical line character, buffer overrun will ensue. Note that this behaviour
/// is deprecated, and it may be removed in the future, so translate your filter strings into a
/// single string using the vertical line character as filter separator.
///
/// \sa <a href="https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamea">
/// OPENFILENAME</a> in the Windows API.
//
void
TOpenSaveDialog::TData::SetFilter(LPCTSTR filter)
{
// Copy filter string
//
if (filter) {
delete[] Filter;
if (_tcschr(filter, _T('|'))) {
uint len = static_cast<uint>(::_tcslen(filter)) + 2; // one for each terminating 0
Filter = ::_tcscpy(new tchar[len], filter);
Filter[len-1] = 0; // in case trailing '|' is missing
}
else {
LPCTSTR p = filter;
while (*p)
p += ::_tcslen(p) + 1; // scan for 00 at end
uint len = uint(p - filter) + 1; // one more for last 0
Filter = new tchar[len];
memcpy(Filter, filter, sizeof(tchar) * len);
}
}
// Stomp |s with 0s
//
if (Filter) {
for (TCharIterator<tchar> i(Filter); ; i++) {
if (!*i.Current())
break;
if (*i.Current() == _T('|'))
*i.Current() = 0;
}
}
}
//
/// String-aware overload.
/// \sa SetFilter(LPCTSTR)
//
void TOpenSaveDialog::TData::SetFilter(const tstring& filter) { SetFilter(filter.c_str()); }
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(TOpenSaveDialog, TCommonDialog)
END_RESPONSE_TABLE;
const uint TOpenSaveDialog::ShareViMsgId = ::RegisterWindowMessage(SHAREVISTRING);
//
/// Initializes a TOpenSaveDialog object with the current resource ID.
//
void
TOpenSaveDialog::Init(TResId templateId)
{
memset(&ofn, 0, sizeof(OPENFILENAME));
#if defined(OPENFILENAME_SIZE_VERSION_400)
ofn.lStructSize = TSystem::GetMajorVersion() < 5 ?
OPENFILENAME_SIZE_VERSION_400 : sizeof(OPENFILENAME);
#else
ofn.lStructSize = sizeof(OPENFILENAME);
#endif
ofn.hwndOwner = GetParentO() ? GetParentO()->GetHandle() : 0;
ofn.hInstance = *GetModule();
ofn.Flags = OFN_ENABLEHOOK | Data.Flags;
if (templateId) {
ofn.lpTemplateName = templateId;
ofn.Flags |= OFN_ENABLETEMPLATE;
}
else
ofn.Flags &= ~OFN_ENABLETEMPLATE;
#if WINVER >= 0x500 && !defined(WINELIB)
ofn.FlagsEx = Data.FlagsEx;
#endif
ofn.lpfnHook = 0;
ofn.lpstrFilter = Data.Filter;
ofn.nFilterIndex = Data.FilterIndex;
ofn.lpstrFile = Data.FileName;
ofn.nMaxFile = Data.MaxPath;
ofn.lpstrInitialDir = Data.InitialDir;
ofn.lpstrDefExt = Data.DefExt;
}
//
/// Constructs an open save dialog box object with the supplied parent window, data,
/// resource ID, title, and current module object.
//
TOpenSaveDialog::TOpenSaveDialog(TWindow* parent, TData& data, TModule* module)
:
TCommonDialog(parent, 0, module),
Data(data)
{
}
//
//
//
TOpenSaveDialog::TOpenSaveDialog(TWindow* parent,
TData& data,
TResId templateId,
LPCTSTR title,
TModule* module)
:
TCommonDialog(parent, 0, module),
Data(data),
Title(title ? title : _T(""))
{
Init(templateId);
ofn.lpstrTitle = title ? &Title[0] : 0;
}
//
/// String-aware overload
//
TOpenSaveDialog::TOpenSaveDialog(
TWindow* parent,
TData& data,
TResId templateId,
const tstring& title,
TModule* module)
:
TCommonDialog(parent, 0, module),
Data(data),
Title(title)
{
Init(templateId);
ofn.lpstrTitle = &Title[0];
}
//
/// Handles CDN_SHAREVIOLATION and the older SHAREVISTRING registered message.
/// Calls the base implementation to handle all other messages.
//
INT_PTR
TOpenSaveDialog::DialogFunction(TMsgId msg, TParam1 param1, TParam2 param2)
{
const bool explorerStyle = ofn.Flags & OFN_EXPLORER;
// First check for the new message.
//
TNotify* n = (msg == WM_NOTIFY) ? reinterpret_cast<TNotify*>(param2) : 0;
if (n && n->code == CDN_SHAREVIOLATION)
{
WARN(!explorerStyle, _T("CDN_SHAREVIOLATION was sent to old-style dialog."));
int r = ShareViolation();
// The documentation for CDN_SHAREVIOLATION states that 0 should be
// returned to get the standard warning message for a sharing violation.
// It does not list OFN_SHAREWARN as a valid DWL_MSGRESULT value.
// Note that this causes the old SHAREVISTRING to be sent; see below.
//
if (r == OFN_SHAREWARN)
return 0;
CHECK(!OWL_STRICT || r == OFN_SHAREFALLTHROUGH || r == OFN_SHARENOWARN);
WARN(r != OFN_SHAREFALLTHROUGH && r != OFN_SHARENOWARN, _T("ShareViolation returned undefined value: ") << r);
return SetMsgResult(r);
}
// Now check for the old message.
//
else if (msg == TOpenSaveDialog::ShareViMsgId)
{
int r = ShareViolation();
// Do not handle SHAREVISTRING for Explorer-style dialogs.
// The message is a result of the default handling for CDN_SHAREVIOLATION, i.e.
// when CDN_SHAREVIOLATION returns 0 then SHAREVISTRING is sent.
//
if (explorerStyle)
{
WARN(r != OFN_SHAREWARN, _T("ShareViolation returned unexpected value in Explorer-style dialog:") << r);
return 0;
}
CHECK(!OWL_STRICT || r == OFN_SHAREFALLTHROUGH || r == OFN_SHARENOWARN || r == OFN_SHAREWARN);
WARN(r != OFN_SHAREFALLTHROUGH && r != OFN_SHARENOWARN && r != OFN_SHAREWARN,
_T("ShareViolation returned undefined value: ") << r);
// The documentation for SHAREVISTRING states that the result should be
// returned directly, rather than through DWL_MSGRESULT.
//
return r;
}
// If we get here, pass the message on to the base.
//
return TCommonDialog::DialogFunction(msg, param1, param2);
}
//
/// If a sharing violation occurs when a file is opened or saved, ShareViolation is
/// called to obtain a response. The default return value is OFN_SHAREWARN. Other
/// sharing violation responses are listed in the following table.
/// - \c \b OFN_SHAREFALLTHROUGH Specifies that the file name can be used and that the
/// dialog box should return it to the application.
/// - \c \b OFN_SHARENOWARN Instructs the dialog box to perform no further action with
/// the file name and not to warn the user of the situation.
/// - \c \b OFN_SHAREWARN This is the default response that is defined as 0. Instructs the
/// dialog box to display a standard warning message.
//
int
TOpenSaveDialog::ShareViolation()
{
return OFN_SHAREWARN;
}
//----------------------------------------------------------------------------
//
/// Constructs and initializes the TFileOpen object based on information in the
/// TOpenSaveDialog::TData data structure. The parent argument points to the dialog
/// box's parent window. data is a reference to the TData object. templateID is the
/// ID for a custom template. title is an optional title. module points to the
/// module instance.
//
TFileOpenDialog::TFileOpenDialog(TWindow* parent,
TData& data,
TResId templateId,
LPCTSTR title,
TModule* module)
:
TOpenSaveDialog(parent, data, templateId, title, module)
{
}
//
/// Creates the TFileOpenDialog object.
//
int
TFileOpenDialog::DoExecute()
{
ofn.lpfnHook = LPOFNHOOKPROC(StdDlgProc);
int ret = GetOpenFileName(&ofn);
Data.Error = CommDlgExtendedError();
if (ret) {
Data.Flags = ofn.Flags;
Data.FilterIndex = (int)ofn.nFilterIndex;
}
return ret ? IDOK : IDCANCEL;
}
//----------------------------------------------------------------------------
//
/// Constructs and initializes the TFileOpen object based on the
/// TOpenSaveDialog::TData structure, which contains information about the file
/// name, file directory, and file name search filers.
//
TFileSaveDialog::TFileSaveDialog(TWindow* parent,
TData& data,
TResId templateId,
LPCTSTR title,
TModule* module)
:
TOpenSaveDialog(parent, data, templateId, title, module)
{
}
//
/// Creates the TFileSaveDialog object.
//
int
TFileSaveDialog::DoExecute()
{
ofn.lpfnHook = LPOFNHOOKPROC(StdDlgProc);
int ret = GetSaveFileName(&ofn);
Data.Error = CommDlgExtendedError();
if (ret) {
Data.Flags = ofn.Flags;
Data.FilterIndex = ofn.nFilterIndex;
}
return ret ? IDOK : IDCANCEL;
}
//
//
//
void
TOpenSaveDialog::TData::Read(ipstream& is)
{
is >> Flags;
#if defined(UNICODE)
_USES_CONVERSION;
char* fileName = is.readString();
char* filter = is.freadString();
char* customFilter = is.freadString(); // Dummy for backward compatibility.
if (customFilter) delete [] customFilter; // Discard dummy.
FileName = strnewdup(_A2W(fileName));
Filter = strnewdup(_A2W(filter));
delete[] fileName;
delete[] filter;
is >> FilterIndex;
char* initDir = is.freadString();
char* defExt = is.freadString();
InitialDir = strnewdup(_A2W(initDir));
DefExt = strnewdup(_A2W(defExt));
delete[] initDir;
delete[] defExt;
#else
FileName = is.readString();
Filter = is.freadString();
const auto customFilter = is.freadString(); // Dummy for backward compatibility.
if (customFilter) delete[] customFilter; // Discard dummy.
is >> FilterIndex;
InitialDir = is.freadString();
DefExt = is.freadString();
#endif
is >> FlagsEx;
}
//
//
//
void
TOpenSaveDialog::TData::Write(opstream& os)
{
_USES_CONVERSION;
os << Flags;
os.writeString(_W2A(FileName));
os.fwriteString(_W2A(Filter));
const auto customFilter = nullptr; // Dummy for backward compatibility.
os.fwriteString(customFilter);
os << FilterIndex;
os.fwriteString(_W2A(InitialDir));
os.fwriteString(_W2A(DefExt));
os << FlagsEx;
}
//
/// Returns a vector containing the full paths of every file entry in member FileName.
/// This function is only useful in conjunction with a multi-selection TFileOpenDialog using the
/// Explorer style.
///
/// \note It is assumed that the buffer pointed to by member FileName contains a null-separated
/// list of file names terminated by two null characters, as will be the case if filled in by a
/// multi-selection Explorer-style TFileOpenDialog. Note that this function does not work unless
/// the flags OFN_EXPLORER and OFN_ALLOWMULTISELECT were both set!
///
/// Usage:
///
/// \code
/// void TBmpViewWindow::CmFileOpen()
/// {
/// TOpenSaveDialog::TData data
/// {
/// OFN_EXPLORER | OFN_ALLOWMULTISELECT | OFN_FILEMUSTEXIST, // flags
/// "Bitmap Files (*.bmp)|*.bmp", // filter
/// nullptr, // customFilter
/// nullptr, // initialDir
/// nullptr, // defExt
/// 65536 // maxPath (ample buffer space for multiple selections)
/// };
/// TFileOpenDialog dlg{this, data};
/// const auto r = dlg.Execute();
/// if (CommDlgExtendedError() == FNERR_BUFFERTOOSMALL)
/// {
/// MessageBox("Buffer too small!", "File Open Error", MB_OK | MB_ICONEXCLAMATION);
/// return;
/// }
/// if (r == IDOK)
/// for (const auto& f : data.GetFileNames())
/// OpenFile(f);
/// }
/// \endcode
///
/// \sa http://docs.microsoft.com/en-us/windows/desktop/api/Commdlg/ns-commdlg-tagofna
//
auto TOpenSaveDialog::TData::GetFileNames() const -> std::vector<tstring>
{
PRECONDITION((Flags & OFN_EXPLORER) && (Flags & OFN_ALLOWMULTISELECT));
auto v = std::vector<tstring>{};
auto p = FileName;
const auto d = tstring{p};
p += d.size() + 1;
CHECK(static_cast<int>(p - FileName) < MaxPath);
// NOTE: We assume double null-termination here, even for a single file!
//
if (*p == _T('\0'))
{
// We have a single entry, being the full path to the file.
//
v.push_back(d);
}
else
{
// We have multiple entries following the directory path, each being the bare file name without a path.
// Compose the full path to each file by prepending the directory path.
//
while (*p != _T('\0'))
{
const auto n = tstring{p};
const auto f = d + _T('\\') + n;
v.push_back(f);
p += n.size() + 1;
CHECK(static_cast<int>(p - FileName) < MaxPath);
}
}
return v;
}
} // OWL namespace
/* ========================================================================== */
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: ofn.
↑ V803 Decreased performance. In case 'i' is iterator it's more effective to use prefix form of increment. Replace iterator++ with ++iterator.