1355 lines
30 KiB
C
1355 lines
30 KiB
C
/*++
|
|
|
|
Copyright (c) 1997 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
prntsave.c
|
|
|
|
Abstract:
|
|
|
|
Routines to print or save the incompatibility reports. Functions
|
|
PrintReport and SaveReport are called when the user clicks
|
|
Save As... or Print... buttons in the UI. We then present
|
|
a common dialog box to the user and perform the operation.
|
|
|
|
Author:
|
|
|
|
Jim Schmidt (jimschm) 13-Mar-1997
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
#include "uip.h"
|
|
|
|
#include <commdlg.h>
|
|
#include <winspool.h>
|
|
|
|
|
|
|
|
#define DBG_PRINTSAVE "Print/Save"
|
|
|
|
|
|
GROWBUFFER g_PunctTable = GROWBUF_INIT;
|
|
|
|
|
|
VOID
|
|
BuildPunctTable (
|
|
VOID
|
|
)
|
|
{
|
|
INFSTRUCT is = INITINFSTRUCT_GROWBUFFER;
|
|
WORD cp;
|
|
TCHAR cpString[16];
|
|
PTSTR p;
|
|
PTSTR q;
|
|
MULTISZ_ENUM e;
|
|
DWORD d;
|
|
|
|
GetGlobalCodePage (&cp, NULL);
|
|
|
|
wsprintf (cpString, TEXT("%u"), (UINT) cp);
|
|
g_PunctTable.End = 0;
|
|
|
|
if (InfFindFirstLine (g_Win95UpgInf, TEXT("Wrap Exceptions"), cpString, &is)) {
|
|
|
|
p = InfGetMultiSzField (&is, 1);
|
|
|
|
if (EnumFirstMultiSz (&e, p)) {
|
|
|
|
do {
|
|
|
|
p = (PTSTR) e.CurrentString;
|
|
|
|
while (*p) {
|
|
|
|
q = p;
|
|
|
|
if (StringIPrefix (p, TEXT("0x"))) {
|
|
d = _tcstoul (p + 2, &p, 16);
|
|
} else {
|
|
d = _tcstoul (p, &p, 10);
|
|
}
|
|
|
|
if (q == p) {
|
|
break;
|
|
}
|
|
|
|
GrowBufAppendDword (&g_PunctTable, d);
|
|
|
|
if (*p) {
|
|
p = (PTSTR) SkipSpace (p);
|
|
}
|
|
|
|
if (*p == TEXT(',')) {
|
|
p++;
|
|
}
|
|
}
|
|
|
|
} while (EnumNextMultiSz (&e));
|
|
}
|
|
}
|
|
|
|
GrowBufAppendDword (&g_PunctTable, 0);
|
|
|
|
InfCleanUpInfStruct (&is);
|
|
}
|
|
|
|
|
|
VOID
|
|
FreePunctTable (
|
|
VOID
|
|
)
|
|
{
|
|
FreeGrowBuffer (&g_PunctTable);
|
|
}
|
|
|
|
|
|
BOOL
|
|
IsPunct (
|
|
MBCHAR Char
|
|
)
|
|
{
|
|
PDWORD p;
|
|
|
|
if (_ismbcpunct (Char)) {
|
|
return TRUE;
|
|
}
|
|
|
|
p = (PDWORD) g_PunctTable.Buf;
|
|
|
|
if (p) {
|
|
while (*p) {
|
|
|
|
if (*p == Char) {
|
|
return TRUE;
|
|
}
|
|
|
|
p++;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pGetSaveAsName (
|
|
IN HWND ParentWnd,
|
|
IN OUT PTSTR Buffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Calls common dialog box to obtain the file name in which to save
|
|
the compatibility report text file.
|
|
|
|
Arguments:
|
|
|
|
ParentWnd - A handle to the parent of the save as dialog
|
|
Buffer - A caller-supplied buffer. Supplies a file name to
|
|
initialize the common dialog box with, and receives
|
|
the file name the user chose to use.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the user clicked OK, or FALSE if the user canceled or an
|
|
error occurred.
|
|
|
|
--*/
|
|
|
|
{
|
|
TCHAR CwdSave[MAX_TCHAR_PATH];
|
|
OPENFILENAME ofn;
|
|
BOOL SaveFlag;
|
|
TCHAR Filter[512];
|
|
PCTSTR FilterResStr;
|
|
PTSTR p;
|
|
TCHAR desktopFolder[MAX_TCHAR_PATH];
|
|
LPITEMIDLIST pIDL;
|
|
BOOL b = FALSE;
|
|
|
|
#define MAX_EXT 3
|
|
|
|
PCTSTR ext[MAX_EXT];
|
|
INT i = 0;
|
|
|
|
FilterResStr = GetStringResource (MSG_FILE_NAME_FILTER);
|
|
if (FilterResStr) {
|
|
StringCopy (Filter, FilterResStr);
|
|
FreeStringResource (FilterResStr);
|
|
} else {
|
|
MYASSERT (FALSE);
|
|
Filter[0] = 0;
|
|
}
|
|
|
|
for (p = Filter ; *p ; p = _tcsinc (p)) {
|
|
if (*p == TEXT('|')) {
|
|
*p = 0;
|
|
}
|
|
}
|
|
for (p = Filter; *p; p = GetEndOfString (p) + 1) {
|
|
if (i & 1) {
|
|
//
|
|
// skip the * in "*.ext"
|
|
// if extension is "*.*" reduce that to an empty string (no extension)
|
|
//
|
|
MYASSERT (i / 2 < MAX_EXT);
|
|
ext[i / 2] = _tcsinc (p);
|
|
if (StringMatch (ext[i / 2], TEXT(".*"))) {
|
|
ext[i / 2] = S_EMPTY;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (SHGetSpecialFolderLocation (ParentWnd, CSIDL_DESKTOP, &pIDL) == S_OK) {
|
|
LPMALLOC pMalloc;
|
|
b = SHGetPathFromIDList (pIDL, desktopFolder);
|
|
if (SHGetMalloc (&pMalloc) == S_OK) {
|
|
IMalloc_Free (pMalloc, pIDL);
|
|
IMalloc_Release (pMalloc);
|
|
}
|
|
}
|
|
if (!b) {
|
|
desktopFolder[0] = 0;
|
|
}
|
|
|
|
// Initialize OPENFILENAME
|
|
ZeroMemory (&ofn, sizeof(OPENFILENAME));
|
|
ofn.lStructSize = sizeof(OPENFILENAME);
|
|
ofn.hwndOwner = ParentWnd;
|
|
ofn.lpstrFile = Buffer;
|
|
ofn.lpstrFilter = Filter;
|
|
ofn.nMaxFile = MAX_TCHAR_PATH;
|
|
|
|
// Force to begin at Desktop
|
|
// ofn.lpstrInitialDir = TEXT("::{20D04FE0-3AEA-1069-A2D8-08002B30309D}");
|
|
ofn.lpstrInitialDir = desktopFolder;
|
|
ofn.Flags = OFN_NOCHANGEDIR | // leave the CWD unchanged
|
|
OFN_EXPLORER |
|
|
OFN_OVERWRITEPROMPT |
|
|
OFN_HIDEREADONLY;
|
|
|
|
// Let user select disk or directory
|
|
GetCurrentDirectory (sizeof (CwdSave), CwdSave);
|
|
SaveFlag = GetSaveFileName (&ofn);
|
|
SetCurrentDirectory (CwdSave);
|
|
|
|
//
|
|
// if no extension provided, append the default one
|
|
// this was selected by the user and returned in ofn.nFilterIndex
|
|
//
|
|
|
|
#define DEFAULT_EXTENSION TEXT(".htm")
|
|
|
|
p = (PTSTR)GetFileNameFromPath (Buffer);
|
|
if (!p) {
|
|
p = Buffer;
|
|
}
|
|
if (!_tcschr (p, TEXT('.'))) {
|
|
if (SizeOfString (Buffer) + sizeof (DEFAULT_EXTENSION) <= MAX_TCHAR_PATH * sizeof (TCHAR)) {
|
|
if (ofn.nFilterIndex >= MAX_EXT + 1) {
|
|
ofn.nFilterIndex = 1;
|
|
}
|
|
StringCat (p, ext[ofn.nFilterIndex - 1]);
|
|
}
|
|
}
|
|
return SaveFlag;
|
|
}
|
|
|
|
|
|
UINT
|
|
pCreateWordWrappedString (
|
|
OUT PTSTR Buffer,
|
|
IN PCTSTR Str,
|
|
IN UINT FirstLineSize,
|
|
IN UINT RestLineSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Converts a string to a series of lines, where no line is bigger
|
|
than LineSize. If a buffer is not supplied, this function estimates
|
|
the number of bytes needed.
|
|
|
|
If the code page is a far-east code page, then lines are broken at any
|
|
multi-byte character, as well as at the spaces.
|
|
|
|
Arguments:
|
|
|
|
Buffer - If non-NULL, supplies address of buffer big enough to hold
|
|
enlarged, wrapped string. If NULL, parameter is ignored.
|
|
|
|
|
|
Str - Supplies the string that needs to be wrapped.
|
|
|
|
FirstLineSize - Specifies the max size the first line can be.
|
|
|
|
RestLineSize - Specifies the max size the remaining lines can be.
|
|
|
|
Return Value:
|
|
|
|
The size of the string copied to Buffer including the terminating NULL,
|
|
or the size of the needed buffer if Buffer is NULL.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCTSTR p, Start;
|
|
UINT Col;
|
|
PCTSTR LastSpace;
|
|
CHARTYPE c;
|
|
UINT Size;
|
|
BOOL PrevCharMb;
|
|
UINT LineSize;
|
|
|
|
LineSize = FirstLineSize;
|
|
|
|
p = Str;
|
|
if (!p)
|
|
return 0;
|
|
Size = SizeOfString(Str);
|
|
|
|
if (Buffer) {
|
|
*Buffer = 0;
|
|
}
|
|
|
|
while (*p) {
|
|
// Beginning of line
|
|
Col = 0;
|
|
LastSpace = NULL;
|
|
Start = p;
|
|
PrevCharMb = FALSE;
|
|
|
|
do {
|
|
// Is this a hard-coded line break?
|
|
c = _tcsnextc (p);
|
|
if (c == TEXT('\r') || c == TEXT('\n')) {
|
|
LastSpace = p;
|
|
p = _tcsinc (p);
|
|
|
|
if (c == TEXT('\r') && _tcsnextc (p) == TEXT('\n')) {
|
|
p = _tcsinc (p);
|
|
} else {
|
|
Size += sizeof (TCHAR);
|
|
}
|
|
|
|
c = TEXT('\n');
|
|
break;
|
|
}
|
|
else if (_istspace (c)) {
|
|
LastSpace = p;
|
|
}
|
|
else if (IsLeadByte (*p)) {
|
|
// MB chars are usually two cols wide
|
|
Col++;
|
|
|
|
if (PrevCharMb) {
|
|
//
|
|
// If this char is not punctuation, then we can
|
|
// break here
|
|
//
|
|
|
|
if (!IsPunct (c)) {
|
|
LastSpace = p;
|
|
}
|
|
}
|
|
|
|
PrevCharMb = TRUE;
|
|
}
|
|
else {
|
|
if (PrevCharMb) {
|
|
LastSpace = p;
|
|
}
|
|
|
|
PrevCharMb = FALSE;
|
|
}
|
|
|
|
// Continue until line gets too long
|
|
Col++;
|
|
p = _tcsinc (p);
|
|
} while (*p && Col < LineSize);
|
|
|
|
// If no more text, or line that has no space needs to be broken
|
|
if (!(*p) || (c != TEXT('\n') && !LastSpace)) {
|
|
LastSpace = p;
|
|
}
|
|
|
|
if (Buffer) {
|
|
StringCopyAB (Buffer, Start, LastSpace);
|
|
Buffer = GetEndOfString (Buffer);
|
|
}
|
|
|
|
if (*p && c != TEXT('\n')) {
|
|
p = LastSpace;
|
|
Size += sizeof (TCHAR) * 2;
|
|
}
|
|
|
|
// remove space at start of wrapped line
|
|
while (_tcsnextc (p) == TEXT(' ')) {
|
|
Size -= sizeof (TCHAR);
|
|
p = _tcsinc (p);
|
|
}
|
|
|
|
if (Buffer && *p) {
|
|
Buffer = _tcsappend (Buffer, TEXT("\r\n"));
|
|
}
|
|
|
|
LineSize = RestLineSize;
|
|
}
|
|
|
|
return Size;
|
|
}
|
|
|
|
|
|
PCTSTR
|
|
CreateIndentedString (
|
|
IN PCTSTR UnwrappedStr,
|
|
IN UINT Indent,
|
|
IN INT HangingIndent,
|
|
IN UINT LineLen
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Takes an unwrapped string, word-wraps it (via pCreateWordWrappedString),
|
|
inserts spaces before each line, optionally skipping the first line.
|
|
|
|
If the code page is a far-east code page, then lines are broken at any
|
|
multi-byte character, as well as at the spaces.
|
|
|
|
Arguments:
|
|
|
|
UnwrappedStr - A pointer to the string that is to be word-wrapped and
|
|
adjusted with space inserts.
|
|
Indent - The number of spaces to insert before each line.
|
|
HangingIndent - The adjustment made to the indent after the first line
|
|
LineLen - The maximum line size. Spaces must always be smaller
|
|
than LineLen (and should be considerably smaller).
|
|
FirstLine - If TRUE, the first line is indented. If FALSE, the
|
|
first line is skipped ("hanging indent").
|
|
|
|
Return Value:
|
|
|
|
A pointer to the indented string, or NULL if MemAlloc failed. The
|
|
caller must free the string with MemFree.
|
|
|
|
--*/
|
|
|
|
{
|
|
UINT Size;
|
|
UINT Count;
|
|
PCTSTR p, q;
|
|
PTSTR d;
|
|
PTSTR Dest;
|
|
PTSTR Str;
|
|
UINT FirstLineLen;
|
|
UINT RestLineLen;
|
|
UINT FirstLineIndent;
|
|
UINT RestLineIndent;
|
|
UINT RealIndent;
|
|
|
|
if (!UnwrappedStr) {
|
|
return NULL;
|
|
}
|
|
|
|
MYASSERT ((INT)Indent + HangingIndent >= 0);
|
|
FirstLineIndent = Indent;
|
|
RestLineIndent = Indent + HangingIndent;
|
|
|
|
MYASSERT (LineLen > FirstLineIndent);
|
|
MYASSERT (LineLen > RestLineIndent);
|
|
FirstLineLen = LineLen - FirstLineIndent;
|
|
RestLineLen = LineLen - RestLineIndent;
|
|
|
|
//
|
|
// Estimate line size, then do the wrapping
|
|
//
|
|
|
|
Str = (PTSTR) MemAlloc (
|
|
g_hHeap,
|
|
0,
|
|
pCreateWordWrappedString (
|
|
NULL,
|
|
UnwrappedStr,
|
|
FirstLineLen,
|
|
RestLineLen
|
|
)
|
|
);
|
|
|
|
if (!Str) {
|
|
return NULL;
|
|
}
|
|
|
|
pCreateWordWrappedString (
|
|
Str,
|
|
UnwrappedStr,
|
|
FirstLineLen,
|
|
RestLineLen
|
|
);
|
|
|
|
if (!FirstLineIndent && !RestLineIndent) {
|
|
return Str;
|
|
}
|
|
|
|
//
|
|
// Count number of lines
|
|
//
|
|
|
|
for (Count = 1, p = Str ; *p ; p = _tcsinc (p)) {
|
|
if (*p == TEXT('\n')) {
|
|
Count++;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Allocate a new buffer that is big enough for all the indented text
|
|
//
|
|
|
|
Size = max (FirstLineIndent, RestLineIndent) * Count + SizeOfString (Str);
|
|
Dest = MemAlloc (g_hHeap, 0, Size);
|
|
if (Dest) {
|
|
|
|
*Dest = 0;
|
|
|
|
//
|
|
// Indent each line
|
|
//
|
|
|
|
p = Str;
|
|
d = Dest;
|
|
|
|
RealIndent = FirstLineIndent;
|
|
|
|
while (*p) {
|
|
for (Count = 0 ; Count < RealIndent ; Count++) {
|
|
*d++ = TEXT(' ');
|
|
}
|
|
|
|
q = _tcschr (p, TEXT('\n'));
|
|
if (!q) {
|
|
q = GetEndOfString (p);
|
|
} else {
|
|
q = _tcsinc (q);
|
|
}
|
|
|
|
StringCopyAB (d, p, q);
|
|
d = GetEndOfString (d);
|
|
|
|
p = q;
|
|
|
|
RealIndent = RestLineIndent;
|
|
}
|
|
}
|
|
|
|
MemFree (g_hHeap, 0, Str);
|
|
return Dest;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pSaveReportToDisk (
|
|
IN HWND Parent,
|
|
IN PCTSTR FileSpec,
|
|
IN BOOL Html,
|
|
IN DWORD MinLevel
|
|
)
|
|
{
|
|
HANDLE File;
|
|
BOOL b;
|
|
PCTSTR Msg;
|
|
|
|
//
|
|
// Create the report file
|
|
//
|
|
|
|
File = CreateFile (
|
|
FileSpec,
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
|
|
if (File == INVALID_HANDLE_VALUE) {
|
|
LOG ((LOG_ERROR, "Error while saving report to %s", FileSpec));
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Save the report text to disk
|
|
//
|
|
|
|
b = FALSE;
|
|
|
|
Msg = CreateReportText (Html, 70, MinLevel, FALSE);
|
|
|
|
if (Msg) {
|
|
b = WriteFileString (File, Msg);
|
|
}
|
|
FreeReportText();
|
|
|
|
//
|
|
// Close the file and alert the user to save errors!
|
|
//
|
|
|
|
CloseHandle (File);
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
BOOL
|
|
SaveReport (
|
|
IN HWND Parent, OPTIONAL
|
|
IN PCTSTR Path OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Obtains a file name from the user via common Save As dialog,
|
|
creates the file and writes an incompatibility list to disk.
|
|
|
|
Arguments:
|
|
|
|
Parent - The parent of the common dialog box. Optional If this is NULL,
|
|
no UI is displayed.
|
|
Path - The path to save to. If this is NULL, parent must be specified.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the report was saved, or FALSE if the user canceled the
|
|
operation or the save failed. The user is alerted when the
|
|
save fails.
|
|
|
|
--*/
|
|
|
|
{
|
|
TCHAR Buffer[MAX_TCHAR_PATH + 4];
|
|
PCTSTR Str;
|
|
BOOL b;
|
|
DWORD attributes;
|
|
PTSTR p;
|
|
BOOL saved = FALSE;
|
|
BOOL bSaveBothFormats = FALSE;
|
|
|
|
MYASSERT(Parent != NULL || Path != NULL);
|
|
|
|
//
|
|
// Obtain a path, or use caller-supplied path
|
|
//
|
|
|
|
if (Path) {
|
|
attributes = GetFileAttributes(Path);
|
|
}
|
|
|
|
if (!Path || attributes != 0xFFFFFFFF && (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
if (Path) {
|
|
StringCopy(Buffer,Path);
|
|
AppendPathWack(Buffer);
|
|
bSaveBothFormats = TRUE;
|
|
} else {
|
|
Buffer[0] = 0;
|
|
}
|
|
Str = GetStringResource (MSG_DEFAULT_REPORT_FILE);
|
|
MYASSERT (Str);
|
|
if (!Str) {
|
|
return FALSE;
|
|
}
|
|
StringCat (Buffer, Str);
|
|
FreeStringResource (Str);
|
|
} else {
|
|
StringCopy(Buffer,Path);
|
|
}
|
|
|
|
if (Parent) {
|
|
if (!pGetSaveAsName (Parent, Buffer)) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
p = _tcsrchr (Buffer, TEXT('.'));
|
|
if (!p || _tcschr (p, TEXT('\\'))) {
|
|
p = GetEndOfString (Buffer);
|
|
}
|
|
|
|
//
|
|
// Save as text, if extension is .txt
|
|
//
|
|
|
|
if (StringIMatch (p, TEXT(".txt"))) {
|
|
b = pSaveReportToDisk (Parent, Buffer, FALSE, REPORTLEVEL_VERBOSE);
|
|
|
|
if (Parent) {
|
|
saved = b;
|
|
}
|
|
} else {
|
|
b = TRUE;
|
|
}
|
|
|
|
//
|
|
// Save as HTML unless user chose to save as .txt
|
|
//
|
|
|
|
if (b && !saved) {
|
|
if (!Parent && p) {
|
|
StringCopy (p, TEXT(".htm"));
|
|
}
|
|
|
|
b = pSaveReportToDisk (Parent, Buffer, TRUE, REPORTLEVEL_VERBOSE);
|
|
}
|
|
|
|
if (bSaveBothFormats) {
|
|
StringCopy (p, TEXT(".txt"));
|
|
b = pSaveReportToDisk (Parent, Buffer, FALSE, REPORTLEVEL_VERBOSE);
|
|
}
|
|
|
|
if (!b) {
|
|
if (Parent) {
|
|
ResourceMessageBox (Parent, MSG_CANT_SAVE, MB_OK, NULL);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
pFreePrintMem (
|
|
IN OUT PRINTDLG *ppd
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees all memory associated with PRINTDLG structure.
|
|
|
|
Arguments:
|
|
|
|
ppd - Pointer to PRINTDLG structure.
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
if (ppd->hDevMode) {
|
|
GlobalFree (ppd->hDevMode);
|
|
ppd->hDevMode = NULL;
|
|
}
|
|
|
|
if(ppd->hDevNames) {
|
|
GlobalFree (ppd->hDevNames);
|
|
ppd->hDevNames = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
pInitPrintDlgStruct (
|
|
OUT PRINTDLG *ppd,
|
|
IN HWND Parent,
|
|
IN DWORD Flags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes PRINTDLG structure, setting the owner window and
|
|
print dialog flags.
|
|
|
|
Arguments:
|
|
|
|
ppd - Pointer to PRINTDLG structure to be initialized
|
|
Parent - Handle to parent window for the dialog
|
|
Flags - PrintDlg flags (PD_*)
|
|
|
|
Return Value:
|
|
|
|
none (structure is initialized)
|
|
|
|
--*/
|
|
|
|
{
|
|
ZeroMemory (ppd, sizeof (PRINTDLG));
|
|
ppd->lStructSize = sizeof (PRINTDLG);
|
|
ppd->hwndOwner = Parent;
|
|
ppd->Flags = Flags;
|
|
ppd->hInstance = g_hInst;
|
|
}
|
|
|
|
|
|
HDC
|
|
pGetPrintDC (
|
|
IN HWND Parent
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Displays common dialog box to the user, and if the user chooses
|
|
a printer, returns a device context handle.
|
|
|
|
Arguments:
|
|
|
|
Parent - The parent of the common dialog to be displayed
|
|
|
|
Return Value:
|
|
|
|
A handle to a device context of the chosen printer, or NULL if the
|
|
user canceled printing.
|
|
|
|
--*/
|
|
|
|
{
|
|
PRINTDLG pd;
|
|
|
|
pInitPrintDlgStruct (
|
|
&pd,
|
|
Parent,
|
|
PD_ALLPAGES|PD_NOPAGENUMS|PD_NOSELECTION|PD_RETURNDC
|
|
);
|
|
|
|
if (PrintDlg (&pd)) {
|
|
pFreePrintMem (&pd);
|
|
return pd.hDC;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
HDC hdc;
|
|
INT Page;
|
|
INT Line;
|
|
RECT HeaderRect; // in logical units
|
|
RECT PrintableRect; // in logical units
|
|
TEXTMETRIC tm;
|
|
INT LineHeight;
|
|
INT TotalLines; // printable height/line height
|
|
INT TotalCols; // printable width/char width
|
|
HFONT FontHandle;
|
|
BOOL PageActive;
|
|
} PRINT_POSITION, *PPRINT_POSITION;
|
|
|
|
PCTSTR
|
|
pDrawLineText (
|
|
IN OUT PPRINT_POSITION PrintPos,
|
|
IN PCTSTR Text,
|
|
IN DWORD Flags,
|
|
IN BOOL Header
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Draws a single line of text on a printer device context and
|
|
returns a pointer to the next line or nul terminator.
|
|
|
|
Arguments:
|
|
|
|
PrintPos - A pointer to the current PRINT_POSITION structure
|
|
that gives page position settings.
|
|
Text - A pointer to the text string containing the line.
|
|
Flags - Additional DrawText flags (DT_LEFT, DT_CENTER,
|
|
DT_RIGHT and/or DT_RTLREADING)
|
|
Header - TRUE if text should be written to header, or
|
|
FALSE if it should be written to the current line
|
|
|
|
Return Value:
|
|
|
|
A pointer to the next line within the string, a pointer to the
|
|
nul terminator, or NULL if an error occurred.
|
|
|
|
--*/
|
|
|
|
{
|
|
RECT rect;
|
|
PCTSTR p;
|
|
CHARTYPE ch;
|
|
|
|
if (Header) {
|
|
CopyMemory (&rect, &PrintPos->HeaderRect, sizeof (RECT));
|
|
} else {
|
|
CopyMemory (&rect, &PrintPos->PrintableRect, sizeof (RECT));
|
|
rect.top += PrintPos->LineHeight * PrintPos->Line;
|
|
}
|
|
|
|
Flags = Flags & (DT_CENTER|DT_LEFT|DT_RIGHT|DT_RTLREADING);
|
|
Flags |= DT_TOP|DT_EDITCONTROL|DT_NOPREFIX|DT_EXTERNALLEADING;
|
|
|
|
for (ch = 0, p = Text ; *p ; p = _tcsinc (p)) {
|
|
ch = _tcsnextc (p);
|
|
if (ch == TEXT('\n') || ch == TEXT('\r')) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (p != Text) {
|
|
if (!DrawText (PrintPos->hdc, Text, p - Text, &rect, Flags)) {
|
|
LOG ((LOG_ERROR, "Failure while sending text to printer."));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// Skip past line break
|
|
if (ch == TEXT('\r')) {
|
|
p = _tcsinc (p);
|
|
ch = _tcsnextc (p);
|
|
}
|
|
|
|
if (ch == TEXT('\n')) {
|
|
p = _tcsinc (p);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pPrintString (
|
|
IN OUT PPRINT_POSITION PrintPos,
|
|
IN PCTSTR MultiLineString
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Dumps a multi-line string to the printer. If necessary, the
|
|
string may be printed on a new page. This function tries to
|
|
eliminate widows and orphans by printing the entire string
|
|
on the same page if possible.
|
|
|
|
Arguments:
|
|
|
|
PrintPos - The current position information, describing
|
|
the printer device context, page number, line
|
|
number and metrics.
|
|
|
|
MultiLineString - A pointer to the string to print.
|
|
|
|
Return Value:
|
|
|
|
TRUE if printing was successful, or FALSE if an error occurrred.
|
|
|
|
--*/
|
|
|
|
{
|
|
INT LineCount;
|
|
PCTSTR p;
|
|
PCTSTR Str;
|
|
PCTSTR Args[1];
|
|
TCHAR Buffer[32];
|
|
CHARTYPE ch;
|
|
HDC hdc;
|
|
|
|
hdc = PrintPos->hdc;
|
|
|
|
//
|
|
// Count lines in MultiLineString
|
|
//
|
|
|
|
ch = TEXT('\n');
|
|
for (LineCount = 0, p = MultiLineString ; *p ; p = _tcsinc (p)) {
|
|
ch = _tcsnextc (p);
|
|
if (ch == TEXT('\n')) {
|
|
LineCount++;
|
|
}
|
|
}
|
|
|
|
if (ch != TEXT('\n')) {
|
|
LineCount++;
|
|
}
|
|
|
|
//
|
|
// Widow/orphan suppression: If all lines do not fit on
|
|
|
|
// the page, and we are more than half way down the page,
|
|
// roll to the next page.
|
|
//
|
|
if (PrintPos->Line + LineCount > PrintPos->TotalLines) {
|
|
if (PrintPos->Line > PrintPos->TotalLines / 2) {
|
|
// Move to next page
|
|
EndPage (hdc);
|
|
PrintPos->PageActive = FALSE;
|
|
PrintPos->Page++;
|
|
PrintPos->Line = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Send each line in MultiLineString
|
|
//
|
|
|
|
while (*MultiLineString) {
|
|
|
|
//
|
|
// Draw header if necessary
|
|
//
|
|
|
|
if (!PrintPos->Line) {
|
|
StartPage (hdc);
|
|
PrintPos->PageActive = TRUE;
|
|
|
|
SetMapMode (hdc, MM_TWIPS);
|
|
SelectObject (hdc, PrintPos->FontHandle);
|
|
SetBkMode (hdc, TRANSPARENT);
|
|
|
|
//Rectangle (hdc, PrintPos->HeaderRect.left, PrintPos->HeaderRect.top, PrintPos->HeaderRect.right, PrintPos->HeaderRect.bottom);
|
|
//Rectangle (hdc, PrintPos->PrintableRect.left, PrintPos->PrintableRect.top, PrintPos->PrintableRect.right, PrintPos->PrintableRect.bottom);
|
|
|
|
wsprintf (Buffer, TEXT("%u"), PrintPos->Page);
|
|
Args[0] = Buffer;
|
|
|
|
// Left side
|
|
Str = ParseMessageID (
|
|
MSG_REPORT_HEADER_LEFT,
|
|
Args
|
|
);
|
|
|
|
if (Str && *Str) {
|
|
p = pDrawLineText (PrintPos, Str, DT_LEFT, TRUE);
|
|
|
|
if (!p) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// Center
|
|
Str = ParseMessageID (
|
|
MSG_REPORT_HEADER_CENTER,
|
|
Args
|
|
);
|
|
|
|
if (Str && *Str) {
|
|
|
|
p = pDrawLineText (PrintPos, Str, DT_CENTER, TRUE);
|
|
|
|
if (!p) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// Right side
|
|
Str = ParseMessageID (
|
|
MSG_REPORT_HEADER_RIGHT,
|
|
Args
|
|
);
|
|
|
|
if (Str && *Str) {
|
|
|
|
p = pDrawLineText (PrintPos, Str, DT_RIGHT, TRUE);
|
|
|
|
if (!p) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Draw line
|
|
//
|
|
|
|
MultiLineString = pDrawLineText (
|
|
PrintPos,
|
|
MultiLineString,
|
|
DT_LEFT,
|
|
FALSE
|
|
);
|
|
|
|
if (!MultiLineString) {
|
|
return FALSE;
|
|
}
|
|
|
|
PrintPos->Line++;
|
|
if (PrintPos->Line >= PrintPos->TotalLines) {
|
|
EndPage (hdc);
|
|
PrintPos->PageActive = FALSE;
|
|
PrintPos->Page++;
|
|
PrintPos->Line = 0;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
pCalculatePageMetrics (
|
|
IN OUT PPRINT_POSITION PrintPos
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Calculates all the page metrics (margins, header position,
|
|
line count, col count, etc). Positions are in TWIPS and
|
|
counts are in characters or lines.
|
|
|
|
Arguments:
|
|
|
|
PrintPos - Pointer to PRINT_POSITION structure which gives
|
|
the printer device context. Structure receives
|
|
metrics.
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
INT WidthPixels, HeightPixels;
|
|
INT DpiX, DpiY;
|
|
INT UnprintableLeftPixels, UnprintableTopPixels;
|
|
POINT TempPoint;
|
|
HDC hdc;
|
|
|
|
hdc = PrintPos->hdc;
|
|
|
|
//
|
|
// Make no assumptions about hdc
|
|
//
|
|
|
|
SetMapMode (hdc, MM_TWIPS);
|
|
SelectObject (hdc, PrintPos->FontHandle);
|
|
GetTextMetrics (hdc, &PrintPos->tm);
|
|
|
|
//
|
|
// Get device dimensions
|
|
//
|
|
|
|
DpiX = GetDeviceCaps (hdc, LOGPIXELSX);
|
|
DpiY = GetDeviceCaps (hdc, LOGPIXELSY);
|
|
UnprintableLeftPixels = GetDeviceCaps (hdc, PHYSICALOFFSETX);
|
|
UnprintableTopPixels = GetDeviceCaps (hdc, PHYSICALOFFSETY);
|
|
WidthPixels = GetDeviceCaps (hdc, PHYSICALWIDTH);
|
|
HeightPixels = GetDeviceCaps (hdc, PHYSICALHEIGHT);
|
|
|
|
// Calulate 3/4 inch left/right margins
|
|
PrintPos->HeaderRect.left = (DpiX * 3 / 4) - UnprintableLeftPixels;
|
|
PrintPos->HeaderRect.right = WidthPixels - (DpiX * 3 / 4) - UnprintableLeftPixels;
|
|
|
|
// Calculate 1/2 inch top margin for header
|
|
PrintPos->HeaderRect.top = (DpiY / 2) - UnprintableTopPixels;
|
|
PrintPos->HeaderRect.bottom = DpiY - UnprintableTopPixels;
|
|
|
|
// Convert pixels (device units) into logical units
|
|
DPtoLP (hdc, (LPPOINT) (&PrintPos->HeaderRect), 2);
|
|
|
|
// Copy header's left & right margins to printable rect
|
|
// Copy header's bottom margin to printable rect's top margin
|
|
PrintPos->PrintableRect.left = PrintPos->HeaderRect.left;
|
|
PrintPos->PrintableRect.right = PrintPos->HeaderRect.right;
|
|
PrintPos->PrintableRect.top = PrintPos->HeaderRect.bottom;
|
|
|
|
// Calculate printable rect's bottom margin (3/4 inch)
|
|
TempPoint.x = 0;
|
|
TempPoint.y = HeightPixels - (DpiY * 3 / 4) - UnprintableTopPixels;
|
|
DPtoLP (hdc, &TempPoint, 1);
|
|
PrintPos->PrintableRect.bottom = TempPoint.y;
|
|
|
|
PrintPos->LineHeight = -(PrintPos->tm.tmHeight + PrintPos->tm.tmInternalLeading + PrintPos->tm.tmExternalLeading);
|
|
MYASSERT (PrintPos->LineHeight);
|
|
|
|
PrintPos->TotalLines = (PrintPos->PrintableRect.bottom - PrintPos->PrintableRect.top) / PrintPos->LineHeight;
|
|
PrintPos->TotalCols = (PrintPos->PrintableRect.right - PrintPos->PrintableRect.left) / PrintPos->tm.tmAveCharWidth;
|
|
|
|
}
|
|
|
|
|
|
BOOL
|
|
PrintReport (
|
|
IN HWND Parent,
|
|
IN DWORD Level
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Obtains a printer from the user via common Print dialog,
|
|
starts the print job and sends an incompatibility list to
|
|
one or more pages.
|
|
|
|
Arguments:
|
|
|
|
Parent - A handle to the parent of the print dialog
|
|
|
|
Return Value:
|
|
|
|
TRUE if printing completed, or FALSE if it was canceled or an
|
|
error occurred.
|
|
|
|
--*/
|
|
|
|
{
|
|
HDC hdc;
|
|
PRINT_POSITION pp;
|
|
LOGFONT Font;
|
|
BOOL b;
|
|
DOCINFO di;
|
|
INT JobId;
|
|
PCTSTR Msg;
|
|
HANDLE DefaultUiFont;
|
|
|
|
hdc = pGetPrintDC (Parent);
|
|
if (!hdc) {
|
|
return FALSE; // user canceled print dialog
|
|
}
|
|
|
|
if (!BeginMessageProcessing()) {
|
|
// unexpected out-of-memory
|
|
DeleteDC (hdc);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Initialize PRINT_POSITION
|
|
//
|
|
|
|
b = TRUE;
|
|
TurnOnWaitCursor();
|
|
|
|
ZeroMemory (&pp, sizeof (pp));
|
|
pp.hdc = hdc;
|
|
pp.Page = 1;
|
|
|
|
//
|
|
// Start doc
|
|
//
|
|
|
|
ZeroMemory (&di, sizeof (di));
|
|
di.cbSize = sizeof (di);
|
|
di.lpszDocName = GetStringResource (MSG_REPORT_DOC_NAME);
|
|
MYASSERT (di.lpszDocName);
|
|
if (di.lpszDocName) {
|
|
|
|
JobId = StartDoc (hdc, &di);
|
|
if (!JobId) {
|
|
LOG ((LOG_ERROR, "Cannot start print job."));
|
|
ResourceMessageBox (Parent, MSG_CANT_PRINT, MB_OK, NULL);
|
|
b = FALSE;
|
|
}
|
|
} else {
|
|
//
|
|
// not enough memory
|
|
//
|
|
JobId = 0;
|
|
b = FALSE;
|
|
}
|
|
|
|
if (b) {
|
|
//
|
|
// Create font
|
|
//
|
|
|
|
ZeroMemory (&Font, sizeof (Font));
|
|
DefaultUiFont = (HFONT) GetStockObject (DEFAULT_GUI_FONT);
|
|
if (DefaultUiFont) {
|
|
GetObject (DefaultUiFont, sizeof (Font), &Font);
|
|
|
|
|
|
Font.lfHeight = 12 * 20; // height in TWIPS (1/20 of a point)
|
|
Font.lfWeight = FW_NORMAL;
|
|
Font.lfOutPrecision = OUT_TT_PRECIS;
|
|
Font.lfPitchAndFamily = FIXED_PITCH|FF_MODERN;
|
|
|
|
pp.FontHandle = CreateFontIndirect (&Font);
|
|
if (!pp.FontHandle) {
|
|
LOG ((LOG_ERROR, "Cannot create font for print operation."));
|
|
//
|
|
// deferred this call to the end
|
|
//
|
|
//ResourceMessageBox (Parent, MSG_CANT_PRINT, MB_OK, NULL);
|
|
b = FALSE;
|
|
}
|
|
} else {
|
|
b = FALSE;
|
|
}
|
|
}
|
|
|
|
if (b) {
|
|
//
|
|
// Create page metrics
|
|
//
|
|
|
|
pCalculatePageMetrics (&pp);
|
|
|
|
DEBUGMSG ((DBG_PRINTSAVE, "PrintReport: LineHeight=%i", pp.LineHeight));
|
|
DEBUGMSG ((DBG_PRINTSAVE, "PrintReport: TotalLines=%i", pp.TotalLines));
|
|
DEBUGMSG ((DBG_PRINTSAVE, "PrintReport: TotalCols=%i", pp.TotalCols));
|
|
DEBUGMSG ((DBG_PRINTSAVE, "PrintReport: Header rect: (%i, %i)-(%i, %i)", pp.HeaderRect.left, pp.HeaderRect.top, pp.HeaderRect.right, pp.HeaderRect.bottom));
|
|
DEBUGMSG ((DBG_PRINTSAVE, "PrintReport: Printable rect: (%i, %i)-(%i, %i)", pp.PrintableRect.left, pp.PrintableRect.top, pp.PrintableRect.right, pp.PrintableRect.bottom));
|
|
|
|
//
|
|
// Print the report
|
|
//
|
|
|
|
Msg = CreateReportText (FALSE, pp.TotalCols, Level, FALSE);
|
|
if (Msg) {
|
|
b = pPrintString (&pp, Msg);
|
|
}
|
|
FreeReportText();
|
|
}
|
|
|
|
if (JobId) {
|
|
if (b) {
|
|
if (pp.PageActive) {
|
|
EndPage (hdc);
|
|
}
|
|
|
|
EndDoc (hdc);
|
|
} else {
|
|
AbortDoc (hdc);
|
|
}
|
|
}
|
|
|
|
DeleteDC (hdc);
|
|
if (pp.FontHandle) {
|
|
DeleteObject (pp.FontHandle);
|
|
}
|
|
|
|
TurnOffWaitCursor();
|
|
|
|
if (!b) {
|
|
ResourceMessageBox (Parent, MSG_CANT_PRINT, MB_OK, NULL);
|
|
}
|
|
|
|
EndMessageProcessing();
|
|
return b;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|