// SPDX-License-Identifier: GPL-2.0-or-later
/* Authors:
 *   Lauris Kaplinski <lauris@kaplinski.com>
 *   bulia byak <buliabyak@users.sf.net>
 *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
 *   Peter Bostrom
 *   Jon A. Cruz <jon@joncruz.org>
 *   Abhishek Sharma
 *   Kris De Gussem <Kris.DeGussem@gmail.com>
 *
 * Copyright (C) 1999-2007, 2012 Authors
 * Copyright (C) 2001-2002 Ximian, Inc.
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

// This has to be included prior to anything that includes setjmp.h, it croaks otherwise
#include <png.h>

#include <gtkmm/box.h>
#include <gtkmm/buttonbox.h>
#include <gtkmm/dialog.h>
#include <gtkmm/entry.h>
#include <gtkmm/grid.h>
#include <gtkmm/spinbutton.h>

#include <glibmm/i18n.h>
#include <glibmm/miscutils.h>

#include <gdl/gdl-dock-item.h>

#include "document-undo.h"
#include "document.h"
#include "file.h"
#include "inkscape.h"
#include "preferences.h"
#include "selection-chemistry.h"
#include "verbs.h"

// required to set status message after export
#include "desktop.h"
#include "message-stack.h"

#include "helper/png-write.h"

#include "io/resource.h"
#include "io/sys.h"

#include "object/sp-namedview.h"
#include "object/sp-root.h"

#include "ui/dialog-events.h"
#include "ui/interface.h"
#include "ui/widget/unit-menu.h"

#include "extension/db.h"
#include "extension/output.h"


#ifdef _WIN32
#include <windows.h>
#include <commdlg.h>
#include <gdk/gdkwin32.h>
#include <glibmm/fileutils.h>
#endif

#define SP_EXPORT_MIN_SIZE 1.0

#define DPI_BASE Inkscape::Util::Quantity::convert(1, "in", "px")

#define EXPORT_COORD_PRECISION 3

#include "export.h"

using Inkscape::Util::unit_table;

namespace {

class MessageCleaner
{
public:
    MessageCleaner(Inkscape::MessageId messageId, SPDesktop *desktop) :
        _desktop(desktop),
        _messageId(messageId)
    {
    }

    ~MessageCleaner()
    {
        if (_messageId && _desktop) {
            _desktop->messageStack()->cancel(_messageId);
        }
    }

private:
    MessageCleaner(MessageCleaner const &other) = delete;
    MessageCleaner &operator=(MessageCleaner const &other) = delete;

    SPDesktop *_desktop;
    Inkscape::MessageId _messageId;
};

} // namespace

namespace Inkscape {
namespace UI {
namespace Dialog {

/** A list of strings that is used both in the preferences, and in the
    data fields to describe the various values of \c selection_type. */
static const char * selection_names[SELECTION_NUMBER_OF] = {
    "page", "drawing", "selection", "custom"
};

/** The names on the buttons for the various selection types. */
static const char * selection_labels[SELECTION_NUMBER_OF] = {
    N_("_Page"), N_("_Drawing"), N_("_Selection"), N_("_Custom")
};

