/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <gtk/gtk.h>
#include <gtk/gtkunixprint.h>
#include <stdlib.h>

#include "mozilla/ArrayUtils.h"

#include "mozcontainer.h"
#include "nsIPrintSettings.h"
#include "nsIWidget.h"
#include "nsPrintDialogGTK.h"
#include "nsPrintSettingsGTK.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsIFile.h"
#include "nsIStringBundle.h"
#include "nsIPrintSettingsService.h"
#include "nsIDOMWindow.h"
#include "nsPIDOMWindow.h"
#include "nsIBaseWindow.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocShell.h"
#include "nsIGIOService.h"
#include "WidgetUtils.h"
#include "nsIObserverService.h"

// for gdk_x11_window_get_xid
#include <gdk/gdkx.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <gio/gunixfdlist.h>

// for dlsym
#include <dlfcn.h>
#include "MainThreadUtils.h"

using namespace mozilla;
using namespace mozilla::widget;

static const char header_footer_tags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"};

#define CUSTOM_VALUE_INDEX gint(ArrayLength(header_footer_tags))

static GtkWindow* get_gtk_window_for_nsiwidget(nsIWidget* widget) {
  return GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET));
}

static void ShowCustomDialog(GtkComboBox* changed_box, gpointer user_data) {
  if (gtk_combo_box_get_active(changed_box) != CUSTOM_VALUE_INDEX) {
    g_object_set_data(G_OBJECT(changed_box), "previous-active",
                      GINT_TO_POINTER(gtk_combo_box_get_active(changed_box)));
    return;
  }

  GtkWindow* printDialog = GTK_WINDOW(user_data);
  nsCOMPtr<nsIStringBundleService> bundleSvc =
      do_GetService(NS_STRINGBUNDLE_CONTRACTID);

  nsCOMPtr<nsIStringBundle> printBundle;
  bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
                          getter_AddRefs(printBundle));
  nsAutoString intlString;

  printBundle->GetStringFromName("headerFooterCustom", intlString);
  GtkWidget* prompt_dialog = gtk_dialog_new_with_buttons(
      NS_ConvertUTF16toUTF8(intlString).get(), printDialog,
      (GtkDialogFlags)(GTK_DIALOG_MODAL), GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
      GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, nullptr);
  gtk_dialog_set_default_response(GTK_DIALOG(prompt_dialog),
                                  GTK_RESPONSE_ACCEPT);
  gtk_dialog_set_alternative_button_order(
      GTK_DIALOG(prompt_dialog), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_REJECT, -1);

  printBundle->GetStringFromName("customHeaderFooterPrompt", intlString);
  GtkWidget* custom_label =
      gtk_label_new(NS_ConvertUTF16toUTF8(intlString).get());
  GtkWidget* custom_entry = gtk_entry_new();
  GtkWidget* question_icon =
      gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);

  // To be convenient, prefill the textbox with the existing value, if any, and
  // select it all so they can easily both edit it and type in a new one.
  const char* current_text =
      (const char*)g_object_get_data(G_OBJECT(changed_box), "custom-text");
  if (current_text) {
    gtk_entry_set_text(GTK_ENTRY(custom_entry), current_text);
    gtk_editable_select_region(GTK_EDITABLE(custom_entry), 0, -1);
  }
  gtk_entry_set_activates_default(GTK_ENTRY(custom_entry), TRUE);

  GtkWidget* custom_vbox = gtk_vbox_new(TRUE, 2);
  gtk_box_pack_start(GTK_BOX(custom_vbox), custom_label, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(custom_vbox), custom_entry, FALSE, FALSE,
                     5);  // Make entry 5px underneath label
  GtkWidget* custom_hbox = gtk_hbox_new(FALSE, 2);
  gtk_box_pack_start(GTK_BOX(custom_hbox), question_icon, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(custom_hbox), custom_vbox, FALSE, FALSE,
                     10);  // Make question icon 10px away from content
  gtk_container_set_border_width(GTK_CONTAINER(custom_hbox), 2);
  gtk_widget_show_all(custom_hbox);

  gtk_box_pack_start(
      GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(prompt_dialog))),
      custom_hbox, FALSE, FALSE, 0);
  gint diag_response = gtk_dialog_run(GTK_DIALOG(prompt_dialog));

  if (diag_response == GTK_RESPONSE_ACCEPT) {
    const gchar* response_text = gtk_entry_get_text(GTK_ENTRY(custom_entry));
    g_object_set_data_full(G_OBJECT(changed_box), "custom-text",
                           strdup(response_text), (GDestroyNotify)free);
    g_object_set_data(G_OBJECT(changed_box), "previous-active",
                      GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
  } else {
    // Go back to the previous index
    gint previous_active = GPOINTER_TO_INT(
        g_object_get_data(G_OBJECT(changed_box), "previous-active"));
    gtk_combo_box_set_active(changed_box, previous_active);
  }

  gtk_widget_destroy(prompt_dialog);
}

class nsPrintDialogWidgetGTK {
 public:
  nsPrintDialogWidgetGTK(nsPIDOMWindowOuter* aParent,
                         nsIPrintSettings* aPrintSettings);
  ~nsPrintDialogWidgetGTK() { gtk_widget_destroy(dialog); }
  NS_ConvertUTF16toUTF8 GetUTF8FromBundle(const char* aKey);
  gint Run();

  nsresult ImportSettings(nsIPrintSettings* aNSSettings);
  nsresult ExportSettings(nsIPrintSettings* aNSSettings);

 private:
  GtkWidget* dialog;
  GtkWidget* radio_as_laid_out;
  GtkWidget* radio_selected_frame;
  GtkWidget* radio_separate_frames;
  GtkWidget* shrink_to_fit_toggle;
  GtkWidget* print_bg_colors_toggle;
  GtkWidget* print_bg_images_toggle;
  GtkWidget* selection_only_toggle;
  GtkWidget* header_dropdown[3];  // {left, center, right}
  GtkWidget* footer_dropdown[3];

