//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1992, 1996 by Borland International, All Rights Reserved
//
/// \file
/// Implementation of class TPrintDialog, a Print and PrintSetup common Dialog
/// encapsulation
//----------------------------------------------------------------------------
#include <owl/pch.h>
#include <owl/printdia.h>
#include <owl/dc.h>
#include <owl/framewin.h>
#include <memory>
 
#if defined(__BORLANDC__)
# pragma option -w-ccc // Disable "Condition is always true/false"
# pragma option -w-rch // Disable "Unreachable code"
#endif
 
namespace owl {
 
OWL_DIAGINFO;
 
 
DEFINE_RESPONSE_TABLE1(TPrintDialog, TCommonDialog)
END_RESPONSE_TABLE;
 
namespace
{
 
tstring ToString(TResId id)
{return id.IsString() ? tstring(id.GetString()) : tstring();}
 
} // namespace
 
//
/// Constructs a print or print setup dialog box with specified data from the
/// TPrintDialog::TData struct structure, parent window, window caption, print and
/// setup templates, and module.
//
TPrintDialog::TPrintDialog(TWindow* parent, TData& data, TResId printTemplate, TResId setupTemplate,
  LPCTSTR title, TModule* module)
:
  TCommonDialog(parent, title, module),
  Data(data),
  PrintTemplate(printTemplate),
  PrintTemplateName(ToString(printTemplate)),
  SetupTemplate(setupTemplate),
  SetupTemplateName(ToString(setupTemplate))
{
  Init();
}
 
//
/// String-aware overload
//
TPrintDialog::TPrintDialog(TWindow* parent, TData& data, const tstring& printTemplateName, const tstring& setupTemplateName,
  const tstring& title, TModule* module)
:
  TCommonDialog(parent, title.c_str(), module),
  Data(data),
  PrintTemplate(),
  PrintTemplateName(printTemplateName),
  SetupTemplate(),
  SetupTemplateName(setupTemplateName)
{
  Init();
}
 
//
/// Common initialization of the contained data structures.
//
void
TPrintDialog::Init()
{
  // If our dialog template identifiers are given as strings, then redirect the resource
  // identifiers to our buffered copies (copied in the constructor). This prevents run-time errors
  // caused by dangling string pointers.
  //
  if (!PrintTemplateName.empty())
    PrintTemplate = TResId{PrintTemplateName.c_str()};
 
  if (!SetupTemplateName.empty())
    SetupTemplate = TResId{SetupTemplateName.c_str()};
 
  memset(&Pd, 0, sizeof Pd);
  Pd.lStructSize = sizeof Pd;
  Pd.hwndOwner = GetParentO() ? GetParentO()->GetHandle() : 0;
  Pd.hInstance = *GetModule();
  Pd.Flags = PD_ENABLEPRINTHOOK | PD_ENABLESETUPHOOK | Data.Flags;
  Pd.Flags &= ~PD_RETURNDEFAULT;
 
  if (PrintTemplate)
  {
    Pd.lpPrintTemplateName = PrintTemplate.GetPointerRepresentation();
    Pd.Flags |= PD_ENABLEPRINTTEMPLATE;
  }
  else
    Pd.Flags &= ~PD_ENABLEPRINTTEMPLATE;
 
  if (SetupTemplate)
  {
    Pd.lpSetupTemplateName = SetupTemplate.GetPointerRepresentation();
    Pd.Flags |= PD_ENABLESETUPTEMPLATE;
  }
  else
    Pd.Flags &= ~PD_ENABLESETUPTEMPLATE;
 
  Pd.lpfnPrintHook = 0;
  Pd.lpfnSetupHook = 0;
  Pd.nFromPage = (uint16)Data.FromPage;
  Pd.nToPage = (uint16)Data.ToPage;
  Pd.nMinPage = (uint16)Data.MinPage;
  Pd.nMaxPage = (uint16)Data.MaxPage;
  Pd.nCopies = (uint16)Data.Copies;
 
  memset(&Psd, 0, sizeof Psd);
  Psd.lStructSize = sizeof Psd;
  Psd.hwndOwner = GetParentO() ? GetParentO()->GetHandle() : 0;
  Psd.hInstance = *GetModule();
  Psd.Flags = PSD_ENABLEPAGESETUPHOOK | PSD_ENABLEPAGEPAINTHOOK | Data.PageSetupFlags;
 
  if (SetupTemplate)
  {
    Psd.lpPageSetupTemplateName = SetupTemplate.GetPointerRepresentation();
    Psd.Flags |= PSD_ENABLEPAGESETUPTEMPLATE;
  }
  else
    Psd.Flags &= ~PSD_ENABLEPAGESETUPTEMPLATE;
 
  Psd.lpfnPageSetupHook = 0;
  Psd.lpfnPagePaintHook = 0;
  Psd.ptPaperSize = Data.PaperSize;
  Psd.rtMinMargin = Data.MinMargin;
  Psd.rtMargin = Data.Margin;
 
  memset(&Pde, 0, sizeof Pde);
  Pde.lStructSize = sizeof Pde;
 
  // Note: PRINTDLGEX::hwndOwner cannot be NULL (see Windows API).
  // But, we need to maintain compatibility with older versions which allowed Parent == 0.
  // So, if no parent has been passed we'll try to assign the main window handle.
  // If this fails we'll handle the problem in DoExecute by reverting to the old dialog.
 
  TWindow* main_window = GetApplication() ? GetApplication()->GetMainWindow() : 0;
  Pde.hwndOwner = GetParentO() ? GetParentO()->GetHandle() : main_window ? main_window->GetHandle() : NULL;
  Pde.hInstance = *GetModule();
  Pde.Flags = Data.Flags;
  Pde.Flags &= ~PD_RETURNDEFAULT;
  Pde.Flags2 = 0;
  Pde.nStartPage = START_PAGE_GENERAL;   // Needed, won't work otherwise even for PD_RETURNDEFAULT
 
  if (PrintTemplate)
  {
    Pde.lpPrintTemplateName = PrintTemplate.GetPointerRepresentation();
    Pde.Flags |= PD_ENABLEPRINTTEMPLATE;
    Pde.hInstance = GetModule()->GetHandle();
  }
  else
  {
    Pde.Flags &= ~PD_ENABLEPRINTTEMPLATE;
    Pde.hInstance = 0;
  }
 
  Pde.lpCallback = 0;
  Pde.nMaxPageRanges = 1;
  Pde.lpPageRanges = &PdeRange;
  Pde.lpPageRanges[0].nFromPage = (uint16)Data.FromPage;
  Pde.lpPageRanges[0].nToPage = (uint16)Data.ToPage;
  Pde.nMinPage = (uint16)Data.MinPage;
  Pde.nMaxPage = (uint16)Data.MaxPage;
  Pde.nCopies = (uint16)Data.Copies;
}
 
//
/// Default processing.
/// Responds to the click of the setup button with an EV_COMMAND message.
//
void TPrintDialog::CmSetup()
{
  DefaultProcessing();
}
 
//
/// Returns the PRINTDLG structure used by the dialog.
//
PRINTDLG& TPrintDialog::GetPD()
{
  return Pd;
}
 
//
/// Sets the PRINTDLG structure used by the dialog.
//
void TPrintDialog::SetPD(const PRINTDLG& pd)
{
  Pd = pd;
}
 
//
/// Returns the transfer data of the dialog.
//
TPrintDialog::TData& TPrintDialog::GetData()
{
  return Data;
}
 
//
/// Returns true if a message is handled.
//
INT_PTR
TPrintDialog::DialogFunction(TMsgId msg, TParam1 param1, TParam2 param2)
{
  return TCommonDialog::DialogFunction(msg, param1, param2);
}
 
//
/// If no error occurs, DoExecute copies flags and print specifications into the
/// data argument in the constructor. If an error occurs, DoExecute sets the error
/// number of data to an error code from TPrintDialog::TData::Error.
//
int
TPrintDialog::DoExecute()
{
  if (Data.DoPageSetup)
  {
    Psd.lpfnPageSetupHook = LPPAGESETUPHOOK(StdDlgProc);
    Psd.lpfnPagePaintHook = LPPAGEPAINTHOOK(StdDlgProc);
 
    Data.Unlock();
 
    Psd.hDevMode = Data.HDevMode;
    Psd.hDevNames = Data.HDevNames;
 
    int ret = PageSetupDlg(&Psd);
    if (ret)
    {
      Data.PageSetupFlags = Psd.Flags;
      Data.Error = 0;
      Data.PaperSize = Psd.ptPaperSize;
      Data.MinMargin = Psd.rtMinMargin;
      Data.Margin = Psd.rtMargin;
    }
    else
    {
      Data.Error = CommDlgExtendedError();
    }
 
    Data.HDevMode = Psd.hDevMode;
    Data.HDevNames = Psd.hDevNames;
 
    Data.Lock();
 
    return ret ? IDOK : IDCANCEL;
  }
 
  Pd.lpfnPrintHook = LPPRINTHOOKPROC(StdDlgProc);
  Pd.lpfnSetupHook = LPSETUPHOOKPROC(StdDlgProc);
 
  Data.Unlock();
  Pd.hDevMode = Data.HDevMode;
  Pd.hDevNames = Data.HDevNames;
 
  int result;
 
  HWND hDummyWnd = NULL;
 
  if (!Data.UseOldDialog && TSystem::GetMajorVersion() > 4 && Pde.hwndOwner == NULL)
  {
    //If no TWindow is provided, create a dummy window to pass to the new print dialog
    //
    hDummyWnd = ::CreateWindow(_T("STATIC"), _T(""), 0,
       0, 0, 0, 0,
       NULL, NULL,
       GetApplication() ? GetApplication()->GetHandle() : NULL, NULL);
 
    Pde.hwndOwner = hDummyWnd;
  }
 
  if (!Data.UseOldDialog && TSystem::GetMajorVersion() > 4 && Pde.hwndOwner != NULL)
  {
    Pde.hDevMode = Data.HDevMode;
    Pde.hDevNames = Data.HDevNames;
    CHECK(Pde.hwndOwner); // cannot be NULL
    HRESULT ret = PrintDlgEx(&Pde);
    if (ret == S_OK)
    {
      if (!(Pde.Flags & PD_RETURNDEFAULT) && Pde.dwResultAction == PD_RESULT_CANCEL)
      {
        Data.Error = 0;
        result = IDCANCEL;
      }
      else
      {
        Data.Flags = Pde.Flags;
        Data.Error = 0;
        Data.HDc = Pde.hDC;
        Data.FromPage = Pde.lpPageRanges[0].nFromPage;
        Data.ToPage = Pde.lpPageRanges[0].nToPage;
        Data.Copies = Pde.nCopies;
 
        if ((Pde.Flags & PD_RETURNDEFAULT) || Pde.dwResultAction == PD_RESULT_PRINT)
        {
          result = IDOK;
        }
        else
        {
          result = IDCANCEL;
        }
      }
    }
    else
    {
      Data.Error = CommDlgExtendedError();
      result = IDCANCEL;
    }
    Data.HDevMode = Pde.hDevMode;
    Data.HDevNames = Pde.hDevNames;
 
    if (hDummyWnd != NULL)
    {
      ::DestroyWindow(hDummyWnd);
    }
  }
  else
  {
    int ret = PrintDlg(&Pd);
    if (ret)
    {
      Data.Flags = Pd.Flags;
      Data.Error = 0;
      Data.HDc = Pd.hDC;
      Data.FromPage = Pd.nFromPage;
      Data.ToPage = Pd.nToPage;
      Data.Copies = Pd.nCopies;
      result = IDOK;
    }
    else
    {
      Data.Error = CommDlgExtendedError();
      result = IDCANCEL;
    }
    Data.HDevMode = Pd.hDevMode;
    Data.HDevNames = Pd.hDevNames;
  }
 
  Data.Lock();
  return result;
}
 
//
/// Without displaying a dialog box, GetDefaultPrinter gets the device mode and name
/// that are initialized for the system default printer.
//
bool
TPrintDialog::GetDefaultPrinter()
{
  Pd.Flags |= PD_RETURNDEFAULT;
  int flags = Pde.Flags;
  Pde.Flags = PD_RETURNDEFAULT;
  Data.ClearDevMode();
  Data.ClearDevNames();
  int result = DoExecute();
  Pde.Flags = flags;
  return result == IDOK;
}
 
//----------------------------------------------------------------------------
 
TPrintDialog::TData::TData()
:
  Flags(PD_ALLPAGES|PD_COLLATE),
  Error(0),
  FromPage(-1), ToPage(-1),
  MinPage(-1), MaxPage(-1),
  Copies(1),
  PageSetupFlags(PSD_DEFAULTMINMARGINS),
  DoPageSetup(false),
  UseOldDialog(false),
  HDevMode(0),
  HDevNames(0), HDc(0),
  DevMode(0), DevNames(0)
{
}
 
TPrintDialog::TData::~TData()
{
  if (HDevMode)
  {
    ::GlobalUnlock(HDevMode);
    ::GlobalFree(HDevMode);
  }
  if (HDevNames)
  {
    ::GlobalUnlock(HDevNames);
    ::GlobalFree(HDevNames);
  }
  if (HDc)
    ::DeleteDC(HDc);
}
 
//
/// Gets a pointer to a DEVMODE structure (a structure containing information
/// necessary to initialize the dialog controls).
//
const DEVMODE* TPrintDialog::TData::GetDevMode() const
{
  return DevMode;
}
 
//
/// Gets a pointer to a non-const DEVMODE structure (a structure containing information
/// necessary to initialize the dialog controls).
//
DEVMODE* TPrintDialog::TData::GetDevMode()
{
  return DevMode;
}
 
//
/// Gets a pointer to a DEVNAMES structure (a structure containing three strings
/// used to specify the driver name, the printer name, and the output port name).
//
const DEVNAMES * TPrintDialog::TData::GetDevNames() const
{
  return DevNames;
}
 
//
/// Locks memory associated with the DEVMODE and DEVNAMES structures.
//
void
TPrintDialog::TData::Lock()
{
  if (HDevMode)
    DevMode = (DEVMODE *)::GlobalLock(HDevMode);
  else
    DevMode = 0;
  if (HDevNames)
    DevNames = (DEVNAMES *)::GlobalLock(HDevNames);
  else
    DevNames = 0;
}
 
//
/// Unlocks memory associated with the DEVMODE and DEVNAMES structures.
//
void
TPrintDialog::TData::Unlock()
{
  if (HDevMode)
  {
    ::GlobalUnlock(HDevMode);
    DevMode = 0;
  }
  if (HDevNames)
  {
    ::GlobalUnlock(HDevNames);
    DevNames = 0;
  }
  if (HDc)
  {
    ::DeleteDC(HDc);
    HDc = 0;
  }
}
 
//
/// Clears device mode information (information necessary to initialize the dialog
/// controls).
//
void
TPrintDialog::TData::ClearDevMode()
{
  if (HDevMode)
  {
    ::GlobalUnlock(HDevMode);
    ::GlobalFree(HDevMode);
    HDevMode = 0;
    DevMode = 0;
  }
}
 
//
/// Sets the values for the DEVMODE structure.
//
void
TPrintDialog::TData::SetDevMode(const DEVMODE* devMode)
{
  ClearDevMode();
  if (devMode)
  {
    int size = devMode->dmSize + devMode->dmDriverExtra;
    HDevMode = ::GlobalAlloc(GHND, size);
    DevMode = (DEVMODE *)::GlobalLock(HDevMode);
    memcpy(DevMode, devMode, size);
  }
}
 
//
/// Clears the device name information (information that contains three strings used
/// to specify the driver name, the printer name, and the output port name).
//
void
TPrintDialog::TData::ClearDevNames()
{
  if (HDevNames)
  {
    ::GlobalUnlock(HDevNames);
    ::GlobalFree(HDevNames);
    HDevNames = 0;
    DevNames = 0;
  }
}
 
//
/// Gets the name of the printer device driver.
//
LPCTSTR
TPrintDialog::TData::GetDriverName() const
{
  return DevNames ? reinterpret_cast<LPCTSTR>(DevNames) + DevNames->wDriverOffset : 0;
}
 
//
/// Gets the name of the output device.
//
LPCTSTR
TPrintDialog::TData::GetDeviceName() const
{
  return DevNames ? reinterpret_cast<LPCTSTR>(DevNames) + DevNames->wDeviceOffset : 0;
}
 
//
/// Gets the name of the physical output medium.
//
LPCTSTR
TPrintDialog::TData::GetOutputName() const
{
  return DevNames ? reinterpret_cast<LPCTSTR>(DevNames) + DevNames->wOutputOffset : 0;
}
 
//
/// Sets the values for the DEVNAMES structure.
//
void
TPrintDialog::TData::SetDevNames(const tstring& driver, const tstring& device, const tstring& output)
{
  ClearDevNames();
 
  // Calculate the required buffer size, as the number of characters incl. null-terminators,
  // and the resulting total size, in bytes, of the DEVNAMES structure including trailing strings.
  // Then allocate and lock the required amount of global memory.
  //
  const int n = static_cast<int>(driver.length() + 1 + device.length() + 1 + output.length() + 1);
  const int size = sizeof(DEVNAMES) + (n * sizeof(tchar));
  HDevNames = ::GlobalAlloc(GHND, size);
  DevNames = static_cast<DEVNAMES*>(::GlobalLock(HDevNames));
  DevNames->wDefault = false;
 
  // Calculate the offsets to the strings within DEVNAMES.
  // Then copy the given names into DEVNAMES (actually, behind the fixed part of DEVNAMES).
  //
  // NB! Offsets are in character counts. Here we assume the size of the fixed part of DEVNAMES is divisible
  // by the character size. Otherwise the driver name will overwrite the last byte of the fixed part.
  // But DEVNAMES is divisible and forever set in stone, so we don't need to consider an odd struct size.
  //
  CHECK(sizeof(DEVNAMES) % sizeof(tchar) == 0);
  const LPTSTR base = reinterpret_cast<LPTSTR>(DevNames);
  const LPTSTR pDriver = reinterpret_cast<LPTSTR>(DevNames + 1); // Jump past the the fixed part, assuming even struct size.
  const LPTSTR pDevice = pDriver + driver.length() + 1; // Jump past the preceding string, including null-terminator.
  const LPTSTR pOutput = pDevice + device.length() + 1;
  DevNames->wDriverOffset = static_cast<WORD>(pDriver - base);
  DevNames->wDeviceOffset = static_cast<WORD>(pDevice - base);
  DevNames->wOutputOffset = static_cast<WORD>(pOutput - base);
  _tcscpy(pDriver, driver.c_str());
  _tcscpy(pDevice, device.c_str());
  _tcscpy(pOutput, output.c_str());
}
 
//
/// Creates and returns a TPrintDC with the current settings.
/// Pass ownership of our hDC to the caller thru the new's TPrintDC object
//
TPrintDC*
TPrintDialog::TData::TransferDC()
{
  if (!HDc)
    return 0;
  HDC hdc = HDc;
  HDc = 0;
  return new TPrintDC(hdc, AutoDelete);
}
 
 
//
// Read the persistent object from the stream.
//
void*
TPrintDialog::TData::Read(ipstream& is, uint32 version)
{
  is >> Flags;
  is >> FromPage;
  is >> ToPage;
  is >> MinPage;
  is >> MaxPage;
  is >> Copies;
  const auto driverNarrow = std::unique_ptr<char[]>(is.freadString());
  const auto deviceNarrow = std::unique_ptr<char[]>(is.freadString());
  const auto outputNarrow = std::unique_ptr<char[]>(is.freadString());
  uint16 deflt;
  is >> deflt;
  _USES_CONVERSION;
  const auto driver = _A2W(driverNarrow.get());
  const auto device = _A2W(deviceNarrow.get());
  const auto output = _A2W(outputNarrow.get());
  SetDevNames(driver, device, output);
  if (DevNames)
    DevNames->wDefault = deflt;
 
  int16 size;
  is >> size;
  if (size)
  {
    DEVMODE * devMode = (DEVMODE *)new  tchar[size];
    is.freadBytes(devMode, size);
    SetDevMode(devMode);
    delete[] devMode;
  }
  else
    ClearDevMode();
 
  if (version > 1)
  {
    is >> PageSetupFlags;
    is >> PaperSize;
    is >> MinMargin;
    is >> Margin;
  }
 
  return this;
}
 
//
/// Writes the object to a peristent stream.
//
void
TPrintDialog::TData::Write(opstream& os)
{
  _USES_CONVERSION;
 
  os << Flags;
  os << FromPage;
  os << ToPage;
  os << MinPage;
  os << MaxPage;
  os << Copies;
  os.fwriteString(_W2A(GetDriverName()));
  os.fwriteString(_W2A(GetDeviceName()));
  os.fwriteString(_W2A(GetOutputName()));
  os << (DevNames ? DevNames->wDefault : uint16(0));
 
  if (DevMode)
  {
    int16 size = int16(DevMode->dmSize + DevMode->dmDriverExtra);
    os << size;
    os.fwriteBytes(DevMode, size); // NB! Problem with Unicode?
  }
  else
    os << int16(0);
 
  os << PageSetupFlags;
  os << PaperSize;
  os << MinMargin;
  os << Margin;
}
 
 
} // OWL namespace
 

V1063 The modulo by 1 operation is meaningless. The result will always be zero.

V601 The 'false' value is implicitly cast to the integer type.