Export::Export () :
    UI::Widget::Panel("/dialogs/export/", SP_VERB_DIALOG_EXPORT),
    current_key(SELECTION_PAGE),
    original_name(),
    doc_export_name(),
    filename_modified(false),
    was_empty(true),
    update(false),
    togglebox(true, 0),
    area_box(false, 3),
    singleexport_box(false, 0),
    size_box(false, 3),
    file_box(false, 3),
    unitbox(false, 0),
    unit_selector(),
    units_label(_("Units:")),
    filename_box(false, 5),
    browse_label(_("_Export As..."), true),
    browse_image(),
    batch_box(false, 5),
    batch_export(_("B_atch export all selected objects")),
    interlacing(_("Use interlacing")),
    bitdepth_label(_("Bit depth")),
    bitdepth_cb(),
    zlib_label(_("Compression")),
    zlib_compression(),
    pHYs_label(_("pHYs dpi")),
    pHYs_sb(pHYs_adj, 1.0, 2),
    antialiasing_label(_("Antialiasing")),
    antialiasing_cb(),
    hide_box(false, 3),
    hide_export(_("Hide all except selected")),
    closeWhenDone(_("Close when complete")),
    button_box(false, 3),
    _prog(),
    prog_dlg(nullptr),
    interrupted(false),
    prefs(nullptr),
    desktop(nullptr),
    deskTrack(),
    selectChangedConn(),
    subselChangedConn(),
    selectModifiedConn()
{
    batch_export.set_use_underline();
    batch_export.set_tooltip_text(_("Export each selected object into its own PNG file, using export hints if any (caution, overwrites without asking!)"));
    hide_export.set_use_underline();
    hide_export.set_tooltip_text(_("In the exported image, hide all objects except those that are selected"));
    interlacing.set_use_underline();
    interlacing.set_tooltip_text(_("Enables ADAM7 interlacing for PNG output. This results in slightly heavier images, but big images will look better sooner when loading the file"));
    closeWhenDone.set_use_underline();
    closeWhenDone.set_tooltip_text(_("Once the export completes, close this dialog"));
    prefs = Inkscape::Preferences::get();

    singleexport_box.set_border_width(0);

    /* Export area frame */
    {
        Gtk::Label* lbl = new Gtk::Label(_("<b>Export area</b>"), Gtk::ALIGN_START);
        lbl->set_use_markup(true);
        area_box.pack_start(*lbl);

        /* Units box */
        /* gets added to the vbox later, but the unit selector is needed
           earlier than that */
        unit_selector.setUnitType(Inkscape::Util::UNIT_TYPE_LINEAR);

        SPDesktop *desktop = SP_ACTIVE_DESKTOP;
        if (desktop) {
            unit_selector.setUnit(desktop->getNamedView()->display_units->abbr);
        }
        unitChangedConn = unit_selector.signal_changed().connect(sigc::mem_fun(*this, &Export::onUnitChanged));
        unitbox.pack_end(unit_selector, false, false, 0);
        unitbox.pack_end(units_label, false, false, 3);

        for (int i = 0; i < SELECTION_NUMBER_OF; i++) {
            selectiontype_buttons[i] = new Gtk::RadioButton(_(selection_labels[i]), true);
            if (i > 0) {
                Gtk::RadioButton::Group group = selectiontype_buttons[0]->get_group();
                selectiontype_buttons[i]->set_group(group);
            }
            selectiontype_buttons[i]->set_mode(false);
            togglebox.pack_start(*selectiontype_buttons[i], false, true, 0);
            selectiontype_buttons[i]->signal_clicked().connect(sigc::mem_fun(*this, &Export::onAreaToggled));
        }

        auto t = new Gtk::Grid();
        t->set_row_spacing(4);
        t->set_column_spacing(4);

        x0_adj = createSpinbutton ( "x0", 0.0, -1000000.0, 1000000.0, 0.1, 1.0,
                                    t, 0, 0, _("_x0:"), "", EXPORT_COORD_PRECISION, 1,
                                    &Export::onAreaX0Change);

        x1_adj = createSpinbutton ( "x1", 0.0, -1000000.0, 1000000.0, 0.1, 1.0,
                                    t, 0, 1, _("x_1:"), "", EXPORT_COORD_PRECISION, 1,
                                    &Export::onAreaX1Change);

        width_adj = createSpinbutton ( "width", 0.0, 0.0, PNG_UINT_31_MAX, 0.1, 1.0,
                                       t, 0, 2, _("Wid_th:"), "", EXPORT_COORD_PRECISION, 1,
                                       &Export::onAreaWidthChange);

        y0_adj = createSpinbutton ( "y0", 0.0, -1000000.0, 1000000.0, 0.1, 1.0,
                                    t, 2, 0, _("_y0:"), "", EXPORT_COORD_PRECISION, 1,
                                    &Export::onAreaY0Change);

        y1_adj = createSpinbutton ( "y1", 0.0, -1000000.0, 1000000.0, 0.1, 1.0,
                                    t, 2, 1, _("y_1:"), "", EXPORT_COORD_PRECISION, 1,
                                    &Export::onAreaY1Change);

        height_adj = createSpinbutton ( "height", 0.0, 0.0, PNG_UINT_31_MAX, 0.1, 1.0,
                                        t, 2, 2, _("Hei_ght:"), "", EXPORT_COORD_PRECISION, 1,
                                        &Export::onAreaHeightChange);

        area_box.pack_start(togglebox, false, false, 3);
        area_box.pack_start(*t, false, false, 0);
        area_box.pack_start(unitbox, false, false, 0);

        area_box.set_border_width(3);
        singleexport_box.pack_start(area_box, false, false, 0);

    } // end of area box

    /* Bitmap size frame */
    {
        size_box.set_border_width(3);
        bm_label = new Gtk::Label(_("<b>Image size</b>"), Gtk::ALIGN_START);
        bm_label->set_use_markup(true);
        size_box.pack_start(*bm_label, false, false, 0);

        auto t = new Gtk::Grid();
        t->set_row_spacing(4);
        t->set_column_spacing(4);

        size_box.pack_start(*t);

        bmwidth_adj = createSpinbutton ( "bmwidth", 16.0, 1.0, 1000000.0, 1.0, 10.0,
                                         t, 0, 0,
                                         _("_Width:"), _("pixels at"), 0, 1,
                                         &Export::onBitmapWidthChange);

        xdpi_adj = createSpinbutton ( "xdpi",
                                      prefs->getDouble("/dialogs/export/defaultxdpi/value", DPI_BASE),
                                      0.01, 100000.0, 0.1, 1.0, t, 3, 0,
                                      "", _("dp_i"), 2, 1,
                                      &Export::onExportXdpiChange);

        bmheight_adj = createSpinbutton ( "bmheight", 16.0, 1.0, 1000000.0, 1.0, 10.0,
                                          t, 0, 1,
                                          _("_Height:"), _("pixels at"), 0, 1,
                                          &Export::onBitmapHeightChange);

        /** TODO
         *  There's no way to set ydpi currently, so we use the defaultxdpi value here, too...
         */
        ydpi_adj = createSpinbutton ( "ydpi", prefs->getDouble("/dialogs/export/defaultxdpi/value", DPI_BASE),
                                      0.01, 100000.0, 0.1, 1.0, t, 3, 1,
                                      "", _("dpi"), 2, 0, nullptr );

        singleexport_box.pack_start(size_box, Gtk::PACK_SHRINK);
    }

    /* File entry */
    {
        file_box.set_border_width(3);
        flabel = new Gtk::Label(_("<b>_Filename</b>"), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, true);
        flabel->set_use_markup(true);
        file_box.pack_start(*flabel, false, false, 0);

        set_default_filename();

        filename_box.pack_start (filename_entry, true, true, 0);

        Gtk::HBox* browser_im_label = new Gtk::HBox(false, 3);
        browse_image.set_from_icon_name("folder", Gtk::ICON_SIZE_BUTTON);
        browser_im_label->pack_start(browse_image);
        browser_im_label->pack_start(browse_label);
        browse_button.add(*browser_im_label);
        filename_box.pack_end (browse_button, false, false);

        file_box.add(filename_box);

        original_name = filename_entry.get_text();

        // focus is in the filename initially:
        filename_entry.grab_focus();

        // mnemonic in frame label moves focus to filename:
        flabel->set_mnemonic_widget(filename_entry);

        singleexport_box.pack_start(file_box, Gtk::PACK_SHRINK);
    }

    batch_export.set_sensitive(true);
    batch_box.pack_start(batch_export, false, false, 3);

    hide_export.set_sensitive(true);
    hide_export.set_active (prefs->getBool("/dialogs/export/hideexceptselected/value", false));
    hide_box.pack_start(hide_export, false, false, 3);


    /* Export Button row */
    export_button.set_label(_("_Export"));
    export_button.set_use_underline();
    export_button.set_tooltip_text (_("Export the bitmap file with these settings"));

    button_box.set_border_width(3);
    button_box.pack_start(closeWhenDone, true, true, 0);
    button_box.pack_end(export_button, false, false, 0);

    /*Advanced*/
    Gtk::Label *label_advanced = Gtk::manage(new Gtk::Label(_("Advanced"),true));
    expander.set_label_widget(*label_advanced);
    expander.set_vexpand(false);
    const char* const modes_list[]={"Gray_1", "Gray_2","Gray_4","Gray_8","Gray_16","RGB_8","RGB_16","GrayAlpha_8","GrayAlpha_16","RGBA_8","RGBA_16"};
    for(auto i : modes_list)
        bitdepth_cb.append(i);
    bitdepth_cb.set_active_text("RGBA_8");
    bitdepth_cb.set_hexpand();
    const char* const zlist[]={"Z_NO_COMPRESSION","Z_BEST_SPEED","2","3","4","5","Z_DEFAULT_COMPRESSION","7","8","Z_BEST_COMPRESSION"};
    for(auto i : zlist)
        zlib_compression.append(i);
    zlib_compression.set_active_text("Z_DEFAULT_COMPRESSION");
    pHYs_adj = Gtk::Adjustment::create(0, 0, 100000, 0.1, 1.0, 0);
    pHYs_sb.set_adjustment(pHYs_adj);
    pHYs_sb.set_width_chars(7);
    pHYs_sb.set_tooltip_text( _("Will force-set the physical dpi for the png file. Set this to 72 if you're planning to work on your png with Photoshop") );
    zlib_compression.set_hexpand();
    const char* const antialising_list[] = {"CAIRO_ANTIALIAS_NONE","CAIRO_ANTIALIAS_FAST","CAIRO_ANTIALIAS_GOOD (default)","CAIRO_ANTIALIAS_BEST"};
    for(auto i : antialising_list)
        antialiasing_cb.append(i);
    antialiasing_cb.set_active_text(antialising_list[2]);
    bitdepth_label.set_halign(Gtk::ALIGN_START);
    zlib_label.set_halign(Gtk::ALIGN_START);
    pHYs_label.set_halign(Gtk::ALIGN_START);
    antialiasing_label.set_halign(Gtk::ALIGN_START);
    auto table = new Gtk::Grid();
    expander.add(*table);
    // gtk_container_add(GTK_CONTAINER(expander.gobj()), (GtkWidget*)(table->gobj()));
    table->set_border_width(4);
    table->attach(interlacing,0,0,1,1);
    table->attach(bitdepth_label,0,1,1,1);
    table->attach(bitdepth_cb,1,1,1,1);
    table->attach(zlib_label,0,2,1,1);
    table->attach(zlib_compression,1,2,1,1);
    table->attach(pHYs_label,0,3,1,1);
    table->attach(pHYs_sb,1,3,1,1);
    table->attach(antialiasing_label,0,4,1,1);
    table->attach(antialiasing_cb,1,4,1,1);
    table->show();

    /* Main dialog */
    Gtk::Box *contents = _getContents();
    contents->set_spacing(0);
    contents->pack_start(singleexport_box, Gtk::PACK_SHRINK);
    contents->pack_start(batch_box, Gtk::PACK_SHRINK);
    contents->pack_start(hide_box, Gtk::PACK_SHRINK);
    contents->pack_start(expander, Gtk::PACK_SHRINK);
    contents->pack_end(button_box, Gtk::PACK_SHRINK);
    contents->pack_end(_prog, Gtk::PACK_SHRINK);

    /* Signal handlers */
    filename_entry.signal_changed().connect( sigc::mem_fun(*this, &Export::onFilenameModified) );
    // pressing enter in the filename field is the same as clicking export:
    filename_entry.signal_activate().connect(sigc::mem_fun(*this, &Export::onExport) );
    browse_button.signal_clicked().connect(sigc::mem_fun(*this, &Export::onBrowse));
    batch_export.signal_clicked().connect(sigc::mem_fun(*this, &Export::onBatchClicked));
    export_button.signal_clicked().connect(sigc::mem_fun(*this, &Export::onExport));
    hide_export.signal_clicked().connect(sigc::mem_fun(*this, &Export::onHideExceptSelected));

    desktopChangeConn = deskTrack.connectDesktopChanged( sigc::mem_fun(*this, &Export::setTargetDesktop) );
    deskTrack.connect(GTK_WIDGET(gobj()));

    show_all_children();
    setExporting(false);

    findDefaultSelection();
    onAreaToggled();
}

Export::~Export ()
{
    was_empty = TRUE;

    selectModifiedConn.disconnect();
    subselChangedConn.disconnect();
    selectChangedConn.disconnect();
    desktopChangeConn.disconnect();
    deskTrack.disconnect();
}

void Export::setDesktop(SPDesktop *desktop)
{
    Panel::setDesktop(desktop);
    deskTrack.setBase(desktop);
}