  nsCOMPtr<nsIStringBundle> printBundle;

  bool useNativeSelection;

  GtkWidget* ConstructHeaderFooterDropdown(const char16_t* currentString);
  const char* OptionWidgetToString(GtkWidget* dropdown);

  /* Code to copy between GTK and NS print settings structures.
   * In the following,
   *   "Import" means to copy from NS to GTK
   *   "Export" means to copy from GTK to NS
   */
  void ExportFramePrinting(nsIPrintSettings* aNS, GtkPrintSettings* aSettings);
  void ExportHeaderFooter(nsIPrintSettings* aNS);
};

nsPrintDialogWidgetGTK::nsPrintDialogWidgetGTK(nsPIDOMWindowOuter* aParent,
                                               nsIPrintSettings* aSettings) {
  nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
  NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
  GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
  NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");

  nsCOMPtr<nsIStringBundleService> bundleSvc =
      do_GetService(NS_STRINGBUNDLE_CONTRACTID);
  bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
                          getter_AddRefs(printBundle));

  dialog = gtk_print_unix_dialog_new(GetUTF8FromBundle("printTitleGTK").get(),
                                     gtkParent);

  gtk_print_unix_dialog_set_manual_capabilities(
      GTK_PRINT_UNIX_DIALOG(dialog),
      GtkPrintCapabilities(
          GTK_PRINT_CAPABILITY_PAGE_SET | GTK_PRINT_CAPABILITY_COPIES |
          GTK_PRINT_CAPABILITY_COLLATE | GTK_PRINT_CAPABILITY_REVERSE |
          GTK_PRINT_CAPABILITY_SCALE | GTK_PRINT_CAPABILITY_GENERATE_PDF));

  // The vast majority of magic numbers in this widget construction are padding.
  // e.g. for the set_border_width below, 12px matches that of just about every
  // other window.
  GtkWidget* custom_options_tab = gtk_vbox_new(FALSE, 0);
  gtk_container_set_border_width(GTK_CONTAINER(custom_options_tab), 12);
  GtkWidget* tab_label =
      gtk_label_new(GetUTF8FromBundle("optionsTabLabelGTK").get());

  int16_t frameUIFlag;
  aSettings->GetHowToEnableFrameUI(&frameUIFlag);
  radio_as_laid_out = gtk_radio_button_new_with_mnemonic(
      nullptr, GetUTF8FromBundle("asLaidOut").get());
  if (frameUIFlag == nsIPrintSettings::kFrameEnableNone)
    gtk_widget_set_sensitive(radio_as_laid_out, FALSE);

  radio_selected_frame = gtk_radio_button_new_with_mnemonic_from_widget(
      GTK_RADIO_BUTTON(radio_as_laid_out),
      GetUTF8FromBundle("selectedFrame").get());
  if (frameUIFlag == nsIPrintSettings::kFrameEnableNone ||
      frameUIFlag == nsIPrintSettings::kFrameEnableAsIsAndEach)
    gtk_widget_set_sensitive(radio_selected_frame, FALSE);

  radio_separate_frames = gtk_radio_button_new_with_mnemonic_from_widget(
      GTK_RADIO_BUTTON(radio_as_laid_out),
      GetUTF8FromBundle("separateFrames").get());
  if (frameUIFlag == nsIPrintSettings::kFrameEnableNone)
    gtk_widget_set_sensitive(radio_separate_frames, FALSE);

  // "Print Frames" options label, bold and center-aligned
  GtkWidget* print_frames_label = gtk_label_new(nullptr);
  char* pangoMarkup = g_markup_printf_escaped(
      "<b>%s</b>", GetUTF8FromBundle("printFramesTitleGTK").get());
  gtk_label_set_markup(GTK_LABEL(print_frames_label), pangoMarkup);
  g_free(pangoMarkup);
  gtk_misc_set_alignment(GTK_MISC(print_frames_label), 0, 0);

  // Align the radio buttons slightly so they appear to fall under the
  // aforementioned label as per the GNOME HIG
  GtkWidget* frames_radio_container = gtk_alignment_new(0, 0, 0, 0);
  gtk_alignment_set_padding(GTK_ALIGNMENT(frames_radio_container), 8, 0, 12, 0);

  // Radio buttons for the print frames options
  GtkWidget* frames_radio_list = gtk_vbox_new(TRUE, 2);
  gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_as_laid_out, FALSE,
                     FALSE, 0);
  gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_selected_frame, FALSE,
                     FALSE, 0);
  gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_separate_frames, FALSE,
                     FALSE, 0);
  gtk_container_add(GTK_CONTAINER(frames_radio_container), frames_radio_list);

  // Check buttons for shrink-to-fit and print selection
  GtkWidget* check_buttons_container = gtk_vbox_new(TRUE, 2);
  shrink_to_fit_toggle = gtk_check_button_new_with_mnemonic(
      GetUTF8FromBundle("shrinkToFit").get());
  gtk_box_pack_start(GTK_BOX(check_buttons_container), shrink_to_fit_toggle,
                     FALSE, FALSE, 0);

  // GTK+2.18 and above allow us to add a "Selection" option to the main
  // settings screen, rather than adding an option on a custom tab like we must
  // do on older versions.
  bool canSelectText;
  aSettings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB,
                             &canSelectText);
  if (gtk_major_version > 2 ||
      (gtk_major_version == 2 && gtk_minor_version >= 18)) {
    useNativeSelection = true;
    g_object_set(dialog, "support-selection", TRUE, "has-selection",
                 canSelectText, "embed-page-setup", TRUE, nullptr);
  } else {
    useNativeSelection = false;
    selection_only_toggle = gtk_check_button_new_with_mnemonic(
        GetUTF8FromBundle("selectionOnly").get());
    gtk_widget_set_sensitive(selection_only_toggle, canSelectText);
    gtk_box_pack_start(GTK_BOX(check_buttons_container), selection_only_toggle,
                       FALSE, FALSE, 0);
  }

  // Check buttons for printing background
  GtkWidget* appearance_buttons_container = gtk_vbox_new(TRUE, 2);
  print_bg_colors_toggle = gtk_check_button_new_with_mnemonic(
      GetUTF8FromBundle("printBGColors").get());
  print_bg_images_toggle = gtk_check_button_new_with_mnemonic(
      GetUTF8FromBundle("printBGImages").get());
  gtk_box_pack_start(GTK_BOX(appearance_buttons_container),
                     print_bg_colors_toggle, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(appearance_buttons_container),
                     print_bg_images_toggle, FALSE, FALSE, 0);

  // "Appearance" options label, bold and center-aligned
  GtkWidget* appearance_label = gtk_label_new(nullptr);
  pangoMarkup = g_markup_printf_escaped(
      "<b>%s</b>", GetUTF8FromBundle("printBGOptions").get());
  gtk_label_set_markup(GTK_LABEL(appearance_label), pangoMarkup);
  g_free(pangoMarkup);
  gtk_misc_set_alignment(GTK_MISC(appearance_label), 0, 0);

  GtkWidget* appearance_container = gtk_alignment_new(0, 0, 0, 0);
  gtk_alignment_set_padding(GTK_ALIGNMENT(appearance_container), 8, 0, 12, 0);
  gtk_container_add(GTK_CONTAINER(appearance_container),
                    appearance_buttons_container);

  GtkWidget* appearance_vertical_squasher = gtk_vbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher), appearance_label,
                     FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher),
                     appearance_container, FALSE, FALSE, 0);

  // "Header & Footer" options label, bold and center-aligned
  GtkWidget* header_footer_label = gtk_label_new(nullptr);
  pangoMarkup = g_markup_printf_escaped(
      "<b>%s</b>", GetUTF8FromBundle("headerFooter").get());
  gtk_label_set_markup(GTK_LABEL(header_footer_label), pangoMarkup);
  g_free(pangoMarkup);
  gtk_misc_set_alignment(GTK_MISC(header_footer_label), 0, 0);

  GtkWidget* header_footer_container = gtk_alignment_new(0, 0, 0, 0);
  gtk_alignment_set_padding(GTK_ALIGNMENT(header_footer_container), 8, 0, 12,
                            0);

  // --- Table for making the header and footer options ---
  GtkWidget* header_footer_table = gtk_table_new(3, 3, FALSE);  // 3x3 table
  nsString header_footer_str[3];

  aSettings->GetHeaderStrLeft(header_footer_str[0]);
  aSettings->GetHeaderStrCenter(header_footer_str[1]);
  aSettings->GetHeaderStrRight(header_footer_str[2]);

  for (unsigned int i = 0; i < ArrayLength(header_dropdown); i++) {
    header_dropdown[i] =
        ConstructHeaderFooterDropdown(header_footer_str[i].get());
    // Those 4 magic numbers in the middle provide the position in the table.
    // The last two numbers mean 2 px padding on every side.
    gtk_table_attach(GTK_TABLE(header_footer_table), header_dropdown[i], i,
                     (i + 1), 0, 1, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
                     2);
  }

  const char labelKeys[][7] = {"left", "center", "right"};
  for (unsigned int i = 0; i < ArrayLength(labelKeys); i++) {
    gtk_table_attach(GTK_TABLE(header_footer_table),
                     gtk_label_new(GetUTF8FromBundle(labelKeys[i]).get()), i,
                     (i + 1), 1, 2, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
                     2);
  }

  aSettings->GetFooterStrLeft(header_footer_str[0]);
  aSettings->GetFooterStrCenter(header_footer_str[1]);
  aSettings->GetFooterStrRight(header_footer_str[2]);

  for (unsigned int i = 0; i < ArrayLength(footer_dropdown); i++) {
    footer_dropdown[i] =
        ConstructHeaderFooterDropdown(header_footer_str[i].get());
    gtk_table_attach(GTK_TABLE(header_footer_table), footer_dropdown[i], i,
                     (i + 1), 2, 3, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
                     2);
  }
  // ---

  gtk_container_add(GTK_CONTAINER(header_footer_container),
                    header_footer_table);

  GtkWidget* header_footer_vertical_squasher = gtk_vbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher),
                     header_footer_label, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher),
                     header_footer_container, FALSE, FALSE, 0);

  // Construction of everything
  gtk_box_pack_start(GTK_BOX(custom_options_tab), print_frames_label, FALSE,
                     FALSE, 0);
  gtk_box_pack_start(GTK_BOX(custom_options_tab), frames_radio_container, FALSE,
                     FALSE, 0);
  gtk_box_pack_start(GTK_BOX(custom_options_tab), check_buttons_container,
                     FALSE, FALSE, 10);  // 10px padding
  gtk_box_pack_start(GTK_BOX(custom_options_tab), appearance_vertical_squasher,
                     FALSE, FALSE, 10);
  gtk_box_pack_start(GTK_BOX(custom_options_tab),
                     header_footer_vertical_squasher, FALSE, FALSE, 0);

  gtk_print_unix_dialog_add_custom_tab(GTK_PRINT_UNIX_DIALOG(dialog),
                                       custom_options_tab, tab_label);
  gtk_widget_show_all(custom_options_tab);
}

