print_job_worker.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
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/print_job_worker.h"
6
7#include "base/message_loop.h"
8#include "chrome/browser/browser_thread.h"
9#include "chrome/browser/printing/print_job.h"
10#include "chrome/common/notification_service.h"
11#include "printing/printed_document.h"
12#include "printing/printed_page.h"
13
14namespace printing {
15
16class PrintJobWorker::NotificationTask : public Task {
17 public:
18  NotificationTask() : print_job_(NULL), details_(NULL) {
19  }
20  ~NotificationTask() {
21  }
22
23  // Initializes the object. This object can't be initialized in the constructor
24  // since it is not created directly.
25  void Init(PrintJobWorkerOwner* print_job,
26            JobEventDetails::Type detail_type,
27            PrintedDocument* document,
28            PrintedPage* page) {
29    DCHECK(!print_job_);
30    DCHECK(!details_);
31    print_job_ = print_job;
32    details_ = new JobEventDetails(detail_type, document, page);
33  }
34
35  virtual void Run() {
36    // Send the notification in the right thread.
37    NotificationService::current()->Notify(
38        NotificationType::PRINT_JOB_EVENT,
39        // We know that is is a PrintJob object in this circumstance.
40        Source<PrintJob>(static_cast<PrintJob*>(print_job_.get())),
41        Details<JobEventDetails>(details_));
42  }
43
44  // The job which originates this notification.
45  scoped_refptr<PrintJobWorkerOwner> print_job_;
46  scoped_refptr<JobEventDetails> details_;
47};
48
49
50PrintJobWorker::PrintJobWorker(PrintJobWorkerOwner* owner)
51    : Thread("Printing_Worker"),
52      owner_(owner) {
53  // The object is created in the IO thread.
54  DCHECK_EQ(owner_->message_loop(), MessageLoop::current());
55
56  printing_context_.reset(PrintingContext::Create());
57}
58
59PrintJobWorker::~PrintJobWorker() {
60  // The object is deleted in the UI thread.
61  DCHECK_EQ(owner_->message_loop(), MessageLoop::current());
62}
63
64void PrintJobWorker::SetNewOwner(PrintJobWorkerOwner* new_owner) {
65  DCHECK(page_number_ == PageNumber::npos());
66  owner_ = new_owner;
67}
68
69void PrintJobWorker::GetSettings(bool ask_user_for_settings,
70                                 gfx::NativeView parent_view,
71                                 int document_page_count,
72                                 bool has_selection,
73                                 bool use_overlays) {
74  DCHECK_EQ(message_loop(), MessageLoop::current());
75  DCHECK_EQ(page_number_, PageNumber::npos());
76
77  // Recursive task processing is needed for the dialog in case it needs to be
78  // destroyed by a task.
79  MessageLoop::current()->SetNestableTasksAllowed(true);
80  printing_context_->set_use_overlays(use_overlays);
81
82  if (ask_user_for_settings) {
83#if defined(OS_MACOSX) || defined(USE_X11)
84    BrowserThread::PostTask(
85        BrowserThread::UI, FROM_HERE,
86        NewRunnableMethod(this, &PrintJobWorker::GetSettingsWithUI,
87                          parent_view, document_page_count,
88                          has_selection));
89#else
90    printing_context_->AskUserForSettings(
91        parent_view,
92        document_page_count,
93        has_selection,
94        NewCallback(this, &PrintJobWorker::GetSettingsDone));
95#endif  // defined(OS_MACOSX) || defined(USE_X11)
96  } else {
97    PrintingContext::Result result = printing_context_->UseDefaultSettings();
98    GetSettingsDone(result);
99  }
100}
101
102void PrintJobWorker::GetSettingsDone(PrintingContext::Result result) {
103  // Most PrintingContext functions may start a message loop and process
104  // message recursively, so disable recursive task processing.
105  MessageLoop::current()->SetNestableTasksAllowed(false);
106
107  // We can't use OnFailure() here since owner_ may not support notifications.
108
109  // PrintJob will create the new PrintedDocument.
110  owner_->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
111      owner_,
112      &PrintJobWorkerOwner::GetSettingsDone,
113      printing_context_->settings(),
114      result));
115}
116
117#if defined(OS_MACOSX) || defined(USE_X11)
118void PrintJobWorker::GetSettingsWithUI(gfx::NativeView parent_view,
119                                       int document_page_count,
120                                       bool has_selection) {
121  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
122
123  printing_context_->AskUserForSettings(
124      parent_view,
125      document_page_count,
126      has_selection,
127      NewCallback(this, &PrintJobWorker::GetSettingsWithUIDone));
128}
129
130void PrintJobWorker::GetSettingsWithUIDone(PrintingContext::Result result) {
131  message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
132      this, &PrintJobWorker::GetSettingsDone, result));
133}
134#endif  // defined(OS_MACOSX) || defined(USE_X11)
135
136void PrintJobWorker::StartPrinting(PrintedDocument* new_document) {
137  DCHECK_EQ(message_loop(), MessageLoop::current());
138  DCHECK_EQ(page_number_, PageNumber::npos());
139  DCHECK_EQ(document_, new_document);
140  DCHECK(document_.get());
141  DCHECK(new_document->settings().Equals(printing_context_->settings()));
142
143  if (!document_.get() || page_number_ != PageNumber::npos() ||
144      document_ != new_document) {
145    return;
146  }
147
148  PrintingContext::Result result =
149      printing_context_->NewDocument(document_->name());
150  if (result != PrintingContext::OK) {
151    OnFailure();
152    return;
153  }
154
155  // Try to print already cached data. It may already have been generated for
156  // the print preview.
157  OnNewPage();
158  // Don't touch this anymore since the instance could be destroyed. It happens
159  // if all the pages are printed a one sweep and the client doesn't have a
160  // handle to us anymore. There's a timing issue involved between the worker
161  // thread and the UI thread. Take no chance.
162}
163
164void PrintJobWorker::OnDocumentChanged(PrintedDocument* new_document) {
165  DCHECK_EQ(message_loop(), MessageLoop::current());
166  DCHECK_EQ(page_number_, PageNumber::npos());
167  DCHECK(!new_document ||
168         new_document->settings().Equals(printing_context_->settings()));
169
170  if (page_number_ != PageNumber::npos())
171    return;
172
173  document_ = new_document;
174}
175
176void PrintJobWorker::OnNewPage() {
177  if (!document_.get()) {
178    // Spurious message.
179    return;
180  }
181  // message_loop() could return NULL when the print job is cancelled.
182  DCHECK_EQ(message_loop(), MessageLoop::current());
183
184  if (page_number_ == PageNumber::npos()) {
185    // Find first page to print.
186    int page_count = document_->page_count();
187    if (!page_count) {
188      // We still don't know how many pages the document contains. We can't
189      // start to print the document yet since the header/footer may refer to
190      // the document's page count.
191      return;
192    }
193    // We have enough information to initialize page_number_.
194    page_number_.Init(document_->settings(), page_count);
195  }
196  DCHECK_NE(page_number_, PageNumber::npos());
197
198  for (;;) {
199    // Is the page available?
200    scoped_refptr<PrintedPage> page;
201    if (!document_->GetPage(page_number_.ToInt(), &page)) {
202      // We need to wait for the page to be available.
203      MessageLoop::current()->PostDelayedTask(
204          FROM_HERE,
205          NewRunnableMethod(this, &PrintJobWorker::OnNewPage),
206          500);
207      break;
208    }
209    // The page is there, print it.
210    SpoolPage(*page);
211    ++page_number_;
212    if (page_number_ == PageNumber::npos()) {
213      OnDocumentDone();
214      // Don't touch this anymore since the instance could be destroyed.
215      break;
216    }
217  }
218}
219
220void PrintJobWorker::Cancel() {
221  // This is the only function that can be called from any thread.
222  printing_context_->Cancel();
223  // Cannot touch any member variable since we don't know in which thread
224  // context we run.
225}
226
227void PrintJobWorker::DismissDialog() {
228  printing_context_->DismissDialog();
229}
230
231void PrintJobWorker::OnDocumentDone() {
232  DCHECK_EQ(message_loop(), MessageLoop::current());
233  DCHECK_EQ(page_number_, PageNumber::npos());
234  DCHECK(document_.get());
235
236  if (printing_context_->DocumentDone() != PrintingContext::OK) {
237    OnFailure();
238    return;
239  }
240
241  // Tell everyone!
242  NotificationTask* task = new NotificationTask();
243  task->Init(owner_,
244             JobEventDetails::DOC_DONE,
245             document_.get(),
246             NULL);
247  owner_->message_loop()->PostTask(FROM_HERE, task);
248
249  // Makes sure the variables are reinitialized.
250  document_ = NULL;
251}
252
253void PrintJobWorker::SpoolPage(PrintedPage& page) {
254  DCHECK_EQ(message_loop(), MessageLoop::current());
255  DCHECK_NE(page_number_, PageNumber::npos());
256
257  // Signal everyone that the page is about to be printed.
258  NotificationTask* task = new NotificationTask();
259  task->Init(owner_,
260             JobEventDetails::NEW_PAGE,
261             document_.get(),
262             &page);
263  owner_->message_loop()->PostTask(FROM_HERE, task);
264
265  // Preprocess.
266  if (printing_context_->NewPage() != PrintingContext::OK) {
267    OnFailure();
268    return;
269  }
270
271  // Actual printing.
272  document_->RenderPrintedPage(page, printing_context_->context());
273
274  // Postprocess.
275  if (printing_context_->PageDone() != PrintingContext::OK) {
276    OnFailure();
277    return;
278  }
279
280  // Signal everyone that the page is printed.
281  task = new NotificationTask();
282  task->Init(owner_,
283             JobEventDetails::PAGE_DONE,
284             document_.get(),
285             &page);
286  owner_->message_loop()->PostTask(FROM_HERE, task);
287}
288
289void PrintJobWorker::OnFailure() {
290  DCHECK_EQ(message_loop(), MessageLoop::current());
291
292  // We may loose our last reference by broadcasting the FAILED event.
293  scoped_refptr<PrintJobWorkerOwner> handle(owner_);
294
295  NotificationTask* task = new NotificationTask();
296  task->Init(owner_,
297             JobEventDetails::FAILED,
298             document_.get(),
299             NULL);
300  owner_->message_loop()->PostTask(FROM_HERE, task);
301  Cancel();
302
303  // Makes sure the variables are reinitialized.
304  document_ = NULL;
305  page_number_ = PageNumber::npos();
306}
307
308}  // namespace printing
309
310void RunnableMethodTraits<printing::PrintJobWorker>::RetainCallee(
311    printing::PrintJobWorker* obj) {
312  DCHECK(!owner_.get());
313  owner_ = obj->owner_;
314}
315
316void RunnableMethodTraits<printing::PrintJobWorker>::ReleaseCallee(
317    printing::PrintJobWorker* obj) {
318  DCHECK_EQ(owner_, obj->owner_);
319  owner_ = NULL;
320}
321