void Export::setTargetDesktop(SPDesktop *desktop)
{
    if (this->desktop != desktop) {
        if (this->desktop) {
            selectModifiedConn.disconnect();
            subselChangedConn.disconnect();
            selectChangedConn.disconnect();
        }
        this->desktop = desktop;
        if (desktop && desktop->selection) {

            selectChangedConn = desktop->selection->connectChanged(sigc::hide(sigc::mem_fun(*this, &Export::onSelectionChanged)));
            subselChangedConn = desktop->connectToolSubselectionChanged(sigc::hide(sigc::mem_fun(*this, &Export::onSelectionChanged)));

            //// Must check flags, so can't call widget_setup() directly.
            selectModifiedConn = desktop->selection->connectModified(sigc::hide<0>(sigc::mem_fun(*this, &Export::onSelectionModified)));
        }
    }
}

/*
 * set the default filename to be that of the current path + document
 * with .png extension
 *
 * One thing to notice here is that this filename may get
 * overwritten, but it won't happen here.  The filename gets
 * written into the text field, but then the button to select
 * the area gets set.  In that code the filename can be changed
 * if there are some with presidence in the document.  So, while
 * this code sets the name first, it may not be the one users
 * really see.
 */
void Export::set_default_filename () {

    if ( SP_ACTIVE_DOCUMENT && SP_ACTIVE_DOCUMENT->getDocumentURI() )
    {
        SPDocument * doc = SP_ACTIVE_DOCUMENT;
        const gchar *uri = doc->getDocumentURI();
        auto &&text_extension = get_file_save_extension(Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS);
        Inkscape::Extension::Output * oextension = nullptr;

        if (!text_extension.empty()) {
            oextension = dynamic_cast<Inkscape::Extension::Output *>(Inkscape::Extension::db.get(text_extension.c_str()));
        }

        if (oextension != nullptr) {
            gchar * old_extension = oextension->get_extension();
            if (g_str_has_suffix(uri, old_extension)) {
                gchar * uri_copy;
                gchar * extension_point;
                gchar * final_name;

                uri_copy = g_strdup(uri);
                extension_point = g_strrstr(uri_copy, old_extension);
                extension_point[0] = '\0';

                final_name = g_strconcat(uri_copy, ".png", NULL);
                filename_entry.set_text(final_name);
                filename_entry.set_position(strlen(final_name));

                g_free(final_name);
                g_free(uri_copy);
            }
        } else {
            gchar *name = g_strconcat(uri, ".png", NULL);
            filename_entry.set_text(name);
            filename_entry.set_position(strlen(name));

            g_free(name);
        }

        doc_export_name = filename_entry.get_text();
    }
    else if ( SP_ACTIVE_DOCUMENT )
    {
        Glib::ustring filename = create_filepath_from_id (_("bitmap"), filename_entry.get_text());
        filename_entry.set_text(filename);
        filename_entry.set_position(filename.length());

        doc_export_name = filename_entry.get_text();
    }
}

Glib::RefPtr<Gtk::Adjustment> Export::createSpinbutton( gchar const * /*key*/, float val, float min, float max,
        float step, float page,
        Gtk::Grid *t, int x, int y,
        const Glib::ustring& ll, const Glib::ustring& lr,
        int digits, unsigned int sensitive,
        void (Export::*cb)() )
{
    auto adj = Gtk::Adjustment::create(val, min, max, step, page, 0);

    int pos = 0;
    Gtk::Label *l = nullptr;

    if (!ll.empty()) {
        l = new Gtk::Label(ll,true);
        l->set_halign(Gtk::ALIGN_END);
        l->set_valign(Gtk::ALIGN_CENTER);
        l->set_hexpand();
        t->attach(*l, x + pos, y, 1, 1);
        l->set_sensitive(sensitive);
        pos++;
    }

    auto sb = new Gtk::SpinButton(adj, 1.0, digits);
    sb->set_hexpand();
    t->attach(*sb, x + pos, y, 1, 1);

    sb->set_width_chars(7);
    sb->set_sensitive (sensitive);
    pos++;

    if (l) {
        l->set_mnemonic_widget(*sb);
    }

    if (!lr.empty()) {
        l = new Gtk::Label(lr,true);
        l->set_halign(Gtk::ALIGN_START);
        l->set_valign(Gtk::ALIGN_CENTER);
        l->set_hexpand();
        t->attach(*l, x + pos, y, 1, 1);
        l->set_sensitive (sensitive);
        pos++;
        l->set_mnemonic_widget (*sb);
    }

    if (cb) {
        adj->signal_value_changed().connect( sigc::mem_fun(*this, cb) );
    }

    return adj;
} // end of createSpinbutton()


Glib::ustring Export::create_filepath_from_id (Glib::ustring id, const Glib::ustring &file_entry_text)
{
    if (id.empty())
    {   /* This should never happen */
        id = "bitmap";
    }

    Glib::ustring directory;

    if (!file_entry_text.empty()) {
        directory = Glib::path_get_dirname(file_entry_text);
    }

    if (directory.empty()) {
        /* Grab document directory */
        const gchar* docURI = SP_ACTIVE_DOCUMENT->getDocumentURI();
        if (docURI) {
            directory = Glib::path_get_dirname(docURI);
        }
    }

    if (directory.empty()) {
        directory = Inkscape::IO::Resource::homedir_path(nullptr);
    }

    Glib::ustring filename = Glib::build_filename(directory, id+".png");
    return filename;
}

void Export::onBatchClicked ()
{
    if (batch_export.get_active()) {
        singleexport_box.set_sensitive(false);
    } else {
        singleexport_box.set_sensitive(true);
    }
}

void Export::updateCheckbuttons ()
{
    gint num = (gint) boost::distance(SP_ACTIVE_DESKTOP->getSelection()->items());
    if (num >= 2) {
        batch_export.set_sensitive(true);
        batch_export.set_label(g_strdup_printf (ngettext("B_atch export %d selected object","B_atch export %d selected objects",num), num));
    } else {
        batch_export.set_active (false);
        batch_export.set_sensitive(false);
    }

    //hide_export.set_sensitive (num > 0);
}

inline void Export::findDefaultSelection()
{
    selection_type key = SELECTION_NUMBER_OF;

    if ((SP_ACTIVE_DESKTOP->getSelection())->isEmpty() == false) {
        key = SELECTION_SELECTION;
    }

    /* Try using the preferences */
    if (key == SELECTION_NUMBER_OF) {

        int i = SELECTION_NUMBER_OF;

        Glib::ustring what = prefs->getString("/dialogs/export/exportarea/value");

        if (!what.empty()) {
            for (i = 0; i < SELECTION_NUMBER_OF; i++) {
                if (what == selection_names[i]) {
                    break;
                }
            }
        }

        key = (selection_type)i;
    }

    if (key == SELECTION_NUMBER_OF) {
        key = SELECTION_SELECTION;
    }

    current_key = key;
    selectiontype_buttons[current_key]->set_active(true);
    updateCheckbuttons ();
}


/**
 * If selection changed or a different document activated, we must
 * recalculate any chosen areas.
 */
void Export::onSelectionChanged()
{
    Inkscape::Selection *selection = SP_ACTIVE_DESKTOP->getSelection();

    if ((current_key == SELECTION_DRAWING || current_key == SELECTION_PAGE) &&
            (SP_ACTIVE_DESKTOP->getSelection())->isEmpty() == false &&
            was_empty) {
        current_key = SELECTION_SELECTION;
        selectiontype_buttons[current_key]->set_active(true);
    }
    was_empty = (SP_ACTIVE_DESKTOP->getSelection())->isEmpty();

    if ( selection &&
            SELECTION_CUSTOM != current_key) {
        onAreaToggled();
    }

    updateCheckbuttons ();
}

void Export::onSelectionModified ( guint /*flags*/ )
{
    Inkscape::Selection * Sel;
    switch (current_key) {
    case SELECTION_DRAWING:
        if ( SP_ACTIVE_DESKTOP ) {
            SPDocument *doc;
            doc = SP_ACTIVE_DESKTOP->getDocument();
            Geom::OptRect bbox = doc->getRoot()->desktopVisualBounds();
            if (bbox) {
                setArea ( bbox->left(),
                          bbox->top(),
                          bbox->right(),
                          bbox->bottom());
            }
        }
        break;
    case SELECTION_SELECTION:
        Sel = SP_ACTIVE_DESKTOP->getSelection();
        if (Sel->isEmpty() == false) {
            Geom::OptRect bbox = Sel->visualBounds();
            if (bbox)
            {
                setArea ( bbox->left(),
                          bbox->top(),
                          bbox->right(),
                          bbox->bottom());
            }
        }
        break;
    default:
        /* Do nothing for page or for custom */
        break;
    }

    return;
}

