// SPDX-License-Identifier: GPL-2.0-or-later
/**
 * @file
 *
 * Paper-size widget and helper functions
 */
/*
 * Authors:
 *   bulia byak <buliabyak@users.sf.net>
 *   Lauris Kaplinski <lauris@kaplinski.com>
 *   Jon Phillips <jon@rejon.org>
 *   Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm)
 *   Bob Jamison <ishmal@users.sf.net>
 *   Abhishek Sharma
 *
 * Copyright (C) 2000 - 2006 Authors
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "page-sizer.h"
#include "pages-skeleton.h"
#include <glib.h>
#include <glibmm/i18n.h>
#include "verbs.h"
#include "helper/action.h"
#include "object/sp-root.h"
#include "io/resource.h"

namespace Inkscape {
namespace UI {
namespace Widget {


//########################################################################
//# P A G E    S I Z E R
//########################################################################

/**
 * Constructor
 */
PageSizer::PageSizer(Registry & _wr)
    : Gtk::VBox(false,4),
      _dimensionUnits( _("U_nits:"), "units", _wr ),
      _dimensionWidth( _("_Width:"), _("Width of paper"), "width", _dimensionUnits, _wr ),
      _dimensionHeight( _("_Height:"), _("Height of paper"), "height", _dimensionUnits, _wr ),
      _marginLock( _("Loc_k margins"), _("Lock margins"), "lock-margins", _wr, false, nullptr, nullptr),
      _lock_icon(),
      _marginTop( _("T_op:"), _("Top margin"), "fit-margin-top", _wr ),
      _marginLeft( _("L_eft:"), _("Left margin"), "fit-margin-left", _wr),
      _marginRight( _("Ri_ght:"), _("Right margin"), "fit-margin-right", _wr),
      _marginBottom( _("Botto_m:"), _("Bottom margin"), "fit-margin-bottom", _wr),
      _lockMarginUpdate(false),
      _scaleX(_("Scale _x:"), _("Scale X"), "scale-x", _wr),
      _scaleY(_("Scale _y:"), _("While SVG allows non-uniform scaling it is recommended to use only uniform scaling in Inkscape. To set a non-uniform scaling, set the 'viewBox' directly."), "scale-y", _wr),
      _lockScaleUpdate(false),
      _viewboxX(_("X:"),      _("X"),      "viewbox-x", _wr),
      _viewboxY(_("Y:"),      _("Y"),      "viewbox-y", _wr),
      _viewboxW(_("Width:"),  _("Width"),  "viewbox-width", _wr),
      _viewboxH(_("Height:"), _("Height"), "viewbox-height", _wr),
      _lockViewboxUpdate(false),
      _widgetRegistry(&_wr)
{
    // set precision of scalar entry boxes
    _wr.setUpdating (true);
    _dimensionWidth.setDigits(5);
    _dimensionHeight.setDigits(5);
    _marginTop.setDigits(5);
    _marginLeft.setDigits(5);
    _marginRight.setDigits(5);
    _marginBottom.setDigits(5);
    _scaleX.setDigits(5);
    _scaleY.setDigits(5);
    _viewboxX.setDigits(5);
    _viewboxY.setDigits(5);
    _viewboxW.setDigits(5);
    _viewboxH.setDigits(5);
    _dimensionWidth.setRange( 0.00001, 10000000 );
    _dimensionHeight.setRange( 0.00001, 10000000 );
    _scaleX.setRange( 0.00001, 100000 );
    _scaleY.setRange( 0.00001, 100000 );
    _viewboxX.setRange( -10000000, 10000000 );
    _viewboxY.setRange( -10000000, 10000000 );
    _viewboxW.setRange( 0.00001, 10000000 );
    _viewboxH.setRange( 0.00001, 10000000 );

    _scaleY.set_sensitive (false); // We only want to display Y scale.

    _wr.setUpdating (false);

    //# Set up the Paper Size combo box
    _paperSizeListStore = Gtk::ListStore::create(_paperSizeListColumns);
    _paperSizeList.set_model(_paperSizeListStore);
    _paperSizeList.append_column(_("Name"),
                                 _paperSizeListColumns.nameColumn);
    _paperSizeList.append_column(_("Description"),
                                 _paperSizeListColumns.descColumn);
    _paperSizeList.set_headers_visible(false);
    _paperSizeListSelection = _paperSizeList.get_selection();
    _paper_size_list_connection =
        _paperSizeListSelection->signal_changed().connect (
            sigc::mem_fun (*this, &PageSizer::on_paper_size_list_changed));
    _paperSizeListScroller.add(_paperSizeList);
    _paperSizeListScroller.set_shadow_type(Gtk::SHADOW_IN);
    _paperSizeListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
    _paperSizeListScroller.set_size_request(-1, 130);


    char *path = Inkscape::IO::Resource::profile_path("pages.csv");
    if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
        if (!g_file_set_contents(path, pages_skeleton, sizeof(pages_skeleton) , nullptr)) {
            g_warning("%s", _("Failed to create the page file."));
        }
    }

    gchar *content = nullptr;
    if (g_file_get_contents(path, &content, nullptr, nullptr)) {

        gchar **lines = g_strsplit_set(content, "\n", 0);

        for (int i = 0; lines && lines[i]; ++i) {
            gchar **line = g_strsplit_set(lines[i], ",", 5);
            if (!line[0] || !line[1] || !line[2] || !line[3] || line[0][0]=='#')
                continue;
            //name, width, height, unit
            double width = g_ascii_strtod(line[1], nullptr);
            double height = g_ascii_strtod(line[2], nullptr);
            Glib::ustring name = line[0];
            char formatBuf[80];
            snprintf(formatBuf, 79, "%0.1f x %0.1f", width, height);
            Glib::ustring desc = formatBuf;
            desc.append(" " + std::string(line[3]));
            PaperSize paper(name, width, height, Inkscape::Util::unit_table.getUnit(line[3]));
            _paperSizeTable[name] = paper;
            Gtk::TreeModel::Row row = *(_paperSizeListStore->append());
            row[_paperSizeListColumns.nameColumn] = name;
            row[_paperSizeListColumns.descColumn] = desc;
            g_strfreev(line);
        }
        g_strfreev(lines);
        g_free(content);
    }
    g_free(path);

    pack_start (_paperSizeListScroller, true, true, 0);

    //## Set up orientation radio buttons
    pack_start (_orientationBox, false, false, 0);
    _orientationLabel.set_label(_("Orientation:"));
    _orientationBox.pack_start(_orientationLabel, false, false, 0);
    _landscapeButton.set_use_underline();
    _landscapeButton.set_label(_("_Landscape"));
    _landscapeButton.set_active(true);
    Gtk::RadioButton::Group group = _landscapeButton.get_group();
    _orientationBox.pack_end (_landscapeButton, false, false, 5);
    _portraitButton.set_use_underline();
    _portraitButton.set_label(_("_Portrait"));
    _portraitButton.set_active(true);
    _orientationBox.pack_end (_portraitButton, false, false, 5);
    _portraitButton.set_group (group);
    _portraitButton.set_active (true);

    // Setting default custom unit to document unit
    SPDesktop *dt = SP_ACTIVE_DESKTOP;
    SPNamedView *nv = dt->getNamedView();
    _wr.setUpdating (true);
    if (nv->page_size_units) {
        _dimensionUnits.setUnit(nv->page_size_units->abbr);
    } else if (nv->display_units) {
        _dimensionUnits.setUnit(nv->display_units->abbr);
    }
    _wr.setUpdating (false);


    //## Set up custom size frame
    _customFrame.set_label(_("Custom size"));
    pack_start (_customFrame, false, false, 0);
    _customFrame.add(_customDimTable);

    _customDimTable.set_border_width(4);
    _customDimTable.set_row_spacing(4);
    _customDimTable.set_column_spacing(4);

    _dimensionHeight.set_halign(Gtk::ALIGN_CENTER);
    _dimensionUnits.set_halign(Gtk::ALIGN_END);
    _customDimTable.attach(_dimensionWidth,        0, 0, 1, 1);
    _customDimTable.attach(_dimensionHeight,       1, 0, 1, 1);
    _customDimTable.attach(_dimensionUnits,        2, 0, 1, 1);

    _customDimTable.attach(_fitPageMarginExpander, 0, 1, 3, 1);

    _dimTabOrderList.clear();
    _dimTabOrderList.push_back(&_dimensionWidth);
    _dimTabOrderList.push_back(&_dimensionHeight);
    _dimTabOrderList.push_back(&_dimensionUnits);
    _dimTabOrderList.push_back(&_fitPageMarginExpander);
    _customDimTable.set_focus_chain(_dimTabOrderList);

    //## Set up fit page expander
    _fitPageMarginExpander.set_use_underline();
    _fitPageMarginExpander.set_label(_("Resi_ze page to content..."));
    _fitPageMarginExpander.add(_marginTable);

    _marginTable.set_border_width(4);
    _marginTable.set_row_spacing(4);
    _marginTable.set_column_spacing(4);

    //### margin label and lock button
    _marginLabel.set_markup(Glib::ustring("<b><i>") + _("Margins") + "</i></b>");
    _marginLabel.set_halign(Gtk::ALIGN_CENTER);

    _lock_icon.set_from_icon_name("object-unlocked", Gtk::ICON_SIZE_LARGE_TOOLBAR);
    _lock_icon.show();
    _marginLock.set_active(false);
    _marginLock.add(_lock_icon);

    _marginBox.set_spacing(4);
    _marginBox.add(_marginLabel);
    _marginBox.add(_marginLock);
    _marginBox.set_halign(Gtk::ALIGN_CENTER);
    _marginTable.attach(_marginBox,      1, 1, 1, 1);

    //### margins
    _marginTop.set_halign(Gtk::ALIGN_CENTER);
    _marginLeft.set_halign(Gtk::ALIGN_START);
    _marginRight.set_halign(Gtk::ALIGN_END);
    _marginBottom.set_halign(Gtk::ALIGN_CENTER);

    _marginTable.attach(_marginTop,      0, 0, 3, 1);
    _marginTable.attach(_marginLeft,     0, 1, 1, 1);
    _marginTable.attach(_marginRight,    2, 1, 1, 1);
    _marginTable.attach(_marginBottom,   0, 2, 3, 1);

    //### fit page to drawing button
    _fitPageButton.set_use_underline();
    _fitPageButton.set_label(_("_Resize page to drawing or selection (Ctrl+Shift+R)"));
    _fitPageButton.set_tooltip_text(_("Resize the page to fit the current selection, or the entire drawing if there is no selection"));

    _fitPageButton.set_hexpand();
    _fitPageButton.set_halign(Gtk::ALIGN_CENTER);
    _marginTable.attach(_fitPageButton,  0, 3, 3, 1);


    //## Set up scale frame
    _scaleFrame.set_label(_("Scale"));
    pack_start (_scaleFrame, false, false, 0);
    _scaleFrame.add(_scaleTable);

    _scaleTable.set_border_width(4);
    _scaleTable.set_row_spacing(4);
    _scaleTable.set_column_spacing(4);

    _scaleTable.attach(_scaleX,          0, 0, 1, 1);
    _scaleTable.attach(_scaleY,          1, 0, 1, 1);
    _scaleTable.attach(_scaleLabel,      2, 0, 1, 1);

    _viewboxExpander.set_hexpand();
    _scaleTable.attach(_viewboxExpander, 0, 2, 3, 1);

    _viewboxExpander.set_use_underline();
    _viewboxExpander.set_label(_("_Viewbox..."));
    _viewboxExpander.add(_viewboxTable);

    _viewboxTable.set_border_width(4);
    _viewboxTable.set_row_spacing(4);
    _viewboxTable.set_column_spacing(4);

    _viewboxX.set_halign(Gtk::ALIGN_END);
    _viewboxY.set_halign(Gtk::ALIGN_END);
    _viewboxW.set_halign(Gtk::ALIGN_END);
    _viewboxH.set_halign(Gtk::ALIGN_END);
    _viewboxSpacer.set_hexpand();
    _viewboxTable.attach(_viewboxX,      0, 0, 1, 1);
    _viewboxTable.attach(_viewboxY,      1, 0, 1, 1);
    _viewboxTable.attach(_viewboxW,      0, 1, 1, 1);
    _viewboxTable.attach(_viewboxH,      1, 1, 1, 1);
    _viewboxTable.attach(_viewboxSpacer, 2, 0, 3, 1);

    _wr.setUpdating (true);
    updateScaleUI();
    _wr.setUpdating (false);
}