NS_ConvertUTF16toUTF8 nsPrintDialogWidgetGTK::GetUTF8FromBundle(
    const char* aKey) {
  nsAutoString intlString;
  printBundle->GetStringFromName(aKey, intlString);
  return NS_ConvertUTF16toUTF8(
      intlString);  // Return the actual object so we don't lose reference
}

const char* nsPrintDialogWidgetGTK::OptionWidgetToString(GtkWidget* dropdown) {
  gint index = gtk_combo_box_get_active(GTK_COMBO_BOX(dropdown));

  NS_ASSERTION(index <= CUSTOM_VALUE_INDEX,
               "Index of dropdown is higher than expected!");

  if (index == CUSTOM_VALUE_INDEX)
    return (const char*)g_object_get_data(G_OBJECT(dropdown), "custom-text");
  else
    return header_footer_tags[index];
}

gint nsPrintDialogWidgetGTK::Run() {
  const gint response = gtk_dialog_run(GTK_DIALOG(dialog));
  gtk_widget_hide(dialog);
  return response;
}

void nsPrintDialogWidgetGTK::ExportFramePrinting(nsIPrintSettings* aNS,
                                                 GtkPrintSettings* aSettings) {
  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_as_laid_out)))
    aNS->SetPrintFrameType(nsIPrintSettings::kFramesAsIs);
  else if (gtk_toggle_button_get_active(
               GTK_TOGGLE_BUTTON(radio_selected_frame)))
    aNS->SetPrintFrameType(nsIPrintSettings::kSelectedFrame);
  else if (gtk_toggle_button_get_active(
               GTK_TOGGLE_BUTTON(radio_separate_frames)))
    aNS->SetPrintFrameType(nsIPrintSettings::kEachFrameSep);
  else
    aNS->SetPrintFrameType(nsIPrintSettings::kNoFrames);
}