/// Called when one of the selection buttons was toggled.
void Export::onAreaToggled ()
{
    if (update) {
        return;
    }

    /* Find which button is active */
    selection_type key = current_key;
    for (int i = 0; i < SELECTION_NUMBER_OF; i++) {
        if (selectiontype_buttons[i]->get_active()) {
            key = (selection_type)i;
        }
    }

    if ( SP_ACTIVE_DESKTOP )
    {
        SPDocument *doc;
        Geom::OptRect bbox;
        bbox = Geom::Rect(Geom::Point(0.0, 0.0),Geom::Point(0.0, 0.0));
        doc = SP_ACTIVE_DESKTOP->getDocument();

        /* Notice how the switch is used to 'fall through' here to get
           various backups.  If you modify this without noticing you'll
           probably screw something up. */
        switch (key) {
        case SELECTION_SELECTION:
            if ((SP_ACTIVE_DESKTOP->getSelection())->isEmpty() == false)
            {
                bbox = SP_ACTIVE_DESKTOP->getSelection()->visualBounds();
                /* Only if there is a selection that we can set
                   do we break, otherwise we fall through to the
                   drawing */
                // std::cout << "Using selection: SELECTION" << std::endl;
                key = SELECTION_SELECTION;
                break;
            }
        case SELECTION_DRAWING:
            /** \todo
             * This returns wrong values if the document has a viewBox.
             */
            bbox = doc->getRoot()->desktopVisualBounds();
            /* If the drawing is valid, then we'll use it and break
               otherwise we drop through to the page settings */
            if (bbox) {
                // std::cout << "Using selection: DRAWING" << std::endl;
                key = SELECTION_DRAWING;
                break;
            }
        case SELECTION_PAGE:
            bbox = Geom::Rect(Geom::Point(0.0, 0.0),
                              Geom::Point(doc->getWidth().value("px"), doc->getHeight().value("px")));

            // std::cout << "Using selection: PAGE" << std::endl;
            key = SELECTION_PAGE;
            break;
        case SELECTION_CUSTOM:
        default:
            break;
        } // switch

        current_key = key;

        // remember area setting
        prefs->setString("/dialogs/export/exportarea/value", selection_names[current_key]);

        if ( key != SELECTION_CUSTOM && bbox ) {
            setArea ( bbox->min()[Geom::X],
                      bbox->min()[Geom::Y],
                      bbox->max()[Geom::X],
                      bbox->max()[Geom::Y]);
        }

    } // end of if ( SP_ACTIVE_DESKTOP )

    if (SP_ACTIVE_DESKTOP && !filename_modified) {

        Glib::ustring filename;
        float xdpi = 0.0, ydpi = 0.0;

        switch (key) {
        case SELECTION_PAGE:
        case SELECTION_DRAWING: {
            SPDocument * doc = SP_ACTIVE_DOCUMENT;
            sp_document_get_export_hints (doc, filename, &xdpi, &ydpi);

            if (filename.empty()) {
                if (!doc_export_name.empty()) {
                    filename = doc_export_name;
                }
            }
            break;
        }
        case SELECTION_SELECTION:
            if ((SP_ACTIVE_DESKTOP->getSelection())->isEmpty() == false) {

                SP_ACTIVE_DESKTOP->getSelection()->getExportHints(filename, &xdpi, &ydpi);

                /* If we still don't have a filename -- let's build
                   one that's nice */
                if (filename.empty()) {
                    const gchar * id = "object";
                    auto reprlst = SP_ACTIVE_DESKTOP->getSelection()->xmlNodes();
                    for(auto i=reprlst.begin(); reprlst.end() != i; ++i) {
                        Inkscape::XML::Node * repr = *i;
                        if (repr->attribute("id")) {
                            id = repr->attribute("id");
                            break;
                        }
                    }

                    filename = create_filepath_from_id (id, filename_entry.get_text());
                }
            }
            break;
        case SELECTION_CUSTOM:
        default:
            break;
        }

        if (!filename.empty()) {
            original_name = filename;
            filename_entry.set_text(filename);
            filename_entry.set_position(filename.length());
        }

        if (xdpi != 0.0) {
            setValue(xdpi_adj, xdpi);
        }

        /* These can't be separate, and setting x sets y, so for
           now setting this is disabled.  Hopefully it won't be in
           the future */
        if (FALSE && ydpi != 0.0) {
            setValue(ydpi_adj, ydpi);
        }
    }

    return;
} // end of sp_export_area_toggled()

/// Called when dialog is deleted
bool Export::onProgressDelete (GdkEventAny * /*event*/)
{
    interrupted = true;
    return TRUE;
} // end of sp_export_progress_delete()


/// Called when progress is cancelled
void Export::onProgressCancel ()
{
    interrupted = true;
} // end of sp_export_progress_cancel()


/// Called for every progress iteration
unsigned int Export::onProgressCallback(float value, void *dlg)
{
    Gtk::Dialog *dlg2 = reinterpret_cast<Gtk::Dialog*>(dlg);

    Export *self = reinterpret_cast<Export *>(dlg2->get_data("exportPanel"));
    if (self->interrupted)
        return FALSE;
    
    gint current = GPOINTER_TO_INT(dlg2->get_data("current"));
    gint total = GPOINTER_TO_INT(dlg2->get_data("total"));
    if (total > 0) {
        double completed = current;
        completed /= static_cast<double>(total);

        value = completed + (value / static_cast<double>(total));
    }

    Gtk::ProgressBar *prg = reinterpret_cast<Gtk::ProgressBar *>(dlg2->get_data("progress"));
    prg->set_fraction(value);

    if (self) {
        self->_prog.set_fraction(value);
    }

    int evtcount = 0;
    while ((evtcount < 16) && gdk_events_pending()) {
        gtk_main_iteration_do(FALSE);
        evtcount += 1;
    }

    gtk_main_iteration_do(FALSE);
    return TRUE;
} // end of sp_export_progress_callback()

void Export::setExporting(bool exporting, Glib::ustring const &text)
{
    if (exporting) {
        _prog.set_text(text);
        _prog.set_fraction(0.0);
        _prog.set_sensitive(true);

        export_button.set_sensitive(false);
    } else {
        _prog.set_text("");
        _prog.set_fraction(0.0);
        _prog.set_sensitive(false);

        export_button.set_sensitive(true);
    }
}

Gtk::Dialog * Export::create_progress_dialog (Glib::ustring progress_text) {
    Gtk::Dialog *dlg = new Gtk::Dialog(_("Export in progress"), TRUE);
    dlg->set_transient_for( *(INKSCAPE.active_desktop()->getToplevel()) );

    Gtk::ProgressBar *prg = new Gtk::ProgressBar ();
    prg->set_text(progress_text);
    dlg->set_data ("progress", prg);
    auto CA = dlg->get_content_area();
    CA->pack_start(*prg, FALSE, FALSE, 4);

    Gtk::Button* btn = dlg->add_button (_("_Cancel"),Gtk::RESPONSE_CANCEL );

    btn->signal_clicked().connect( sigc::mem_fun(*this, &Export::onProgressCancel) );
    dlg->signal_delete_event().connect( sigc::mem_fun(*this, &Export::onProgressDelete) );

    dlg->show_all ();
    return dlg;
}

// FIXME: Some lib function should be available to do this ...
Glib::ustring Export::filename_add_extension (Glib::ustring filename, Glib::ustring extension)
{
    auto pos = int(filename.size()) - int(extension.size());
    if (pos > 0 && filename[pos - 1] == '.' && filename.substr(pos).lowercase() == extension.lowercase()) {
        return filename;
    }

    return filename + "." + extension;
}

Glib::ustring Export::absolutize_path_from_document_location (SPDocument *doc, const Glib::ustring &filename)
{
    Glib::ustring path;
    //Make relative paths go from the document location, if possible:
    if (!Glib::path_is_absolute(filename) && doc->getDocumentURI()) {
        Glib::ustring dirname = Glib::path_get_dirname(doc->getDocumentURI());
        if (!dirname.empty()) {
            path = Glib::build_filename(dirname, filename);
        }
    }
    if (path.empty()) {
        path = filename;
    }
    return path;
}

