summaryrefslogtreecommitdiffstats
path: root/chrome/browser/printing/win_printing_context.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/printing/win_printing_context.cc')
-rw-r--r--chrome/browser/printing/win_printing_context.cc629
1 files changed, 629 insertions, 0 deletions
diff --git a/chrome/browser/printing/win_printing_context.cc b/chrome/browser/printing/win_printing_context.cc
new file mode 100644
index 0000000..81315f3
--- /dev/null
+++ b/chrome/browser/printing/win_printing_context.cc
@@ -0,0 +1,629 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "chrome/browser/printing/win_printing_context.h"
+
+#include <winspool.h>
+
+#include "base/file_util.h"
+#include "base/gfx/platform_device.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/printing/print_job_manager.h"
+#include "chrome/common/gfx/emf.h"
+#include "chrome/common/time_format.h"
+
+namespace {
+
+// Retrieves the content of a GetPrinter call.
+void GetPrinterHelper(HANDLE printer, int level, scoped_array<uint8>* buffer) {
+ DWORD buf_size = 0;
+ GetPrinter(printer, level, NULL, 0, &buf_size);
+ if (buf_size) {
+ buffer->reset(new uint8[buf_size]);
+ memset(buffer->get(), 0, buf_size);
+ if (!GetPrinter(printer, level, buffer->get(), buf_size, &buf_size)) {
+ buffer->reset();
+ }
+ }
+}
+
+} // namespace
+
+namespace printing {
+
+class PrintingContext::CallbackHandler
+ : public IPrintDialogCallback,
+ public IObjectWithSite {
+ public:
+ CallbackHandler(PrintingContext& owner, HWND owner_hwnd)
+ : owner_(owner),
+ owner_hwnd_(owner_hwnd),
+ services_(NULL) {
+ }
+
+ ~CallbackHandler() {
+ if (services_)
+ services_->Release();
+ }
+
+ IUnknown* ToIUnknown() {
+ return static_cast<IUnknown*>(static_cast<IPrintDialogCallback*>(this));
+ }
+
+ // IUnknown
+ virtual HRESULT WINAPI QueryInterface(REFIID riid, void**object) {
+ if (riid == IID_IUnknown) {
+ *object = ToIUnknown();
+ } else if (riid == IID_IPrintDialogCallback) {
+ *object = static_cast<IPrintDialogCallback*>(this);
+ } else if (riid == IID_IObjectWithSite) {
+ *object = static_cast<IObjectWithSite*>(this);
+ } else {
+ return E_NOINTERFACE;
+ }
+ return S_OK;
+ }
+
+ // No real ref counting.
+ virtual ULONG WINAPI AddRef() {
+ return 1;
+ }
+ virtual ULONG WINAPI Release() {
+ return 1;
+ }
+
+ // IPrintDialogCallback methods
+ virtual HRESULT WINAPI InitDone() {
+ return S_OK;
+ }
+
+ virtual HRESULT WINAPI SelectionChange() {
+ if (services_) {
+ // TODO(maruel): Get the devmode for the new printer with
+ // services_->GetCurrentDevMode(&devmode, &size), send that information
+ // back to our client and continue. The client needs to recalculate the
+ // number of rendered pages and send back this information here.
+ }
+ return S_OK;
+ }
+
+ virtual HRESULT WINAPI HandleMessage(HWND dialog,
+ UINT message,
+ WPARAM wparam,
+ LPARAM lparam,
+ LRESULT* result) {
+ // Cheap way to retrieve the window handle.
+ if (!owner_.dialog_box_) {
+ // The handle we receive is the one of the groupbox in the General tab. We
+ // need to get the grand-father to get the dialog box handle.
+ owner_.dialog_box_ = GetAncestor(dialog, GA_ROOT);
+ // Trick to enable the owner window. This can cause issues with navigation
+ // events so it may have to be disabled if we don't fix the side-effects.
+ EnableWindow(owner_hwnd_, TRUE);
+ }
+ return S_FALSE;
+ }
+
+ virtual HRESULT WINAPI SetSite(IUnknown* site) {
+ if (!site) {
+ DCHECK(services_);
+ services_->Release();
+ services_ = NULL;
+ // The dialog box is destroying, PrintJob::Worker don't need the handle
+ // anymore.
+ owner_.dialog_box_ = NULL;
+ } else {
+ DCHECK(services_ == NULL);
+ HRESULT hr = site->QueryInterface(IID_IPrintDialogServices,
+ reinterpret_cast<void**>(&services_));
+ DCHECK(SUCCEEDED(hr));
+ }
+ return S_OK;
+ }
+
+ virtual HRESULT WINAPI GetSite(REFIID riid, void** site) {
+ return E_NOTIMPL;
+ }
+
+ private:
+ PrintingContext& owner_;
+ HWND owner_hwnd_;
+ IPrintDialogServices* services_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(CallbackHandler);
+};
+
+PrintingContext::PrintingContext()
+ : hdc_(NULL),
+#ifdef _DEBUG
+ page_number_(-1),
+#endif
+ dialog_box_(NULL),
+ dialog_box_dismissed_(false),
+ abort_printing_(false),
+ in_print_job_(false) {
+}
+
+PrintingContext::~PrintingContext() {
+ ResetSettings();
+}
+
+PrintingContext::Result PrintingContext::AskUserForSettings(HWND window,
+ int max_pages) {
+ DCHECK(window);
+ DCHECK(!in_print_job_);
+ dialog_box_dismissed_ = false;
+ // Show the OS-dependent dialog box.
+ // If the user press
+ // - OK, the settings are reset and reinitialized with the new settings. OK is
+ // returned.
+ // - Apply then Cancel, the settings are reset and reinitialized with the new
+ // settings. CANCEL is returned.
+ // - Cancel, the settings are not changed, the previous setting, if it was
+ // initialized before, are kept. CANCEL is returned.
+ // On failure, the settings are reset and FAILED is returned.
+ PRINTDLGEX dialog_options = { sizeof(PRINTDLGEX) };
+ dialog_options.hwndOwner = window;
+ // Disables the Current Page and Selection radio buttons since WebKit can't
+ // print a part of the webpage and we don't know which page is the current
+ // one.
+ // TODO(maruel): Reuse the previously loaded settings!
+ dialog_options.Flags = PD_RETURNDC | PD_USEDEVMODECOPIESANDCOLLATE |
+ PD_NOSELECTION | PD_NOCURRENTPAGE | PD_HIDEPRINTTOFILE;
+ PRINTPAGERANGE ranges[32];
+ dialog_options.nStartPage = START_PAGE_GENERAL;
+ if (max_pages) {
+ // Default initialize to print all the pages.
+ memset(ranges, 0, sizeof(ranges));
+ ranges[0].nFromPage = 1;
+ ranges[0].nToPage = max_pages;
+ dialog_options.nPageRanges = 1;
+ dialog_options.nMaxPageRanges = arraysize(ranges);
+ dialog_options.nMaxPage = max_pages;
+ dialog_options.lpPageRanges = ranges;
+ } else {
+ // No need to bother, we don't know how many pages are available.
+ dialog_options.Flags |= PD_NOPAGENUMS;
+ }
+
+ {
+ CallbackHandler handler(*this, window);
+ dialog_options.lpCallback = handler.ToIUnknown();
+ if (PrintDlgEx(&dialog_options) != S_OK) {
+ ResetSettings();
+ return FAILED;
+ }
+ }
+ // TODO(maruel): Support PD_PRINTTOFILE.
+ return ParseDialogResultEx(dialog_options);
+}
+
+PrintingContext::Result PrintingContext::UseDefaultSettings() {
+ DCHECK(!in_print_job_);
+
+ PRINTDLG dialog_options = { sizeof(PRINTDLG) };
+ dialog_options.Flags = PD_RETURNDC | PD_RETURNDEFAULT;
+ if (PrintDlg(&dialog_options) == 0) {
+ ResetSettings();
+ return FAILED;
+ }
+ return ParseDialogResult(dialog_options);
+}
+
+PrintingContext::Result PrintingContext::InitWithSettings(
+ const PrintSettings& settings) {
+ DCHECK(!in_print_job_);
+ settings_ = settings;
+ // TODO(maruel): settings_->ToDEVMODE()
+ HANDLE printer;
+ if (!OpenPrinter(const_cast<wchar_t*>(settings_.device_name().c_str()),
+ &printer,
+ NULL))
+ return FAILED;
+
+ Result status = OK;
+
+ if (!GetPrinterSettings(printer, settings_.device_name()))
+ status = FAILED;
+
+ // Close the printer after retrieving the context.
+ ClosePrinter(printer);
+
+ if (status != OK)
+ ResetSettings();
+ return status;
+}
+
+void PrintingContext::ResetSettings() {
+ if (hdc_ != NULL) {
+ DeleteDC(hdc_);
+ hdc_ = NULL;
+ }
+ settings_.Clear();
+ in_print_job_ = false;
+
+#ifdef _DEBUG
+ page_number_ = -1;
+#endif
+}
+
+PrintingContext::Result PrintingContext::NewDocument(
+ const std::wstring& document_name) {
+ DCHECK(!in_print_job_);
+ if (!hdc_)
+ return OnErrror();
+
+ // Set the flag used by the AbortPrintJob dialog procedure.
+ abort_printing_ = false;
+
+ in_print_job_ = true;
+
+ // Register the application's AbortProc function with GDI.
+ if (SP_ERROR == SetAbortProc(hdc_, &AbortProc))
+ return OnErrror();
+
+ DOCINFO di = { sizeof(DOCINFO) };
+ di.lpszDocName = document_name.c_str();
+
+ // Is there a debug dump directory specified? If so, force to print to a file.
+ std::wstring debug_dump_path;
+ if (!g_browser_process || !g_browser_process->print_job_manager()) {
+ // Happens only inside a unit test.
+ debug_dump_path = L".";
+ } else {
+ debug_dump_path = g_browser_process->print_job_manager()->debug_dump_path();
+ }
+
+ if (!debug_dump_path.empty()) {
+ // Create a filename.
+ std::wstring filename;
+ Time now(Time::Now());
+ filename = TimeFormat::ShortDateNumeric(now);
+ filename += L"_";
+ filename += TimeFormat::TimeOfDay(now);
+ filename += L"_";
+ filename += document_name;
+ filename += L"_";
+ filename += L"buffer.prn";
+ file_util::ReplaceIllegalCharacters(&filename, '_');
+ file_util::AppendToPath(&debug_dump_path, filename);
+ di.lpszOutput = debug_dump_path.c_str();
+ }
+
+ DCHECK_EQ(MessageLoop::current()->NestableTasksAllowed(), false);
+ // Begin a print job by calling the StartDoc function.
+ // NOTE: StartDoc() starts a message loop. That causes a lot of problems with
+ // IPC. Make sure recursive task processing is disabled.
+ if (StartDoc(hdc_, &di) <= 0)
+ return OnErrror();
+
+#ifdef _DEBUG
+ page_number_ = 0;
+#endif
+ return OK;
+}
+
+PrintingContext::Result PrintingContext::NewPage() {
+ if (abort_printing_)
+ return CANCEL;
+ DCHECK(in_print_job_);
+
+ // Inform the driver that the application is about to begin sending data.
+ if (StartPage(hdc_) <= 0)
+ return OnErrror();
+
+#ifdef _DEBUG
+ ++page_number_;
+#endif
+
+ return OK;
+}
+
+PrintingContext::Result PrintingContext::PageDone() {
+ if (abort_printing_)
+ return CANCEL;
+ DCHECK(in_print_job_);
+
+ if (EndPage(hdc_) <= 0)
+ return OnErrror();
+ return OK;
+}
+
+PrintingContext::Result PrintingContext::DocumentDone() {
+ if (abort_printing_)
+ return CANCEL;
+ DCHECK(in_print_job_);
+
+ // Inform the driver that document has ended.
+ if (EndDoc(hdc_) <= 0)
+ return OnErrror();
+
+ ResetSettings();
+ return OK;
+}
+
+void PrintingContext::Cancel() {
+ abort_printing_ = true;
+ in_print_job_ = false;
+ if (hdc_)
+ CancelDC(hdc_);
+ DismissDialog();
+}
+
+void PrintingContext::DismissDialog() {
+ if (dialog_box_) {
+ DestroyWindow(dialog_box_);
+ dialog_box_dismissed_ = true;
+ }
+}
+
+PrintingContext::Result PrintingContext::OnErrror() {
+ // This will close hdc_ and clear settings_.
+ ResetSettings();
+ return abort_printing_ ? CANCEL : FAILED;
+}
+
+// static
+BOOL PrintingContext::AbortProc(HDC hdc, int nCode) {
+ if (nCode) {
+ // TODO(maruel): Need a way to find the right instance to set. Should
+ // leverage PrintJobManager here?
+ // abort_printing_ = true;
+ }
+ return true;
+}
+
+bool PrintingContext::InitializeSettings(const DEVMODE& dev_mode,
+ const std::wstring& new_device_name,
+ const PRINTPAGERANGE* ranges,
+ int number_ranges) {
+ gfx::PlatformDevice::InitializeDC(hdc_);
+ DCHECK(GetDeviceCaps(hdc_, CLIPCAPS));
+ DCHECK(GetDeviceCaps(hdc_, RASTERCAPS) & RC_STRETCHDIB);
+ DCHECK(GetDeviceCaps(hdc_, RASTERCAPS) & RC_BITMAP64);
+ // Some printers don't advertise these.
+ // DCHECK(GetDeviceCaps(hdc_, RASTERCAPS) & RC_SCALING);
+ // DCHECK(GetDeviceCaps(hdc_, SHADEBLENDCAPS) & SB_CONST_ALPHA);
+ // DCHECK(GetDeviceCaps(hdc_, SHADEBLENDCAPS) & SB_PIXEL_ALPHA);
+
+ // StretchDIBits() support is needed for printing.
+ if (!(GetDeviceCaps(hdc_, RASTERCAPS) & RC_STRETCHDIB) ||
+ !(GetDeviceCaps(hdc_, RASTERCAPS) & RC_BITMAP64)) {
+ NOTREACHED();
+ ResetSettings();
+ return false;
+ }
+
+ DCHECK(!in_print_job_);
+ DCHECK(hdc_);
+ // Convert the PRINTPAGERANGE array to a PrintSettings::PageRanges vector.
+ PageRanges ranges_vector;
+ ranges_vector.reserve(number_ranges);
+ for (int i = 0; i < number_ranges; ++i) {
+ PageRange range;
+ // Transfert from 1-based to 0-based.
+ range.from = ranges[i].nFromPage - 1;
+ range.to = ranges[i].nToPage - 1;
+ ranges_vector.push_back(range);
+ }
+ settings_.Init(hdc_, dev_mode, ranges_vector, new_device_name);
+ PageMargins margins;
+ margins.header = 500;
+ margins.footer = 500;
+ margins.left = 500;
+ margins.top = 500;
+ margins.right = 500;
+ margins.bottom = 500;
+ settings_.UpdateMarginsMilliInch(margins);
+ return true;
+}
+
+bool PrintingContext::GetPrinterSettings(HANDLE printer,
+ const std::wstring& device_name) {
+ DCHECK(!in_print_job_);
+ scoped_array<uint8> buffer;
+
+ // A PRINTER_INFO_9 structure specifying the per-user default printer
+ // settings.
+ GetPrinterHelper(printer, 9, &buffer);
+ if (buffer.get()) {
+ PRINTER_INFO_9* info_9 = reinterpret_cast<PRINTER_INFO_9*>(buffer.get());
+ if (info_9->pDevMode != NULL) {
+ if (!AllocateContext(device_name, info_9->pDevMode)) {
+ ResetSettings();
+ return false;
+ }
+ return InitializeSettings(*info_9->pDevMode, device_name, NULL, 0);
+ }
+ buffer.reset();
+ }
+
+ // A PRINTER_INFO_8 structure specifying the global default printer settings.
+ GetPrinterHelper(printer, 8, &buffer);
+ if (buffer.get()) {
+ PRINTER_INFO_8* info_8 = reinterpret_cast<PRINTER_INFO_8*>(buffer.get());
+ if (info_8->pDevMode != NULL) {
+ if (!AllocateContext(device_name, info_8->pDevMode)) {
+ ResetSettings();
+ return false;
+ }
+ return InitializeSettings(*info_8->pDevMode, device_name, NULL, 0);
+ }
+ buffer.reset();
+ }
+
+ // A PRINTER_INFO_2 structure specifying the driver's default printer
+ // settings.
+ GetPrinterHelper(printer, 2, &buffer);
+ if (buffer.get()) {
+ PRINTER_INFO_2* info_2 = reinterpret_cast<PRINTER_INFO_2*>(buffer.get());
+ if (info_2->pDevMode != NULL) {
+ if (!AllocateContext(device_name, info_2->pDevMode)) {
+ ResetSettings();
+ return false;
+ }
+ return InitializeSettings(*info_2->pDevMode, device_name, NULL, 0);
+ }
+ buffer.reset();
+ }
+ // Failed to retrieve the printer settings.
+ ResetSettings();
+ return false;
+}
+
+bool PrintingContext::AllocateContext(const std::wstring& printer_name,
+ const DEVMODE* dev_mode) {
+ hdc_ = CreateDC(L"WINSPOOL", printer_name.c_str(), NULL, dev_mode);
+ DCHECK(hdc_);
+ return hdc_ != NULL;
+}
+
+PrintingContext::Result PrintingContext::ParseDialogResultEx(
+ const PRINTDLGEX& dialog_options) {
+ // If the user clicked OK or Apply then Cancel, but not only Cancel.
+ if (dialog_options.dwResultAction != PD_RESULT_CANCEL) {
+ // Start fresh.
+ ResetSettings();
+
+ DEVMODE* dev_mode = NULL;
+ if (dialog_options.hDevMode) {
+ dev_mode =
+ reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode));
+ DCHECK(dev_mode);
+ }
+
+ std::wstring device_name;
+ if (dialog_options.hDevNames) {
+ DEVNAMES* dev_names =
+ reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames));
+ DCHECK(dev_names);
+ if (dev_names) {
+ device_name =
+ reinterpret_cast<const wchar_t*>(
+ reinterpret_cast<const wchar_t*>(dev_names) +
+ dev_names->wDeviceOffset);
+ GlobalUnlock(dialog_options.hDevNames);
+ }
+ }
+
+ bool success = false;
+ if (dev_mode && !device_name.empty()) {
+ hdc_ = dialog_options.hDC;
+ if (dialog_options.Flags & PD_PAGENUMS) {
+ success = InitializeSettings(*dev_mode,
+ device_name,
+ dialog_options.lpPageRanges,
+ dialog_options.nPageRanges);
+ } else {
+ success = InitializeSettings(*dev_mode, device_name, NULL, 0);
+ }
+ }
+
+ if (!success && dialog_options.hDC) {
+ DeleteDC(dialog_options.hDC);
+ hdc_ = NULL;
+ }
+
+ if (dev_mode) {
+ GlobalUnlock(dialog_options.hDevMode);
+ }
+ } else {
+ if (dialog_options.hDC) {
+ DeleteDC(dialog_options.hDC);
+ }
+ }
+
+ if (dialog_options.hDevMode != NULL)
+ GlobalFree(dialog_options.hDevMode);
+ if (dialog_options.hDevNames != NULL)
+ GlobalFree(dialog_options.hDevNames);
+
+ switch (dialog_options.dwResultAction) {
+ case PD_RESULT_PRINT:
+ return hdc_ ? OK : FAILED;
+ case PD_RESULT_APPLY:
+ return hdc_ ? CANCEL : FAILED;
+ case PD_RESULT_CANCEL:
+ return CANCEL;
+ default:
+ return FAILED;
+ }
+}
+
+PrintingContext::Result PrintingContext::ParseDialogResult(
+ const PRINTDLG& dialog_options) {
+ // If the user clicked OK or Apply then Cancel, but not only Cancel.
+ // Start fresh.
+ ResetSettings();
+
+ DEVMODE* dev_mode = NULL;
+ if (dialog_options.hDevMode) {
+ dev_mode =
+ reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode));
+ DCHECK(dev_mode);
+ }
+
+ std::wstring device_name;
+ if (dialog_options.hDevNames) {
+ DEVNAMES* dev_names =
+ reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames));
+ DCHECK(dev_names);
+ if (dev_names) {
+ device_name =
+ reinterpret_cast<const wchar_t*>(
+ reinterpret_cast<const wchar_t*>(dev_names) +
+ dev_names->wDeviceOffset);
+ GlobalUnlock(dialog_options.hDevNames);
+ }
+ }
+
+ bool success = false;
+ if (dev_mode && !device_name.empty()) {
+ hdc_ = dialog_options.hDC;
+ success = InitializeSettings(*dev_mode, device_name, NULL, 0);
+ }
+
+ if (!success && dialog_options.hDC) {
+ DeleteDC(dialog_options.hDC);
+ hdc_ = NULL;
+ }
+
+ if (dev_mode) {
+ GlobalUnlock(dialog_options.hDevMode);
+ }
+
+ if (dialog_options.hDevMode != NULL)
+ GlobalFree(dialog_options.hDevMode);
+ if (dialog_options.hDevNames != NULL)
+ GlobalFree(dialog_options.hDevNames);
+
+ return hdc_ ? OK : FAILED;
+}
+
+} // namespace printing