void nsPrintDialogWidgetGTK::ExportHeaderFooter(nsIPrintSettings* aNS) {
  const char* header_footer_str;
  header_footer_str = OptionWidgetToString(header_dropdown[0]);
  aNS->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(header_footer_str));

  header_footer_str = OptionWidgetToString(header_dropdown[1]);
  aNS->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(header_footer_str));

  header_footer_str = OptionWidgetToString(header_dropdown[2]);
  aNS->SetHeaderStrRight(NS_ConvertUTF8toUTF16(header_footer_str));

  header_footer_str = OptionWidgetToString(footer_dropdown[0]);
  aNS->SetFooterStrLeft(NS_ConvertUTF8toUTF16(header_footer_str));

  header_footer_str = OptionWidgetToString(footer_dropdown[1]);
  aNS->SetFooterStrCenter(NS_ConvertUTF8toUTF16(header_footer_str));

  header_footer_str = OptionWidgetToString(footer_dropdown[2]);
  aNS->SetFooterStrRight(NS_ConvertUTF8toUTF16(header_footer_str));
}

nsresult nsPrintDialogWidgetGTK::ImportSettings(nsIPrintSettings* aNSSettings) {
  MOZ_ASSERT(aNSSettings, "aSettings must not be null");
  NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);

  nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
  if (!aNSSettingsGTK) return NS_ERROR_FAILURE;

  GtkPrintSettings* settings = aNSSettingsGTK->GetGtkPrintSettings();
  GtkPageSetup* setup = aNSSettingsGTK->GetGtkPageSetup();

  bool geckoBool;
  aNSSettings->GetShrinkToFit(&geckoBool);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle),
                               geckoBool);

  aNSSettings->GetPrintBGColors(&geckoBool);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_colors_toggle),
                               geckoBool);

  aNSSettings->GetPrintBGImages(&geckoBool);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_images_toggle),
                               geckoBool);

  gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog), settings);
  gtk_print_unix_dialog_set_page_setup(GTK_PRINT_UNIX_DIALOG(dialog), setup);

  return NS_OK;
}

nsresult nsPrintDialogWidgetGTK::ExportSettings(nsIPrintSettings* aNSSettings) {
  MOZ_ASSERT(aNSSettings, "aSettings must not be null");
  NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);

  GtkPrintSettings* settings =
      gtk_print_unix_dialog_get_settings(GTK_PRINT_UNIX_DIALOG(dialog));
  GtkPageSetup* setup =
      gtk_print_unix_dialog_get_page_setup(GTK_PRINT_UNIX_DIALOG(dialog));
  GtkPrinter* printer =
      gtk_print_unix_dialog_get_selected_printer(GTK_PRINT_UNIX_DIALOG(dialog));
  if (settings && setup && printer) {
    ExportFramePrinting(aNSSettings, settings);
    ExportHeaderFooter(aNSSettings);

    aNSSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatNative);

    // Print-to-file is true by default. This must be turned off or else
    // printing won't occur! (We manually copy the spool file when this flag is
    // set, because we love our embedders) Even if it is print-to-file in GTK's
    // case, GTK does The Right Thing when we send the job.
    aNSSettings->SetPrintToFile(false);

    aNSSettings->SetShrinkToFit(
        gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle)));

    aNSSettings->SetPrintBGColors(gtk_toggle_button_get_active(
        GTK_TOGGLE_BUTTON(print_bg_colors_toggle)));
    aNSSettings->SetPrintBGImages(gtk_toggle_button_get_active(
        GTK_TOGGLE_BUTTON(print_bg_images_toggle)));

    // Try to save native settings in the session object
    nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
    if (aNSSettingsGTK) {
      aNSSettingsGTK->SetGtkPrintSettings(settings);
      aNSSettingsGTK->SetGtkPageSetup(setup);
      aNSSettingsGTK->SetGtkPrinter(printer);
      bool printSelectionOnly;
      if (useNativeSelection) {
        _GtkPrintPages pageSetting =
            (_GtkPrintPages)gtk_print_settings_get_print_pages(settings);
        printSelectionOnly = (pageSetting == _GTK_PRINT_PAGES_SELECTION);
      } else {
        printSelectionOnly = gtk_toggle_button_get_active(
            GTK_TOGGLE_BUTTON(selection_only_toggle));
      }
      aNSSettingsGTK->SetForcePrintSelectionOnly(printSelectionOnly);
    }
  }

  if (settings) g_object_unref(settings);
  return NS_OK;
}