// Called when unit is changed
void Export::onUnitChanged()
{
    onAreaToggled();
}

void Export::onHideExceptSelected ()
{
    prefs->setBool("/dialogs/export/hideexceptselected/value", hide_export.get_active());
}

/// Called when export button is clicked
void Export::onExport ()
{
    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
    if (!desktop) return;

    SPNamedView *nv = desktop->getNamedView();
    SPDocument *doc = desktop->getDocument();

    bool exportSuccessful = false;

    bool hide = hide_export.get_active ();

    // Advanced parameters
    bool do_interlace = (interlacing.get_active());
    float pHYs = 0;
    int zlib = zlib_compression.get_active_row_number() ;
    const char* const modes_list[]={"Gray_1", "Gray_2","Gray_4","Gray_8","Gray_16","RGB_8","RGB_16","GrayAlpha_8","GrayAlpha_16","RGBA_8","RGBA_16"};
    int colortypes[] = {0,0,0,0,0,2,2,4,4,6,6}; //keep in sync with modes_list in Export constructor. values are from libpng doc.
    int bitdepths[] = {1,2,4,8,16,8,16,8,16,8,16};
    int color_type = colortypes[bitdepth_cb.get_active_row_number()] ;
    int bit_depth = bitdepths[bitdepth_cb.get_active_row_number()] ;
    int antialiasing = antialiasing_cb.get_active_row_number();


    if (batch_export.get_active ()) {
        // Batch export of selected objects

        gint num = (gint) boost::distance(desktop->getSelection()->items());
        gint n = 0;

        if (num < 1) {
            desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No items selected."));
            return;
        }

        prog_dlg = create_progress_dialog(Glib::ustring::compose(_("Exporting %1 files"), num));
        prog_dlg->set_data("exportPanel", this);
        setExporting(true, Glib::ustring::compose(_("Exporting %1 files"), num));

        gint export_count = 0;

        auto itemlist= desktop->getSelection()->items();
        for(auto i = itemlist.begin();i!=itemlist.end() && !interrupted ;++i){
            SPItem *item = *i;

            prog_dlg->set_data("current", GINT_TO_POINTER(n));
            prog_dlg->set_data("total", GINT_TO_POINTER(num));
            onProgressCallback(0.0, prog_dlg);

            // retrieve export filename hint
            const gchar *filename = item->getRepr()->attribute("inkscape:export-filename");
            Glib::ustring path;
            if (!filename) {
                Glib::ustring tmp;
                path = create_filepath_from_id(item->getId(), tmp);
            } else {
                path = absolutize_path_from_document_location(doc, filename);
            }

            // retrieve export dpi hints
            const gchar *dpi_hint = item->getRepr()->attribute("inkscape:export-xdpi"); // only xdpi, ydpi is always the same now
            gdouble dpi = 0.0;
            if (dpi_hint) {
                dpi = atof(dpi_hint);
            }
            if (dpi == 0.0) {
                dpi = getValue(xdpi_adj);
            }
            pHYs = (pHYs_adj->get_value() > 0.01) ? pHYs_adj->get_value() : dpi;

            Geom::OptRect area = item->documentVisualBounds();
            if (area) {
                gint width = (gint) (area->width() * dpi / DPI_BASE + 0.5);
                gint height = (gint) (area->height() * dpi / DPI_BASE + 0.5);

                if (width > 1 && height > 1) {
                    // Do export
                    gchar * safeFile = Inkscape::IO::sanitizeString(path.c_str());
                    MessageCleaner msgCleanup(desktop->messageStack()->pushF(Inkscape::IMMEDIATE_MESSAGE,
                                              _("Exporting file <b>%s</b>..."), safeFile), desktop);
                    MessageCleaner msgFlashCleanup(desktop->messageStack()->flashF(Inkscape::IMMEDIATE_MESSAGE,
                                                   _("Exporting file <b>%s</b>..."), safeFile), desktop);
                    std::vector<SPItem*> x;
                    std::vector<SPItem*> selected(desktop->getSelection()->items().begin(), desktop->getSelection()->items().end());
                    if (!sp_export_png_file (doc, path.c_str(),
                                             *area, width, height, pHYs, pHYs,
                                             nv->pagecolor,
                                             onProgressCallback, (void*)prog_dlg,
                                             TRUE,  // overwrite without asking
                                             hide ? selected : x, 
                                             do_interlace, color_type, bit_depth, zlib, antialiasing
                                            )) {
                        gchar * error = g_strdup_printf(_("Could not export to filename %s.\n"), safeFile);

                        desktop->messageStack()->flashF(Inkscape::ERROR_MESSAGE,
                                                        _("Could not export to filename <b>%s</b>."), safeFile);

                        sp_ui_error_dialog(error);
                        g_free(error);
                    } else {
                        ++export_count; // one more item exported successfully
                    }
                    g_free(safeFile);
                }
            }

            n++;
        }

        desktop->messageStack()->flashF(Inkscape::INFORMATION_MESSAGE,
                                        _("Successfully exported <b>%d</b> files from <b>%d</b> selected items."), export_count, num);

        setExporting(false);
        delete prog_dlg;
        prog_dlg = nullptr;
        interrupted = false;
        exportSuccessful = (export_count > 0);
    } else {
        Glib::ustring filename = filename_entry.get_text();

        if (filename.empty()) {
            desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You have to enter a filename."));
            sp_ui_error_dialog(_("You have to enter a filename"));
            return;
        }

        float const x0 = getValuePx(x0_adj);
        float const y0 = getValuePx(y0_adj);
        float const x1 = getValuePx(x1_adj);
        float const y1 = getValuePx(y1_adj);
        float const xdpi = getValue(xdpi_adj);
        float const ydpi = getValue(ydpi_adj);
        pHYs = (pHYs_adj->get_value() > 0.01) ? pHYs_adj->get_value() : xdpi;
        unsigned long int const width = int(getValue(bmwidth_adj) + 0.5);
        unsigned long int const height = int(getValue(bmheight_adj) + 0.5);

        if (!((x1 > x0) && (y1 > y0) && (width > 0) && (height > 0))) {
            desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The chosen area to be exported is invalid."));
            sp_ui_error_dialog(_("The chosen area to be exported is invalid"));
            return;
        }

        // make sure that .png is the extension of the file:
        Glib::ustring const filename_ext = filename_add_extension(filename, "png");
        filename_entry.set_text(filename_ext);
        filename_entry.set_position(filename_ext.length());
        Glib::ustring path = absolutize_path_from_document_location(doc, filename_ext);

        Glib::ustring dirname = Glib::path_get_dirname(path);
        if ( dirname.empty()
                || !Inkscape::IO::file_test(dirname.c_str(), (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) )
        {
            gchar *safeDir = Inkscape::IO::sanitizeString(dirname.c_str());
            gchar *error = g_strdup_printf(_("Directory %s does not exist or is not a directory.\n"),
                                           safeDir);

            desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, error);
            sp_ui_error_dialog(error);

            g_free(safeDir);
            g_free(error);
            return;
        }

        Glib::ustring fn = path_get_basename (path);

        /* TRANSLATORS: %1 will be the filename, %2 the width, and %3 the height of the image */
        prog_dlg = create_progress_dialog (Glib::ustring::compose(_("Exporting %1 (%2 x %3)"), fn, width, height));
        prog_dlg->set_data("exportPanel", this);
        setExporting(true, Glib::ustring::compose(_("Exporting %1 (%2 x %3)"), fn, width, height));

        prog_dlg->set_data("current", GINT_TO_POINTER(0));
        prog_dlg->set_data("total", GINT_TO_POINTER(0));

        auto area = Geom::Rect(Geom::Point(x0, y0), Geom::Point(x1, y1)) * desktop->dt2doc();

        /* Do export */
        std::vector<SPItem*> x;
        std::vector<SPItem*> selected(desktop->getSelection()->items().begin(), desktop->getSelection()->items().end());
        ExportResult status = sp_export_png_file(desktop->getDocument(), path.c_str(),
                              area, width, height, pHYs, pHYs, //previously xdpi, ydpi.
                              nv->pagecolor,
                              onProgressCallback, (void*)prog_dlg,
                              FALSE,
                              hide ? selected : x, 
                              do_interlace, color_type, bit_depth, zlib, antialiasing
                              );
        if (status == EXPORT_ERROR) {
            gchar * safeFile = Inkscape::IO::sanitizeString(path.c_str());
            gchar * error = g_strdup_printf(_("Could not export to filename %s.\n"), safeFile);

            desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, error);
            sp_ui_error_dialog(error);

            g_free(safeFile);
            g_free(error);
        } else if (status == EXPORT_OK) {
            exportSuccessful = true;
            gchar *safeFile = Inkscape::IO::sanitizeString(path.c_str());

            desktop->messageStack()->flashF(Inkscape::INFORMATION_MESSAGE, _("Drawing exported to <b>%s</b>."), safeFile);

            g_free(safeFile);
        } else {
            desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Export aborted."));
        }

        /* Reset the filename so that it can be changed again by changing
           selections and all that */
        original_name = filename_ext;
        filename_modified = false;

        setExporting(false);
        delete prog_dlg;
        prog_dlg = nullptr;
        interrupted = false;

        /* Setup the values in the document */
        switch (current_key) {
        case SELECTION_PAGE:
        case SELECTION_DRAWING: {
            SPDocument * doc = SP_ACTIVE_DOCUMENT;
            Inkscape::XML::Node * repr = doc->getReprRoot();
            bool modified = false;

            bool saved = DocumentUndo::getUndoSensitive(doc);
            DocumentUndo::setUndoSensitive(doc, false);

            gchar const *temp_string = repr->attribute("inkscape:export-filename");
            if (temp_string == nullptr || (filename_ext != temp_string)) {
                repr->setAttribute("inkscape:export-filename", filename_ext);
                modified = true;
            }
            temp_string = repr->attribute("inkscape:export-xdpi");
            if (temp_string == nullptr || xdpi != atof(temp_string)) {
                sp_repr_set_svg_double(repr, "inkscape:export-xdpi", xdpi);
                modified = true;
            }
            temp_string = repr->attribute("inkscape:export-ydpi");
            if (temp_string == nullptr || ydpi != atof(temp_string)) {
                sp_repr_set_svg_double(repr, "inkscape:export-ydpi", ydpi);
                modified = true;
            }
            DocumentUndo::setUndoSensitive(doc, saved);

            if (modified) {
                doc->setModifiedSinceSave();
            }
            break;
        }
        case SELECTION_SELECTION: {
            SPDocument * doc = SP_ACTIVE_DOCUMENT;
            bool modified = false;

            bool saved = DocumentUndo::getUndoSensitive(doc);
            DocumentUndo::setUndoSensitive(doc, false);
            auto reprlst = desktop->getSelection()->xmlNodes();

            for(auto i=reprlst.begin(); reprlst.end() != i; ++i) {
                Inkscape::XML::Node * repr = *i;
                const gchar * temp_string;
                Glib::ustring dir = Glib::path_get_dirname(filename.c_str());
                const gchar* docURI=SP_ACTIVE_DOCUMENT->getDocumentURI();
                Glib::ustring docdir;
                if (docURI)
                {
                    docdir = Glib::path_get_dirname(docURI);
                }
                if (repr->attribute("id") == nullptr ||
                        !(filename_ext.find_last_of(repr->attribute("id")) &&
                          ( !docURI ||
                            (dir == docdir)))) {
                    temp_string = repr->attribute("inkscape:export-filename");
                    if (temp_string == nullptr || (filename_ext != temp_string)) {
                        repr->setAttribute("inkscape:export-filename", filename_ext);
                        modified = true;
                    }
                }
                temp_string = repr->attribute("inkscape:export-xdpi");
                if (temp_string == nullptr || xdpi != atof(temp_string)) {
                    sp_repr_set_svg_double(repr, "inkscape:export-xdpi", xdpi);
                    modified = true;
                }
                temp_string = repr->attribute("inkscape:export-ydpi");
                if (temp_string == nullptr || ydpi != atof(temp_string)) {
                    sp_repr_set_svg_double(repr, "inkscape:export-ydpi", ydpi);
                    modified = true;
                }
            }
            DocumentUndo::setUndoSensitive(doc, saved);

            if (modified) {
                doc->setModifiedSinceSave();
            }
            break;
        }
        default:
            break;
        }
    }

    if (exportSuccessful && closeWhenDone.get_active()) {
        for ( Gtk::Container *parent = get_parent(); parent; parent = parent->get_parent()) {
            if ( GDL_IS_DOCK_ITEM(parent->gobj()) ) {
                GdlDockItem *item = GDL_DOCK_ITEM(parent->gobj());
                if (item) {
                    gdl_dock_item_hide_item(item);
                }
                break;
            }
        }
    }
} // end of sp_export_export_clicked()

