1// Copyright (c) 2011 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/ui/webui/print_preview_handler.h"
6
7#include <string>
8
9#include "base/i18n/file_util_icu.h"
10#include "base/json/json_reader.h"
11#include "base/path_service.h"
12#include "base/threading/thread.h"
13#include "base/threading/thread_restrictions.h"
14#include "base/utf_string_conversions.h"
15#include "base/values.h"
16#include "chrome/browser/platform_util.h"
17#include "chrome/browser/printing/print_preview_tab_controller.h"
18#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
19#include "chrome/browser/ui/webui/print_preview_ui_html_source.h"
20#include "chrome/browser/ui/webui/print_preview_ui.h"
21#include "chrome/common/chrome_paths.h"
22#include "chrome/common/print_messages.h"
23#include "content/browser/browser_thread.h"
24#include "content/browser/renderer_host/render_view_host.h"
25#include "content/browser/tab_contents/tab_contents.h"
26#include "printing/backend/print_backend.h"
27#include "printing/metafile.h"
28#include "printing/metafile_impl.h"
29#include "printing/print_job_constants.h"
30
31#if defined(OS_POSIX) && !defined(OS_CHROMEOS)
32#include <cups/cups.h>
33
34#include "base/file_util.h"
35#endif
36
37namespace {
38
39const bool kColorDefaultValue = false;
40const bool kLandscapeDefaultValue = false;
41
42const char kDisableColorOption[] = "disableColorOption";
43const char kSetColorAsDefault[] = "setColorAsDefault";
44
45#if defined(OS_POSIX) && !defined(OS_CHROMEOS)
46const char kColorDevice[] = "ColorDevice";
47#endif
48
49TabContents* GetInitiatorTab(TabContents* preview_tab) {
50  printing::PrintPreviewTabController* tab_controller =
51      printing::PrintPreviewTabController::GetInstance();
52  if (!tab_controller)
53    return NULL;
54  return tab_controller->GetInitiatorTab(preview_tab);
55}
56
57// Get the print job settings dictionary from |args|. The caller takes
58// ownership of the returned DictionaryValue. Returns NULL on failure.
59DictionaryValue* GetSettingsDictionary(const ListValue* args) {
60  std::string json_str;
61  if (!args->GetString(0, &json_str)) {
62    NOTREACHED() << "Could not read JSON argument";
63    return NULL;
64  }
65  if (json_str.empty()) {
66    NOTREACHED() << "Empty print job settings";
67    return NULL;
68  }
69  scoped_ptr<DictionaryValue> settings(static_cast<DictionaryValue*>(
70      base::JSONReader::Read(json_str, false)));
71  if (!settings.get() || !settings->IsType(Value::TYPE_DICTIONARY)) {
72    NOTREACHED() << "Print job settings must be a dictionary.";
73    return NULL;
74  }
75
76  if (settings->empty()) {
77    NOTREACHED() << "Print job settings dictionary is empty";
78    return NULL;
79  }
80
81  return settings.release();
82}
83
84}  // namespace
85
86class PrintSystemTaskProxy
87    : public base::RefCountedThreadSafe<PrintSystemTaskProxy,
88                                        BrowserThread::DeleteOnUIThread> {
89 public:
90  PrintSystemTaskProxy(const base::WeakPtr<PrintPreviewHandler>& handler,
91                             printing::PrintBackend* print_backend)
92      : handler_(handler),
93        print_backend_(print_backend) {
94  }
95
96  void EnumeratePrinters() {
97    ListValue* printers = new ListValue;
98    int default_printer_index = -1;
99
100    printing::PrinterList printer_list;
101    print_backend_->EnumeratePrinters(&printer_list);
102    int i = 0;
103    for (printing::PrinterList::iterator index = printer_list.begin();
104         index != printer_list.end(); ++index, ++i) {
105      printers->Append(new StringValue(index->printer_name));
106      if (index->is_default)
107        default_printer_index = i;
108    }
109
110    BrowserThread::PostTask(
111        BrowserThread::UI, FROM_HERE,
112        NewRunnableMethod(this,
113                          &PrintSystemTaskProxy::SendPrinterList,
114                          printers,
115                          new FundamentalValue(default_printer_index)));
116  }
117
118  void SendPrinterList(ListValue* printers,
119                       FundamentalValue* default_printer_index) {
120    if (handler_)
121      handler_->SendPrinterList(*printers, *default_printer_index);
122    delete printers;
123    delete default_printer_index;
124  }
125
126  void GetPrinterCapabilities(const std::string& printer_name) {
127    printing::PrinterCapsAndDefaults printer_info;
128    bool supports_color = true;
129    if (!print_backend_->GetPrinterCapsAndDefaults(printer_name,
130                                                   &printer_info)) {
131      return;
132    }
133
134  #if defined(OS_POSIX) && !defined(OS_CHROMEOS)
135    FilePath ppd_file_path;
136    if (!file_util::CreateTemporaryFile(&ppd_file_path))
137      return;
138
139    int data_size = printer_info.printer_capabilities.length();
140    if (data_size != file_util::WriteFile(
141                         ppd_file_path,
142                         printer_info.printer_capabilities.data(),
143                         data_size)) {
144      file_util::Delete(ppd_file_path, false);
145      return;
146    }
147
148    ppd_file_t* ppd = ppdOpenFile(ppd_file_path.value().c_str());
149    if (ppd) {
150      ppd_attr_t* attr = ppdFindAttr(ppd, kColorDevice, NULL);
151      if (attr && attr->value)
152        supports_color = ppd->color_device;
153      ppdClose(ppd);
154    }
155    file_util::Delete(ppd_file_path, false);
156  #elif defined(OS_WIN) || defined(OS_CHROMEOS)
157    NOTIMPLEMENTED();
158  #endif
159
160    DictionaryValue settings_info;
161    settings_info.SetBoolean(kDisableColorOption, !supports_color);
162    settings_info.SetBoolean(kSetColorAsDefault, false);
163    BrowserThread::PostTask(
164        BrowserThread::UI, FROM_HERE,
165        NewRunnableMethod(this,
166                          &PrintSystemTaskProxy::SendPrinterCapabilities,
167                          settings_info.DeepCopy()));
168  }
169
170  void SendPrinterCapabilities(DictionaryValue* settings_info) {
171    if (handler_)
172      handler_->SendPrinterCapabilities(*settings_info);
173    delete settings_info;
174  }
175
176 private:
177  friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
178  friend class DeleteTask<PrintSystemTaskProxy>;
179
180  ~PrintSystemTaskProxy() {}
181
182  base::WeakPtr<PrintPreviewHandler> handler_;
183
184  scoped_refptr<printing::PrintBackend> print_backend_;
185
186  DISALLOW_COPY_AND_ASSIGN(PrintSystemTaskProxy);
187};
188
189// A Task implementation that stores a PDF file on disk.
190class PrintToPdfTask : public Task {
191 public:
192  // Takes ownership of |metafile|.
193  PrintToPdfTask(printing::Metafile* metafile, const FilePath& path)
194      : metafile_(metafile), path_(path) {
195  }
196
197  ~PrintToPdfTask() {}
198
199  // Task implementation
200  virtual void Run() {
201    metafile_->SaveTo(path_);
202  }
203
204 private:
205  // The metafile holding the PDF data.
206  scoped_ptr<printing::Metafile> metafile_;
207
208  // The absolute path where the file will be saved.
209  FilePath path_;
210};
211
212// static
213FilePath* PrintPreviewHandler::last_saved_path_ = NULL;
214
215PrintPreviewHandler::PrintPreviewHandler()
216    : print_backend_(printing::PrintBackend::CreateInstance(NULL)) {
217}
218
219PrintPreviewHandler::~PrintPreviewHandler() {
220  if (select_file_dialog_.get())
221    select_file_dialog_->ListenerDestroyed();
222}
223
224void PrintPreviewHandler::RegisterMessages() {
225  web_ui_->RegisterMessageCallback("getPrinters",
226      NewCallback(this, &PrintPreviewHandler::HandleGetPrinters));
227  web_ui_->RegisterMessageCallback("getPreview",
228      NewCallback(this, &PrintPreviewHandler::HandleGetPreview));
229  web_ui_->RegisterMessageCallback("print",
230      NewCallback(this, &PrintPreviewHandler::HandlePrint));
231  web_ui_->RegisterMessageCallback("getPrinterCapabilities",
232      NewCallback(this, &PrintPreviewHandler::HandleGetPrinterCapabilities));
233}
234
235void PrintPreviewHandler::HandleGetPrinters(const ListValue*) {
236  scoped_refptr<PrintSystemTaskProxy> task =
237      new PrintSystemTaskProxy(AsWeakPtr(), print_backend_.get());
238  BrowserThread::PostTask(
239      BrowserThread::FILE, FROM_HERE,
240      NewRunnableMethod(task.get(),
241                        &PrintSystemTaskProxy::EnumeratePrinters));
242}
243
244void PrintPreviewHandler::HandleGetPreview(const ListValue* args) {
245  TabContents* initiator_tab = GetInitiatorTab(web_ui_->tab_contents());
246  if (!initiator_tab)
247    return;
248  scoped_ptr<DictionaryValue> settings(GetSettingsDictionary(args));
249  if (!settings.get())
250    return;
251
252  RenderViewHost* rvh = initiator_tab->render_view_host();
253  rvh->Send(new PrintMsg_PrintPreview(rvh->routing_id(), *settings));
254}
255
256void PrintPreviewHandler::HandlePrint(const ListValue* args) {
257  TabContents* initiator_tab = GetInitiatorTab(web_ui_->tab_contents());
258  if (initiator_tab) {
259    RenderViewHost* rvh = initiator_tab->render_view_host();
260    rvh->Send(new PrintMsg_ResetScriptedPrintCount(rvh->routing_id()));
261  }
262
263  scoped_ptr<DictionaryValue> settings(GetSettingsDictionary(args));
264  if (!settings.get())
265    return;
266
267  bool print_to_pdf = false;
268  settings->GetBoolean(printing::kSettingPrintToPDF, &print_to_pdf);
269
270  if (print_to_pdf) {
271    // Pre-populating select file dialog with print job title.
272    TabContentsWrapper* wrapper =
273        TabContentsWrapper::GetCurrentWrapperForContents(
274            web_ui_->tab_contents());
275
276    string16 print_job_title_utf16 =
277        wrapper->print_view_manager()->RenderSourceName();
278
279#if defined(OS_WIN)
280    FilePath::StringType print_job_title(print_job_title_utf16);
281#elif defined(OS_POSIX)
282    FilePath::StringType print_job_title = UTF16ToUTF8(print_job_title_utf16);
283#endif
284
285    file_util::ReplaceIllegalCharactersInPath(&print_job_title, '_');
286    FilePath default_filename(print_job_title);
287    default_filename =
288        default_filename.ReplaceExtension(FILE_PATH_LITERAL("pdf"));
289
290    SelectFile(default_filename);
291  } else {
292    RenderViewHost* rvh = web_ui_->GetRenderViewHost();
293    rvh->Send(new PrintMsg_PrintForPrintPreview(rvh->routing_id(), *settings));
294  }
295}
296
297void PrintPreviewHandler::HandleGetPrinterCapabilities(
298    const ListValue* args) {
299  std::string printer_name;
300  bool ret = args->GetString(0, &printer_name);
301  if (!ret || printer_name.empty())
302    return;
303
304  scoped_refptr<PrintSystemTaskProxy> task =
305      new PrintSystemTaskProxy(AsWeakPtr(), print_backend_.get());
306
307  BrowserThread::PostTask(
308      BrowserThread::FILE, FROM_HERE,
309      NewRunnableMethod(task.get(),
310                        &PrintSystemTaskProxy::GetPrinterCapabilities,
311                        printer_name));
312}
313
314void PrintPreviewHandler::SendPrinterCapabilities(
315    const DictionaryValue& settings_info) {
316  web_ui_->CallJavascriptFunction("updateWithPrinterCapabilities",
317                                  settings_info);
318}
319
320void PrintPreviewHandler::SendPrinterList(
321    const ListValue& printers,
322    const FundamentalValue& default_printer_index) {
323  web_ui_->CallJavascriptFunction("setPrinters", printers,
324                                  default_printer_index);
325}
326
327void PrintPreviewHandler::SelectFile(const FilePath& default_filename) {
328  SelectFileDialog::FileTypeInfo file_type_info;
329  file_type_info.extensions.resize(1);
330  file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("pdf"));
331
332  // Initializing last_saved_path_ if it is not already initialized.
333  if (!last_saved_path_) {
334    last_saved_path_ = new FilePath();
335    // Allowing IO operation temporarily. It is ok to do so here because
336    // the select file dialog performs IO anyway in order to display the
337    // folders and also it is modal.
338    base::ThreadRestrictions::ScopedAllowIO allow_io;
339    PathService::Get(chrome::DIR_USER_DOCUMENTS, last_saved_path_);
340  }
341
342  if (!select_file_dialog_.get())
343    select_file_dialog_ = SelectFileDialog::Create(this);
344
345  select_file_dialog_->SelectFile(
346      SelectFileDialog::SELECT_SAVEAS_FILE,
347      string16(),
348      last_saved_path_->Append(default_filename),
349      &file_type_info,
350      0,
351      FILE_PATH_LITERAL(""),
352      web_ui_->tab_contents(),
353      platform_util::GetTopLevel(
354          web_ui_->tab_contents()->GetNativeView()),
355      NULL);
356}
357
358void PrintPreviewHandler::FileSelected(const FilePath& path,
359                                       int index, void* params) {
360  PrintPreviewUIHTMLSource::PrintPreviewData data;
361  PrintPreviewUI* pp_ui = reinterpret_cast<PrintPreviewUI*>(web_ui_);
362  pp_ui->html_source()->GetPrintPreviewData(&data);
363  DCHECK(data.first != NULL);
364  DCHECK(data.second > 0);
365
366  printing::PreviewMetafile* metafile = new printing::PreviewMetafile;
367  metafile->InitFromData(data.first->memory(), data.second);
368
369  // Updating last_saved_path_ to the newly selected folder.
370  *last_saved_path_ = path.DirName();
371
372  PrintToPdfTask* task = new PrintToPdfTask(metafile, path);
373  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, task);
374}
375