/**
 * Destructor
 */
PageSizer::~PageSizer()
= default;



/**
 * Initialize or reset this widget
 */
void
PageSizer::init ()
{
    _landscape_connection = _landscapeButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_landscape));
    _portrait_connection = _portraitButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_portrait));
    _changedw_connection = _dimensionWidth.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed));
    _changedh_connection = _dimensionHeight.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed));
    _changedu_connection = _dimensionUnits.getUnitMenu()->signal_changed().connect (sigc::mem_fun (*this, &PageSizer::on_units_changed));
    _fitPageButton.signal_clicked().connect(sigc::mem_fun(*this, &PageSizer::fire_fit_canvas_to_selection_or_drawing));
    _changeds_connection  = _scaleX.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_scale_changed));
    _changedvx_connection = _viewboxX.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_viewbox_changed));
    _changedvy_connection = _viewboxY.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_viewbox_changed));
    _changedvw_connection = _viewboxW.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_viewbox_changed));
    _changedvh_connection = _viewboxH.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_viewbox_changed));
    _changedlk_connection = _marginLock.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_margin_lock_changed));
    _changedmt_connection = _marginTop.signal_value_changed().connect (sigc::bind<RegisteredScalar*>(sigc::mem_fun (*this, &PageSizer::on_margin_changed), &_marginTop));
    _changedmb_connection = _marginBottom.signal_value_changed().connect (sigc::bind<RegisteredScalar*>(sigc::mem_fun (*this, &PageSizer::on_margin_changed), &_marginBottom));
    _changedml_connection = _marginLeft.signal_value_changed().connect (sigc::bind<RegisteredScalar*>(sigc::mem_fun (*this, &PageSizer::on_margin_changed), &_marginLeft));
    _changedmr_connection = _marginRight.signal_value_changed().connect (sigc::bind<RegisteredScalar*>(sigc::mem_fun (*this, &PageSizer::on_margin_changed), &_marginRight));
    show_all_children();
}