/// Called when Browse button is clicked
/// @todo refactor this code to use ui/dialogs/filedialog.cpp
void Export::onBrowse ()
{
    GtkWidget *fs;
    Glib::ustring filename;

    fs = gtk_file_chooser_dialog_new (_("Select a filename for exporting"),
                                      (GtkWindow*)desktop->getToplevel(),
                                      GTK_FILE_CHOOSER_ACTION_SAVE,
                                      _("_Cancel"), GTK_RESPONSE_CANCEL,
                                      _("_Save"),   GTK_RESPONSE_ACCEPT,
                                      NULL );

    gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(fs), false);

    sp_transientize (fs);

    gtk_window_set_modal(GTK_WINDOW (fs), true);

    filename = filename_entry.get_text();

    if (filename.empty()) {
        Glib::ustring tmp;
        filename = create_filepath_from_id(tmp, tmp);
    }

    gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (fs), filename.c_str());

#ifdef _WIN32
    // code in this section is borrowed from ui/dialogs/filedialogimpl-win32.cpp
    OPENFILENAMEW opf;
    WCHAR filter_string[20];
    wcsncpy(filter_string, L"PNG#*.png##", 11);
    filter_string[3] = L'\0';
    filter_string[9] = L'\0';
    filter_string[10] = L'\0';
    WCHAR* title_string = (WCHAR*)g_utf8_to_utf16(_("Select a filename for exporting"), -1, NULL, NULL, NULL);
    WCHAR* extension_string = (WCHAR*)g_utf8_to_utf16("*.png", -1, NULL, NULL, NULL);
    // Copy the selected file name, converting from UTF-8 to UTF-16
    std::string dirname = Glib::path_get_dirname(filename.raw());
    if ( !Glib::file_test(dirname, Glib::FILE_TEST_EXISTS) ||
            Glib::file_test(filename, Glib::FILE_TEST_IS_DIR) ||
            dirname.empty() )
    {
        Glib::ustring tmp;
        filename = create_filepath_from_id(tmp, tmp);
    }
    WCHAR _filename[_MAX_PATH + 1];
    memset(_filename, 0, sizeof(_filename));
    gunichar2* utf16_path_string = g_utf8_to_utf16(filename.c_str(), -1, NULL, NULL, NULL);
    wcsncpy(_filename, reinterpret_cast<wchar_t*>(utf16_path_string), _MAX_PATH);
    g_free(utf16_path_string);

    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
    Glib::RefPtr<const Gdk::Window> parentWindow = desktop->getToplevel()->get_window();
    g_assert(parentWindow->gobj() != NULL);

    opf.hwndOwner = (HWND)gdk_win32_window_get_handle((GdkWindow*)parentWindow->gobj());
    opf.lpstrFilter = filter_string;
    opf.lpstrCustomFilter = 0;
    opf.nMaxCustFilter = 0L;
    opf.nFilterIndex = 1L;
    opf.lpstrFile = _filename;
    opf.nMaxFile = _MAX_PATH;
    opf.lpstrFileTitle = NULL;
    opf.nMaxFileTitle=0;
    opf.lpstrInitialDir = 0;
    opf.lpstrTitle = title_string;
    opf.nFileOffset = 0;
    opf.nFileExtension = 2;
    opf.lpstrDefExt = extension_string;
    opf.lpfnHook = NULL;
    opf.lCustData = 0;
    opf.Flags = OFN_PATHMUSTEXIST;
    opf.lStructSize = sizeof(OPENFILENAMEW);
    if (GetSaveFileNameW(&opf) != 0)
    {
        // Copy the selected file name, converting from UTF-16 to UTF-8
        gchar *utf8string = g_utf16_to_utf8((const gunichar2*)opf.lpstrFile, _MAX_PATH, NULL, NULL, NULL);
        filename_entry.set_text(utf8string);
        filename_entry.set_position(strlen(utf8string));
        g_free(utf8string);

    }
    g_free(extension_string);
    g_free(title_string);

