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