/**
 * Set document dimensions (if not called by Doc prop's update()) and
 * set the PageSizer's widgets and text entries accordingly. If
 * 'changeList' is true, then adjust the paperSizeList to show the closest
 * standard page size.
 *
 * \param w, h
 * \param changeList whether to modify the paper size list
 */
void
PageSizer::setDim (Inkscape::Util::Quantity w, Inkscape::Util::Quantity h, bool changeList, bool changeSize)
{
    static bool _called = false;
    if (_called) {
        return;
    }

    _called = true;

    _paper_size_list_connection.block();
    _landscape_connection.block();
    _portrait_connection.block();
    _changedw_connection.block();
    _changedh_connection.block();

    _unit = w.unit->abbr;

    if (SP_ACTIVE_DESKTOP && !_widgetRegistry->isUpdating()) {
        SPDocument *doc = SP_ACTIVE_DESKTOP->getDocument();
        Inkscape::Util::Quantity const old_height = doc->getHeight();
        doc->setWidthAndHeight (w, h, changeSize);
        // The origin for the user is in the lower left corner; this point should remain stationary when
        // changing the page size. The SVG's origin however is in the upper left corner, so we must compensate for this
        if (changeSize && !SP_ACTIVE_DESKTOP->is_yaxisdown()) {
            Geom::Translate const vert_offset(Geom::Point(0, (old_height.value("px") - h.value("px"))));
            doc->getRoot()->translateChildItems(vert_offset);
        }
        DocumentUndo::done(doc, SP_VERB_NONE, _("Set page size"));
    }

    if ( w != h ) {
        _landscapeButton.set_sensitive(true);
        _portraitButton.set_sensitive (true);
        _landscape = ( w > h );
        _landscapeButton.set_active(_landscape ? true : false);
        _portraitButton.set_active (_landscape ? false : true);
    } else {
        _landscapeButton.set_sensitive(false);
        _portraitButton.set_sensitive (false);
    }

    if (changeList)
        {
        Gtk::TreeModel::Row row = (*find_paper_size(w, h));
        if (row)
            _paperSizeListSelection->select(row);
        }

    _dimensionWidth.setUnit(w.unit->abbr);
    _dimensionWidth.setValue (w.quantity);
    _dimensionHeight.setUnit(h.unit->abbr);
    _dimensionHeight.setValue (h.quantity);


    _paper_size_list_connection.unblock();
    _landscape_connection.unblock();
    _portrait_connection.unblock();
    _changedw_connection.unblock();
    _changedh_connection.unblock();

    _called = false;
}