#else
    if (gtk_dialog_run (GTK_DIALOG (fs)) == GTK_RESPONSE_ACCEPT)
    {
        gchar *file;

        file = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (fs));

        gchar * utf8file = g_filename_to_utf8( file, -1, nullptr, nullptr, nullptr );
        filename_entry.set_text (utf8file);
        filename_entry.set_position(strlen(utf8file));

        g_free(utf8file);
        g_free(file);
    }
#endif

    gtk_widget_destroy (fs);

    return;
} // end of sp_export_browse_clicked()

// TODO: Move this to nr-rect-fns.h.
bool Export::bbox_equal(Geom::Rect const &one, Geom::Rect const &two)
{
    double const epsilon = pow(10.0, -EXPORT_COORD_PRECISION);
    return (
               (fabs(one.min()[Geom::X] - two.min()[Geom::X]) < epsilon) &&
               (fabs(one.min()[Geom::Y] - two.min()[Geom::Y]) < epsilon) &&
               (fabs(one.max()[Geom::X] - two.max()[Geom::X]) < epsilon) &&
               (fabs(one.max()[Geom::Y] - two.max()[Geom::Y]) < epsilon)
           );
}

/**
 *This function is used to detect the current selection setting
 * based on the values in the x0, y0, x1 and y0 fields.
 *
 * One of the most confusing parts of this function is why the array
 * is built at the beginning.  What needs to happen here is that we
 * should always check the current selection to see if it is the valid
 * one.  While this is a performance improvement it is also a usability
 * one during the cases where things like selections and drawings match
 * size.  This way buttons change less 'randomly' (at least in the eyes
 * of the user).  To do this an array is built where the current selection
 * type is placed first, and then the others in an order from smallest
 * to largest (this can be configured by reshuffling \c test_order).
 *
 * All of the values in this function are rounded to two decimal places
 * because that is what is shown to the user.  While everything is kept
 * more accurate than that, the user can't control more accurate than
 * that, so for this to work for them - it needs to check on that level
 * of accuracy.
 *
 * @todo finish writing this up.
 */
void Export::detectSize() {
    static const selection_type test_order[SELECTION_NUMBER_OF] = {SELECTION_SELECTION, SELECTION_DRAWING, SELECTION_PAGE, SELECTION_CUSTOM};
    selection_type this_test[SELECTION_NUMBER_OF + 1];
    selection_type key = SELECTION_NUMBER_OF;

    Geom::Point x(getValuePx(x0_adj),
                  getValuePx(y0_adj));
    Geom::Point y(getValuePx(x1_adj),
                  getValuePx(y1_adj));
    Geom::Rect current_bbox(x, y);

    this_test[0] = current_key;
    for (int i = 0; i < SELECTION_NUMBER_OF; i++) {
        this_test[i + 1] = test_order[i];
    }

    for (int i = 0;
            i < SELECTION_NUMBER_OF + 1 &&
            key == SELECTION_NUMBER_OF &&
            SP_ACTIVE_DESKTOP != nullptr;
            i++) {
        switch (this_test[i]) {
        case SELECTION_SELECTION:
            if ((SP_ACTIVE_DESKTOP->getSelection())->isEmpty() == false) {
                Geom::OptRect bbox = (SP_ACTIVE_DESKTOP->getSelection())->bounds(SPItem::VISUAL_BBOX);

                if ( bbox && bbox_equal(*bbox,current_bbox)) {
                    key = SELECTION_SELECTION;
                }
            }
            break;
        case SELECTION_DRAWING: {
            SPDocument *doc = SP_ACTIVE_DESKTOP->getDocument();

            Geom::OptRect bbox = doc->getRoot()->desktopVisualBounds();

            if ( bbox && bbox_equal(*bbox,current_bbox) ) {
                key = SELECTION_DRAWING;
            }
            break;
        }

        case SELECTION_PAGE: {
            SPDocument *doc;

            doc = SP_ACTIVE_DESKTOP->getDocument();

            Geom::Point x(0.0, 0.0);
            Geom::Point y(doc->getWidth().value("px"),
                          doc->getHeight().value("px"));
            Geom::Rect bbox(x, y);

            if (bbox_equal(bbox,current_bbox)) {
                key = SELECTION_PAGE;
            }

            break;
        }
        default:
            break;
        }
    }
    // std::cout << std::endl;

    if (key == SELECTION_NUMBER_OF) {
        key = SELECTION_CUSTOM;
    }

    current_key = key;
    selectiontype_buttons[current_key]->set_active(true);

    return;
} /* sp_export_detect_size */

/// Called when area x0 value is changed
void Export::areaXChange(Glib::RefPtr<Gtk::Adjustment>& adj)
{
    float x0, x1, xdpi, width;

    if (update) {
        return;
    }

    update = true;

    x0 = getValuePx(x0_adj);
    x1 = getValuePx(x1_adj);
    xdpi = getValue(xdpi_adj);

    width = floor ((x1 - x0) * xdpi / DPI_BASE + 0.5);

    if (width < SP_EXPORT_MIN_SIZE) {
        width = SP_EXPORT_MIN_SIZE;

        if (adj == x1_adj) {
            x1 = x0 + width * DPI_BASE / xdpi;
            setValuePx(x1_adj, x1);
        } else {
            x0 = x1 - width * DPI_BASE / xdpi;
            setValuePx(x0_adj, x0);
        }
    }

    setValuePx(width_adj, x1 - x0);
    setValue(bmwidth_adj, width);

    detectSize();

    update = false;

    return;
} // end of sp_export_area_x_value_changed()

/// Called when area y0 value is changed.
void Export::areaYChange(Glib::RefPtr<Gtk::Adjustment>& adj)
{
    float y0, y1, ydpi, height;

    if (update) {
        return;
    }

    update = true;

    y0 = getValuePx(y0_adj);
    y1 = getValuePx(y1_adj);
    ydpi = getValue(ydpi_adj);

    height = floor ((y1 - y0) * ydpi / DPI_BASE + 0.5);

    if (height < SP_EXPORT_MIN_SIZE) {
        //const gchar *key;
        height = SP_EXPORT_MIN_SIZE;
        //key = (const gchar *)g_object_get_data(G_OBJECT (adj), "key");
        if (adj == y1_adj) {
            //if (!strcmp (key, "y0")) {
            y1 = y0 + height * DPI_BASE / ydpi;
            setValuePx(y1_adj, y1);
        } else {
            y0 = y1 - height * DPI_BASE / ydpi;
            setValuePx(y0_adj, y0);
        }
    }

    setValuePx(height_adj, y1 - y0);
    setValue(bmheight_adj, height);

    detectSize();

    update = false;

    return;
} // end of sp_export_area_y_value_changed()

/// Called when x1-x0 or area width is changed
void Export::onAreaWidthChange()
{
    if (update) {
        return;
    }

    update = true;

    float x0 = getValuePx(x0_adj);
    float xdpi = getValue(xdpi_adj);
    float width = getValuePx(width_adj);
    float bmwidth = floor(width * xdpi / DPI_BASE + 0.5);

    if (bmwidth < SP_EXPORT_MIN_SIZE) {

        bmwidth = SP_EXPORT_MIN_SIZE;
        width = bmwidth * DPI_BASE / xdpi;
        setValuePx(width_adj, width);
    }

    setValuePx(x1_adj, x0 + width);
    setValue(bmwidth_adj, bmwidth);

    update = false;

    return;
} // end of sp_export_area_width_value_changed()

/// Called when y1-y0 or area height is changed.
void Export::onAreaHeightChange()
{
    if (update) {
        return;
    }

    update = true;

    float y0 = getValuePx(y0_adj);
    //float y1 = sp_export_value_get_px(y1_adj);
    float ydpi = getValue(ydpi_adj);
    float height = getValuePx(height_adj);
    float bmheight = floor (height * ydpi / DPI_BASE + 0.5);

    if (bmheight < SP_EXPORT_MIN_SIZE) {
        bmheight = SP_EXPORT_MIN_SIZE;
        height = bmheight * DPI_BASE / ydpi;
        setValuePx(height_adj, height);
    }

    setValuePx(y1_adj, y0 + height);
    setValue(bmheight_adj, bmheight);

    update = false;

    return;
} // end of sp_export_area_height_value_changed()

