blob: 8706b054a8092f19d1cdadbbae55a4d05ad11914 [file] [log] [blame]
license.botbf09a502008-08-24 00:55:551// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
initial.commit09911bf2008-07-26 23:55:294
[email protected]8ff1d422009-07-07 21:31:395#include "printing/printing_context.h"
initial.commit09911bf2008-07-26 23:55:296
7#include <winspool.h>
8
9#include "base/file_util.h"
[email protected]dcccb942009-02-01 18:23:0010#include "base/message_loop.h"
11#include "base/time.h"
[email protected]5cca3a52008-08-19 22:35:2912#include "base/time_format.h"
[email protected]8ff1d422009-07-07 21:31:3913#include "printing/printed_document.h"
[email protected]c399a8a2008-11-22 19:38:0014#include "skia/ext/platform_device_win.h"
initial.commit09911bf2008-07-26 23:55:2915
[email protected]e1acf6f2008-10-27 20:43:3316using base::Time;
17
initial.commit09911bf2008-07-26 23:55:2918namespace {
19
20// Retrieves the content of a GetPrinter call.
21void GetPrinterHelper(HANDLE printer, int level, scoped_array<uint8>* buffer) {
22 DWORD buf_size = 0;
23 GetPrinter(printer, level, NULL, 0, &buf_size);
24 if (buf_size) {
25 buffer->reset(new uint8[buf_size]);
26 memset(buffer->get(), 0, buf_size);
27 if (!GetPrinter(printer, level, buffer->get(), buf_size, &buf_size)) {
28 buffer->reset();
29 }
30 }
31}
32
33} // namespace
34
35namespace printing {
36
37class PrintingContext::CallbackHandler
38 : public IPrintDialogCallback,
39 public IObjectWithSite {
40 public:
41 CallbackHandler(PrintingContext& owner, HWND owner_hwnd)
42 : owner_(owner),
43 owner_hwnd_(owner_hwnd),
44 services_(NULL) {
45 }
46
47 ~CallbackHandler() {
48 if (services_)
49 services_->Release();
50 }
51
52 IUnknown* ToIUnknown() {
53 return static_cast<IUnknown*>(static_cast<IPrintDialogCallback*>(this));
54 }
55
56 // IUnknown
57 virtual HRESULT WINAPI QueryInterface(REFIID riid, void**object) {
58 if (riid == IID_IUnknown) {
59 *object = ToIUnknown();
60 } else if (riid == IID_IPrintDialogCallback) {
61 *object = static_cast<IPrintDialogCallback*>(this);
62 } else if (riid == IID_IObjectWithSite) {
63 *object = static_cast<IObjectWithSite*>(this);
64 } else {
65 return E_NOINTERFACE;
66 }
67 return S_OK;
68 }
69
70 // No real ref counting.
71 virtual ULONG WINAPI AddRef() {
72 return 1;
73 }
74 virtual ULONG WINAPI Release() {
75 return 1;
76 }
77
78 // IPrintDialogCallback methods
79 virtual HRESULT WINAPI InitDone() {
80 return S_OK;
81 }
82
83 virtual HRESULT WINAPI SelectionChange() {
84 if (services_) {
85 // TODO(maruel): Get the devmode for the new printer with
86 // services_->GetCurrentDevMode(&devmode, &size), send that information
87 // back to our client and continue. The client needs to recalculate the
88 // number of rendered pages and send back this information here.
89 }
90 return S_OK;
91 }
92
93 virtual HRESULT WINAPI HandleMessage(HWND dialog,
94 UINT message,
95 WPARAM wparam,
96 LPARAM lparam,
97 LRESULT* result) {
98 // Cheap way to retrieve the window handle.
99 if (!owner_.dialog_box_) {
100 // The handle we receive is the one of the groupbox in the General tab. We
101 // need to get the grand-father to get the dialog box handle.
102 owner_.dialog_box_ = GetAncestor(dialog, GA_ROOT);
103 // Trick to enable the owner window. This can cause issues with navigation
104 // events so it may have to be disabled if we don't fix the side-effects.
105 EnableWindow(owner_hwnd_, TRUE);
106 }
107 return S_FALSE;
108 }
109
110 virtual HRESULT WINAPI SetSite(IUnknown* site) {
111 if (!site) {
112 DCHECK(services_);
113 services_->Release();
114 services_ = NULL;
115 // The dialog box is destroying, PrintJob::Worker don't need the handle
116 // anymore.
117 owner_.dialog_box_ = NULL;
118 } else {
119 DCHECK(services_ == NULL);
120 HRESULT hr = site->QueryInterface(IID_IPrintDialogServices,
121 reinterpret_cast<void**>(&services_));
122 DCHECK(SUCCEEDED(hr));
123 }
124 return S_OK;
125 }
126
127 virtual HRESULT WINAPI GetSite(REFIID riid, void** site) {
128 return E_NOTIMPL;
129 }
130
131 private:
132 PrintingContext& owner_;
133 HWND owner_hwnd_;
134 IPrintDialogServices* services_;
135
136 DISALLOW_EVIL_CONSTRUCTORS(CallbackHandler);
137};
138
139PrintingContext::PrintingContext()
140 : hdc_(NULL),
[email protected]0ae80b892008-10-15 17:56:40141#ifndef NDEBUG
initial.commit09911bf2008-07-26 23:55:29142 page_number_(-1),
143#endif
144 dialog_box_(NULL),
145 dialog_box_dismissed_(false),
[email protected]8ff1d422009-07-07 21:31:39146 in_print_job_(false),
147 abort_printing_(false) {
initial.commit09911bf2008-07-26 23:55:29148}
149
150PrintingContext::~PrintingContext() {
151 ResetSettings();
152}
153
[email protected]c8ad40c2009-06-08 17:05:21154PrintingContext::Result PrintingContext::AskUserForSettings(
155 HWND window,
156 int max_pages,
157 bool has_selection) {
initial.commit09911bf2008-07-26 23:55:29158 DCHECK(window);
159 DCHECK(!in_print_job_);
160 dialog_box_dismissed_ = false;
161 // Show the OS-dependent dialog box.
162 // If the user press
163 // - OK, the settings are reset and reinitialized with the new settings. OK is
164 // returned.
165 // - Apply then Cancel, the settings are reset and reinitialized with the new
166 // settings. CANCEL is returned.
167 // - Cancel, the settings are not changed, the previous setting, if it was
168 // initialized before, are kept. CANCEL is returned.
169 // On failure, the settings are reset and FAILED is returned.
170 PRINTDLGEX dialog_options = { sizeof(PRINTDLGEX) };
171 dialog_options.hwndOwner = window;
[email protected]c8ad40c2009-06-08 17:05:21172 // Disable options we don't support currently.
initial.commit09911bf2008-07-26 23:55:29173 // TODO(maruel): Reuse the previously loaded settings!
174 dialog_options.Flags = PD_RETURNDC | PD_USEDEVMODECOPIESANDCOLLATE |
[email protected]c8ad40c2009-06-08 17:05:21175 PD_NOCURRENTPAGE | PD_HIDEPRINTTOFILE;
176 if (!has_selection)
177 dialog_options.Flags |= PD_NOSELECTION;
178
initial.commit09911bf2008-07-26 23:55:29179 PRINTPAGERANGE ranges[32];
180 dialog_options.nStartPage = START_PAGE_GENERAL;
181 if (max_pages) {
182 // Default initialize to print all the pages.
183 memset(ranges, 0, sizeof(ranges));
184 ranges[0].nFromPage = 1;
185 ranges[0].nToPage = max_pages;
186 dialog_options.nPageRanges = 1;
187 dialog_options.nMaxPageRanges = arraysize(ranges);
[email protected]3a0e4a32009-06-09 19:07:05188 dialog_options.nMinPage = 1;
initial.commit09911bf2008-07-26 23:55:29189 dialog_options.nMaxPage = max_pages;
190 dialog_options.lpPageRanges = ranges;
191 } else {
192 // No need to bother, we don't know how many pages are available.
193 dialog_options.Flags |= PD_NOPAGENUMS;
194 }
195
196 {
initial.commit09911bf2008-07-26 23:55:29197 if (PrintDlgEx(&dialog_options) != S_OK) {
198 ResetSettings();
199 return FAILED;
200 }
201 }
202 // TODO(maruel): Support PD_PRINTTOFILE.
203 return ParseDialogResultEx(dialog_options);
204}
205
206PrintingContext::Result PrintingContext::UseDefaultSettings() {
207 DCHECK(!in_print_job_);
208
209 PRINTDLG dialog_options = { sizeof(PRINTDLG) };
210 dialog_options.Flags = PD_RETURNDC | PD_RETURNDEFAULT;
211 if (PrintDlg(&dialog_options) == 0) {
212 ResetSettings();
213 return FAILED;
214 }
215 return ParseDialogResult(dialog_options);
216}
217
218PrintingContext::Result PrintingContext::InitWithSettings(
219 const PrintSettings& settings) {
220 DCHECK(!in_print_job_);
221 settings_ = settings;
222 // TODO(maruel): settings_->ToDEVMODE()
223 HANDLE printer;
224 if (!OpenPrinter(const_cast<wchar_t*>(settings_.device_name().c_str()),
225 &printer,
226 NULL))
227 return FAILED;
228
229 Result status = OK;
230
231 if (!GetPrinterSettings(printer, settings_.device_name()))
232 status = FAILED;
233
234 // Close the printer after retrieving the context.
235 ClosePrinter(printer);
236
237 if (status != OK)
238 ResetSettings();
239 return status;
240}
241
242void PrintingContext::ResetSettings() {
243 if (hdc_ != NULL) {
244 DeleteDC(hdc_);
245 hdc_ = NULL;
246 }
247 settings_.Clear();
248 in_print_job_ = false;
249
[email protected]0ae80b892008-10-15 17:56:40250#ifndef NDEBUG
initial.commit09911bf2008-07-26 23:55:29251 page_number_ = -1;
252#endif
253}
254
255PrintingContext::Result PrintingContext::NewDocument(
256 const std::wstring& document_name) {
257 DCHECK(!in_print_job_);
258 if (!hdc_)
[email protected]c8ad40c2009-06-08 17:05:21259 return OnError();
initial.commit09911bf2008-07-26 23:55:29260
261 // Set the flag used by the AbortPrintJob dialog procedure.
262 abort_printing_ = false;
263
264 in_print_job_ = true;
265
266 // Register the application's AbortProc function with GDI.
267 if (SP_ERROR == SetAbortProc(hdc_, &AbortProc))
[email protected]c8ad40c2009-06-08 17:05:21268 return OnError();
initial.commit09911bf2008-07-26 23:55:29269
270 DOCINFO di = { sizeof(DOCINFO) };
271 di.lpszDocName = document_name.c_str();
272
273 // Is there a debug dump directory specified? If so, force to print to a file.
[email protected]e1504d82009-07-03 15:27:15274 std::wstring debug_dump_path = PrintedDocument::debug_dump_path();
initial.commit09911bf2008-07-26 23:55:29275 if (!debug_dump_path.empty()) {
276 // Create a filename.
277 std::wstring filename;
278 Time now(Time::Now());
[email protected]5cca3a52008-08-19 22:35:29279 filename = base::TimeFormatShortDateNumeric(now);
initial.commit09911bf2008-07-26 23:55:29280 filename += L"_";
[email protected]5cca3a52008-08-19 22:35:29281 filename += base::TimeFormatTimeOfDay(now);
initial.commit09911bf2008-07-26 23:55:29282 filename += L"_";
283 filename += document_name;
284 filename += L"_";
285 filename += L"buffer.prn";
286 file_util::ReplaceIllegalCharacters(&filename, '_');
287 file_util::AppendToPath(&debug_dump_path, filename);
288 di.lpszOutput = debug_dump_path.c_str();
289 }
290
[email protected]daee4972009-07-09 14:28:24291 // No message loop running in unit tests.
292 DCHECK(!MessageLoop::current() ? true :
293 !MessageLoop::current()->NestableTasksAllowed());
294
initial.commit09911bf2008-07-26 23:55:29295 // Begin a print job by calling the StartDoc function.
296 // NOTE: StartDoc() starts a message loop. That causes a lot of problems with
297 // IPC. Make sure recursive task processing is disabled.
298 if (StartDoc(hdc_, &di) <= 0)
[email protected]c8ad40c2009-06-08 17:05:21299 return OnError();
initial.commit09911bf2008-07-26 23:55:29300
[email protected]0ae80b892008-10-15 17:56:40301#ifndef NDEBUG
initial.commit09911bf2008-07-26 23:55:29302 page_number_ = 0;
303#endif
304 return OK;
305}
306
307PrintingContext::Result PrintingContext::NewPage() {
308 if (abort_printing_)
309 return CANCEL;
310 DCHECK(in_print_job_);
311
312 // Inform the driver that the application is about to begin sending data.
313 if (StartPage(hdc_) <= 0)
[email protected]c8ad40c2009-06-08 17:05:21314 return OnError();
initial.commit09911bf2008-07-26 23:55:29315
[email protected]0ae80b892008-10-15 17:56:40316#ifndef NDEBUG
initial.commit09911bf2008-07-26 23:55:29317 ++page_number_;
318#endif
319
320 return OK;
321}
322
323PrintingContext::Result PrintingContext::PageDone() {
324 if (abort_printing_)
325 return CANCEL;
326 DCHECK(in_print_job_);
327
328 if (EndPage(hdc_) <= 0)
[email protected]c8ad40c2009-06-08 17:05:21329 return OnError();
initial.commit09911bf2008-07-26 23:55:29330 return OK;
331}
332
333PrintingContext::Result PrintingContext::DocumentDone() {
334 if (abort_printing_)
335 return CANCEL;
336 DCHECK(in_print_job_);
337
338 // Inform the driver that document has ended.
339 if (EndDoc(hdc_) <= 0)
[email protected]c8ad40c2009-06-08 17:05:21340 return OnError();
initial.commit09911bf2008-07-26 23:55:29341
342 ResetSettings();
343 return OK;
344}
345
346void PrintingContext::Cancel() {
347 abort_printing_ = true;
348 in_print_job_ = false;
349 if (hdc_)
350 CancelDC(hdc_);
351 DismissDialog();
352}
353
354void PrintingContext::DismissDialog() {
355 if (dialog_box_) {
356 DestroyWindow(dialog_box_);
357 dialog_box_dismissed_ = true;
358 }
359}
360
[email protected]c8ad40c2009-06-08 17:05:21361PrintingContext::Result PrintingContext::OnError() {
initial.commit09911bf2008-07-26 23:55:29362 // This will close hdc_ and clear settings_.
363 ResetSettings();
364 return abort_printing_ ? CANCEL : FAILED;
365}
366
367// static
368BOOL PrintingContext::AbortProc(HDC hdc, int nCode) {
369 if (nCode) {
370 // TODO(maruel): Need a way to find the right instance to set. Should
371 // leverage PrintJobManager here?
372 // abort_printing_ = true;
373 }
374 return true;
375}
376
377bool PrintingContext::InitializeSettings(const DEVMODE& dev_mode,
378 const std::wstring& new_device_name,
379 const PRINTPAGERANGE* ranges,
[email protected]c8ad40c2009-06-08 17:05:21380 int number_ranges,
381 bool selection_only) {
[email protected]bd63dc92009-06-14 15:14:53382 skia::PlatformDevice::InitializeDC(hdc_);
initial.commit09911bf2008-07-26 23:55:29383 DCHECK(GetDeviceCaps(hdc_, CLIPCAPS));
384 DCHECK(GetDeviceCaps(hdc_, RASTERCAPS) & RC_STRETCHDIB);
385 DCHECK(GetDeviceCaps(hdc_, RASTERCAPS) & RC_BITMAP64);
386 // Some printers don't advertise these.
387 // DCHECK(GetDeviceCaps(hdc_, RASTERCAPS) & RC_SCALING);
388 // DCHECK(GetDeviceCaps(hdc_, SHADEBLENDCAPS) & SB_CONST_ALPHA);
389 // DCHECK(GetDeviceCaps(hdc_, SHADEBLENDCAPS) & SB_PIXEL_ALPHA);
390
391 // StretchDIBits() support is needed for printing.
392 if (!(GetDeviceCaps(hdc_, RASTERCAPS) & RC_STRETCHDIB) ||
393 !(GetDeviceCaps(hdc_, RASTERCAPS) & RC_BITMAP64)) {
394 NOTREACHED();
395 ResetSettings();
396 return false;
397 }
398
399 DCHECK(!in_print_job_);
400 DCHECK(hdc_);
initial.commit09911bf2008-07-26 23:55:29401 PageRanges ranges_vector;
[email protected]82270452009-06-19 15:58:01402 if (!selection_only) {
403 // Convert the PRINTPAGERANGE array to a PrintSettings::PageRanges vector.
404 ranges_vector.reserve(number_ranges);
405 for (int i = 0; i < number_ranges; ++i) {
406 PageRange range;
407 // Transfer from 1-based to 0-based.
408 range.from = ranges[i].nFromPage - 1;
409 range.to = ranges[i].nToPage - 1;
410 ranges_vector.push_back(range);
411 }
initial.commit09911bf2008-07-26 23:55:29412 }
[email protected]c8ad40c2009-06-08 17:05:21413 settings_.Init(hdc_,
414 dev_mode,
415 ranges_vector,
416 new_device_name,
417 selection_only);
initial.commit09911bf2008-07-26 23:55:29418 return true;
419}
420
421bool PrintingContext::GetPrinterSettings(HANDLE printer,
422 const std::wstring& device_name) {
423 DCHECK(!in_print_job_);
424 scoped_array<uint8> buffer;
425
426 // A PRINTER_INFO_9 structure specifying the per-user default printer
427 // settings.
428 GetPrinterHelper(printer, 9, &buffer);
429 if (buffer.get()) {
430 PRINTER_INFO_9* info_9 = reinterpret_cast<PRINTER_INFO_9*>(buffer.get());
431 if (info_9->pDevMode != NULL) {
432 if (!AllocateContext(device_name, info_9->pDevMode)) {
433 ResetSettings();
434 return false;
435 }
[email protected]c8ad40c2009-06-08 17:05:21436 return InitializeSettings(*info_9->pDevMode, device_name, NULL, 0, false);
initial.commit09911bf2008-07-26 23:55:29437 }
438 buffer.reset();
439 }
440
441 // A PRINTER_INFO_8 structure specifying the global default printer settings.
442 GetPrinterHelper(printer, 8, &buffer);
443 if (buffer.get()) {
444 PRINTER_INFO_8* info_8 = reinterpret_cast<PRINTER_INFO_8*>(buffer.get());
445 if (info_8->pDevMode != NULL) {
446 if (!AllocateContext(device_name, info_8->pDevMode)) {
447 ResetSettings();
448 return false;
449 }
[email protected]c8ad40c2009-06-08 17:05:21450 return InitializeSettings(*info_8->pDevMode, device_name, NULL, 0, false);
initial.commit09911bf2008-07-26 23:55:29451 }
452 buffer.reset();
453 }
454
455 // A PRINTER_INFO_2 structure specifying the driver's default printer
456 // settings.
457 GetPrinterHelper(printer, 2, &buffer);
458 if (buffer.get()) {
459 PRINTER_INFO_2* info_2 = reinterpret_cast<PRINTER_INFO_2*>(buffer.get());
460 if (info_2->pDevMode != NULL) {
461 if (!AllocateContext(device_name, info_2->pDevMode)) {
462 ResetSettings();
463 return false;
464 }
[email protected]c8ad40c2009-06-08 17:05:21465 return InitializeSettings(*info_2->pDevMode, device_name, NULL, 0, false);
initial.commit09911bf2008-07-26 23:55:29466 }
467 buffer.reset();
468 }
469 // Failed to retrieve the printer settings.
470 ResetSettings();
471 return false;
472}
473
474bool PrintingContext::AllocateContext(const std::wstring& printer_name,
475 const DEVMODE* dev_mode) {
476 hdc_ = CreateDC(L"WINSPOOL", printer_name.c_str(), NULL, dev_mode);
477 DCHECK(hdc_);
478 return hdc_ != NULL;
479}
480
481PrintingContext::Result PrintingContext::ParseDialogResultEx(
482 const PRINTDLGEX& dialog_options) {
483 // If the user clicked OK or Apply then Cancel, but not only Cancel.
484 if (dialog_options.dwResultAction != PD_RESULT_CANCEL) {
485 // Start fresh.
486 ResetSettings();
487
488 DEVMODE* dev_mode = NULL;
489 if (dialog_options.hDevMode) {
490 dev_mode =
491 reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode));
492 DCHECK(dev_mode);
493 }
494
495 std::wstring device_name;
496 if (dialog_options.hDevNames) {
497 DEVNAMES* dev_names =
498 reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames));
499 DCHECK(dev_names);
500 if (dev_names) {
501 device_name =
502 reinterpret_cast<const wchar_t*>(
503 reinterpret_cast<const wchar_t*>(dev_names) +
504 dev_names->wDeviceOffset);
505 GlobalUnlock(dialog_options.hDevNames);
506 }
507 }
508
509 bool success = false;
510 if (dev_mode && !device_name.empty()) {
511 hdc_ = dialog_options.hDC;
[email protected]c8ad40c2009-06-08 17:05:21512 PRINTPAGERANGE* page_ranges = NULL;
513 DWORD num_page_ranges = 0;
514 bool print_selection_only = false;
initial.commit09911bf2008-07-26 23:55:29515 if (dialog_options.Flags & PD_PAGENUMS) {
[email protected]c8ad40c2009-06-08 17:05:21516 page_ranges = dialog_options.lpPageRanges;
517 num_page_ranges = dialog_options.nPageRanges;
initial.commit09911bf2008-07-26 23:55:29518 }
[email protected]c8ad40c2009-06-08 17:05:21519 if (dialog_options.Flags & PD_SELECTION) {
520 print_selection_only = true;
521 }
522 success = InitializeSettings(*dev_mode,
523 device_name,
524 dialog_options.lpPageRanges,
525 dialog_options.nPageRanges,
526 print_selection_only);
initial.commit09911bf2008-07-26 23:55:29527 }
528
529 if (!success && dialog_options.hDC) {
530 DeleteDC(dialog_options.hDC);
531 hdc_ = NULL;
532 }
533
534 if (dev_mode) {
535 GlobalUnlock(dialog_options.hDevMode);
536 }
537 } else {
538 if (dialog_options.hDC) {
539 DeleteDC(dialog_options.hDC);
540 }
541 }
542
543 if (dialog_options.hDevMode != NULL)
544 GlobalFree(dialog_options.hDevMode);
545 if (dialog_options.hDevNames != NULL)
546 GlobalFree(dialog_options.hDevNames);
547
548 switch (dialog_options.dwResultAction) {
549 case PD_RESULT_PRINT:
550 return hdc_ ? OK : FAILED;
551 case PD_RESULT_APPLY:
552 return hdc_ ? CANCEL : FAILED;
553 case PD_RESULT_CANCEL:
554 return CANCEL;
555 default:
556 return FAILED;
557 }
558}
559
560PrintingContext::Result PrintingContext::ParseDialogResult(
561 const PRINTDLG& dialog_options) {
562 // If the user clicked OK or Apply then Cancel, but not only Cancel.
563 // Start fresh.
564 ResetSettings();
565
566 DEVMODE* dev_mode = NULL;
567 if (dialog_options.hDevMode) {
568 dev_mode =
569 reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode));
570 DCHECK(dev_mode);
571 }
572
573 std::wstring device_name;
574 if (dialog_options.hDevNames) {
575 DEVNAMES* dev_names =
576 reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames));
577 DCHECK(dev_names);
578 if (dev_names) {
579 device_name =
580 reinterpret_cast<const wchar_t*>(
581 reinterpret_cast<const wchar_t*>(dev_names) +
582 dev_names->wDeviceOffset);
583 GlobalUnlock(dialog_options.hDevNames);
584 }
585 }
586
587 bool success = false;
588 if (dev_mode && !device_name.empty()) {
589 hdc_ = dialog_options.hDC;
[email protected]c8ad40c2009-06-08 17:05:21590 success = InitializeSettings(*dev_mode, device_name, NULL, 0, false);
initial.commit09911bf2008-07-26 23:55:29591 }
592
593 if (!success && dialog_options.hDC) {
594 DeleteDC(dialog_options.hDC);
595 hdc_ = NULL;
596 }
597
598 if (dev_mode) {
599 GlobalUnlock(dialog_options.hDevMode);
600 }
601
602 if (dialog_options.hDevMode != NULL)
603 GlobalFree(dialog_options.hDevMode);
604 if (dialog_options.hDevNames != NULL)
605 GlobalFree(dialog_options.hDevNames);
606
607 return hdc_ ? OK : FAILED;
608}
609
610} // namespace printing