/**
 * Updates the scalar widgets for the fit margins.  (Just changes the value
 * of the ui widgets to match the xml).
 */
void
PageSizer::updateFitMarginsUI(Inkscape::XML::Node *nv_repr)
{
    if (!_lockMarginUpdate) {
        double value = 0.0;
        if (sp_repr_get_double(nv_repr, "fit-margin-top", &value)) {
            _marginTop.setValue(value);
        }
        if (sp_repr_get_double(nv_repr, "fit-margin-left", &value)) {
            _marginLeft.setValue(value);
        }
        if (sp_repr_get_double(nv_repr, "fit-margin-right", &value)) {
            _marginRight.setValue(value);
        }
        if (sp_repr_get_double(nv_repr, "fit-margin-bottom", &value)) {
            _marginBottom.setValue(value);
        }
    }
}


/**
 * Returns an iterator pointing to a row in paperSizeListStore which
 * contains a paper of the specified size, or
 * paperSizeListStore->children().end() if no such paper exists.
 *
 * The code is not tested for the case where w and h have different units.
 */
Gtk::ListStore::iterator
PageSizer::find_paper_size (Inkscape::Util::Quantity w, Inkscape::Util::Quantity h) const
{
    // The code below assumes that w < h, so make sure that's the case:
    if ( h < w ) {
        std::swap(h,w);
    }

    g_return_val_if_fail(w <= h, _paperSizeListStore->children().end());

    std::map<Glib::ustring, PaperSize>::const_iterator iter;
    for (iter = _paperSizeTable.begin() ;
         iter != _paperSizeTable.end() ; ++iter) {
        PaperSize paper = iter->second;
        Inkscape::Util::Quantity smallX (paper.smaller, paper.unit);
        Inkscape::Util::Quantity largeX (paper.larger, paper.unit);

        g_return_val_if_fail(smallX.quantity < largeX.quantity + 0.001, _paperSizeListStore->children().end());

        if ( are_near(w, smallX, 0.1) && are_near(h, largeX, 0.1) ) {
            Gtk::ListStore::iterator p = _paperSizeListStore->children().begin();
            Gtk::ListStore::iterator pend = _paperSizeListStore->children().end();
            // We need to search paperSizeListStore explicitly for the
            // specified paper size because it is sorted in a different
            // way than paperSizeTable (which is sorted alphabetically)
            for ( ; p != pend; ++p) {
                if ((*p)[_paperSizeListColumns.nameColumn] == paper.name) {
                    return p;
                }
            }
        }
    }
    return _paperSizeListStore->children().end();
}



