1/*
2 * Copyright (C) 2012 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "WebKitPrintOperation.h"
22
23#include "WebKitPrintCustomWidgetPrivate.h"
24#include "WebKitPrintOperationPrivate.h"
25#include "WebKitPrivate.h"
26#include "WebKitWebViewPrivate.h"
27#include "WebPageProxy.h"
28#include <WebCore/GtkUtilities.h>
29#include <WebCore/NotImplemented.h>
30#include <glib/gi18n-lib.h>
31#include <wtf/glib/GRefPtr.h>
32#include <wtf/glib/GUniquePtr.h>
33#include <wtf/glib/WTFGType.h>
34#include <wtf/text/CString.h>
35
36#if HAVE(GTK_UNIX_PRINTING)
37#include <gtk/gtkunixprint.h>
38#endif
39
40using namespace WebKit;
41
42/**
43 * SECTION: WebKitPrintOperation
44 * @Short_description: Controls a print operation
45 * @Title: WebKitPrintOperation
46 *
47 * A #WebKitPrintOperation controls a print operation in WebKit. With
48 * a similar API to #GtkPrintOperation, it lets you set the print
49 * settings with webkit_print_operation_set_print_settings() or
50 * display the print dialog with webkit_print_operation_run_dialog().
51 *
52 */
53
54enum {
55 PROP_0,
56
57 PROP_WEB_VIEW,
58 PROP_PRINT_SETTINGS,
59 PROP_PAGE_SETUP
60};
61
62enum {
63 FINISHED,
64 FAILED,
65 CREATE_CUSTOM_WIDGET,
66
67 LAST_SIGNAL
68};
69
70struct _WebKitPrintOperationPrivate {
71 ~_WebKitPrintOperationPrivate()
72 {
73 if (webView)
74 g_object_remove_weak_pointer(G_OBJECT(webView), reinterpret_cast<void**>(&webView));
75 }
76
77 WebKitWebView* webView;
78 PrintInfo::PrintMode printMode;
79
80 GRefPtr<GtkPrintSettings> printSettings;
81 GRefPtr<GtkPageSetup> pageSetup;
82};
83
84static guint signals[LAST_SIGNAL] = { 0, };
85
86WEBKIT_DEFINE_TYPE(WebKitPrintOperation, webkit_print_operation, G_TYPE_OBJECT)
87
88static void webkitPrintOperationConstructed(GObject* object)
89{
90 G_OBJECT_CLASS(webkit_print_operation_parent_class)->constructed(object);
91
92 WebKitPrintOperationPrivate* priv = WEBKIT_PRINT_OPERATION(object)->priv;
93 g_object_add_weak_pointer(G_OBJECT(priv->webView), reinterpret_cast<void**>(&priv->webView));
94}
95
96static void webkitPrintOperationGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
97{
98 WebKitPrintOperation* printOperation = WEBKIT_PRINT_OPERATION(object);
99
100 switch (propId) {
101 case PROP_WEB_VIEW:
102 g_value_take_object(value, printOperation->priv->webView);
103 break;
104 case PROP_PRINT_SETTINGS:
105 g_value_set_object(value, printOperation->priv->printSettings.get());
106 break;
107 case PROP_PAGE_SETUP:
108 g_value_set_object(value, printOperation->priv->pageSetup.get());
109 break;
110 default:
111 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
112 }
113}
114
115static void webkitPrintOperationSetProperty(GObject* object, guint propId, const GValue* value, GParamSpec* paramSpec)
116{
117 WebKitPrintOperation* printOperation = WEBKIT_PRINT_OPERATION(object);
118
119 switch (propId) {
120 case PROP_WEB_VIEW:
121 printOperation->priv->webView = WEBKIT_WEB_VIEW(g_value_get_object(value));
122 break;
123 case PROP_PRINT_SETTINGS:
124 webkit_print_operation_set_print_settings(printOperation, GTK_PRINT_SETTINGS(g_value_get_object(value)));
125 break;
126 case PROP_PAGE_SETUP:
127 webkit_print_operation_set_page_setup(printOperation, GTK_PAGE_SETUP(g_value_get_object(value)));
128 break;
129 default:
130 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
131 }
132}
133
134static gboolean webkitPrintOperationAccumulatorObjectHandled(GSignalInvocationHint*, GValue* returnValue, const GValue* handlerReturn, gpointer)
135{
136 void* object = g_value_get_object(handlerReturn);
137 if (object)
138 g_value_set_object(returnValue, object);
139
140 return !object;
141}
142
143static void webkit_print_operation_class_init(WebKitPrintOperationClass* printOperationClass)
144{
145 GObjectClass* gObjectClass = G_OBJECT_CLASS(printOperationClass);
146 gObjectClass->constructed = webkitPrintOperationConstructed;
147 gObjectClass->get_property = webkitPrintOperationGetProperty;
148 gObjectClass->set_property = webkitPrintOperationSetProperty;
149
150 /**
151 * WebKitPrintOperation:web-view:
152 *
153 * The #WebKitWebView that will be printed.
154 */
155 g_object_class_install_property(gObjectClass,
156 PROP_WEB_VIEW,
157 g_param_spec_object("web-view",
158 _("Web View"),
159 _("The web view that will be printed"),
160 WEBKIT_TYPE_WEB_VIEW,
161 static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
162
163 /**
164 * WebKitPrintOperation:print-settings:
165 *
166 * The initial #GtkPrintSettings for the print operation.
167 */
168 g_object_class_install_property(gObjectClass,
169 PROP_PRINT_SETTINGS,
170 g_param_spec_object("print-settings",
171 _("Print Settings"),
172 _("The initial print settings for the print operation"),
173 GTK_TYPE_PRINT_SETTINGS,
174 WEBKIT_PARAM_READWRITE));
175 /**
176 * WebKitPrintOperation:page-setup:
177 *
178 * The initial #GtkPageSetup for the print operation.
179 */
180 g_object_class_install_property(gObjectClass,
181 PROP_PAGE_SETUP,
182 g_param_spec_object("page-setup",
183 _("Page Setup"),
184 _("The initial page setup for the print operation"),
185 GTK_TYPE_PAGE_SETUP,
186 WEBKIT_PARAM_READWRITE));
187
188 /**
189 * WebKitPrintOperation::finished:
190 * @print_operation: the #WebKitPrintOperation on which the signal was emitted
191 *
192 * Emitted when the print operation has finished doing everything
193 * required for printing.
194 */
195 signals[FINISHED] =
196 g_signal_new("finished",
197 G_TYPE_FROM_CLASS(gObjectClass),
198 G_SIGNAL_RUN_LAST,
199 0, 0, 0,
200 g_cclosure_marshal_VOID__VOID,
201 G_TYPE_NONE, 0);
202
203 /**
204 * WebKitPrintOperation::failed:
205 * @print_operation: the #WebKitPrintOperation on which the signal was emitted
206 * @error: the #GError that was triggered
207 *
208 * Emitted when an error occurs while printing. The given @error, of the domain
209 * %WEBKIT_PRINT_ERROR, contains further details of the failure.
210 * The #WebKitPrintOperation::finished signal is emitted after this one.
211 */
212 signals[FAILED] =
213 g_signal_new(
214 "failed",
215 G_TYPE_FROM_CLASS(gObjectClass),
216 G_SIGNAL_RUN_LAST,
217 0, 0, 0,
218 g_cclosure_marshal_VOID__BOXED,
219 G_TYPE_NONE, 1,
220 G_TYPE_ERROR | G_SIGNAL_TYPE_STATIC_SCOPE);
221
222 /**
223 * WebKitPrintOperation::create-custom-widget:
224 * @print_operation: the #WebKitPrintOperation on which the signal was emitted
225 *
226 * Emitted when displaying the print dialog with webkit_print_operation_run_dialog().
227 * The returned #WebKitPrintCustomWidget will be added to the print dialog and
228 * it will be owned by the @print_operation. However, the object is guaranteed
229 * to be alive until the #WebKitPrintCustomWidget::apply is emitted.
230 *
231 * Returns: (transfer full): A #WebKitPrintCustomWidget that will be embedded in the dialog.
232 *
233 * Since: 2.16
234 */
235 signals[CREATE_CUSTOM_WIDGET] =
236 g_signal_new(
237 "create-custom-widget",
238 G_TYPE_FROM_CLASS(gObjectClass),
239 G_SIGNAL_RUN_LAST,
240 0,
241 webkitPrintOperationAccumulatorObjectHandled, 0,
242 g_cclosure_marshal_generic,
243 WEBKIT_TYPE_PRINT_CUSTOM_WIDGET, 0);
244}
245
246#if HAVE(GTK_UNIX_PRINTING)
247static void notifySelectedPrinterCallback(GtkPrintUnixDialog* dialog, GParamSpec*, WebKitPrintCustomWidget* printCustomWidget)
248{
249 webkitPrintCustomWidgetEmitUpdateCustomWidgetSignal(printCustomWidget, gtk_print_unix_dialog_get_page_setup(dialog), gtk_print_unix_dialog_get_settings(dialog));
250}
251
252static WebKitPrintOperationResponse webkitPrintOperationRunDialog(WebKitPrintOperation* printOperation, GtkWindow* parent)
253{
254 GtkPrintUnixDialog* printDialog = GTK_PRINT_UNIX_DIALOG(gtk_print_unix_dialog_new(0, parent));
255 gtk_print_unix_dialog_set_manual_capabilities(printDialog, static_cast<GtkPrintCapabilities>(GTK_PRINT_CAPABILITY_NUMBER_UP
256 | GTK_PRINT_CAPABILITY_NUMBER_UP_LAYOUT
257 | GTK_PRINT_CAPABILITY_PAGE_SET
258 | GTK_PRINT_CAPABILITY_REVERSE
259 | GTK_PRINT_CAPABILITY_COPIES
260 | GTK_PRINT_CAPABILITY_COLLATE
261 | GTK_PRINT_CAPABILITY_SCALE));
262
263 WebKitPrintOperationPrivate* priv = printOperation->priv;
264 // Make sure the initial settings of the GtkPrintUnixDialog is a valid
265 // GtkPrintSettings object to work around a crash happening in the GTK+
266 // file print backend. https://bugzilla.gnome.org/show_bug.cgi?id=703784.
267 if (!priv->printSettings)
268 priv->printSettings = adoptGRef(gtk_print_settings_new());
269 gtk_print_unix_dialog_set_settings(printDialog, priv->printSettings.get());
270
271 if (priv->pageSetup)
272 gtk_print_unix_dialog_set_page_setup(printDialog, priv->pageSetup.get());
273
274 gtk_print_unix_dialog_set_embed_page_setup(printDialog, TRUE);
275
276 GRefPtr<WebKitPrintCustomWidget> customWidget;
277 g_signal_emit(printOperation, signals[CREATE_CUSTOM_WIDGET], 0, &customWidget.outPtr());
278 if (customWidget) {
279 const gchar* widgetTitle = webkit_print_custom_widget_get_title(customWidget.get());
280 GtkWidget* widget = webkit_print_custom_widget_get_widget(customWidget.get());
281
282 g_signal_connect(printDialog, "notify::selected-printer", G_CALLBACK(notifySelectedPrinterCallback), customWidget.get());
283 gtk_print_unix_dialog_add_custom_tab(printDialog, widget, gtk_label_new(widgetTitle));
284 }
285
286 WebKitPrintOperationResponse returnValue = WEBKIT_PRINT_OPERATION_RESPONSE_CANCEL;
287 if (gtk_dialog_run(GTK_DIALOG(printDialog)) == GTK_RESPONSE_OK) {
288 priv->printSettings = adoptGRef(gtk_print_unix_dialog_get_settings(printDialog));
289 priv->pageSetup = gtk_print_unix_dialog_get_page_setup(printDialog);
290 returnValue = WEBKIT_PRINT_OPERATION_RESPONSE_PRINT;
291 if (customWidget)
292 webkitPrintCustomWidgetEmitCustomWidgetApplySignal(customWidget.get());
293 }
294
295 gtk_widget_destroy(GTK_WIDGET(printDialog));
296
297 return returnValue;
298}
299#else
300// TODO: We need to add an implementation for Windows.
301static WebKitPrintOperationResponse webkitPrintOperationRunDialog(WebKitPrintOperation*, GtkWindow*)
302{
303 notImplemented();
304 return WEBKIT_PRINT_OPERATION_RESPONSE_CANCEL;
305}
306#endif
307
308static void drawPagesForPrintingCompleted(API::Error* wkPrintError, WebKitPrintOperation* printOperation)
309{
310 // When running synchronously WebPageProxy::printFrame() calls endPrinting().
311 if (printOperation->priv->printMode == PrintInfo::PrintModeAsync && printOperation->priv->webView)
312 webkitWebViewGetPage(printOperation->priv->webView).endPrinting();
313
314 const WebCore::ResourceError& resourceError = wkPrintError ? wkPrintError->platformError() : WebCore::ResourceError();
315 if (!resourceError.isNull()) {
316 GUniquePtr<GError> printError(g_error_new_literal(g_quark_from_string(resourceError.domain().utf8().data()),
317 toWebKitError(resourceError.errorCode()), resourceError.localizedDescription().utf8().data()));
318 g_signal_emit(printOperation, signals[FAILED], 0, printError.get());
319 }
320 g_signal_emit(printOperation, signals[FINISHED], 0, NULL);
321}
322
323static void webkitPrintOperationPrintPagesForFrame(WebKitPrintOperation* printOperation, WebFrameProxy* webFrame, GtkPrintSettings* printSettings, GtkPageSetup* pageSetup)
324{
325 PrintInfo printInfo(printSettings, pageSetup, printOperation->priv->printMode);
326 auto& page = webkitWebViewGetPage(printOperation->priv->webView);
327 g_object_ref(printOperation);
328 page.drawPagesForPrinting(webFrame, printInfo, PrintFinishedCallback::create([printOperation](API::Error* printError, CallbackBase::Error) {
329 drawPagesForPrintingCompleted(printError, adoptGRef(printOperation).get());
330 }));
331}
332
333WebKitPrintOperationResponse webkitPrintOperationRunDialogForFrame(WebKitPrintOperation* printOperation, GtkWindow* parent, WebFrameProxy* webFrame)
334{
335 WebKitPrintOperationPrivate* priv = printOperation->priv;
336 if (!parent) {
337 GtkWidget* toplevel = gtk_widget_get_toplevel(GTK_WIDGET(priv->webView));
338 if (WebCore::widgetIsOnscreenToplevelWindow(toplevel))
339 parent = GTK_WINDOW(toplevel);
340 }
341
342 WebKitPrintOperationResponse response = webkitPrintOperationRunDialog(printOperation, parent);
343 if (response == WEBKIT_PRINT_OPERATION_RESPONSE_CANCEL)
344 return response;
345
346 webkitPrintOperationPrintPagesForFrame(printOperation, webFrame, priv->printSettings.get(), priv->pageSetup.get());
347 return response;
348}
349
350void webkitPrintOperationSetPrintMode(WebKitPrintOperation* printOperation, PrintInfo::PrintMode printMode)
351{
352 printOperation->priv->printMode = printMode;
353}
354
355/**
356 * webkit_print_operation_new:
357 * @web_view: a #WebKitWebView
358 *
359 * Create a new #WebKitPrintOperation to print @web_view contents.
360 *
361 * Returns: (transfer full): a new #WebKitPrintOperation.
362 */
363WebKitPrintOperation* webkit_print_operation_new(WebKitWebView* webView)
364{
365 g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), 0);
366
367 return WEBKIT_PRINT_OPERATION(g_object_new(WEBKIT_TYPE_PRINT_OPERATION, "web-view", webView, NULL));
368}
369
370/**
371 * webkit_print_operation_get_print_settings:
372 * @print_operation: a #WebKitPrintOperation
373 *
374 * Return the current print settings of @print_operation. It returns %NULL until
375 * either webkit_print_operation_set_print_settings() or webkit_print_operation_run_dialog()
376 * have been called.
377 *
378 * Returns: (transfer none): the current #GtkPrintSettings of @print_operation.
379 */
380GtkPrintSettings* webkit_print_operation_get_print_settings(WebKitPrintOperation* printOperation)
381{
382 g_return_val_if_fail(WEBKIT_IS_PRINT_OPERATION(printOperation), 0);
383
384 return printOperation->priv->printSettings.get();
385}
386
387/**
388 * webkit_print_operation_set_print_settings:
389 * @print_operation: a #WebKitPrintOperation
390 * @print_settings: a #GtkPrintSettings to set
391 *
392 * Set the current print settings of @print_operation. Current print settings are used for
393 * the initial values of the print dialog when webkit_print_operation_run_dialog() is called.
394 */
395void webkit_print_operation_set_print_settings(WebKitPrintOperation* printOperation, GtkPrintSettings* printSettings)
396{
397 g_return_if_fail(WEBKIT_IS_PRINT_OPERATION(printOperation));
398 g_return_if_fail(GTK_IS_PRINT_SETTINGS(printSettings));
399
400 if (printOperation->priv->printSettings.get() == printSettings)
401 return;
402
403 printOperation->priv->printSettings = printSettings;
404 g_object_notify(G_OBJECT(printOperation), "print-settings");
405}
406
407/**
408 * webkit_print_operation_get_page_setup:
409 * @print_operation: a #WebKitPrintOperation
410 *
411 * Return the current page setup of @print_operation. It returns %NULL until
412 * either webkit_print_operation_set_page_setup() or webkit_print_operation_run_dialog()
413 * have been called.
414 *
415 * Returns: (transfer none): the current #GtkPageSetup of @print_operation.
416 */
417GtkPageSetup* webkit_print_operation_get_page_setup(WebKitPrintOperation* printOperation)
418{
419 g_return_val_if_fail(WEBKIT_IS_PRINT_OPERATION(printOperation), 0);
420
421 return printOperation->priv->pageSetup.get();
422}
423
424/**
425 * webkit_print_operation_set_page_setup:
426 * @print_operation: a #WebKitPrintOperation
427 * @page_setup: a #GtkPageSetup to set
428 *
429 * Set the current page setup of @print_operation. Current page setup is used for the
430 * initial values of the print dialog when webkit_print_operation_run_dialog() is called.
431 */
432void webkit_print_operation_set_page_setup(WebKitPrintOperation* printOperation, GtkPageSetup* pageSetup)
433{
434 g_return_if_fail(WEBKIT_IS_PRINT_OPERATION(printOperation));
435 g_return_if_fail(GTK_IS_PAGE_SETUP(pageSetup));
436
437 if (printOperation->priv->pageSetup.get() == pageSetup)
438 return;
439
440 printOperation->priv->pageSetup = pageSetup;
441 g_object_notify(G_OBJECT(printOperation), "page-setup");
442}
443
444/**
445 * webkit_print_operation_run_dialog:
446 * @print_operation: a #WebKitPrintOperation
447 * @parent: (allow-none): transient parent of the print dialog
448 *
449 * Run the print dialog and start printing using the options selected by
450 * the user. This method returns when the print dialog is closed.
451 * If the print dialog is cancelled %WEBKIT_PRINT_OPERATION_RESPONSE_CANCEL
452 * is returned. If the user clicks on the print button, %WEBKIT_PRINT_OPERATION_RESPONSE_PRINT
453 * is returned and the print operation starts. In this case, the #WebKitPrintOperation::finished
454 * signal is emitted when the operation finishes. If an error occurs while printing, the signal
455 * #WebKitPrintOperation::failed is emitted before #WebKitPrintOperation::finished.
456 * If the print dialog is not cancelled current print settings and page setup of @print_operation
457 * are updated with options selected by the user when Print button is pressed in print dialog.
458 * You can get the updated print settings and page setup by calling
459 * webkit_print_operation_get_print_settings() and webkit_print_operation_get_page_setup()
460 * after this method.
461 *
462 * Returns: the #WebKitPrintOperationResponse of the print dialog
463 */
464WebKitPrintOperationResponse webkit_print_operation_run_dialog(WebKitPrintOperation* printOperation, GtkWindow* parent)
465{
466 g_return_val_if_fail(WEBKIT_IS_PRINT_OPERATION(printOperation), WEBKIT_PRINT_OPERATION_RESPONSE_CANCEL);
467
468 auto& page = webkitWebViewGetPage(printOperation->priv->webView);
469 return webkitPrintOperationRunDialogForFrame(printOperation, parent, page.mainFrame());
470}
471
472/**
473 * webkit_print_operation_print:
474 * @print_operation: a #WebKitPrintOperation
475 *
476 * Start a print operation using current print settings and page setup
477 * without showing the print dialog. If either print settings or page setup
478 * are not set with webkit_print_operation_set_print_settings() and
479 * webkit_print_operation_set_page_setup(), the default options will be used
480 * and the print job will be sent to the default printer.
481 * The #WebKitPrintOperation::finished signal is emitted when the printing
482 * operation finishes. If an error occurs while printing the signal
483 * #WebKitPrintOperation::failed is emitted before #WebKitPrintOperation::finished.
484 */
485void webkit_print_operation_print(WebKitPrintOperation* printOperation)
486{
487 g_return_if_fail(WEBKIT_IS_PRINT_OPERATION(printOperation));
488
489 WebKitPrintOperationPrivate* priv = printOperation->priv;
490 GRefPtr<GtkPrintSettings> printSettings = priv->printSettings ? priv->printSettings : adoptGRef(gtk_print_settings_new());
491 GRefPtr<GtkPageSetup> pageSetup = priv->pageSetup ? priv->pageSetup : adoptGRef(gtk_page_setup_new());
492
493 auto& page = webkitWebViewGetPage(printOperation->priv->webView);
494 webkitPrintOperationPrintPagesForFrame(printOperation, page.mainFrame(), printSettings.get(), pageSetup.get());
495}
496