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