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