GtkWidget* nsPrintDialogWidgetGTK::ConstructHeaderFooterDropdown(
    const char16_t* currentString) {
  GtkWidget* dropdown = gtk_combo_box_text_new();
  const char hf_options[][22] = {"headerFooterBlank", "headerFooterTitle",
                                 "headerFooterURL",   "headerFooterDate",
                                 "headerFooterPage",  "headerFooterPageTotal",
                                 "headerFooterCustom"};

  for (unsigned int i = 0; i < ArrayLength(hf_options); i++) {
    gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(dropdown), nullptr,
                              GetUTF8FromBundle(hf_options[i]).get());
  }

  bool shouldBeCustom = true;
  NS_ConvertUTF16toUTF8 currentStringUTF8(currentString);

  for (unsigned int i = 0; i < ArrayLength(header_footer_tags); i++) {
    if (!strcmp(currentStringUTF8.get(), header_footer_tags[i])) {
      gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), i);
      g_object_set_data(G_OBJECT(dropdown), "previous-active",
                        GINT_TO_POINTER(i));
      shouldBeCustom = false;
      break;
    }
  }

  if (shouldBeCustom) {
    gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), CUSTOM_VALUE_INDEX);
    g_object_set_data(G_OBJECT(dropdown), "previous-active",
                      GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
    char* custom_string = strdup(currentStringUTF8.get());
    g_object_set_data_full(G_OBJECT(dropdown), "custom-text", custom_string,
                           (GDestroyNotify)free);
  }

  g_signal_connect(dropdown, "changed", (GCallback)ShowCustomDialog, dialog);
  return dropdown;
}

NS_IMPL_ISUPPORTS(nsPrintDialogServiceGTK, nsIPrintDialogService)

nsPrintDialogServiceGTK::nsPrintDialogServiceGTK() {}

nsPrintDialogServiceGTK::~nsPrintDialogServiceGTK() {}

NS_IMETHODIMP
nsPrintDialogServiceGTK::Init() { return NS_OK; }

// Used to obtain window handle. The portal use this handle
// to ensure that print dialog is modal.
typedef void (*WindowHandleExported)(GtkWindow* window, const char* handle,
                                     gpointer user_data);

typedef void (*GtkWindowHandleExported)(GtkWindow* window, const char* handle,
                                        gpointer user_data);
#ifdef MOZ_WAYLAND
#  if !GTK_CHECK_VERSION(3, 22, 0)
typedef void (*GdkWaylandWindowExported)(GdkWindow* window, const char* handle,
                                         gpointer user_data);
#  endif

typedef struct {
  GtkWindow* window;
  WindowHandleExported callback;
  gpointer user_data;
} WaylandWindowHandleExportedData;

static void wayland_window_handle_exported(GdkWindow* window,
                                           const char* wayland_handle_str,
                                           gpointer user_data) {
  WaylandWindowHandleExportedData* data =
      static_cast<WaylandWindowHandleExportedData*>(user_data);
  char* handle_str;

  handle_str = g_strdup_printf("wayland:%s", wayland_handle_str);
  data->callback(data->window, handle_str, data->user_data);
  g_free(handle_str);
}
#endif

// Get window handle for the portal, taken from gtk/gtkwindow.c
// (currently not exported)
static gboolean window_export_handle(GtkWindow* window,
                                     GtkWindowHandleExported callback,
                                     gpointer user_data) {
  if (GDK_IS_X11_DISPLAY(gtk_widget_get_display(GTK_WIDGET(window)))) {
    GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
    char* handle_str;
    guint32 xid = (guint32)gdk_x11_window_get_xid(gdk_window);

    handle_str = g_strdup_printf("x11:%x", xid);
    callback(window, handle_str, user_data);
    g_free(handle_str);
    return true;
  }
#ifdef MOZ_WAYLAND
  else {
    GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
    WaylandWindowHandleExportedData* data;

    data = g_new0(WaylandWindowHandleExportedData, 1);
    data->window = window;
    data->callback = callback;
    data->user_data = user_data;

    static auto s_gdk_wayland_window_export_handle =
        reinterpret_cast<gboolean (*)(GdkWindow*, GdkWaylandWindowExported,
                                      gpointer, GDestroyNotify)>(
            dlsym(RTLD_DEFAULT, "gdk_wayland_window_export_handle"));
    if (!s_gdk_wayland_window_export_handle ||
        !s_gdk_wayland_window_export_handle(
            gdk_window, wayland_window_handle_exported, data, g_free)) {
      g_free(data);
      return false;
    } else {
      return true;
    }
  }
#endif

  g_warning("Couldn't export handle, unsupported windowing system");

  return false;
}
/**
 * Communication class with the GTK print portal handler
 *
 * To print document from flatpak we need to use print portal because
 * printers are not directly accessible in the sandboxed environment.
 *
 * At first we request portal to show the print dialog to let user choose
 * printer settings. We use DBUS interface for that (PreparePrint method).
 *
 * Next we force application to print to temporary file and after the writing
 * to the file is finished we pass its file descriptor to the portal.
 * Portal will pass duplicate of the file descriptor to the printer which
 * user selected before (by DBUS Print method).
 *
 * Since DBUS communication is done async while nsPrintDialogServiceGTK::Show
 * is expecting sync execution, we need to create a new GMainLoop during the
 * print portal dialog is running. The loop is stopped after the dialog
 * is closed.
 */