/**
 * Tell the desktop to fit the page size to the selection or drawing.
 */
void
PageSizer::fire_fit_canvas_to_selection_or_drawing()
{
    SPDesktop *dt = SP_ACTIVE_DESKTOP;
    if (!dt) {
        return;
    }
    SPDocument *doc;
    SPNamedView *nv;
    Inkscape::XML::Node *nv_repr;

    if ((doc = SP_ACTIVE_DESKTOP->getDocument())
        && (nv = sp_document_namedview(doc, nullptr))
        && (nv_repr = nv->getRepr())) {
        _lockMarginUpdate = true;
        sp_repr_set_svg_double(nv_repr, "fit-margin-top", _marginTop.getValue());
        sp_repr_set_svg_double(nv_repr, "fit-margin-left", _marginLeft.getValue());
        sp_repr_set_svg_double(nv_repr, "fit-margin-right", _marginRight.getValue());
        sp_repr_set_svg_double(nv_repr, "fit-margin-bottom", _marginBottom.getValue());
        _lockMarginUpdate = false;
    }

    Verb *verb = Verb::get( SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING );
    if (verb) {
        SPAction *action = verb->get_action(Inkscape::ActionContext(dt));
        if (action) {
            sp_action_perform(action, nullptr);
        }
    }
}



/**
 * Paper Size list callback for when a user changes the selection
 */
