//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1992, 1997 by Borland International, All Rights Reserved
//
//----------------------------------------------------------------------------
#include <owl/pch.h>
#include <owl/applicat.h>
#include <owl/appdict.h>
#include <owl/window.h>
#include <owl/framewin.h>
#include <owl/dc.h>
#include <owl/static.h>
#include <owl/printer.h>
using namespace std;
#if defined(__BORLANDC__)
# pragma option -w-ccc // Disable "Condition is always true/false"
#endif
namespace owl {
OWL_DIAGINFO;
//
// Template used to set and clear a bunch of flags at once
//
template <class T1, class T2>
inline void SetClear(T1& var, T2 bitsOn, T2 bitsOff) {
var &= ~bitsOff;
var |= bitsOn;
}
// Define 'MANUAL_ABORT_CALL to enable the explicit abort proc call
//
// #define MANUAL_ABORT_CALL
//----------------------------------------------------------------------------
//
// class TFormattedStatic
// ~~~~~ ~~~~~~~~~~~~~~~~
// Static control that uses its resource title as a printf format string to
// format one or two text strings provided in the constructor
//
class TFormattedStatic: public TStatic {
public:
TFormattedStatic(TWindow* parent, int resId, const tstring& text, const tstring& text2 = tstring());
protected:
void SetupWindow();
private:
tstring Text; // Text to display
tstring Text2;
};
//
// Construct the object.
// Copy the two strings passed.
// Second string is optional.
//
TFormattedStatic::TFormattedStatic(TWindow* parent, int resId, const tstring& text, const tstring& text2)
: TStatic(parent, resId, 0),
Text(text),
Text2(text2)
{}
//
// Override SetupWindow to set the text for the static control.
//
void
TFormattedStatic::SetupWindow()
{
TStatic::SetupWindow();
// Use the text retrieved from the resource as a printf template for
// the one or two text strings, then update the control text with the result.
//
tstring c = GetText();
int len = static_cast<int>(c.length() + Text.length() + Text2.length() + 5);
TAPointer<tchar> buff(new tchar[len]);
_stprintf(buff, c.c_str(), Text.c_str(), Text2.c_str());
SetText(buff);
}
//
// class TNumericStatic
// ~~~~~ ~~~~~~~~~~~~~~
// Static control that uses its resource title as a printf format string to
// format one or two text strings provided in the constructor
//
class TNumericStatic: public TStatic
{
public:
TNumericStatic(TWindow* parent, int resId, int number);
protected:
TResult EvSetNumber(TParam1, TParam2);
private:
int Number; // Number to display
DECLARE_RESPONSE_TABLE(TNumericStatic);
};
#define WM_SETNUMBER WM_USER+100
DEFINE_RESPONSE_TABLE1(TNumericStatic, TStatic)
EV_MESSAGE(WM_SETNUMBER, EvSetNumber),
END_RESPONSE_TABLE;
//
// Construct the object and remember the number to display.
//
TNumericStatic::TNumericStatic(TWindow* parent, int resId, int number)
: TStatic(parent, resId, 0),
Number(number)
{}
//
// Handle our user defined message to set the number displayed in the %d part
// of the Title format string. If the number is <= 0, then hide this window
//
TResult
TNumericStatic::EvSetNumber(TParam1 param1, TParam2)
{
Number = static_cast<int>(param1);
if (Number > 0) {
LPCTSTR c = GetCaption();
CHECK(c);
if (c)
{
int len = static_cast<int>(::_tcslen(c) + sizeof(Number) + 5);
TAPointer<tchar> buff(new tchar[len]);
wsprintf(buff, c, Number);
SetText(buff);
}
}
THandle hWndDefault = GetParentO()->GetDlgItem(-1);
if (Number > 0)
{
const uint flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW;
SetWindowPos(HWND_TOP, 0, 0, 0, 0, flags);
if (hWndDefault)
::ShowWindow(hWndDefault, SW_HIDE);
UpdateWindow();
}
return 0;
}
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(TPrinterAbortDlg, TDialog)
EV_COMMAND(IDCANCEL, CmCancel),
END_RESPONSE_TABLE;
//
/// Constructs an Abort dialog box that contains a Cancel button and displays the
/// given title, device, and port.
//
TPrinterAbortDlg::TPrinterAbortDlg(TWindow* parent, TResId resId, const tstring& title, const tstring& device, const tstring& port, HDC prnDC)
:
TDialog(parent, resId),
PrnDC(prnDC)
{
new TNumericStatic(this, ID_PAGE, 0);
new TFormattedStatic(this, ID_TITLE, title);
new TFormattedStatic(this, ID_DEVICE, device, port);
new TNumericStatic(this, ID_TOPAGE, 0);
}
//
/// Overrides SetupWindow. This function disables the Close system menu option.
//
void
TPrinterAbortDlg::SetupWindow()
{
// Ignore missing controls, resource may be different.
//
// While needlessly flexible, previous versions of OWL allowed a derived
// dialog to ommit the controls required by this base dialog class, and/or
// to redefine the control identifiers.
//
// Note that doing so will cause the dialog to lack the corresponding
// functionality of these controls, as the printing code relies on these
// controls being present with the identifiers defined by the
// IDD_ABORTDIALOG dialog resource (i.e. ID_PAGE and ID_TOPAGE).
// Otherwise no page number progress will be reported by TPrinter::Print.
//
// The updated code below still allows redefinition for backwards
// compatibility, but it logs a warning and uses more stringent exception
// handling to filter out exceptions caused by other issues.
// The original code used a "catch all" handler that did nothing.
//
// Note that this code depends on our controls being last in the children
// list. This can be assumed since TWindow::AddChild inserts children at
// the front of the list, i.e. any additional controls defined by a subclass
// will have already been constructed if and when our controls should fail to
// create due to a missing or redefined resource. Hence the state of the
// derived class is unaffected in this case, and we can safely ignore the
// failed controls. It is also vital that no code relies on these controls
// to function. TPrinter::Print currently passes messages to these controls
// but doesn't rely on that succeeding.
//
try {
TDialog::SetupWindow();
}
catch (const TXWindow& x) {
const TWindow* w = x.GetWindow();
int id = w ? w->GetId() : 0;
switch (id) {
case ID_PAGE:
case ID_TITLE:
case ID_DEVICE:
case ID_TOPAGE:
WARN(true, _T("TPrinterAbortDlg control failure [id=") << id << _T("]: ") << x.what());
break; // Do nothing. Let these controls fail silently.
default:
throw; // The cause is unknown. Rethrow the exception.
}
}
EnableMenuItem(GetSystemMenu(false), SC_CLOSE, MF_GRAYED);
}
//
/// Handles the print-cancel button by setting the user print abort flag in the
/// application.
//
void
TPrinterAbortDlg::CmCancel()
{
TPrinter::SetUserAbort(PrnDC);
THandle hBtn = GetDlgItem(IDCANCEL);
if (hBtn)
::EnableWindow(hBtn, false);
}
//----------------------------------------------------------------------------
//
/// Constructs a TXPrinter object with a default IDS_PRINTERERROR message.
//
TXPrinter::TXPrinter(uint resId)
:
TXOwl(resId)
{
}
//
/// Clone the exception object for safe throwing across stack frames.
//
TXPrinter*
TXPrinter::Clone()
{
return new TXPrinter(*this);
}
//
/// Throws the exception.
//
void
TXPrinter::Throw()
{
throw *this;
}
//
/// Creates the exception object and throws it.
//
void
TXPrinter::Raise(uint resId)
{
TXPrinter(resId).Throw();
}
//----------------------------------------------------------------------------
//
/// Constructs a TXPrinting object with a default IDS_PRINTERERROR message.
//
TXPrinting::TXPrinting(int error)
:
TXOwl(IDS_PRINTERERROR), // TODO: Create specific string for this exception?
Error(error)
{
}
//
/// Throws the exception.
//
void
TXPrinting::Throw()
{
throw *this;
}
//
/// Creates the exception object and throws it.
//
void
TXPrinting::Raise(int error)
{
TXPrinting(error).Throw();
}
//
/// Returns the error message for the current error code.
/// If the given module is null, the message is loaded from the global module.
//
tstring
TXPrinting::GetErrorMessage(TModule* m) const
{
TModule& module = m ? *m : GetGlobalModule();
uint errorMsgId;
switch (Error)
{
case SP_APPABORT:
errorMsgId = IDS_PRNCANCEL;
break;
case SP_USERABORT:
errorMsgId = IDS_PRNMGRABORT;
break;
case SP_OUTOFDISK:
errorMsgId = IDS_PRNOUTOFDISK;
break;
case SP_OUTOFMEMORY:
errorMsgId = IDS_PRNOUTOFMEMORY;
break;
case SP_ERROR:
default:
errorMsgId = IDS_PRNGENERROR;
break;
}
return module.LoadString(errorMsgId);
}
//----------------------------------------------------------------------------
namespace {
auto GetDevMode_(const tstring& device) -> vector<char>
{
// Get the size required for this device's DEVMODE structure.
//
const auto n = const_cast<LPTSTR>(device.c_str());
const auto s = DocumentProperties(nullptr, nullptr, n, nullptr, nullptr, 0);
if (s <= 0) throw TXPrinter{};
// Now, create a buffer of the required size and get the device data.
//
auto buffer = vector<char>(s);
const auto p = reinterpret_cast<PDEVMODE>(buffer.data());
const auto r = DocumentProperties(nullptr, nullptr, n, p, nullptr, DM_OUT_BUFFER);
if (r != IDOK) throw TXPrinter{};
return buffer;
}
} // namespace
//
/// Retrieves the DEVMODE device data for the given device.
//
TPrinter::TDevMode::TDevMode(const tstring& device)
: Buffer{GetDevMode_(device)}
{}
//----------------------------------------------------------------------------
//
/// Set by printing dialog if user cancels.
//
HDC TPrinter::UserAbortDC = 0;
//
/// Retrieves the printer name of the default printer for the current user on the local computer.
/// Wrapper for the Windows API function GetDefaultPrinter.
/// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144876.aspx
/// Throws TXPrinter if there is no default printer, or if an unexpected error occurs.
//
auto TPrinter::GetDefaultPrinter() -> tstring
{
auto b = tstring(15, _T('\0')); // Intentionally small to allow Small String Optimization.
b.resize(b.capacity()); // Ensures we use the full size of our buffer.
auto n = static_cast<DWORD>(b.size()) + 1; // The buffer includes the null-terminator as well.
// We retry calls to ::GetDefaultPrinter in a loop since the initial buffer may be insufficient.
// Also, although unlikely, the printer may change between calls to ::GetDefaultPrinter, asking
// for a larger and larger buffer for each iteration around the loop.
//
while (!::GetDefaultPrinter(&b[0], &n))
switch (GetLastError())
{
case ERROR_INSUFFICIENT_BUFFER:
CHECK(n > b.size() + 1);
b.resize(n - 1); // String size excludes the null-terminator.
break;
case ERROR_FILE_NOT_FOUND: // There is no default printer.
default:
throw TXPrinter{};
}
CHECK(n > 0); // We should at least have a null-terminator.
b.resize(n - 1);
return b;
}
//
/// Associates this TPrinter object with the given printer device.
/// If no name is given, the name of the current default printer is used.
//
TPrinter::TPrinter(const tstring& device)
: Error{0}, Data{nullptr}, PageSize{}, PageSizeInch{}, OutputFile{}
{
SetDevice(device);
}
//
/// Move-constructor; enables auto-declaration style.
//
TPrinter::TPrinter(TPrinter&& other)
: Error{other.Error}, Data{other.Data},
PageSize{other.PageSize}, PageSizeInch{other.PageSizeInch},
OutputFile{move(other.OutputFile)}
{
other.Data = nullptr;
}
//
/// Frees the resources allocated to TPrinter.
//
TPrinter::~TPrinter()
{
delete Data;
}
//
/// Assigns the device to be represented by this TPrinter object.
//
void TPrinter::SetDevice(const tstring& device)
{
auto d = make_unique<TPrintDialog::TData>();
d->SetDevNames(_T(""), device, _T(""));
d->SetDevMode(TDevMode{device}.GetData());
SetData(d.get());
d.release(); // We took ownership.
}
//
/// Returns a reference to the TPrintDialog data structure.
//
TPrintDialog::TData& TPrinter::GetSetup()
{
return *Data;
}
//
/// Returns the device context already obtained in our setup data, or if none, creates a new
/// one based on the device name and device initialization data (DEVMODE).
//
auto TPrinter::GetDC() -> unique_ptr<TPrintDC>
{
const auto d = GetData(); CHECK(d);
const auto dc = d->TransferDC();
return dc ? unique_ptr<TPrintDC>(dc) :
make_unique<TPrintDC>(d->GetDeviceName(), d->GetDevMode());
}
//
/// Sets the user abort DC for the printer.
//
void TPrinter::SetUserAbort(HDC abortDC)
{
UserAbortDC = abortDC;
}
//
/// Returns the abort DC.
//
HDC TPrinter::GetUserAbort()
{
return UserAbortDC;
}
//
/// Returns the error code from the printer.
//
int TPrinter::GetError()
{
return Error;
}
//
/// Returns the common dialog data associated with the printer.
//
TPrintDialog::TData* TPrinter::GetData()
{
return Data;
}
//
/// Sets the common dialog data; takes ownership.
//
void TPrinter::SetData(TPrintDialog::TData* data)
{
if (data == Data) return;
if (Data) delete Data;
Data = data;
}
//
/// Returns the size of the page.
//
TSize TPrinter::GetPageSize() const
{
return PageSize;
}
//
/// Sets the page's size.
//
void TPrinter::SetPageSize(const TSize& pagesize)
{
PageSize = pagesize;
}
//
/// Returns the size of an inch the page.
//
TSize TPrinter::GetPageSizeInch() const
{
return PageSizeInch;
}
//
/// Sets the size of an inch on the page.
//
void TPrinter::SetPageSizeInch(const TSize& pageSizeInch)
{
PageSizeInch = pageSizeInch;
}
//
/// Called by the Destructor, ClearDevice disassociates the device
/// with the current printer. ClearDevice changes the current status of the printer
/// to PF_UNASSOCIATED, which causes the object to ignore all calls to Print until
/// the object is reassociated with a printer.
//
void TPrinter::ClearDevice()
{
Data->ClearDevMode();
Data->ClearDevNames();
}
namespace {
//
// Common implementation of GetCapability for capabilities that return a vector of strings.
//
auto GetCapabilityStrings_(const TPrinter& p, TPrinter::TCapability c, int stringBufferSize) -> vector<tstring>
{
PRECONDITION(c == TPrinter::dcBinNames || c == TPrinter::dcFileDependencies || c == TPrinter::dcMediaReady ||
c == TPrinter::dcMediaTypeNames || c == TPrinter::dcPaperNames || c == TPrinter::dcPersonality);
auto r = vector<tstring>{};
const auto n = p.GetCapability(c, nullptr);
if (n == static_cast<DWORD>(-1)) return r;
auto b = vector<TCHAR>(n * stringBufferSize);
const auto m = p.GetCapability(c, reinterpret_cast<LPTSTR>(b.data()));
if (m == static_cast<DWORD>(-1)) return r;
CHECK(m <= n); // If there are more elements since the last call, we had a buffer overflow!
for (auto i = 0; i != min(m, n); ++i)
{
// The string buffers may not be null-terminated, hence the two-step construction.
//
const auto p = &b[i * stringBufferSize];
r.emplace_back(tstring{p, static_cast<size_t>(stringBufferSize)}.c_str());
}
return r;
}
} // namespace
//
/// Retrieves the names of the printer's paper bins.
/// If the function fails, an empty vector is returned.
/// \sa TCapability::dcBinNames
//
auto TPrinter::GetBinNames() const -> TBinNames
{
return GetCapabilityStrings_(*this, dcBinNames, 24);
}
//
/// Retrieves a list of available paper bins.
/// Each element of the returned vector is a bin identifier, e.g. DMBIN_AUTO.
/// See the description of the dmDefaultSource member of the DEVMODE structure.
/// If the function fails, an empty vector is returned.
/// \sa TCapability::dcBins
//
auto TPrinter::GetBins() const -> TBins
{
auto f = [&](LPTSTR s) { return GetCapability(dcBins, s); };
const auto n = f(nullptr);
if (n == static_cast<DWORD>(-1)) return TBins{};
auto r = TBins(n);
const auto m = f(reinterpret_cast<LPTSTR>(r.data()));
if (m == static_cast<DWORD>(-1)) return TBins{};
CHECK(m <= n); // If there are more elements since the last call, we had a buffer overflow!
return r;
}
//
/// Returns whether the printer supports collating.
/// If the function fails, `false` is returned.
/// \sa TCapability::dcCollate
//
auto TPrinter::CanCollate() const -> bool
{
return GetCapability(dcCollate, nullptr) == 1;
}
//
/// Returns whether the printer supports color printing.
/// If the function fails, `false` is returned.
/// \sa TCapability::dcColorDevice
//
auto TPrinter::IsColorDevice() const -> bool
{
return GetCapability(dcColorDevice, nullptr) == 1;
}
//
/// Return the maximum number of copies the printer can print.
/// If the function fails, 0 is returned.
/// \sa TCapability::dcCopies
//
auto TPrinter::GetMaxCopies() const -> int
{
const auto n = GetCapability(dcCopies, nullptr);
if (n == static_cast<DWORD>(-1)) return 0;
return static_cast<int>(n);
}
//
/// Returns the version number of the printer driver.
/// If the function fails, 0xFFFFFFFF is returned.
/// \sa TCapability::dcDriver
//
auto TPrinter::GetDriverVersion() const -> DWORD
{
return GetCapability(dcDriver, nullptr);
}
//
/// Returns whether the printer supports duplex printing.
/// If the function fails, `false` is returned.
/// \sa TCapability::dcDuplex
//
auto TPrinter::HasDuplexSupport() const -> bool
{
return GetCapability(dcDuplex, nullptr) == 1;
}
//
/// Retrieves a list of the resolutions supported by the printer.
/// If the function fails, an empty vector is returned.
/// \sa TCapability::dcEnumResolutions
//
auto TPrinter::GetResolutions() const -> TResolutions
{
auto r = TResolutions{};
auto f = [&](LPTSTR s) { return GetCapability(dcEnumResolutions, s); };
const auto n = f(nullptr);
if (n == static_cast<DWORD>(-1)) return r;
auto b = vector<LONG>(2 * n);
const auto m = f(reinterpret_cast<LPTSTR>(b.data()));
if (m == static_cast<DWORD>(-1)) return r;
CHECK(m <= n); // If there are more elements since the last call, we had a buffer overflow!
for (auto i = 0; i != 2 * min(m, n); i += 2)
r.emplace_back(b[i], b[i + 1]);
return r;
}
//
/// Returns the number of bytes required for the device-specific portion of the DEVMODE
/// structure for the printer driver.
/// If the function fails, 0 is returned.
/// \sa TCapability::dcExtra
//
auto TPrinter::GetDevModeExtra() const -> int
{
const auto n = GetCapability(dcExtra, nullptr);
if (n == static_cast<DWORD>(-1)) return 0;
return static_cast<int>(n);
}
//
/// Returns the `dmFields` member of the printer driver's DEVMODE structure.
/// The `dmFields` member indicates which members in the device-independent portion of the
/// structure are supported by the printer driver.
/// \sa TCapability::dcFields
//
auto TPrinter::GetDevModeFields() const -> DWORD
{
return GetCapability(dcFields, nullptr);
}
//
/// Returns the names of any additional files that need to be loaded for the printer driver.
/// If the function fails, an empty vector is returned.
/// \sa TCapability::dcFileDependencies
//
auto TPrinter::GetDriverDependencies() const -> TFileDependencies
{
return GetCapabilityStrings_(*this, dcFileDependencies, 64);
}
//
/// Returns the maximum paper size that the printer supports.
/// If the function fails, TSize{0, 0} is returned.
/// \sa TCapability::dcMaxExtent, TCapability::dcMinExtent
//
auto TPrinter::GetMaxExtent() const -> TSize
{
const auto m = GetCapability(dcMaxExtent, nullptr);
if (m == static_cast<DWORD>(-1)) return TSize{0, 0};
return TSize{LoInt16(m), HiInt16(m)};
}
//
/// Returns the names of the paper forms that are currently available for use.
/// If the function fails, an empty vector is returned.
/// \sa TCapability::dcMediaReady
//
auto TPrinter::GetReadyMedia() const -> TPaperForms
{
return GetCapabilityStrings_(*this, dcMediaReady, 64);
}
//
/// Returns the names of the supported media types.
/// If the function fails, an empty vector is returned.
/// \sa TCapability::dcMediaTypeNames
//
auto TPrinter::GetMediaTypeNames() const -> TMediaTypeNames
{
return GetCapabilityStrings_(*this, dcMediaTypeNames, 64);
}
//
/// Returns a list of supported media types.
/// For a list of possible media type values, see the description of the `dmMediaType` member
/// of the DEVMODE structure.
/// If the function fails, an empty vector is returned.
/// \sa TCapability::dcMediaTypes
//
auto TPrinter::GetMediaTypes() const -> TMediaTypes
{
auto f = [&](LPTSTR s) { return GetCapability(dcMediaTypes, s); };
const auto n = f(nullptr);
if (n == static_cast<DWORD>(-1)) return TMediaTypes{};
auto r = TMediaTypes(n);
const auto m = f(reinterpret_cast<LPTSTR>(r.data()));
if (m == static_cast<DWORD>(-1)) return TMediaTypes{};
CHECK(m <= n); // If there are more elements since the last call, we had a buffer overflow!
return r;
}
//
/// Returns the minimum paper size that the printer supports.
/// If the function fails, TSize{0, 0} is returned.
/// \sa TCapability::dcMinExtent, TCapability::dcMaxExtent
//
auto TPrinter::GetMinExtent() const -> TSize
{
const auto m = GetCapability(dcMinExtent, nullptr);
if (m == static_cast<DWORD>(-1)) return TSize{0, 0};
return TSize{LoInt16(m), HiInt16(m)};
}
//
/// Returns the angle of rotation between portrait and landscape orientation.
/// The result is given in terms of the number of degrees that portrait orientation is rotated
/// counter-clockwise to produce landscape orientation. If landscape orientation is not
/// supported, then 0 is returned.
/// If the function fails, 0 is returned.
/// \sa TCapability::dcOrientation
//
auto TPrinter::GetLandscapeOrientation() const -> int
{
const auto r = GetCapability(dcOrientation, nullptr);
if (r == static_cast<DWORD>(-1)) return 0;
return static_cast<int>(r);
}
//
/// Retrieves a list of integers that indicate the printers ability to print multiple document
/// pages per printed page. Each element of the list represent a supported configuration, given
/// by the number of document pages per printed page.
/// If the function fails, an empty vector is returned.
/// \sa TCapability::dcNup
//
auto TPrinter::GetNupConfigurations() const -> TNupConfigurations
{
auto f = [&](LPTSTR s) { return GetCapability(dcNup, s); };
const auto n = f(nullptr);
if (n == static_cast<DWORD>(-1)) return TNupConfigurations{};
auto r = TNupConfigurations(n);
CHECK(sizeof(TNupConfigurations::value_type) == sizeof(DWORD));
const auto m = f(reinterpret_cast<LPTSTR>(r.data()));
if (m == static_cast<DWORD>(-1)) return TNupConfigurations{};
CHECK(m <= n); // If there are more elements since the last call, we had a buffer overflow!
return r;
}
//
/// Returns a list of supported paper names.
/// For example, paper names could be "Letter" or "Legal".
/// If the function fails, an empty vector is returned.
/// \sa TCapability::dcPaperNames
//
auto TPrinter::GetPaperNames() const -> TPaperNames
{
return GetCapabilityStrings_(*this, dcPaperNames, 64);
}
//
/// Returns a list of supported paper sizes.
/// For a list of the possible element values, see the description of the `dmPaperSize` member
/// of the DEVMODE structure.
/// If the function fails, an empty vector is returned.
/// \sa TCapability::dcPapers
//
auto TPrinter::GetPapers() const -> TPapers
{
auto f = [&](LPTSTR s) { return GetCapability(dcPapers, s); };
const auto n = f(nullptr);
if (n == static_cast<DWORD>(-1)) return TPapers{};
auto r = TPapers(n);
const auto m = f(reinterpret_cast<LPTSTR>(r.data()));
if (m == static_cast<DWORD>(-1)) return TPapers{};
CHECK(m <= n); // If there are more elements since the last call, we had a buffer overflow!
return r;
}
//
/// Retrieves the dimensions of each supported paper size.
/// The unit is tenths of a millimeter (LOMETRIC).
/// If the function fails, an empty vector is returned.
/// \sa TCapability::dcPaperSize
//
auto TPrinter::GetPaperSizes() const -> TPaperSizes
{
auto r = TPaperSizes{};
auto f = [&](LPTSTR s) { return GetCapability(dcPaperSize, s); };
const auto n = f(nullptr);
if (n == static_cast<DWORD>(-1)) return r;
auto b = vector<POINT>(n);
const auto m = f(reinterpret_cast<LPTSTR>(b.data()));
if (m == static_cast<DWORD>(-1)) return r;
CHECK(m <= n); // If there are more elements since the last call, we had a buffer overflow!
for (auto i = 0; i != min(m, n); ++i)
r.emplace_back(b[i].x, b[i].y);
return r;
}
//
/// Returns a list of printer description languages supported by the printer.
/// If the function fails, an empty vector is returned.
/// \sa TCapability::dcPersonality
//
auto TPrinter::GetDescriptionLanguages() const -> TDescriptionLanguages
{
return GetCapabilityStrings_(*this, dcPersonality, 32);
}
//
/// Returns the amount of available printer memory, in kilobytes.
/// If the function fails, 0 is returned.
/// \sa TCapability::dcPrinterMem
//
auto TPrinter::GetMemoryCapacity() const -> int
{
const auto r = GetCapability(dcPrinterMem, nullptr);
if (r == static_cast<int>(-1)) return 0;
return static_cast<int>(r);
}
//
/// Returns the printer's print rate.
/// Call GetCapability<TPrinter::dcPrintRateUnit> to determine the units of the returned value.
/// If the function fails, 0 is returned.
/// \sa TCapability::dcPrintRate, TCapability::dcPrintRateUnit, TPrintRateUnit
//
auto TPrinter::GetPrintRate() const -> int
{
const auto r = GetCapability(dcPrintRate, nullptr);
if (r == static_cast<int>(-1)) return 0;
return static_cast<int>(r);
}
//
/// Returns the printer's print rate in pages per minute.
/// If the function fails, 0 is returned.
/// \sa TCapability::dcPrintRatePpm
//
auto TPrinter::GetPrintRatePpm() const -> int
{
const auto r = GetCapability(dcPrintRatePpm, nullptr);
if (r == static_cast<int>(-1)) return 0;
return static_cast<int>(r);
}
//
/// Returns the printer's print rate units.
/// The returned value determines the units for the value returned by GetCapability<TPrinter::dcPrintRate>.
/// If the function fails, `pruUnknown` is returned.
/// \sa TCapability::dcPrintRateUnit, TCapability::dcPrintRate, TPrintRateUnit
//
auto TPrinter::GetPrintRateUnit() const -> TPrintRateUnit
{
const auto r = GetCapability(dcPrintRateUnit, nullptr);
switch (r)
{
case pruPagesPerMinute:
case pruCharactersPerSecond:
case pruLinesPerMinute:
case pruInchesPerMinute:
return static_cast<TPrintRateUnit>(r);
default:
return pruUnknown;
}
}
//
/// Returns the `dmSize` member of the printer driver's DEVMODE structure.
/// The `dmSize` member indicates the size (and hence version) of the DEVMODE structure.
/// The size excludes the private driver-data that follows the structure. See the `dmDriverExtra` member.
/// If the function fails, 0 is returned.
/// \sa TCapability::dcSize
//
auto TPrinter::GetDevModeSize() const -> int
{
const auto n = GetCapability(dcSize, nullptr);
if (n == static_cast<DWORD>(-1)) return 0;
return static_cast<int>(n);
}
//
/// Returns whether the printer supports stapling.
/// If the function fails, `false` is returned.
/// \sa TCapability::dcStaple
//
auto TPrinter::CanStaple() const -> bool
{
return GetCapability(dcStaple, nullptr) == 1;
}
//
/// Retrieves the abilities of the printer to use TrueType fonts.
/// The returned value can be a combination of flags DCTT_BITMAP, DCTT_DOWNLOAD and DCTT_SUBDEV.
/// If the function fails, 0 is returned.
/// \sa TCapability::dcTrueType
//
auto TPrinter::GetTrueTypeCapabilities() const -> DWORD
{
const auto r = GetCapability(dcTrueType, nullptr);
if (r == static_cast<DWORD>(-1)) return 0;
return r;
}
//
/// Returns the specification version to which the printer driver conforms.
/// If the function fails, 0xFFFFFFFF is returned.
/// \sa TCapability::dcVersion
//
auto TPrinter::GetDriverSpecificationVersion() const -> DWORD
{
return GetCapability(dcVersion, nullptr);
}
// Abort procedure used during printing, called by windows. Returns true to
// continue the print job, false to cancel.
//
int CALLBACK
TPrinterAbortProc(HDC hDC, int code)
{
TApplication* appl = OWLGetAppDictionary().GetApplication(0);
if(appl)
appl->PumpWaitingMessages();
// UserAbortDC will have been set by the AbortDialog
//
if (TPrinter::GetUserAbort() == hDC || TPrinter::GetUserAbort() == HDC(-1)) {
TPrinter::SetUserAbort(0);
return false;
}
return code == 0 || code == SP_OUTOFDISK;
}
//
/// Virtual called from within Print() to construct and execute a print dialog
/// before actual printing occurs. Return true to continue printing, false to
/// cancel
//
bool
TPrinter::ExecPrintDialog(TWindow* parent)
{
return TPrintDialog(parent, *Data).Execute() == IDOK;
}
//
// Page setup dialog for Win95 support.
//
#if !defined(__GNUC__) //JJH added removal of pragma warn for gcc
#pragma warn -par
#endif
/// Page setup dialog for Win95 support.
bool
TPrinter::ExecPageSetupDialog(TWindow* parent)
{
Data->DoPageSetup = true;
bool ret = TPrintDialog(parent, *Data).Execute() == IDOK;
Data->DoPageSetup = false;
return ret;
}
#if !defined(__GNUC__) //JJH added removal of pragma warn for gcc
#pragma warn .par
#endif
//
/// Virtual called from withing Print() just before the main printing loop to
/// construct and create the printing status, or abort window. This window
/// should use the control IDs specified in printer.rh
//
TWindow*
TPrinter::CreateAbortWindow(TWindow* parent, TPrintout& printout)
{
TDC* dc = printout.GetPrintDC();
TWindow* win = new TPrinterAbortDlg(parent, IDD_ABORTDIALOG,
printout.GetTitle(),
Data->GetDeviceName(),
Data->GetOutputName(),
dc ? HDC(*dc) : HDC(-1));
win->Create();
return win;
}
//
/// Returns the filename for output redirection.
//
LPCTSTR
TPrinter::GetOutputFile() const
{
return OutputFile.c_str();
}
//
/// Sets the filename for output redirection.
//
void
TPrinter::SetOutputFile(const tstring& outputFile)
{
OutputFile = outputFile;
}
//
/// Updates the PageSize variables by querying the device capabilities of the
/// specified device context.
//
void
TPrinter::SetPageSizes(const TPrintDC& prnDC)
{
// !BB Should we try PHYSICALWIDTH and PHYSICALHEIGHT first and then
// !BB fallback on HORZRES and VERTRES
PageSize.cx = prnDC.GetDeviceCaps(HORZRES);
PageSize.cy = prnDC.GetDeviceCaps(VERTRES);
PageSizeInch.cx = prnDC.GetDeviceCaps(LOGPIXELSX);
PageSizeInch.cy = prnDC.GetDeviceCaps(LOGPIXELSY);
}
namespace
{
struct TPrintErrorHandler
{
friend int operator %(int r, const TPrintErrorHandler&)
{
if (r <= 0) TXPrinting::Raise(r);
return r;
}
};
//
// Calculate flags based on band info from the driver.
// If a driver does not support BANDINFO the Microsoft recommended way
// of determining text only bands is if the first band is the full page,
// all others are graphics only or both.
//
uint CalculateBandFlags(TPrintout& printout, const TRect& bandRect)
{
PRECONDITION(printout.GetPrintDC());
TPrintErrorHandler eh;
TPrintDC& dc = *printout.GetPrintDC();
if (!ToBool(dc.QueryEscSupport(BANDINFO)))
{
const TRect pageRect(TPoint(0, 0), printout.GetPageSize());
return (bandRect == pageRect) ? pfText : pfGraphics;
}
TBandInfo bandInfo;
dc.BandInfo(bandInfo) %eh;
return (bandInfo.HasGraphics ? pfGraphics : 0) |
(bandInfo.HasText ? pfText : 0);
}
//
// Prints a page using the deprecated banding mechanism.
// TODO: Review the necessity of this code.
//
void PrintBandPage(TPrintout& printout, int pageNum)
{
PRECONDITION(printout.GetPrintDC());
TPrintErrorHandler eh;
TPrintDC& dc = *printout.GetPrintDC();
TRect bandRect(TPoint(0, 0), printout.GetPageSize());
while (!bandRect.IsEmpty())
{
dc.NextBand(bandRect) %eh;
uint bandFlags = CalculateBandFlags(printout, bandRect);
if (printout.WantForceAllBands() && (bandFlags & pfBoth) == pfGraphics)
dc.SetPixel(TPoint(0, 0), TColor::Black); // Some old drivers need this.
dc.DPtoLP(bandRect, 2);
printout.PrintPage(pageNum, bandRect, bandFlags);
}
}
//
// Sets up the device context and forwards the call to the printout.
//
void PrintPlainPage(TPrintout& printout, int pageNum)
{
PRECONDITION(printout.GetPrintDC());
TPrintErrorHandler eh;
TPrintDC& dc = *printout.GetPrintDC();
dc.StartPage() %eh;
TRect pageRect(TPoint(0, 0), printout.GetPageSize());
printout.PrintPage(pageNum, pageRect, pfBoth);
dc.EndPage() %eh;
}
//
// Function type for the inner body of the print loop
//
typedef void (*TPageFunc)(TPrintout&, int pagenum);
//
// Exception-safe begin and end code for a print job
//
struct TPrintingScope
{
TWindow& parent;
TPrintout& printout;
TPrintingScope(TWindow& p, TPrintout& po) : parent(p), printout(po)
{
parent.EnableWindow(false);
printout.BeginPrinting();
}
~TPrintingScope()
{
printout.EndPrinting();
parent.EnableWindow(true);
}
};
//
// Exception-safe begin and end code for printing a document
//
struct TDocumentScope
{
TPrintout& printout;
TDocumentScope(TPrintout& p, int fromPage, int toPage) : printout(p)
{
printout.BeginDocument(fromPage, toPage, pfBoth);
}
~TDocumentScope()
{
printout.EndDocument();
}
};
//
// Prints all the pages in the given document.
// If copies are requested, then repeatedly prints the document (for collated copies) or
// pages (for non-collated copies) for the number of copies specified.
//
// TODO: Review the calling sequence in case of exceptions. The code currently calls TPrintout::EndDocument and
// TPrintout::EndPrinting even if printing fails (exceptions). On the other hand, TPrintDC::EndPage and
// TPrintDC::EndDoc are not called if printing fails (exceptions). This is consistent with the original code,
// but the logic of this should be reviewed.
//
void PrintLoop(TPrintout& printout, int fromPage, int toPage, int copies, bool collated, LPCTSTR out,
TWindow& abortWin, TPageFunc printPage)
{
PRECONDITION(printout.GetPrintDC());
PRECONDITION(abortWin.GetParentO());
PRECONDITION(fromPage <= toPage);
PRECONDITION(copies >= 0);
TPrintErrorHandler eh;
TPrintDC& dc = *printout.GetPrintDC();
TPrintingScope printingScope(*abortWin.GetParentO(), printout);
const int documentCopyCount = collated ? copies : 1;
for (int documentCopyIndex = 0; documentCopyIndex != documentCopyCount; ++documentCopyIndex)
{
TDocumentScope documentScope(printout, fromPage, toPage);
dc.StartDoc(printout.GetTitle(), out) %eh;
abortWin.SendDlgItemMessage(ID_TOPAGE, WM_SETNUMBER, toPage);
const int pageCopyCount = collated ? 1 : copies;
for (int pageNum = fromPage; pageNum <= toPage && printout.HasPage(pageNum); ++pageNum)
{
abortWin.SendDlgItemMessage(ID_PAGE, WM_SETNUMBER, pageNum);
for (int pageCopyIndex = 0; pageCopyIndex != pageCopyCount; ++pageCopyIndex)
printPage(printout, pageNum);
}
dc.EndDoc() %eh;
}
}
} // namespace
//
/// Print renders the given printout object on the associated printer device and
/// displays an Abort dialog box while printing. It displays any errors encountered
/// during printing. Prompt allows you to show the user a commdlg print dialog.
///
/// Note: The calling sequence here is somewhat of a catch-22 for the printout.
/// The printout cannot compute how many pages the document really has until the page
/// format is known. The printout doesn't get this information until SetPrintParams is
/// called, which cannot happen until the dialog has returned a device context.
/// Unfortunately, the page range information must be provided by GetDialogInfo which
/// has to be called first to set up the dialog.
///
/// The problem is that the printer/page options are in the same dialog as the page
/// range selection. The only way to handle this is to implement a custom interactive
/// dialog box that updates the page range in real-time depending on the selected
/// printer/page format. Or deactivate the page range altogether.
//
bool
TPrinter::Print(TWindow* parent, TPrintout& printout, bool prompt)
{
PRECONDITION(parent);
//
// Get page range & selection range (if any) from the document.
//
int selFromPage = 0;
int selToPage = 0;
printout.GetDialogInfo(Data->MinPage, Data->MaxPage, selFromPage, selToPage);
if (selFromPage != 0)
{
Data->Flags &= ~PD_NOSELECTION;
Data->FromPage = selFromPage;
Data->ToPage = selToPage;
}
else {
Data->Flags |= PD_NOSELECTION;
Data->FromPage = 0;
Data->ToPage = 999;
}
if (Data->MinPage != 0)
{
Data->Flags &= ~PD_NOPAGENUMS;
if (Data->FromPage < Data->MinPage)
Data->FromPage = Data->MinPage;
else if (Data->FromPage > Data->MaxPage)
Data->FromPage = Data->MaxPage;
if (Data->ToPage < Data->MinPage)
Data->ToPage = Data->MinPage;
else if (Data->ToPage > Data->MaxPage)
Data->ToPage = Data->MaxPage;
}
else
Data->Flags |= PD_NOPAGENUMS;
//
// Create & execute a TPrintDialog (or derived) and have it return a usable
// DC if prompt is enabled. If the dialog fails because the default printer
// changed, clear our device information & try again.
//
TPointer<TPrintDC> prnDC (0); // Pointer to printer DC created by Printer Object
if (prompt)
{
SetClear(Data->Flags, PD_RETURNDC, PD_RETURNDEFAULT|PD_PRINTSETUP);
bool ok = ExecPrintDialog(parent);
if (!ok && Data->Error == PDERR_DEFAULTDIFFERENT)
{
ClearDevice();
ok = ExecPrintDialog(parent);
}
if (!ok)
return false;
prnDC = Data->TransferDC(); // We now own the DC, let prnDC manage it
if (!prnDC)
TXPrinter::Raise();
}
else
{
// Construct the DC directly if prompting was not enabled.
//
prnDC = new TPrintDC{Data->GetDeviceName(), Data->GetDevMode()};
}
// Update the device page format and forward the DC and page size to the printout.
//
SetPageSizes(*prnDC);
printout.SetPrintParams(prnDC, GetPageSize());
// Figure out which page range to use: Selection, Dialog's from/to, whole document
// range or all possible pages.
//
int fromPage;
int toPage;
if ((prompt && (Data->Flags & PD_SELECTION)) || selFromPage)
{
fromPage = selFromPage;
toPage = selToPage;
}
else if (prompt && (Data->Flags & PD_PAGENUMS))
{
fromPage = Data->FromPage;
toPage = Data->ToPage;
}
else if (Data->MinPage)
{
fromPage = Data->MinPage;
toPage = Data->MaxPage;
}
else
{
fromPage = 1;
toPage = INT_MAX;
}
// Redirect output if requested.
//
LPCTSTR out = OutputFile.length() == 0 ? 0 : OutputFile.c_str();
// Only band if the user requests banding and the printer supports banding.
// TODO: Banding is obsolete. Review the necessity of this code.
//
bool banding = printout.WantBanding() && (prnDC->GetDeviceCaps(RASTERCAPS) & RC_BANDING);
// Create modeless abort dialog and start printing.
//
try
{
TPointer<TWindow> abortWin(CreateAbortWindow(parent, printout));
prnDC->SetAbortProc(TPrinterAbortProc);
SetUserAbort(0);
PrintLoop(printout, fromPage, toPage, Data->Copies, Data->Flags & PD_COLLATE, out, *abortWin,
banding ? PrintBandPage : PrintPlainPage);
}
catch (const TXPrinting& x)
{
// Report error if not already reported.
//
Error = x.Error;
if (x.Error & SP_NOTREPORTED)
ReportError(parent, printout);
return false;
}
return true;
}
//
/// Setup lets the user select and/or configure the currently associated printer.
/// Setup opens a dialog box as a child of the given window. The user then selects
/// one of the buttons in the dialog box to select or configure the printer. The
/// form of the dialog box is based on TPrintDialog, the common dialog printer
/// class.
/// The options button allows the user acces to the specific driver's options.
//
void TPrinter::Setup(TWindow* parent)
{
ExecPageSetupDialog(parent);
}
//
/// Reports the current error by bringing up a system message box with an error message
/// composed of the error description and document title.
/// This function can be overridden to show a custom error dialog box.
//
void
TPrinter::ReportError(TWindow* parent, TPrintout& printout)
{
PRECONDITION(parent);
const auto errorCaption = parent->LoadString(IDS_PRNERRORCAPTION);
const auto errorStr = TXPrinting(Error).GetErrorMessage(parent->GetModule());
const auto formatStr = parent->LoadString(IDS_PRNERRORTEMPLATE);
parent->FormatMessageBox(formatStr, errorCaption, MB_OK | MB_ICONSTOP, printout.GetTitle(), errorStr.c_str());
}
//----------------------------------------------------------------------------
IMPLEMENT_STREAMABLE(TPrinter);
#if OWL_PERSISTENT_STREAMS
//
/// Restores the printer object from the persistent stream.
//
void*
TPrinter::Streamer::Read(ipstream& is, uint32 version) const
{
GetObject()->Data->Read(is, version);
return GetObject();
}
//
/// Saves the object into the persistent stream.
//
void
TPrinter::Streamer::Write(opstream& os) const
{
GetObject()->Data->Write(os);
}
#endif
} // OWL namespace
/* ========================================================================== */
↑ V1004 The 'd' pointer was used unsafely after it was verified against nullptr. Check lines: 468, 469.
↑ V1004 The 'parent' pointer was used unsafely after it was verified against nullptr. Check lines: 1411, 1412.
↑ V601 The 'false' value is implicitly cast to the integer type.