class nsFlatpakPrintPortal : public nsIObserver {
  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER
 public:
  explicit nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings);
  nsresult PreparePrintRequest(GtkWindow* aWindow);
  static void OnWindowExportHandleDone(GtkWindow* aWindow,
                                       const char* aWindowHandleStr,
                                       gpointer aUserData);
  void PreparePrint(GtkWindow* aWindow, const char* aWindowHandleStr);
  static void OnPreparePrintResponse(GDBusConnection* connection,
                                     const char* sender_name,
                                     const char* object_path,
                                     const char* interface_name,
                                     const char* signal_name,
                                     GVariant* parameters, gpointer data);
  GtkPrintOperationResult GetResult();

 private:
  virtual ~nsFlatpakPrintPortal();
  void FinishPrintDialog(GVariant* parameters);
  nsCOMPtr<nsPrintSettingsGTK> mPrintAndPageSettings;
  GDBusProxy* mProxy;
  guint32 mToken;
  GMainLoop* mLoop;
  GtkPrintOperationResult mResult;
  guint mResponseSignalId;
  GtkWindow* mParentWindow;
};

NS_IMPL_ISUPPORTS(nsFlatpakPrintPortal, nsIObserver)

nsFlatpakPrintPortal::nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings)
    : mPrintAndPageSettings(aPrintSettings),
      mProxy(nullptr),
      mLoop(nullptr),
      mResponseSignalId(0),
      mParentWindow(nullptr) {}

/**
 * Creates GDBusProxy, query for window handle and create a new GMainLoop.
 *
 * The GMainLoop is to be run from GetResult() and be quitted during
 * FinishPrintDialog.
 *
 * @param aWindow toplevel application window which is used as parent of print
 *                dialog
 */
nsresult nsFlatpakPrintPortal::PreparePrintRequest(GtkWindow* aWindow) {
  MOZ_ASSERT(aWindow, "aWindow must not be null");
  MOZ_ASSERT(mPrintAndPageSettings, "mPrintAndPageSettings must not be null");

  GError* error = nullptr;
  mProxy = g_dbus_proxy_new_for_bus_sync(
      G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr,
      "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
      "org.freedesktop.portal.Print", nullptr, &error);
  if (mProxy == nullptr) {
    NS_WARNING(
        nsPrintfCString("Unable to create dbus proxy: %s", error->message)
            .get());
    g_error_free(error);
    return NS_ERROR_FAILURE;
  }

  // The window handler is returned async, we will continue by PreparePrint
  // method when it is returned.
  if (!window_export_handle(
          aWindow, &nsFlatpakPrintPortal::OnWindowExportHandleDone, this)) {
    NS_WARNING("Unable to get window handle for creating modal print dialog.");
    return NS_ERROR_FAILURE;
  }

  mLoop = g_main_loop_new(NULL, FALSE);
  return NS_OK;
}

void nsFlatpakPrintPortal::OnWindowExportHandleDone(
    GtkWindow* aWindow, const char* aWindowHandleStr, gpointer aUserData) {
  nsFlatpakPrintPortal* printPortal =
      static_cast<nsFlatpakPrintPortal*>(aUserData);
  printPortal->PreparePrint(aWindow, aWindowHandleStr);
}

/**
 * Ask print portal to show the print dialog.
 *
 * Print and page settings and window handle are passed to the portal to prefill
 * last used settings.
 */
void nsFlatpakPrintPortal::PreparePrint(GtkWindow* aWindow,
                                        const char* aWindowHandleStr) {
  GtkPrintSettings* gtkSettings = mPrintAndPageSettings->GetGtkPrintSettings();
  GtkPageSetup* pageSetup = mPrintAndPageSettings->GetGtkPageSetup();

  // We need to remember GtkWindow to unexport window handle after it is
  // no longer needed by the portal dialog (apply only on non-X11 sessions).
  if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
    mParentWindow = aWindow;
  }

  GVariantBuilder opt_builder;
  g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT);
  char* token = g_strdup_printf("mozilla%d", g_random_int_range(0, G_MAXINT));
  g_variant_builder_add(&opt_builder, "{sv}", "handle_token",
                        g_variant_new_string(token));
  g_free(token);
  GVariant* options = g_variant_builder_end(&opt_builder);
  static auto s_gtk_print_settings_to_gvariant =
      reinterpret_cast<GVariant* (*)(GtkPrintSettings*)>(
          dlsym(RTLD_DEFAULT, "gtk_print_settings_to_gvariant"));
  static auto s_gtk_page_setup_to_gvariant =
      reinterpret_cast<GVariant* (*)(GtkPageSetup*)>(
          dlsym(RTLD_DEFAULT, "gtk_page_setup_to_gvariant"));
  if (!s_gtk_print_settings_to_gvariant || !s_gtk_page_setup_to_gvariant) {
    mResult = GTK_PRINT_OPERATION_RESULT_ERROR;
    FinishPrintDialog(nullptr);
    return;
  }

  // Get translated window title
  nsCOMPtr<nsIStringBundleService> bundleSvc =
      do_GetService(NS_STRINGBUNDLE_CONTRACTID);
  nsCOMPtr<nsIStringBundle> printBundle;
  bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
                          getter_AddRefs(printBundle));
  nsAutoString intlPrintTitle;
  printBundle->GetStringFromName("printTitleGTK", intlPrintTitle);

  GError* error = nullptr;
  GVariant* ret = g_dbus_proxy_call_sync(
      mProxy, "PreparePrint",
      g_variant_new(
          "(ss@a{sv}@a{sv}@a{sv})", aWindowHandleStr,
          NS_ConvertUTF16toUTF8(intlPrintTitle).get(),  // Title of the window
          s_gtk_print_settings_to_gvariant(gtkSettings),
          s_gtk_page_setup_to_gvariant(pageSetup), options),
      G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
  if (ret == nullptr) {
    NS_WARNING(
        nsPrintfCString("Unable to call dbus proxy: %s", error->message).get());
    g_error_free(error);
    mResult = GTK_PRINT_OPERATION_RESULT_ERROR;
    FinishPrintDialog(nullptr);
    return;
  }

  const char* handle = nullptr;
  g_variant_get(ret, "(&o)", &handle);
  if (strcmp(aWindowHandleStr, handle) != 0) {
    aWindowHandleStr = g_strdup(handle);
    if (mResponseSignalId) {
      g_dbus_connection_signal_unsubscribe(
          g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId);
    }
  }
  mResponseSignalId = g_dbus_connection_signal_subscribe(
      g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)),
      "org.freedesktop.portal.Desktop", "org.freedesktop.portal.Request",
      "Response", aWindowHandleStr, NULL, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
      &nsFlatpakPrintPortal::OnPreparePrintResponse, this, NULL);
}

