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