1// Copyright 2014 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 "printing/printing_context_system_dialog_win.h"
6
7#include "base/message_loop/message_loop.h"
8#include "printing/backend/win_helper.h"
9#include "printing/print_settings_initializer_win.h"
10#include "skia/ext/platform_device.h"
11
12namespace printing {
13
14PrintingContextSytemDialogWin::PrintingContextSytemDialogWin(Delegate* delegate)
15    : PrintingContextWin(delegate), dialog_box_(NULL) {
16}
17
18PrintingContextSytemDialogWin::~PrintingContextSytemDialogWin() {
19}
20
21void PrintingContextSytemDialogWin::AskUserForSettings(
22    int max_pages,
23    bool has_selection,
24    const PrintSettingsCallback& callback) {
25  DCHECK(!in_print_job_);
26  dialog_box_dismissed_ = false;
27
28  HWND window = GetRootWindow(delegate_->GetParentView());
29  DCHECK(window);
30
31  // Show the OS-dependent dialog box.
32  // If the user press
33  // - OK, the settings are reset and reinitialized with the new settings. OK
34  // is
35  //   returned.
36  // - Apply then Cancel, the settings are reset and reinitialized with the
37  // new
38  //   settings. CANCEL is returned.
39  // - Cancel, the settings are not changed, the previous setting, if it was
40  //   initialized before, are kept. CANCEL is returned.
41  // On failure, the settings are reset and FAILED is returned.
42  PRINTDLGEX dialog_options = {sizeof(PRINTDLGEX)};
43  dialog_options.hwndOwner = window;
44  // Disable options we don't support currently.
45  // TODO(maruel):  Reuse the previously loaded settings!
46  dialog_options.Flags = PD_RETURNDC | PD_USEDEVMODECOPIESANDCOLLATE |
47                         PD_NOCURRENTPAGE | PD_HIDEPRINTTOFILE;
48  if (!has_selection)
49    dialog_options.Flags |= PD_NOSELECTION;
50
51  PRINTPAGERANGE ranges[32];
52  dialog_options.nStartPage = START_PAGE_GENERAL;
53  if (max_pages) {
54    // Default initialize to print all the pages.
55    memset(ranges, 0, sizeof(ranges));
56    ranges[0].nFromPage = 1;
57    ranges[0].nToPage = max_pages;
58    dialog_options.nPageRanges = 1;
59    dialog_options.nMaxPageRanges = arraysize(ranges);
60    dialog_options.nMinPage = 1;
61    dialog_options.nMaxPage = max_pages;
62    dialog_options.lpPageRanges = ranges;
63  } else {
64    // No need to bother, we don't know how many pages are available.
65    dialog_options.Flags |= PD_NOPAGENUMS;
66  }
67
68  if (ShowPrintDialog(&dialog_options) != S_OK) {
69    ResetSettings();
70    callback.Run(FAILED);
71  }
72
73  // TODO(maruel):  Support PD_PRINTTOFILE.
74  callback.Run(ParseDialogResultEx(dialog_options));
75}
76
77void PrintingContextSytemDialogWin::Cancel() {
78  PrintingContextWin::Cancel();
79  if (dialog_box_) {
80    DestroyWindow(dialog_box_);
81    dialog_box_dismissed_ = true;
82  }
83}
84
85HRESULT PrintingContextSytemDialogWin::ShowPrintDialog(PRINTDLGEX* options) {
86  // Note that this cannot use ui::BaseShellDialog as the print dialog is
87  // system modal: opening it from a background thread can cause Windows to
88  // get the wrong Z-order which will make the print dialog appear behind the
89  // browser frame (but still being modal) so neither the browser frame nor
90  // the print dialog will get any input. See http://crbug.com/342697
91  // http://crbug.com/180997 for details.
92  base::MessageLoop::ScopedNestableTaskAllower allow(
93      base::MessageLoop::current());
94
95  return PrintDlgEx(options);
96}
97
98bool PrintingContextSytemDialogWin::InitializeSettings(
99    const DEVMODE& dev_mode,
100    const std::wstring& new_device_name,
101    const PRINTPAGERANGE* ranges,
102    int number_ranges,
103    bool selection_only) {
104  DCHECK(GetDeviceCaps(context(), CLIPCAPS));
105  DCHECK(GetDeviceCaps(context(), RASTERCAPS) & RC_STRETCHDIB);
106  DCHECK(GetDeviceCaps(context(), RASTERCAPS) & RC_BITMAP64);
107  // Some printers don't advertise these.
108  // DCHECK(GetDeviceCaps(context(), RASTERCAPS) & RC_SCALING);
109  // DCHECK(GetDeviceCaps(context(), SHADEBLENDCAPS) & SB_CONST_ALPHA);
110  // DCHECK(GetDeviceCaps(context(), SHADEBLENDCAPS) & SB_PIXEL_ALPHA);
111
112  // StretchDIBits() support is needed for printing.
113  if (!(GetDeviceCaps(context(), RASTERCAPS) & RC_STRETCHDIB) ||
114      !(GetDeviceCaps(context(), RASTERCAPS) & RC_BITMAP64)) {
115    NOTREACHED();
116    ResetSettings();
117    return false;
118  }
119
120  DCHECK(!in_print_job_);
121  DCHECK(context());
122  PageRanges ranges_vector;
123  if (!selection_only) {
124    // Convert the PRINTPAGERANGE array to a PrintSettings::PageRanges vector.
125    ranges_vector.reserve(number_ranges);
126    for (int i = 0; i < number_ranges; ++i) {
127      PageRange range;
128      // Transfer from 1-based to 0-based.
129      range.from = ranges[i].nFromPage - 1;
130      range.to = ranges[i].nToPage - 1;
131      ranges_vector.push_back(range);
132    }
133  }
134
135  settings_.set_ranges(ranges_vector);
136  settings_.set_device_name(new_device_name);
137  settings_.set_selection_only(selection_only);
138  PrintSettingsInitializerWin::InitPrintSettings(
139      context(), dev_mode, &settings_);
140
141  return true;
142}
143
144PrintingContext::Result PrintingContextSytemDialogWin::ParseDialogResultEx(
145    const PRINTDLGEX& dialog_options) {
146  // If the user clicked OK or Apply then Cancel, but not only Cancel.
147  if (dialog_options.dwResultAction != PD_RESULT_CANCEL) {
148    // Start fresh.
149    ResetSettings();
150
151    DEVMODE* dev_mode = NULL;
152    if (dialog_options.hDevMode) {
153      dev_mode =
154          reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode));
155      DCHECK(dev_mode);
156    }
157
158    std::wstring device_name;
159    if (dialog_options.hDevNames) {
160      DEVNAMES* dev_names =
161          reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames));
162      DCHECK(dev_names);
163      if (dev_names) {
164        device_name = reinterpret_cast<const wchar_t*>(dev_names) +
165                      dev_names->wDeviceOffset;
166        GlobalUnlock(dialog_options.hDevNames);
167      }
168    }
169
170    bool success = false;
171    if (dev_mode && !device_name.empty()) {
172      set_context(dialog_options.hDC);
173      PRINTPAGERANGE* page_ranges = NULL;
174      DWORD num_page_ranges = 0;
175      bool print_selection_only = false;
176      if (dialog_options.Flags & PD_PAGENUMS) {
177        page_ranges = dialog_options.lpPageRanges;
178        num_page_ranges = dialog_options.nPageRanges;
179      }
180      if (dialog_options.Flags & PD_SELECTION) {
181        print_selection_only = true;
182      }
183      success = InitializeSettings(*dev_mode,
184                                   device_name,
185                                   page_ranges,
186                                   num_page_ranges,
187                                   print_selection_only);
188    }
189
190    if (!success && dialog_options.hDC) {
191      DeleteDC(dialog_options.hDC);
192      set_context(NULL);
193    }
194
195    if (dev_mode) {
196      GlobalUnlock(dialog_options.hDevMode);
197    }
198  } else {
199    if (dialog_options.hDC) {
200      DeleteDC(dialog_options.hDC);
201    }
202  }
203
204  if (dialog_options.hDevMode != NULL)
205    GlobalFree(dialog_options.hDevMode);
206  if (dialog_options.hDevNames != NULL)
207    GlobalFree(dialog_options.hDevNames);
208
209  switch (dialog_options.dwResultAction) {
210    case PD_RESULT_PRINT:
211      return context() ? OK : FAILED;
212    case PD_RESULT_APPLY:
213      return context() ? CANCEL : FAILED;
214    case PD_RESULT_CANCEL:
215      return CANCEL;
216    default:
217      return FAILED;
218  }
219}
220
221PrintingContext::Result PrintingContextSytemDialogWin::ParseDialogResult(
222    const PRINTDLG& dialog_options) {
223  // If the user clicked OK or Apply then Cancel, but not only Cancel.
224  // Start fresh.
225  ResetSettings();
226
227  DEVMODE* dev_mode = NULL;
228  if (dialog_options.hDevMode) {
229    dev_mode = reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode));
230    DCHECK(dev_mode);
231  }
232
233  std::wstring device_name;
234  if (dialog_options.hDevNames) {
235    DEVNAMES* dev_names =
236        reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames));
237    DCHECK(dev_names);
238    if (dev_names) {
239      device_name = reinterpret_cast<const wchar_t*>(
240          reinterpret_cast<const wchar_t*>(dev_names) +
241          dev_names->wDeviceOffset);
242      GlobalUnlock(dialog_options.hDevNames);
243    }
244  }
245
246  bool success = false;
247  if (dev_mode && !device_name.empty()) {
248    set_context(dialog_options.hDC);
249    success = InitializeSettings(*dev_mode, device_name, NULL, 0, false);
250  }
251
252  if (!success && dialog_options.hDC) {
253    DeleteDC(dialog_options.hDC);
254    set_context(NULL);
255  }
256
257  if (dev_mode) {
258    GlobalUnlock(dialog_options.hDevMode);
259  }
260
261  if (dialog_options.hDevMode != NULL)
262    GlobalFree(dialog_options.hDevMode);
263  if (dialog_options.hDevNames != NULL)
264    GlobalFree(dialog_options.hDevNames);
265
266  return context() ? OK : FAILED;
267}
268
269}  // namespace printing
270