void nsFlatpakPrintPortal::OnPreparePrintResponse(
    GDBusConnection* connection, const char* sender_name,
    const char* object_path, const char* interface_name,
    const char* signal_name, GVariant* parameters, gpointer data) {
  nsFlatpakPrintPortal* printPortal = static_cast<nsFlatpakPrintPortal*>(data);
  printPortal->FinishPrintDialog(parameters);
}

/**
 * When the dialog is accepted, read print and page settings and token.
 *
 * Token is later used for printing portal as print operation identifier.
 * Print and page settings are modified in-place and stored to
 * mPrintAndPageSettings.
 */
void nsFlatpakPrintPortal::FinishPrintDialog(GVariant* parameters) {
  // This ends GetResult() method
  if (mLoop) {
    g_main_loop_quit(mLoop);
    mLoop = nullptr;
  }

  if (!parameters) {
    // mResult should be already defined
    return;
  }

  guint32 response;
  GVariant* options;

  g_variant_get(parameters, "(u@a{sv})", &response, &options);
  mResult = GTK_PRINT_OPERATION_RESULT_CANCEL;
  if (response == 0) {
    GVariant* v;

    char* filename;
    char* uri;
    v = g_variant_lookup_value(options, "settings", G_VARIANT_TYPE_VARDICT);
    static auto s_gtk_print_settings_new_from_gvariant =
        reinterpret_cast<GtkPrintSettings* (*)(GVariant*)>(
            dlsym(RTLD_DEFAULT, "gtk_print_settings_new_from_gvariant"));

    GtkPrintSettings* printSettings = s_gtk_print_settings_new_from_gvariant(v);
    g_variant_unref(v);

    v = g_variant_lookup_value(options, "page-setup", G_VARIANT_TYPE_VARDICT);
    static auto s_gtk_page_setup_new_from_gvariant =
        reinterpret_cast<GtkPageSetup* (*)(GVariant*)>(
            dlsym(RTLD_DEFAULT, "gtk_page_setup_new_from_gvariant"));
    GtkPageSetup* pageSetup = s_gtk_page_setup_new_from_gvariant(v);
    g_variant_unref(v);

    g_variant_lookup(options, "token", "u", &mToken);

    // Force printing to file because only filedescriptor of the file
    // can be passed to portal
    int fd = g_file_open_tmp("gtkprintXXXXXX", &filename, NULL);
    uri = g_filename_to_uri(filename, NULL, NULL);
    gtk_print_settings_set(printSettings, GTK_PRINT_SETTINGS_OUTPUT_URI, uri);
    g_free(uri);
    close(fd);

    // Save native settings in the session object
    mPrintAndPageSettings->SetGtkPrintSettings(printSettings);
    mPrintAndPageSettings->SetGtkPageSetup(pageSetup);

    // Portal consumes PDF file
    mPrintAndPageSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatPDF);

    // We need to set to print to file
    mPrintAndPageSettings->SetPrintToFile(true);

    mResult = GTK_PRINT_OPERATION_RESULT_APPLY;
  }
}

/**
 * Get result of the print dialog.
 *
 * This call blocks until FinishPrintDialog is called.
 *
 */
GtkPrintOperationResult nsFlatpakPrintPortal::GetResult() {
  // If the mLoop has not been initialized we haven't go thru PreparePrint
  // method
  if (!NS_IsMainThread() || !mLoop) {
    return GTK_PRINT_OPERATION_RESULT_ERROR;
  }
  // Calling g_main_loop_run stops current code until g_main_loop_quit is called
  g_main_loop_run(mLoop);

  // Free resources we've allocated in order to show print dialog.
#ifdef MOZ_WAYLAND
  if (mParentWindow) {
    GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(mParentWindow));
    static auto s_gdk_wayland_window_unexport_handle =
        reinterpret_cast<void (*)(GdkWindow*)>(
            dlsym(RTLD_DEFAULT, "gdk_wayland_window_unexport_handle"));
    if (s_gdk_wayland_window_unexport_handle) {
      s_gdk_wayland_window_unexport_handle(gdk_window);
    }
  }
#endif
  return mResult;
}

/**
 * Send file descriptor of the file which contains document to the portal to
 * finish the print operation.
 */
NS_IMETHODIMP
nsFlatpakPrintPortal::Observe(nsISupports* aObject, const char* aTopic,
                              const char16_t* aData) {
  // Check that written file match to the stored filename in case multiple
  // print operations are in progress.
  nsAutoString filenameStr;
  mPrintAndPageSettings->GetToFileName(filenameStr);
  if (!nsDependentString(aData).Equals(filenameStr)) {
    // Different file is finished, not for this instance
    return NS_OK;
  }
  int fd, idx;
  fd = open(NS_ConvertUTF16toUTF8(filenameStr).get(), O_RDONLY | O_CLOEXEC);
  static auto s_g_unix_fd_list_new = reinterpret_cast<GUnixFDList* (*)(void)>(
      dlsym(RTLD_DEFAULT, "g_unix_fd_list_new"));
  NS_ASSERTION(s_g_unix_fd_list_new,
               "Cannot find g_unix_fd_list_new function.");

  GUnixFDList* fd_list = s_g_unix_fd_list_new();
  static auto s_g_unix_fd_list_append =
      reinterpret_cast<gint (*)(GUnixFDList*, gint, GError**)>(
          dlsym(RTLD_DEFAULT, "g_unix_fd_list_append"));
  idx = s_g_unix_fd_list_append(fd_list, fd, NULL);
  close(fd);

  GVariantBuilder opt_builder;
  g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT);
  g_variant_builder_add(&opt_builder, "{sv}", "token",
                        g_variant_new_uint32(mToken));
  g_dbus_proxy_call_with_unix_fd_list(
      mProxy, "Print",
      g_variant_new("(ssh@a{sv})", "", /* window */
                    "Print",           /* title */
                    idx, g_variant_builder_end(&opt_builder)),
      G_DBUS_CALL_FLAGS_NONE, -1, fd_list, NULL,
      NULL,      // TODO portal result cb function
      nullptr);  // data
  g_object_unref(fd_list);

  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
  // Let the nsFlatpakPrintPortal instance die
  os->RemoveObserver(this, "print-to-file-finished");
  return NS_OK;
}

