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