void
PageSizer::on_paper_size_list_changed()
{
    //Glib::ustring name = _paperSizeList.get_active_text();
    Gtk::TreeModel::iterator miter = _paperSizeListSelection->get_selected();
    if(!miter)
        {
        //error?
        return;
        }
    Gtk::TreeModel::Row row = *miter;
    Glib::ustring name = row[_paperSizeListColumns.nameColumn];
    std::map<Glib::ustring, PaperSize>::const_iterator piter =
                    _paperSizeTable.find(name);
    if (piter == _paperSizeTable.end()) {
        g_warning("paper size '%s' not found in table", name.c_str());
        return;
    }
    PaperSize paper = piter->second;
    Inkscape::Util::Quantity w = Inkscape::Util::Quantity(paper.smaller, paper.unit);
    Inkscape::Util::Quantity h = Inkscape::Util::Quantity(paper.larger, paper.unit);

    if ( w > h ) {
        // enforce landscape mode if this is desired for the given page format
        _landscape = true;
    } else {
        // otherwise we keep the current mode
        _landscape = _landscapeButton.get_active();
    }

    if ((_landscape && (w < h)) || (!_landscape && (w > h)))
        setDim (h, w, false);
    else
        setDim (w, h, false);

}


/**
 * Portrait button callback
 */
void
PageSizer::on_portrait()
{
    if (!_portraitButton.get_active())
        return;
    Inkscape::Util::Quantity w = Inkscape::Util::Quantity(_dimensionWidth.getValue(""), _dimensionWidth.getUnit());
    Inkscape::Util::Quantity h = Inkscape::Util::Quantity(_dimensionHeight.getValue(""), _dimensionHeight.getUnit());
    if (h < w) {
        setDim (h, w);
    }
}


/**
 * Landscape button callback
 */
void
PageSizer::on_landscape()
{
    if (!_landscapeButton.get_active())
        return;
    Inkscape::Util::Quantity w = Inkscape::Util::Quantity(_dimensionWidth.getValue(""), _dimensionWidth.getUnit());
    Inkscape::Util::Quantity h = Inkscape::Util::Quantity(_dimensionHeight.getValue(""), _dimensionHeight.getUnit());
    if (w < h) {
        setDim (h, w);
    }
}


/**
 * Update scale widgets
 */
void
PageSizer::updateScaleUI()
{

    static bool _called = false;
    if (_called) {
        return;
    }

    _called = true;

    _changeds_connection.block();
    _changedvx_connection.block();
    _changedvy_connection.block();
    _changedvw_connection.block();
    _changedvh_connection.block();

    SPDesktop *dt = SP_ACTIVE_DESKTOP;
    if (dt) {
        SPDocument *doc = dt->getDocument();

        // Update scale
        Geom::Scale scale = doc->getDocumentScale();
        SPNamedView *nv = dt->getNamedView();

        std::stringstream ss;
        ss << _("User units per ") << nv->display_units->abbr << "." ;
        _scaleLabel.set_text( ss.str() );

        if( !_lockScaleUpdate ) {

            double scaleX_inv =
                Inkscape::Util::Quantity::convert( scale[Geom::X], "px", nv->display_units );
            if( scaleX_inv > 0 ) {
                _scaleX.setValue(1.0/scaleX_inv);
            } else {
                // Should never happen
                std::cerr << "PageSizer::updateScaleUI(): Invalid scale value: " << scaleX_inv << std::endl;
                _scaleX.setValue(1.0);
            }
        }

        {  // Don't need to lock as scaleY widget not linked to callback.
            double scaleY_inv =
                Inkscape::Util::Quantity::convert( scale[Geom::Y], "px", nv->display_units );
            if( scaleY_inv > 0 ) {
                _scaleY.setValue(1.0/scaleY_inv);
            } else {
                // Should never happen
                std::cerr << "PageSizer::updateScaleUI(): Invalid scale value: " << scaleY_inv << std::endl;
                _scaleY.setValue(1.0);
            }
        }

        if( !_lockViewboxUpdate ) {
            Geom::Rect viewBox = doc->getViewBox();
            _viewboxX.setValue( viewBox.min()[Geom::X] );
            _viewboxY.setValue( viewBox.min()[Geom::Y] );
            _viewboxW.setValue( viewBox.width() );
            _viewboxH.setValue( viewBox.height() );
        }

    } else {
        // Should never happen
        std::cerr << "PageSizer::updateScaleUI(): No active desktop." << std::endl;
        _scaleLabel.set_text( "Unknown scale" );
    }

    _changeds_connection.unblock();
    _changedvx_connection.unblock();
    _changedvy_connection.unblock();
    _changedvw_connection.unblock();
    _changedvh_connection.unblock();

    _called = false;
}


