1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include <gtk/gtk.h>
7 #include <gtk/gtkunixprint.h>
8 #include <stdlib.h>
9
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/Services.h"
12
13 #include "MozContainer.h"
14 #include "nsIPrintSettings.h"
15 #include "nsIWidget.h"
16 #include "nsPrintDialogGTK.h"
17 #include "nsPrintSettingsGTK.h"
18 #include "nsString.h"
19 #include "nsReadableUtils.h"
20 #include "nsIStringBundle.h"
21 #include "nsIPrintSettingsService.h"
22 #include "nsPIDOMWindow.h"
23 #include "nsPrintfCString.h"
24 #include "nsIGIOService.h"
25 #include "nsServiceManagerUtils.h"
26 #include "WidgetUtils.h"
27 #include "WidgetUtilsGtk.h"
28 #include "nsIObserverService.h"
29
30 // for gdk_x11_window_get_xid
31 #include <gdk/gdkx.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35 #include <gio/gunixfdlist.h>
36
37 // for dlsym
38 #include <dlfcn.h>
39 #include "MainThreadUtils.h"
40
41 using namespace mozilla;
42 using namespace mozilla::widget;
43
44 static const char header_footer_tags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"};
45
46 #define CUSTOM_VALUE_INDEX gint(ArrayLength(header_footer_tags))
47
get_gtk_window_for_nsiwidget(nsIWidget * widget)48 static GtkWindow* get_gtk_window_for_nsiwidget(nsIWidget* widget) {
49 return GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET));
50 }
51
ShowCustomDialog(GtkComboBox * changed_box,gpointer user_data)52 static void ShowCustomDialog(GtkComboBox* changed_box, gpointer user_data) {
53 if (gtk_combo_box_get_active(changed_box) != CUSTOM_VALUE_INDEX) {
54 g_object_set_data(G_OBJECT(changed_box), "previous-active",
55 GINT_TO_POINTER(gtk_combo_box_get_active(changed_box)));
56 return;
57 }
58
59 GtkWindow* printDialog = GTK_WINDOW(user_data);
60 nsCOMPtr<nsIStringBundleService> bundleSvc =
61 do_GetService(NS_STRINGBUNDLE_CONTRACTID);
62
63 nsCOMPtr<nsIStringBundle> printBundle;
64 bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
65 getter_AddRefs(printBundle));
66 nsAutoString intlString;
67
68 printBundle->GetStringFromName("headerFooterCustom", intlString);
69 GtkWidget* prompt_dialog = gtk_dialog_new_with_buttons(
70 NS_ConvertUTF16toUTF8(intlString).get(), printDialog,
71 (GtkDialogFlags)(GTK_DIALOG_MODAL), GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
72 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, nullptr);
73 gtk_dialog_set_default_response(GTK_DIALOG(prompt_dialog),
74 GTK_RESPONSE_ACCEPT);
75 gtk_dialog_set_alternative_button_order(
76 GTK_DIALOG(prompt_dialog), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_REJECT, -1);
77
78 printBundle->GetStringFromName("customHeaderFooterPrompt", intlString);
79 GtkWidget* custom_label =
80 gtk_label_new(NS_ConvertUTF16toUTF8(intlString).get());
81 GtkWidget* custom_entry = gtk_entry_new();
82 GtkWidget* question_icon =
83 gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
84
85 // To be convenient, prefill the textbox with the existing value, if any, and
86 // select it all so they can easily both edit it and type in a new one.
87 const char* current_text =
88 (const char*)g_object_get_data(G_OBJECT(changed_box), "custom-text");
89 if (current_text) {
90 gtk_entry_set_text(GTK_ENTRY(custom_entry), current_text);
91 gtk_editable_select_region(GTK_EDITABLE(custom_entry), 0, -1);
92 }
93 gtk_entry_set_activates_default(GTK_ENTRY(custom_entry), TRUE);
94
95 GtkWidget* custom_vbox = gtk_vbox_new(TRUE, 2);
96 gtk_box_pack_start(GTK_BOX(custom_vbox), custom_label, FALSE, FALSE, 0);
97 gtk_box_pack_start(GTK_BOX(custom_vbox), custom_entry, FALSE, FALSE,
98 5); // Make entry 5px underneath label
99 GtkWidget* custom_hbox = gtk_hbox_new(FALSE, 2);
100 gtk_box_pack_start(GTK_BOX(custom_hbox), question_icon, FALSE, FALSE, 0);
101 gtk_box_pack_start(GTK_BOX(custom_hbox), custom_vbox, FALSE, FALSE,
102 10); // Make question icon 10px away from content
103 gtk_container_set_border_width(GTK_CONTAINER(custom_hbox), 2);
104 gtk_widget_show_all(custom_hbox);
105
106 gtk_box_pack_start(
107 GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(prompt_dialog))),
108 custom_hbox, FALSE, FALSE, 0);
109 gint diag_response = gtk_dialog_run(GTK_DIALOG(prompt_dialog));
110
111 if (diag_response == GTK_RESPONSE_ACCEPT) {
112 const gchar* response_text = gtk_entry_get_text(GTK_ENTRY(custom_entry));
113 g_object_set_data_full(G_OBJECT(changed_box), "custom-text",
114 strdup(response_text), (GDestroyNotify)free);
115 g_object_set_data(G_OBJECT(changed_box), "previous-active",
116 GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
117 } else {
118 // Go back to the previous index
119 gint previous_active = GPOINTER_TO_INT(
120 g_object_get_data(G_OBJECT(changed_box), "previous-active"));
121 gtk_combo_box_set_active(changed_box, previous_active);
122 }
123
124 gtk_widget_destroy(prompt_dialog);
125 }
126
127 class nsPrintDialogWidgetGTK {
128 public:
129 nsPrintDialogWidgetGTK(nsPIDOMWindowOuter* aParent,
130 nsIPrintSettings* aPrintSettings);
~nsPrintDialogWidgetGTK()131 ~nsPrintDialogWidgetGTK() { gtk_widget_destroy(dialog); }
132 NS_ConvertUTF16toUTF8 GetUTF8FromBundle(const char* aKey);
133 gint Run();
134
135 nsresult ImportSettings(nsIPrintSettings* aNSSettings);
136 nsresult ExportSettings(nsIPrintSettings* aNSSettings);
137
138 private:
139 GtkWidget* dialog;
140 GtkWidget* shrink_to_fit_toggle;
141 GtkWidget* print_bg_colors_toggle;
142 GtkWidget* print_bg_images_toggle;
143 GtkWidget* selection_only_toggle;
144 GtkWidget* header_dropdown[3]; // {left, center, right}
145 GtkWidget* footer_dropdown[3];
146
147 nsCOMPtr<nsIStringBundle> printBundle;
148
149 bool useNativeSelection;
150
151 GtkWidget* ConstructHeaderFooterDropdown(const char16_t* currentString);
152 const char* OptionWidgetToString(GtkWidget* dropdown);
153
154 /* Code to copy between GTK and NS print settings structures.
155 * In the following,
156 * "Import" means to copy from NS to GTK
157 * "Export" means to copy from GTK to NS
158 */
159 void ExportHeaderFooter(nsIPrintSettings* aNS);
160 };
161
nsPrintDialogWidgetGTK(nsPIDOMWindowOuter * aParent,nsIPrintSettings * aSettings)162 nsPrintDialogWidgetGTK::nsPrintDialogWidgetGTK(nsPIDOMWindowOuter* aParent,
163 nsIPrintSettings* aSettings) {
164 nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
165 NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
166 GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
167 NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
168
169 nsCOMPtr<nsIStringBundleService> bundleSvc =
170 do_GetService(NS_STRINGBUNDLE_CONTRACTID);
171 bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
172 getter_AddRefs(printBundle));
173
174 dialog = gtk_print_unix_dialog_new(GetUTF8FromBundle("printTitleGTK").get(),
175 gtkParent);
176
177 gtk_print_unix_dialog_set_manual_capabilities(
178 GTK_PRINT_UNIX_DIALOG(dialog),
179 GtkPrintCapabilities(
180 GTK_PRINT_CAPABILITY_COPIES | GTK_PRINT_CAPABILITY_COLLATE |
181 GTK_PRINT_CAPABILITY_REVERSE | GTK_PRINT_CAPABILITY_SCALE |
182 GTK_PRINT_CAPABILITY_GENERATE_PDF));
183
184 // The vast majority of magic numbers in this widget construction are padding.
185 // e.g. for the set_border_width below, 12px matches that of just about every
186 // other window.
187 GtkWidget* custom_options_tab = gtk_vbox_new(FALSE, 0);
188 gtk_container_set_border_width(GTK_CONTAINER(custom_options_tab), 12);
189 GtkWidget* tab_label =
190 gtk_label_new(GetUTF8FromBundle("optionsTabLabelGTK").get());
191
192 // Check buttons for shrink-to-fit and print selection
193 GtkWidget* check_buttons_container = gtk_vbox_new(TRUE, 2);
194 shrink_to_fit_toggle = gtk_check_button_new_with_mnemonic(
195 GetUTF8FromBundle("shrinkToFit").get());
196 gtk_box_pack_start(GTK_BOX(check_buttons_container), shrink_to_fit_toggle,
197 FALSE, FALSE, 0);
198
199 // GTK+2.18 and above allow us to add a "Selection" option to the main
200 // settings screen, rather than adding an option on a custom tab like we must
201 // do on older versions.
202 bool canSelectText = aSettings->GetIsPrintSelectionRBEnabled();
203 if (gtk_major_version > 2 ||
204 (gtk_major_version == 2 && gtk_minor_version >= 18)) {
205 useNativeSelection = true;
206 g_object_set(dialog, "support-selection", TRUE, "has-selection",
207 canSelectText, "embed-page-setup", TRUE, nullptr);
208 } else {
209 useNativeSelection = false;
210 selection_only_toggle = gtk_check_button_new_with_mnemonic(
211 GetUTF8FromBundle("selectionOnly").get());
212 gtk_widget_set_sensitive(selection_only_toggle, canSelectText);
213 gtk_box_pack_start(GTK_BOX(check_buttons_container), selection_only_toggle,
214 FALSE, FALSE, 0);
215 }
216
217 // Check buttons for printing background
218 GtkWidget* appearance_buttons_container = gtk_vbox_new(TRUE, 2);
219 print_bg_colors_toggle = gtk_check_button_new_with_mnemonic(
220 GetUTF8FromBundle("printBGColors").get());
221 print_bg_images_toggle = gtk_check_button_new_with_mnemonic(
222 GetUTF8FromBundle("printBGImages").get());
223 gtk_box_pack_start(GTK_BOX(appearance_buttons_container),
224 print_bg_colors_toggle, FALSE, FALSE, 0);
225 gtk_box_pack_start(GTK_BOX(appearance_buttons_container),
226 print_bg_images_toggle, FALSE, FALSE, 0);
227
228 // "Appearance" options label, bold and center-aligned
229 GtkWidget* appearance_label = gtk_label_new(nullptr);
230 char* pangoMarkup = g_markup_printf_escaped(
231 "<b>%s</b>", GetUTF8FromBundle("printBGOptions").get());
232 gtk_label_set_markup(GTK_LABEL(appearance_label), pangoMarkup);
233 g_free(pangoMarkup);
234 gtk_misc_set_alignment(GTK_MISC(appearance_label), 0, 0);
235
236 GtkWidget* appearance_container = gtk_alignment_new(0, 0, 0, 0);
237 gtk_alignment_set_padding(GTK_ALIGNMENT(appearance_container), 8, 0, 12, 0);
238 gtk_container_add(GTK_CONTAINER(appearance_container),
239 appearance_buttons_container);
240
241 GtkWidget* appearance_vertical_squasher = gtk_vbox_new(FALSE, 0);
242 gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher), appearance_label,
243 FALSE, FALSE, 0);
244 gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher),
245 appearance_container, FALSE, FALSE, 0);
246
247 // "Header & Footer" options label, bold and center-aligned
248 GtkWidget* header_footer_label = gtk_label_new(nullptr);
249 pangoMarkup = g_markup_printf_escaped(
250 "<b>%s</b>", GetUTF8FromBundle("headerFooter").get());
251 gtk_label_set_markup(GTK_LABEL(header_footer_label), pangoMarkup);
252 g_free(pangoMarkup);
253 gtk_misc_set_alignment(GTK_MISC(header_footer_label), 0, 0);
254
255 GtkWidget* header_footer_container = gtk_alignment_new(0, 0, 0, 0);
256 gtk_alignment_set_padding(GTK_ALIGNMENT(header_footer_container), 8, 0, 12,
257 0);
258
259 // --- Table for making the header and footer options ---
260 GtkWidget* header_footer_table = gtk_table_new(3, 3, FALSE); // 3x3 table
261 nsString header_footer_str[3];
262
263 aSettings->GetHeaderStrLeft(header_footer_str[0]);
264 aSettings->GetHeaderStrCenter(header_footer_str[1]);
265 aSettings->GetHeaderStrRight(header_footer_str[2]);
266
267 for (unsigned int i = 0; i < ArrayLength(header_dropdown); i++) {
268 header_dropdown[i] =
269 ConstructHeaderFooterDropdown(header_footer_str[i].get());
270 // Those 4 magic numbers in the middle provide the position in the table.
271 // The last two numbers mean 2 px padding on every side.
272 gtk_table_attach(GTK_TABLE(header_footer_table), header_dropdown[i], i,
273 (i + 1), 0, 1, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
274 2);
275 }
276
277 const char labelKeys[][7] = {"left", "center", "right"};
278 for (unsigned int i = 0; i < ArrayLength(labelKeys); i++) {
279 gtk_table_attach(GTK_TABLE(header_footer_table),
280 gtk_label_new(GetUTF8FromBundle(labelKeys[i]).get()), i,
281 (i + 1), 1, 2, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
282 2);
283 }
284
285 aSettings->GetFooterStrLeft(header_footer_str[0]);
286 aSettings->GetFooterStrCenter(header_footer_str[1]);
287 aSettings->GetFooterStrRight(header_footer_str[2]);
288
289 for (unsigned int i = 0; i < ArrayLength(footer_dropdown); i++) {
290 footer_dropdown[i] =
291 ConstructHeaderFooterDropdown(header_footer_str[i].get());
292 gtk_table_attach(GTK_TABLE(header_footer_table), footer_dropdown[i], i,
293 (i + 1), 2, 3, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
294 2);
295 }
296 // ---
297
298 gtk_container_add(GTK_CONTAINER(header_footer_container),
299 header_footer_table);
300
301 GtkWidget* header_footer_vertical_squasher = gtk_vbox_new(FALSE, 0);
302 gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher),
303 header_footer_label, FALSE, FALSE, 0);
304 gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher),
305 header_footer_container, FALSE, FALSE, 0);
306
307 // Construction of everything
308 gtk_box_pack_start(GTK_BOX(custom_options_tab), check_buttons_container,
309 FALSE, FALSE, 10); // 10px padding
310 gtk_box_pack_start(GTK_BOX(custom_options_tab), appearance_vertical_squasher,
311 FALSE, FALSE, 10);
312 gtk_box_pack_start(GTK_BOX(custom_options_tab),
313 header_footer_vertical_squasher, FALSE, FALSE, 0);
314
315 gtk_print_unix_dialog_add_custom_tab(GTK_PRINT_UNIX_DIALOG(dialog),
316 custom_options_tab, tab_label);
317 gtk_widget_show_all(custom_options_tab);
318 }
319
GetUTF8FromBundle(const char * aKey)320 NS_ConvertUTF16toUTF8 nsPrintDialogWidgetGTK::GetUTF8FromBundle(
321 const char* aKey) {
322 nsAutoString intlString;
323 printBundle->GetStringFromName(aKey, intlString);
324 return NS_ConvertUTF16toUTF8(
325 intlString); // Return the actual object so we don't lose reference
326 }
327
OptionWidgetToString(GtkWidget * dropdown)328 const char* nsPrintDialogWidgetGTK::OptionWidgetToString(GtkWidget* dropdown) {
329 gint index = gtk_combo_box_get_active(GTK_COMBO_BOX(dropdown));
330
331 NS_ASSERTION(index <= CUSTOM_VALUE_INDEX,
332 "Index of dropdown is higher than expected!");
333
334 if (index == CUSTOM_VALUE_INDEX)
335 return (const char*)g_object_get_data(G_OBJECT(dropdown), "custom-text");
336 return header_footer_tags[index];
337 }
338
Run()339 gint nsPrintDialogWidgetGTK::Run() {
340 const gint response = gtk_dialog_run(GTK_DIALOG(dialog));
341 gtk_widget_hide(dialog);
342 return response;
343 }
344
ExportHeaderFooter(nsIPrintSettings * aNS)345 void nsPrintDialogWidgetGTK::ExportHeaderFooter(nsIPrintSettings* aNS) {
346 const char* header_footer_str;
347 header_footer_str = OptionWidgetToString(header_dropdown[0]);
348 aNS->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(header_footer_str));
349
350 header_footer_str = OptionWidgetToString(header_dropdown[1]);
351 aNS->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(header_footer_str));
352
353 header_footer_str = OptionWidgetToString(header_dropdown[2]);
354 aNS->SetHeaderStrRight(NS_ConvertUTF8toUTF16(header_footer_str));
355
356 header_footer_str = OptionWidgetToString(footer_dropdown[0]);
357 aNS->SetFooterStrLeft(NS_ConvertUTF8toUTF16(header_footer_str));
358
359 header_footer_str = OptionWidgetToString(footer_dropdown[1]);
360 aNS->SetFooterStrCenter(NS_ConvertUTF8toUTF16(header_footer_str));
361
362 header_footer_str = OptionWidgetToString(footer_dropdown[2]);
363 aNS->SetFooterStrRight(NS_ConvertUTF8toUTF16(header_footer_str));
364 }
365
ImportSettings(nsIPrintSettings * aNSSettings)366 nsresult nsPrintDialogWidgetGTK::ImportSettings(nsIPrintSettings* aNSSettings) {
367 MOZ_ASSERT(aNSSettings, "aSettings must not be null");
368 NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
369
370 nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
371 if (!aNSSettingsGTK) return NS_ERROR_FAILURE;
372
373 GtkPrintSettings* settings = aNSSettingsGTK->GetGtkPrintSettings();
374 GtkPageSetup* setup = aNSSettingsGTK->GetGtkPageSetup();
375
376 bool geckoBool;
377 aNSSettings->GetShrinkToFit(&geckoBool);
378 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle),
379 geckoBool);
380
381 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_colors_toggle),
382 aNSSettings->GetPrintBGColors());
383
384 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_images_toggle),
385 aNSSettings->GetPrintBGImages());
386
387 gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog), settings);
388 gtk_print_unix_dialog_set_page_setup(GTK_PRINT_UNIX_DIALOG(dialog), setup);
389
390 return NS_OK;
391 }
392
ExportSettings(nsIPrintSettings * aNSSettings)393 nsresult nsPrintDialogWidgetGTK::ExportSettings(nsIPrintSettings* aNSSettings) {
394 MOZ_ASSERT(aNSSettings, "aSettings must not be null");
395 NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
396
397 GtkPrintSettings* settings =
398 gtk_print_unix_dialog_get_settings(GTK_PRINT_UNIX_DIALOG(dialog));
399 GtkPageSetup* setup =
400 gtk_print_unix_dialog_get_page_setup(GTK_PRINT_UNIX_DIALOG(dialog));
401 GtkPrinter* printer =
402 gtk_print_unix_dialog_get_selected_printer(GTK_PRINT_UNIX_DIALOG(dialog));
403 if (settings && setup && printer) {
404 ExportHeaderFooter(aNSSettings);
405
406 aNSSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatNative);
407
408 // Print-to-file is true by default. This must be turned off or else
409 // printing won't occur! (We manually copy the spool file when this flag is
410 // set, because we love our embedders) Even if it is print-to-file in GTK's
411 // case, GTK does The Right Thing when we send the job.
412 aNSSettings->SetPrintToFile(false);
413
414 aNSSettings->SetShrinkToFit(
415 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle)));
416
417 aNSSettings->SetPrintBGColors(gtk_toggle_button_get_active(
418 GTK_TOGGLE_BUTTON(print_bg_colors_toggle)));
419 aNSSettings->SetPrintBGImages(gtk_toggle_button_get_active(
420 GTK_TOGGLE_BUTTON(print_bg_images_toggle)));
421
422 // Try to save native settings in the session object
423 nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
424 if (aNSSettingsGTK) {
425 aNSSettingsGTK->SetGtkPrintSettings(settings);
426 aNSSettingsGTK->SetGtkPageSetup(setup);
427 aNSSettingsGTK->SetGtkPrinter(printer);
428 bool printSelectionOnly;
429 if (useNativeSelection) {
430 _GtkPrintPages pageSetting =
431 (_GtkPrintPages)gtk_print_settings_get_print_pages(settings);
432 printSelectionOnly = (pageSetting == _GTK_PRINT_PAGES_SELECTION);
433 } else {
434 printSelectionOnly = gtk_toggle_button_get_active(
435 GTK_TOGGLE_BUTTON(selection_only_toggle));
436 }
437 aNSSettingsGTK->SetPrintSelectionOnly(printSelectionOnly);
438 }
439 }
440
441 if (settings) g_object_unref(settings);
442 return NS_OK;
443 }
444
ConstructHeaderFooterDropdown(const char16_t * currentString)445 GtkWidget* nsPrintDialogWidgetGTK::ConstructHeaderFooterDropdown(
446 const char16_t* currentString) {
447 GtkWidget* dropdown = gtk_combo_box_text_new();
448 const char hf_options[][22] = {"headerFooterBlank", "headerFooterTitle",
449 "headerFooterURL", "headerFooterDate",
450 "headerFooterPage", "headerFooterPageTotal",
451 "headerFooterCustom"};
452
453 for (unsigned int i = 0; i < ArrayLength(hf_options); i++) {
454 gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(dropdown), nullptr,
455 GetUTF8FromBundle(hf_options[i]).get());
456 }
457
458 bool shouldBeCustom = true;
459 NS_ConvertUTF16toUTF8 currentStringUTF8(currentString);
460
461 for (unsigned int i = 0; i < ArrayLength(header_footer_tags); i++) {
462 if (!strcmp(currentStringUTF8.get(), header_footer_tags[i])) {
463 gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), i);
464 g_object_set_data(G_OBJECT(dropdown), "previous-active",
465 GINT_TO_POINTER(i));
466 shouldBeCustom = false;
467 break;
468 }
469 }
470
471 if (shouldBeCustom) {
472 gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), CUSTOM_VALUE_INDEX);
473 g_object_set_data(G_OBJECT(dropdown), "previous-active",
474 GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
475 char* custom_string = strdup(currentStringUTF8.get());
476 g_object_set_data_full(G_OBJECT(dropdown), "custom-text", custom_string,
477 (GDestroyNotify)free);
478 }
479
480 g_signal_connect(dropdown, "changed", (GCallback)ShowCustomDialog, dialog);
481 return dropdown;
482 }
483
484 NS_IMPL_ISUPPORTS(nsPrintDialogServiceGTK, nsIPrintDialogService)
485
486 nsPrintDialogServiceGTK::nsPrintDialogServiceGTK() = default;
487
488 nsPrintDialogServiceGTK::~nsPrintDialogServiceGTK() = default;
489
490 NS_IMETHODIMP
Init()491 nsPrintDialogServiceGTK::Init() { return NS_OK; }
492
493 NS_IMETHODIMP
Show(nsPIDOMWindowOuter * aParent,nsIPrintSettings * aSettings)494 nsPrintDialogServiceGTK::Show(nsPIDOMWindowOuter* aParent,
495 nsIPrintSettings* aSettings) {
496 MOZ_ASSERT(aParent, "aParent must not be null");
497 MOZ_ASSERT(aSettings, "aSettings must not be null");
498
499 nsPrintDialogWidgetGTK printDialog(aParent, aSettings);
500 nsresult rv = printDialog.ImportSettings(aSettings);
501
502 NS_ENSURE_SUCCESS(rv, rv);
503
504 const gint response = printDialog.Run();
505
506 // Handle the result
507 switch (response) {
508 case GTK_RESPONSE_OK: // Proceed
509 rv = printDialog.ExportSettings(aSettings);
510 break;
511
512 case GTK_RESPONSE_CANCEL:
513 case GTK_RESPONSE_CLOSE:
514 case GTK_RESPONSE_DELETE_EVENT:
515 case GTK_RESPONSE_NONE:
516 rv = NS_ERROR_ABORT;
517 break;
518
519 case GTK_RESPONSE_APPLY: // Print preview
520 default:
521 NS_WARNING("Unexpected response");
522 rv = NS_ERROR_ABORT;
523 }
524 return rv;
525 }
526
527 NS_IMETHODIMP
ShowPageSetup(nsPIDOMWindowOuter * aParent,nsIPrintSettings * aNSSettings)528 nsPrintDialogServiceGTK::ShowPageSetup(nsPIDOMWindowOuter* aParent,
529 nsIPrintSettings* aNSSettings) {
530 MOZ_ASSERT(aParent, "aParent must not be null");
531 MOZ_ASSERT(aNSSettings, "aSettings must not be null");
532 NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
533
534 nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
535 NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
536 GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
537 NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
538
539 nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
540 if (!aNSSettingsGTK) return NS_ERROR_FAILURE;
541
542 // We need to init the prefs here because aNSSettings in its current form is a
543 // dummy in both uses of the word
544 nsCOMPtr<nsIPrintSettingsService> psService =
545 do_GetService("@mozilla.org/gfx/printsettings-service;1");
546 if (psService) {
547 nsString printName;
548 aNSSettings->GetPrinterName(printName);
549 if (printName.IsVoid()) {
550 psService->GetLastUsedPrinterName(printName);
551 aNSSettings->SetPrinterName(printName);
552 }
553 psService->InitPrintSettingsFromPrefs(aNSSettings, true,
554 nsIPrintSettings::kInitSaveAll);
555 }
556
557 // Frustratingly, gtk_print_run_page_setup_dialog doesn't tell us whether
558 // the user cancelled or confirmed the dialog! So to avoid needlessly
559 // refreshing the preview when Page Setup was cancelled, we compare the
560 // serializations of old and new settings; if they're the same, bail out.
561 GtkPrintSettings* gtkSettings = aNSSettingsGTK->GetGtkPrintSettings();
562 GtkPageSetup* oldPageSetup = aNSSettingsGTK->GetGtkPageSetup();
563 GKeyFile* oldKeyFile = g_key_file_new();
564 gtk_page_setup_to_key_file(oldPageSetup, oldKeyFile, nullptr);
565 gsize oldLength;
566 gchar* oldData = g_key_file_to_data(oldKeyFile, &oldLength, nullptr);
567 g_key_file_free(oldKeyFile);
568
569 GtkPageSetup* newPageSetup =
570 gtk_print_run_page_setup_dialog(gtkParent, oldPageSetup, gtkSettings);
571
572 GKeyFile* newKeyFile = g_key_file_new();
573 gtk_page_setup_to_key_file(newPageSetup, newKeyFile, nullptr);
574 gsize newLength;
575 gchar* newData = g_key_file_to_data(newKeyFile, &newLength, nullptr);
576 g_key_file_free(newKeyFile);
577
578 bool unchanged =
579 (oldLength == newLength && !memcmp(oldData, newData, oldLength));
580 g_free(oldData);
581 g_free(newData);
582 if (unchanged) {
583 g_object_unref(newPageSetup);
584 return NS_ERROR_ABORT;
585 }
586
587 aNSSettingsGTK->SetGtkPageSetup(newPageSetup);
588
589 // Now newPageSetup has a refcount of 2 (SetGtkPageSetup will addref), put it
590 // to 1 so if this gets replaced we don't leak.
591 g_object_unref(newPageSetup);
592
593 if (psService)
594 psService->SavePrintSettingsToPrefs(
595 aNSSettings, true,
596 nsIPrintSettings::kInitSaveOrientation |
597 nsIPrintSettings::kInitSavePaperSize |
598 nsIPrintSettings::kInitSaveUnwriteableMargins);
599
600 return NS_OK;
601 }
602