nsFlatpakPrintPortal::~nsFlatpakPrintPortal() {
  if (mProxy) {
    if (mResponseSignalId) {
      g_dbus_connection_signal_unsubscribe(
          g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId);
    }
    g_object_unref(mProxy);
  }
  if (mLoop) g_main_loop_quit(mLoop);
}

NS_IMETHODIMP
nsPrintDialogServiceGTK::Show(nsPIDOMWindowOuter* aParent,
                              nsIPrintSettings* aSettings,
                              nsIWebBrowserPrint* aWebBrowserPrint) {
  MOZ_ASSERT(aParent, "aParent must not be null");
  MOZ_ASSERT(aSettings, "aSettings must not be null");

  // Check for the flatpak portal first
  nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
  bool shouldUsePortal;
  giovfs->ShouldUseFlatpakPortal(&shouldUsePortal);
  if (shouldUsePortal && gtk_check_version(3, 22, 0) == nullptr) {
    nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
    NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
    GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
    NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");

    nsCOMPtr<nsPrintSettingsGTK> printSettingsGTK(do_QueryInterface(aSettings));
    RefPtr<nsFlatpakPrintPortal> fpPrintPortal =
        new nsFlatpakPrintPortal(printSettingsGTK);

    nsresult rv = fpPrintPortal->PreparePrintRequest(gtkParent);
    NS_ENSURE_SUCCESS(rv, rv);

    // This blocks until nsFlatpakPrintPortal::FinishPrintDialog is called
    GtkPrintOperationResult printDialogResult = fpPrintPortal->GetResult();

    rv = NS_OK;
    switch (printDialogResult) {
      case GTK_PRINT_OPERATION_RESULT_APPLY: {
        nsCOMPtr<nsIObserverService> os =
            mozilla::services::GetObserverService();
        NS_ENSURE_STATE(os);
        // Observer waits until notified that the file with the content
        // to print has been written.
        rv = os->AddObserver(fpPrintPortal, "print-to-file-finished", false);
        NS_ENSURE_SUCCESS(rv, rv);
        break;
      }
      case GTK_PRINT_OPERATION_RESULT_CANCEL:
        rv = NS_ERROR_ABORT;
        break;
      default:
        NS_WARNING("Unexpected response");
        rv = NS_ERROR_ABORT;
    }
    return rv;
  }

  nsPrintDialogWidgetGTK printDialog(aParent, aSettings);
  nsresult rv = printDialog.ImportSettings(aSettings);

  NS_ENSURE_SUCCESS(rv, rv);

  const gint response = printDialog.Run();

  // Handle the result
  switch (response) {
    case GTK_RESPONSE_OK:  // Proceed
      rv = printDialog.ExportSettings(aSettings);
      break;

    case GTK_RESPONSE_CANCEL:
    case GTK_RESPONSE_CLOSE:
    case GTK_RESPONSE_DELETE_EVENT:
    case GTK_RESPONSE_NONE:
      rv = NS_ERROR_ABORT;
      break;

    case GTK_RESPONSE_APPLY:  // Print preview
    default:
      NS_WARNING("Unexpected response");
      rv = NS_ERROR_ABORT;
  }
  return rv;
}

NS_IMETHODIMP
nsPrintDialogServiceGTK::ShowPageSetup(nsPIDOMWindowOuter* aParent,
                                       nsIPrintSettings* aNSSettings) {
  MOZ_ASSERT(aParent, "aParent must not be null");
  MOZ_ASSERT(aNSSettings, "aSettings must not be null");
  NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);

  nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
  NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
  GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
  NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");

  nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
  if (!aNSSettingsGTK) return NS_ERROR_FAILURE;

  // We need to init the prefs here because aNSSettings in its current form is a
  // dummy in both uses of the word
  nsCOMPtr<nsIPrintSettingsService> psService =
      do_GetService("@mozilla.org/gfx/printsettings-service;1");
  if (psService) {
    nsString printName;
    aNSSettings->GetPrinterName(printName);
    if (printName.IsVoid()) {
      psService->GetDefaultPrinterName(printName);
      aNSSettings->SetPrinterName(printName);
    }
    psService->InitPrintSettingsFromPrefs(aNSSettings, true,
                                          nsIPrintSettings::kInitSaveAll);
  }

  GtkPrintSettings* gtkSettings = aNSSettingsGTK->GetGtkPrintSettings();
  GtkPageSetup* oldPageSetup = aNSSettingsGTK->GetGtkPageSetup();

  GtkPageSetup* newPageSetup =
      gtk_print_run_page_setup_dialog(gtkParent, oldPageSetup, gtkSettings);

  aNSSettingsGTK->SetGtkPageSetup(newPageSetup);

  // Now newPageSetup has a refcount of 2 (SetGtkPageSetup will addref), put it
  // to 1 so if this gets replaced we don't leak.
  g_object_unref(newPageSetup);

  if (psService)
    psService->SavePrintSettingsToPrefs(aNSSettings, true,
                                        nsIPrintSettings::kInitSaveAll);

  return NS_OK;
}