/**
 * Callback for the dimension widgets
 */
void
PageSizer::on_value_changed()
{
    if (_widgetRegistry->isUpdating()) return;
    if (_unit != _dimensionUnits.getUnit()->abbr) return;
    setDim (Inkscape::Util::Quantity(_dimensionWidth.getValue(""), _dimensionUnits.getUnit()),
            Inkscape::Util::Quantity(_dimensionHeight.getValue(""), _dimensionUnits.getUnit()));
}

void
PageSizer::on_units_changed()
{
    if (_widgetRegistry->isUpdating()) return;
    _unit = _dimensionUnits.getUnit()->abbr;
    setDim (Inkscape::Util::Quantity(_dimensionWidth.getValue(""), _dimensionUnits.getUnit()),
            Inkscape::Util::Quantity(_dimensionHeight.getValue(""), _dimensionUnits.getUnit()),
            true, false);
}

/**
 * Callback for scale widgets
 */
void
PageSizer::on_scale_changed()
{
    if (_widgetRegistry->isUpdating()) return;

    double value = _scaleX.getValue();
    if( value > 0 ) {

        SPDesktop *dt = SP_ACTIVE_DESKTOP;
        if (dt) {
            SPDocument *doc = dt->getDocument();
            SPNamedView *nv = dt->getNamedView();

            double scaleX_inv = Inkscape::Util::Quantity(1.0/value, nv->display_units ).value("px");

            _lockScaleUpdate = true;
            doc->setDocumentScale( 1.0/scaleX_inv );
            updateScaleUI();
            _lockScaleUpdate = false;
            DocumentUndo::done(doc, SP_VERB_NONE, _("Set page scale"));
        }
    }
}

/**
 * Callback for viewbox widgets
 */
void
PageSizer::on_viewbox_changed()
{
    if (_widgetRegistry->isUpdating()) return;

    double viewboxX = _viewboxX.getValue();
    double viewboxY = _viewboxY.getValue();
    double viewboxW = _viewboxW.getValue();
    double viewboxH = _viewboxH.getValue();

    if( viewboxW > 0 && viewboxH > 0) {
        SPDesktop *dt = SP_ACTIVE_DESKTOP;
        if (dt) {
            SPDocument *doc = dt->getDocument();
            _lockViewboxUpdate = true;
            doc->setViewBox( Geom::Rect::from_xywh( viewboxX, viewboxY, viewboxW, viewboxH ) );
            updateScaleUI();
            _lockViewboxUpdate = false;
            DocumentUndo::done(doc, SP_VERB_NONE, _("Set 'viewBox'"));
        }
    } else {
        std::cerr
            << "PageSizer::on_viewbox_changed(): width and height must both be greater than zero."
            << std::endl;
    }
}

void
PageSizer::on_margin_lock_changed()
{
    if (_marginLock.get_active()) {
        _lock_icon.set_from_icon_name("object-locked", Gtk::ICON_SIZE_LARGE_TOOLBAR);
        double left   = _marginLeft.getValue();
        double right  = _marginRight.getValue();
        double top    = _marginTop.getValue();
        //double bottom = _marginBottom.getValue();
        if (Geom::are_near(left,right)) {
            if (Geom::are_near(left, top)) {
                on_margin_changed(&_marginBottom);
            } else {
                on_margin_changed(&_marginTop);
            }
        } else {
            if (Geom::are_near(left, top)) {
                on_margin_changed(&_marginRight);
            } else {
                on_margin_changed(&_marginLeft);
            }
        }
    } else {
        _lock_icon.set_from_icon_name("object-unlocked", Gtk::ICON_SIZE_LARGE_TOOLBAR);
    }
}

void
PageSizer::on_margin_changed(RegisteredScalar* widg)
{
    double value = widg->getValue();
    if (_widgetRegistry->isUpdating()) return;
    if (_marginLock.get_active() && !_lockMarginUpdate) {
        _lockMarginUpdate = true;
        _marginLeft.setValue(value);
        _marginRight.setValue(value);
        _marginTop.setValue(value);
        _marginBottom.setValue(value);
        _lockMarginUpdate = false;
    }
}

} // namespace Widget
} // namespace UI
} // namespace Inkscape

/*
  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 :