/**
 * A function to set the ydpi.
 * @param base The export dialog.
 *
 * This function grabs all of the y values and then figures out the
 * new bitmap size based on the changing dpi value.  The dpi value is
 * gotten from the xdpi setting as these can not currently be independent.
 */
void Export::setImageY()
{
    float y0, y1, xdpi;

    y0 = getValuePx(y0_adj);
    y1 = getValuePx(y1_adj);
    xdpi = getValue(xdpi_adj);

    setValue(ydpi_adj, xdpi);
    setValue(bmheight_adj, (y1 - y0) * xdpi / DPI_BASE);

    return;
} // end of setImageY()

/**
 * A function to set the xdpi.
 *
 * This function grabs all of the x values and then figures out the
 * new bitmap size based on the changing dpi value.  The dpi value is
 * gotten from the xdpi setting as these can not currently be independent.
 *
 */
void Export::setImageX()
{
    float x0, x1, xdpi;

    x0 = getValuePx(x0_adj);
    x1 = getValuePx(x1_adj);
    xdpi = getValue(xdpi_adj);

    setValue(ydpi_adj, xdpi);
    setValue(bmwidth_adj, (x1 - x0) * xdpi / DPI_BASE);

    return;
} // end of setImageX()

/// Called when pixel width is changed
void Export::onBitmapWidthChange ()
{
    float x0, x1, bmwidth, xdpi;

    if (update) {
        return;
    }

    update = true;

    x0 = getValuePx(x0_adj);
    x1 = getValuePx(x1_adj);
    bmwidth = getValue(bmwidth_adj);

    if (bmwidth < SP_EXPORT_MIN_SIZE) {
        bmwidth = SP_EXPORT_MIN_SIZE;
        setValue(bmwidth_adj, bmwidth);
    }

    xdpi = bmwidth * DPI_BASE / (x1 - x0);
    setValue(xdpi_adj, xdpi);

    setImageY ();

    update = false;

    return;
} // end of sp_export_bitmap_width_value_changed()

/// Called when pixel height is changed
void Export::onBitmapHeightChange ()
{
    float y0, y1, bmheight, xdpi;

    if (update) {
        return;
    }

    update = true;

    y0 = getValuePx(y0_adj);
    y1 = getValuePx(y1_adj);
    bmheight = getValue(bmheight_adj);

    if (bmheight < SP_EXPORT_MIN_SIZE) {
        bmheight = SP_EXPORT_MIN_SIZE;
        setValue(bmheight_adj, bmheight);
    }

    xdpi = bmheight * DPI_BASE / (y1 - y0);
    setValue(xdpi_adj, xdpi);

    setImageX ();

    update = false;

    return;
} // end of sp_export_bitmap_width_value_changed()

/**
 * A function to adjust the bitmap width when the xdpi value changes.
 *
 * The first thing this function checks is to see if we are doing an
 * update.  If we are, this function just returns because there is another
 * instance of it that will handle everything for us.  If there is a
 * units change, we also assume that everyone is being updated appropriately
 * and there is nothing for us to do.
 *
 * If we're the highest level function, we set the update flag, and
 * continue on our way.
 *
 * All of the values are grabbed using the \c sp_export_value_get functions
 * (call to the _pt ones for x0 and x1 but just standard for xdpi).  The
 * xdpi value is saved in the preferences for the next time the dialog
 * is opened.  (does the selection dpi need to be set here?)
 *
 * A check is done to to ensure that we aren't outputting an invalid width,
 * this is set by SP_EXPORT_MIN_SIZE.  If that is the case the dpi is
 * changed to make it valid.
 *
 * After all of this the bitmap width is changed.
 *
 * We also change the ydpi.  This is a temporary hack as these can not
 * currently be independent.  This is likely to change in the future.
 *
 */
void Export::onExportXdpiChange()
{
    float x0, x1, xdpi, bmwidth;

    if (update) {
        return;
    }

    update = true;

    x0 = getValuePx(x0_adj);
    x1 = getValuePx(x1_adj);
    xdpi = getValue(xdpi_adj);

    // remember xdpi setting
    prefs->setDouble("/dialogs/export/defaultxdpi/value", xdpi);

    bmwidth = (x1 - x0) * xdpi / DPI_BASE;

    if (bmwidth < SP_EXPORT_MIN_SIZE) {
        bmwidth = SP_EXPORT_MIN_SIZE;
        if (x1 != x0)
            xdpi = bmwidth * DPI_BASE / (x1 - x0);
        else
            xdpi = DPI_BASE;
        setValue(xdpi_adj, xdpi);
    }

    setValue(bmwidth_adj, bmwidth);

    setImageY ();

    update = false;

    return;
} // end of sp_export_xdpi_value_changed()


/**
 * A function to change the area that is used for the exported.
 * bitmap.
 *
 * This function just calls \c sp_export_value_set_px for each of the
 * parameters that is passed in.  This allows for setting them all in
 * one convenient area.
 *
 * Update is set to suspend all of the other test running while all the
 * values are being set up.  This allows for a performance increase, but
 * it also means that the wrong type won't be detected with only some of
 * the values set.  After all the values are set everyone is told that
 * there has been an update.
 *
 * @param  x0    Horizontal upper left hand corner of the picture in points.
 * @param  y0    Vertical upper left hand corner of the picture in points.
 * @param  x1    Horizontal lower right hand corner of the picture in points.
 * @param  y1    Vertical lower right hand corner of the picture in points.
 */
void Export::setArea( double x0, double y0, double x1, double y1 )
{
    update = true;
    setValuePx(x1_adj, x1);
    setValuePx(y1_adj, y1);
    setValuePx(x0_adj, x0);
    setValuePx(y0_adj, y0);
    update = false;

    areaXChange (x1_adj);
    areaYChange (y1_adj);

    return;
}

/**
 * Sets the value of an adjustment.
 *
 * @param  adj   The adjustment widget
 * @param  val   What value to set it to.
 */
void Export::setValue(Glib::RefPtr<Gtk::Adjustment>& adj, double val )
{
    if (adj) {
        adj->set_value(val);
    }
}

/**
 * A function to set a value using the units points.
 *
 * This function first gets the adjustment for the key that is passed
 * in.  It then figures out what units are currently being used in the
 * dialog.  After doing all of that, it then converts the incoming
 *value and sets the adjustment.
 *
 * @param  adj   The adjustment widget
 * @param  val   What the value should be in points.
 */
void Export::setValuePx(Glib::RefPtr<Gtk::Adjustment>& adj, double val)
{
    Unit const *unit = unit_selector.getUnit();

    setValue(adj, Inkscape::Util::Quantity::convert(val, "px", unit));

    return;
}

/**
 * Get the value of an adjustment in the export dialog.
 *
 * This function gets the adjustment from the data field in the export
 * dialog.  It then grabs the value from the adjustment.
 *
 * @param  adj   The adjustment widget
 *
 * @return The value in the specified adjustment.
 */
float Export::getValue(Glib::RefPtr<Gtk::Adjustment>& adj)
{
    if (!adj) {
        g_message("sp_export_value_get : adj is NULL");
        return 0.0;
    }
    return adj->get_value();
}

/**
 * Grabs a value in the export dialog and converts the unit
 * to points.
 *
 * This function, at its most basic, is a call to \c sp_export_value_get
 * to get the value of the adjustment.  It then finds the units that
 * are being used by looking at the "units" attribute of the export
 * dialog.  Using that it converts the returned value into points.
 *
 * @param  adj   The adjustment widget
 *
 * @return The value in the adjustment in points.
 */
float Export::getValuePx(Glib::RefPtr<Gtk::Adjustment>& adj)
{
    float value = getValue( adj);
    Unit const *unit = unit_selector.getUnit();

    return Inkscape::Util::Quantity::convert(value, unit, "px");
} // end of sp_export_value_get_px()

/**
 * This function is called when the filename is changed by
 * anyone.  It resets the virgin bit.
 *
 * This function gets called when the text area is modified.  It is
 * looking for the case where the text area is modified from its
 * original value.  In that case it sets the "filename-modified" bit
 * to TRUE.  If the text dialog returns back to the original text, the
 * bit gets reset.  This should stop simple mistakes.
 */
void Export::onFilenameModified()
{
    if (original_name == filename_entry.get_text()) {
        filename_modified = false;
    } else {
        filename_modified = true;
    }

    return;
} // end sp_export_filename_modified

}
}
}

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
