/***************************************************************************
**
**  This file is part of SciFigs.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file is distributed in the hope that it will be useful, but WITHOUT
**  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
**  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2002-04-17
**  Copyright: 2002-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "AxisWindow.h"
#include "GraphicObjectFactory.h"
#include "GraphContent.h"
#include "SciFigsGlobal.h"

namespace SciFigs {

  /*!
    \class AxisWindow AxisWindow.h
    \brief AxisWindow widget provides an XY graphic with two axis.

    AxisWindow has always three children:
    \li GraphContent , the content of the graphic where data are plotted
    \li Axis , the X axis either at the top or at the bottom
    \li Axis , the Y axis either on the left or on the right

    \image html AxisWindow.png "A typical multi-layered AxisWindow"
    \image latex AxisWindow.ps "A typical multi-layered AxisWindow" width=10cm

    The content is a 2D space, X and Y where you can stack several layers. Each layer carry distinct types of data and distinct way of plotting them to the content. The layers all inherits GraphContentLayer. Any new type of layers can be implemented and added to any content. The content stores a QPixmap to avoid long redrawing of huge data layers every time the content need refreshing. To force the redrawing of the content call update().

    For basic plotting tasks you generally use only a reduced set of class functions the main ones are:
    \li The constructors
    \li Eventually setGraphContent(), if the GraphContent is not available of the time of the construction
    \li AxisWindow::xAxis and AxisWindow::yAxis to modify the properties of the axis like the minimum and maximum values
    \li graphContent() to get the content Widget, either to change aspect properties or access the embedded data.

    Here is an example that creates a top level graphic (that appear directly on the desktop like any other windows)

    \code
  #include "AxisWindow.h"
  #include "XYValueLines.h"
  #include "ColorMap.h"
  #include "SciFigs.h"

  namespace SciFigs {

  int main(int argc, char ** argv)
  {
    TRACE;
    // Initialization of Qt and SciFigs
    Application a(argc, argv);
    SciFigs s;

    // Creation of graphic object
    AxisWindow * w=new AxisWindow;

    // Creation of a layer containing lines (optimized for high number of items)
    XYValueLines * plot=new XYValueLines(w);

    // Init 65000 lines with 5 points each
    int * pcount=new int[65000];
    for(int i=0;i<65000;i++) pcount[i]=5;
    plot->setPointCount(325000,65000,pcount);

    // Populate the layers with all points
    Point2D * p=plot->points();
    double * values=new double[65000];
    for(int i=0;i<65000;i++) {
      for(int j=0;j<5;j++) {
        p[i*5+j]=Point2D((double)rand()/RAND_MAX,(double)rand()/RAND_MAX);
      }
      values[i]=(double)rand()/RAND_MAX;
    }
    plot->setValues(values);

    // Init the color map
    ColorMap pal;
    pal.generateColorScale(20);
    pal.setVLinear(0,1);
    plot->setColorMap(pal);

    // Change properties of axis
    w->xAxis()->setTitle("Distance X (m)");
    w->xAxis()->setRange(0,1);
    w->yAxis()->setTitle("Distance Y (m)");
    w->yAxis()->setRange(0,1);

    // Show graphic and start event loop
    w->show();
    return a.exec();
  }
    \endcode

    Here is an example that creates a graphic inside an existing GraphicSheet

    \code
    // sheet is assumed to be of type GraphicSheet, graphic is ready for adding new layers
    AxisWindow * w= (AxisWindow *)sheet->addObject("AxisWindow");
    w->setPrintAnchor(sheet->currentOrigin());   // Locate the object at top left corner of the sheet's visible area
    sheet->showObject(w);                        // Insert object in sheet and polish the object
    sheet->autoResizeContent();                  // Make sure that sheet limits are correct

    \endcode

    The zoom and the size functions are usually automatically handled, responding to user actions.
    print() is called on user request or when printing a complete GraphicSheet.
  */

  /*!
    \fn HorizontalAxis AxisWindow::xAxis() const
    The horizontal axis of the graphic, useful to access the axis properties.
  */

  /*!
    \fn VerticalAxis AxisWindow::yAxis() const
    The vertical axis of the graphic, useful to access the axis properties.
  */

  const QString AxisWindow::xmlAxisWindowTag="AxisWindow";

  REGISTER_GRAPHICOBJECT(AxisWindow, AxisWindow::xmlAxisWindowTag,
                         QApplication::translate("AxisWindow", "Graph"),
                         QApplication::translate("AxisWindow", "&Graph"),
                         QApplication::translate("AxisWindow", "Ctrl+G"),
                         QApplication::translate("AxisWindow", "A Graph with X and Y axis"))

  /*!
    Construct a graphic with an empty content
  */
  AxisWindow::AxisWindow(QWidget *parent, Qt::WindowFlags f) :
      GraphicObject(parent, f)
  {
    TRACE;
    _xSBar=new QScrollBar(Qt::Horizontal, this);
    _ySBar=new QScrollBar(Qt::Vertical, this);
    _xSBar->hide(); // because no zoom at the beginning
    _ySBar->hide();

    _xSBar->setMinimum(0);
    _xSBar->setMaximum(0);
    _xSBar->setSingleStep(1);
    _xSBar->setPageStep(10);
    _xSBar->setValue(1);

    _ySBar->setMinimum(0);
    _ySBar->setMaximum(0);
    _ySBar->setSingleStep(1);
    _ySBar->setPageStep(10);
    _ySBar->setValue(1);

    _content=new GraphContent(this);
    _content->setObjectName("content");

    _xAxis=new Axis(this);
    _yAxis=new Axis(this);
    _xAxis->setObjectName("XAxis");
    _yAxis->setObjectName("YAxis");

    _xAxis->setTitle("X");
    _yAxis->setTitle("Y");
    _xAxis->setOrientation(Axis::North);
    _yAxis->setOrientation(Axis::East);
    _xAxis->setGraphContent(_content);
    _yAxis->setGraphContent(_content);
    _xAxis->setOrientationBlocked(true);
    _yAxis->setOrientationBlocked(true);

    setPrintWidth(10.0);
    setPrintHeight(10.0);
    _xAxis->setSizeInfo(10.0);
    _yAxis->setSizeInfo(10.0);

    connect(_xAxis, SIGNAL(rangeChanged()), this, SLOT(checkZoomRects()) );
    connect(_yAxis, SIGNAL(rangeChanged()), this, SLOT(checkZoomRects()) );
    connect(_xAxis, SIGNAL(sizeChanged()), this, SLOT(updateExternalGeometry()) );
    connect(_yAxis, SIGNAL(sizeChanged()), this, SLOT(updateExternalGeometry()) );
    connect(_xAxis, SIGNAL(contentChanged()), this, SLOT(deepUpdate()) );
    connect(_yAxis, SIGNAL(contentChanged()), this, SLOT(deepUpdate()) );
    connect(_content, SIGNAL(zoomIn(double, double, double, double)),
            this, SLOT(zoomIn(double, double, double, double)));
    connect(_content, SIGNAL(zoomOut()), this, SLOT(zoomOut()));
    connect(_content, SIGNAL(browse(QRect&)), this, SLOT(browse(QRect&)));
    connect(_content, SIGNAL(mouseMoved( QPoint, Qt::MouseButtons, Qt::KeyboardModifiers) ), _xAxis, SLOT(trackMouse( QPoint) ));
    connect(_content, SIGNAL(mouseMoved( QPoint, Qt::MouseButtons, Qt::KeyboardModifiers) ), _yAxis, SLOT(trackMouse( QPoint) ));
    connect(_content, SIGNAL(mouseInside( bool) ), _xAxis, SLOT(setMouseTrack( bool) ));
    connect(_content, SIGNAL(mouseInside( bool) ), _yAxis, SLOT(setMouseTrack( bool) ));

    // Scroll bars signals
    connect (_xSBar, SIGNAL(valueChanged( int) ), this, SLOT(xScrollChanged( int) ));
    connect (_ySBar, SIGNAL(valueChanged( int) ), this, SLOT(yScrollChanged( int) ));
  }

  /*!
    \fn GraphContent * AxisWindow::graphContent() const
    Return the content widget
  */

  /*!
    Destructor
  */
  AxisWindow::~AxisWindow()
  {
    TRACE;
  }

  /*!
    Ensure automatic initialization of the size of children
  */
  void AxisWindow::polish()
  {
    TRACE;
    if(_content) _content->polish();
    GraphicObject::polish();
    if( !sheet()) {
      updateInternalGeometry();
    }
  }

  /*!
    Intercept resize event (necessary for stand-alone mode)
  */
  void AxisWindow::resizeEvent(QResizeEvent * )
  {
    TRACE;
    updateInternalGeometry();
  }

  /*!
    Resize the graphic calculating the total size from geometry of inherited
    GraphicOject. The size of internal widgets (Axis and content) are changed
    accordingly.

    Call this function every time you modify the global geometry of the graphic to
    update the properties of axis.
  */
  void AxisWindow::updateInternalGeometry()
  {
    TRACE;
    int xOffset, yOffset;
    if(_xSBar->isVisible()) xOffset=16; else xOffset=0;
    if(_ySBar->isVisible()) yOffset=16; else yOffset=0;
    if(sheet()) {
      setAxisGeometry(SciFigsGlobal::screenResolution());
      double xWidth, xHeight, yWidth, yHeight;
      calcSize(SciFigsGlobal::screenResolution(), xWidth, xHeight, yWidth, yHeight, xOffset, yOffset);
      setAxisGeometry(qRound(xWidth), qRound(yWidth), qRound(xHeight), qRound(yHeight));
    } else {
      int xWidth, xHeight, yWidth, yHeight;
      calcScreenSize(SciFigsGlobal::screenResolution(), xWidth, yHeight, xOffset, yOffset);
      xHeight=height()-yOffset-yHeight;
      yWidth=width()-xOffset-xWidth;
      setAxisGeometry(xWidth, yWidth, xHeight, yHeight);
    }
    if(transparentMask()) updateMask();
  }

  /*!
    Overload of the function inherited from GraphicObject
    Call to updateInternalGeometry()
  */
  void AxisWindow::updateGeometry()
  {
    TRACE;
    updateInternalGeometry();
    GraphicObject::updateGeometry();
  }

  /*!
    Resize the graphic calculating the total size from the size of internal
    widgets (Axis and content). Axis calls this function automatically when
    the aspect of labels or the title has changed. For scaled maps, changing the
    minimum and maximum of axis has an influence over the total size of the widget.

    Call this function every time you modify an axis property that may have an influence
    over the global geometry of the graphic.
  */
  void AxisWindow::updateExternalGeometry()
  {
    TRACE;
    int xOffset, yOffset;
    if(_xSBar->isVisible()) xOffset=16; else xOffset=0;
    if(_ySBar->isVisible()) yOffset=16; else yOffset=0;
    double xWidth, xHeight, yWidth, yHeight;
    calcSize(SciFigsGlobal::screenResolution(), xWidth, xHeight, yWidth, yHeight, xOffset, yOffset);
    _printWidth=(xWidth+yWidth+xOffset)/SciFigsGlobal::screenResolution();
    _printHeight=(xHeight+yHeight+yOffset)/SciFigsGlobal::screenResolution();
    bool tooBig=false;        // Control on size to avoid excess and X11 errors
    if(_printWidth>300.0) {   // Mainly produced by Scaled axis
      _printWidth=300.0;
      tooBig=true;
    }
    if(_printHeight>300.0) {
      _printHeight=300.0;
      tooBig=true;
    }
    if(tooBig) {
      updateInternalGeometry();
      return;
    }
    if(sheet()) {
      setAxisGeometry(qRound(xWidth), qRound(yWidth), qRound(xHeight), qRound(yHeight));
      emit sizeChanged();
      if(transparentMask()) updateMask();
    } else updateInternalGeometry();
  }

  /*!
    Force redrawing of axis and and deep redrawing of content
  */
  void AxisWindow::deepUpdate()
  {
    TRACE;
    _xAxis->update();
    _yAxis->update();
    _content->deepUpdate();
    if(transparentMask()) updateMask();
  }

  /*!
    Force redrawing of axis and light redrawing of content
  */
  void AxisWindow::update()
  {
    TRACE;
    _xAxis->update();
    _yAxis->update();
    if(_content) _content->update();
    if(transparentMask()) updateMask();
  }

  /*!
    Zoom in to a specified rectangle
  */
  void AxisWindow::zoomIn(double rx1, double ry1, double rx2, double ry2)
  {
    TRACE;
    // Avoid integer overflow for scrollbars
    if(_xSBar->maximum() > INT_MAX/2 || _ySBar->maximum() > INT_MAX/2) return ;
    // save actual visible rectangle to zoom rectangles stack
    if(!isZoomed()) {
      _xSBar->show();
      _ySBar->show();
      updateInternalGeometry();
      QAction * a;
      a=_content->action("Unzoom");
      if(a) a->setEnabled(true);
      a=_content->action("Browse");
      if(a) a->setEnabled(true);
    }
    _zoomRects.push_back(Rect( _xAxis->visibleMinimum(), _yAxis->visibleMinimum(),
                                   _xAxis->visibleMaximum(), _yAxis->visibleMaximum()) );
    if(_xAxis->sizeType()==Axis::Scaled &&
         _yAxis->sizeType()==Axis::Scaled) {
      // Both axis are scaled, hence zoom in must respect the x/y ratio
      double ratio=(_xAxis->visibleMaximum() - _xAxis->visibleMinimum()) /
                     (_yAxis->visibleMaximum() - _yAxis->visibleMinimum());
      if(ratio > 0 && ratio < 1000) {
        if(rx2 < rx1) {double tmp=rx2; rx2=rx1; rx1=tmp;}
        double dx=rx2 - rx1;
        if(ry2 < ry1) {double tmp=ry2; ry2=ry1; ry1=tmp;}
        double dy=ry2 - ry1;
        if(dy * ratio > dx) rx2=rx1 + dy * ratio;
        else ry1=ry2 - dx/ratio;
      }
    }
    if(_xAxis->zoomEnabled()) _xAxis->setVisibleRange(rx1, rx2);
    if(_yAxis->zoomEnabled()) _yAxis->setVisibleRange(ry1, ry2);
    updateScrollBars();
    graphContent()->deepUpdate();
  }

  /*!
    Zoom out to the visible area before last zoomIn
  */
  void AxisWindow::zoomOut()
  {
    TRACE;
    // retrieve last visible rectangle
    if(!isZoomed()) return;
    Rect r=_zoomRects.back();
    _zoomRects.pop_back();
    if(!isZoomed()) {
      QAction * a;
      a=_content->action("Unzoom");
      if(a) a->setEnabled(false);
      a=_content->action("Browse");
      if(a) a->setEnabled(false);
      _content->browse(); // Force removal of Browse tracking
      _xSBar->hide();
      _ySBar->hide();
      updateInternalGeometry();
    }

    if(_xAxis->zoomEnabled()) _xAxis->setVisibleRange(r.x1(), r.x2());
    if(_yAxis->zoomEnabled()) _yAxis->setVisibleRange(r.y1(), r.y2());

    updateScrollBars();
    graphContent()->deepUpdate();
  }


  /*!
    Browses from top left to right bottom corner.
  */
  void AxisWindow::browse(QRect& r)
  {
    TRACE;
    int dx=qRound(10.0*r.width()/_content->width());
    if(dx!=0) {
      setHorizontalLine(_xAxis->currentLine()-dx);
      r.setLeft(r.left()+qRound(0.1*dx*_content->width()));
    }
    int dy=qRound(10.0*r.height()/_content->height());
    if(dy!=0) {
      setVerticalLine(_yAxis->currentLine()-dy);
      r.setTop(r.top()+qRound(0.1*dy*_content->height()));
    }
  }

  /*!
    When changing the absolute limits of the graph, some stored zoom rect might not be valid any more.
  */
  void AxisWindow::checkZoomRects()
  {
    TRACE;
    // checks if zoomRects are still inside the min and max of axis, if not deletes it
    // Constructs a rect with actual xmin,xmax,ymin,ymax
    Rect r(_xAxis->minimum(), _yAxis->minimum(), _xAxis->maximum(), _yAxis->maximum());

    if(!_zoomRects.empty()) {
      QVector<Rect>::iterator it;
      for(it=_zoomRects.begin();it!=_zoomRects.end(); )
        if( !r.includes( *it) ) it=_zoomRects.erase(it);
        else ++it;


      if(_zoomRects.empty()) {
        // All zoom rects are deleted,
        // then the actual visible area must be checked
        _zoomRects.push_back(r);
        // find intersection between old visible area and new total area
        Rect oldVis(_xAxis->visibleMinimum(), _yAxis->visibleMinimum(),
                        _xAxis->visibleMaximum(), _yAxis->visibleMaximum());
        Rect newVis=r.intersect(oldVis);
        if(_xAxis->zoomEnabled()) _xAxis->setVisibleRange(newVis.x1(), newVis.x2());
        if(_yAxis->zoomEnabled()) _yAxis->setVisibleRange(newVis.y1(), newVis.y2());
      } else
        // If there's at least one zoom rect the actual visible area is still ok
        _zoomRects.begin() ->setLimits(_xAxis->minimum(), _yAxis->minimum(),
                                        _xAxis->maximum(), _yAxis->maximum());
    } else {
      // old visible area is equal to the total area, the last has changed, thus
      // updates the former one
      //if(_xAxis->zoomEnabled()) _xAxis->setVisibleRange(_xAxis->minimum(), _xAxis->maximum());
      //if(_yAxis->zoomEnabled()) _yAxis->setVisibleRange(_yAxis->minimum(), _yAxis->maximum());
    }
    updateScrollBars();
  }

  /*!
    Automatically adjust scrollbars to the current visible area
  */
  void AxisWindow::updateScrollBars()
  {
    TRACE;
    _xSBar->blockSignals(true);
    _xSBar->setMaximum(_xAxis->lineCount()-1);
    _xSBar->blockSignals(false);
    _ySBar->blockSignals(true);
    _ySBar->setMaximum(_yAxis->lineCount()-1);
    _ySBar->blockSignals(false);

    _xSBar->setValue(_xAxis->currentLine());
    _ySBar->setValue(_yAxis->currentLine());
    deepUpdate();
  }

  /*!
    Scroll along Y axis
  */
  void AxisWindow::yScrollChanged(int val)
  {
    TRACE;
    _yAxis->setCurrentLine(val);
    _content->deepUpdate();
    _yAxis->update();
  }

  /*!
    Scroll along X axis
  */
  void AxisWindow::xScrollChanged(int val)
  {
    TRACE;
    _xAxis->setCurrentLine(val);
    _content->deepUpdate();
    _xAxis->update();
  }

  void AxisWindow::setHorizontalLine(int l)
  {
    TRACE;
    _xSBar->setValue(l);
  }

  void AxisWindow::setVerticalLine(int l)
  {
    TRACE;
    _ySBar->setValue(l);
  }

  void AxisWindow::updateMask()
  {
    TRACE;
    if( !_content) return ;
    // Repaint the widget in a pixmap (like it appear on the screen)
    QPixmap pixmap(width(), height());
    QPainter p;
    p.begin(&pixmap);
    p.fillRect(0, 0, width(), height(), Qt::white);
    int w, h;
    w=_xAxis->width();
    h=_xAxis->height() - 1;
    p.setWindow(0, 0, w, h);
    p.setViewport(_xAxis->x(), _xAxis->y(), w, h);
    _xAxis->paint(p, SciFigsGlobal::screenResolution(), w, h, false);
    w=_yAxis->width() - 1;
    h=_yAxis->height();
    p.setWindow(0, 0, w, h);
    p.setViewport(_yAxis->x(), _yAxis->y(), w, h);
    _yAxis->paint(p, SciFigsGlobal::screenResolution(), w, h, false);
  #ifdef GRAPHCONTENT_PIXMAP
    QPixmap * contentPixmap=_content->pixmap();
    if(contentPixmap)
      p.drawPixmap(_content->x(), _content->y(), *contentPixmap);
  #else
    QImage * contentPixmap=_content->pixmap();
    if(contentPixmap)
      p.drawImage(_content->x(), _content->y(), *contentPixmap);
  #endif
    else
      p.fillRect(_content->x(), _content->y(), _content->width(), _content->height(), Qt::color1);
    p.end();
    QBitmap mask=SciFigsGlobal::colorMask(pixmap, 0xFFFFFFFF);
    if(_content && !_content->transparentMask()) {
      p.begin(&mask);
      p.fillRect(_content->x(), _content->y(), _content->width(), _content->height(), Qt::color1);
      p.end();
    }
    setMask(mask);
  }

  /*!
    For some high resolution bitmap image, fonts must scaled

    Re-implentation to scale fonts in sub-widgets (axis and content)
  */
  void AxisWindow::scaleFonts(FontScales& original, double scale)
  {
    TRACE;
    original.add(this, scaleWidgetFonts(_xAxis, scale));
    original.add(this, scaleWidgetFonts(_yAxis, scale));
    original.add(this, scaleWidgetFonts(_content, scale));
    original.add(this, _content->printBitmap());
    _content->setPrintBitmap(true);
  }

  /*!
    Restore a saved scale for fonts
  */
  void AxisWindow::restoreScaleFonts(const FontScales& original)
  {
    TRACE;
    QFont f=_xAxis->font();
    f.setPointSizeF(original.values(this).at(0));
    _xAxis->setFont(f);
    f=_yAxis->font();
    f.setPointSizeF(original.values(this).at(1));
    _yAxis->setFont(f);
    f=_content->font();
    f.setPointSizeF(original.values(this).at(2));
    _content->setFont(f);
    _content->setPrintBitmap(original.values(this).at(3));
  }

  /*!
    Draw the object to any device. This exclude the screen. For screen refresh the children repaint their
    area alone. This function is used only for printing and to export the graph as an image.
  */
  void AxisWindow::print(QPainter& p, double dotpercm, int x0Sheet, int y0Sheet, bool mask)
  {
    TRACE;
    // Compute size of both axis
    double dxWidth, dxHeight, dyWidth, dyHeight;
    calcSize(dotpercm, dxWidth, dxHeight, dyWidth, dyHeight, 0, 0, &p);
    int xWidth, xHeight, yWidth, yHeight;
    xWidth=qRound(dxWidth);
    xHeight=qRound(dxHeight);
    yWidth=qRound(dyWidth);
    yHeight=qRound(dyHeight);

    // Use exact size to compute origin (may slightly vary for other media than screen due to font scaling)
    int orX=qRound(printLeft((dxWidth+dyWidth)/dotpercm)*dotpercm)-x0Sheet;
    int orY=qRound(printTop((dxHeight+dyHeight)/dotpercm)*dotpercm)-y0Sheet;
    // Store screen scales and set print scales
    Scale originalScaleX=_content->constScaleX();
    Scale originalScaleY=_content->constScaleY();
    _xAxis->setPrintScale(xWidth, xHeight);
    _yAxis->setPrintScale(yWidth, yHeight);
    // Print GraphContent
    if(_xAxis->orientation()==Axis::North) {
      if(_yAxis->orientation()==Axis::East) {
        p.setWindow(0, 0, xWidth, yHeight);
        p.setViewport(orX + yWidth, orY, xWidth, yHeight);
      } else {
        p.setWindow(0, 0, xWidth, yHeight);
        p.setViewport(orX, orY, xWidth, yHeight);
      }
    } else {
      if(_yAxis->orientation()==Axis::East) {
        p.setWindow(0, 0, xWidth, yHeight);
        p.setViewport(orX + yWidth, orY + xHeight, xWidth, yHeight);
      } else {
        p.setWindow(0, 0, xWidth, yHeight);
        p.setViewport(orX, orY + xHeight, xWidth, yHeight);
      }
    }
    _content->print(p, dotpercm, xWidth, yHeight, mask);
    if(!mask) {
      // Print X Axis
      if(_xAxis->isEnabled()) {
        if(_xAxis->orientation()==Axis::North) {
          if(_yAxis->orientation()==Axis::East) {
            p.setWindow(0, 0, xWidth, xHeight);
            p.setViewport(orX + yWidth, orY + yHeight, xWidth, xHeight);
          } else {
            p.setWindow(0, 0, xWidth, xHeight);
            p.setViewport(orX, orY + yHeight, xWidth, xHeight);
          }
        } else {
          if(_yAxis->orientation()==Axis::East) {
            p.setWindow(0, 0, xWidth, xHeight);
            p.setViewport(orX + yWidth, orY, xWidth, xHeight);
          } else {
            p.setWindow(0, 0, xWidth, xHeight);
            p.setViewport(orX, orY, xWidth, xHeight);
          }
        }
        _xAxis->paint(p, dotpercm, xWidth, xHeight, mask);
      }
      if(_yAxis->isEnabled()) {
        // Print Y Axis
        if(_xAxis->orientation()==Axis::North) {
          if(_yAxis->orientation()==Axis::East) {
            p.setWindow(0, 0, yWidth, yHeight);
            p.setViewport(orX, orY, yWidth, yHeight);
          } else {
            p.setWindow(0, 0, yWidth, yHeight);
            p.setViewport(orX + xWidth - 1, orY, yWidth, yHeight);
          }
        } else {
          if(_yAxis->orientation()==Axis::East) {
            p.setWindow(0, 0, yWidth, yHeight);
            p.setViewport(orX, orY + xHeight, yWidth, yHeight);
          } else {
            p.setWindow(0, 0, yWidth, yHeight);
            p.setViewport(orX + xWidth - 1, orY + xHeight, yWidth, yHeight);
          }
        }
        _yAxis->paint(p, dotpercm, yWidth, yHeight, mask);
      }
    }
    // Restore screen scales
    _content->scaleX()=originalScaleX;
    _content->scaleY()=originalScaleY;
    _content->setProgressEnd(); // Progress label may persist
  }

  /*!
    Calculate size of children for printing

    Input:
    - dotpercm: the target resolution in dot per cm
    - xOffset, y Offset: offset subtracted to total width to reserve space for scrollbars (if any)

    Output:
    - xWidth, xHeight: width and height of xAxis
    - yWidth, yHeight: width and height of yAxis
  */
  void AxisWindow::calcSize(double dotpercm, double& xWidth, double& xHeight,
                            double& yWidth, double& yHeight, double xOffset, double yOffset,
                            QPainter * p) const
  {
    TRACE;
    // Avoid division by zero if size info is null
    if(_xAxis->sizeInfo()==0.0 || _yAxis->sizeInfo()==0.0) return;
    if(_xAxis->isEnabled()) {
      if(_yAxis->isEnabled()) {
        if(_xAxis->sizeType()==Axis::TotalSize) {
          if(_yAxis->sizeType()==Axis::TotalSize) {
            // iterations to calculate the size of axis from total size
            double totalWidth=_xAxis->sizeInfo()*dotpercm-xOffset;
            double totalHeight=_yAxis->sizeInfo()*dotpercm-yOffset;
            calcFromTotalSize(dotpercm, totalWidth, totalHeight, xWidth, yHeight, p);
            xHeight=totalHeight-yHeight;
            yWidth=totalWidth-xWidth;
          } else {
            if(_yAxis->sizeType()==Axis::Scaled) {
              yHeight=(_yAxis->maximum()-_yAxis->minimum())/_yAxis->sizeInfo() * 100.0 * dotpercm - yOffset;
            } else {
              yHeight=_yAxis->sizeInfo()*dotpercm-yOffset;
            }
            yWidth=_yAxis->printThickness(qRound(yHeight), dotpercm, p);
            xWidth=_xAxis->sizeInfo()*dotpercm-yWidth-xOffset;
            xHeight=_xAxis->printThickness(qRound(xWidth), dotpercm, p);
          }
        } else {
          if(_xAxis->sizeType()==Axis::Scaled) {
            xWidth=(_xAxis->maximum()-_xAxis->minimum())/_xAxis->sizeInfo()*100.0*dotpercm-xOffset;
          } else {
            xWidth=_xAxis->sizeInfo() * dotpercm - xOffset;
          }
          xHeight=_xAxis->printThickness(qRound(xWidth), dotpercm, p);
          switch (_yAxis->sizeType()) {
          case Axis::TotalSize:
            yHeight=_yAxis->sizeInfo()*dotpercm-xHeight-yOffset;
            break;
          case Axis::Scaled:
            yHeight=(_yAxis->maximum() - _yAxis->minimum())/_yAxis->sizeInfo()*100.0*dotpercm-yOffset;
            break;
          case Axis::AxisSize:
            yHeight=_yAxis->sizeInfo()*dotpercm-yOffset;
            break;
          }
          yWidth=_yAxis->printThickness(qRound(yHeight), dotpercm, p);
        }
      } else {
        // only xAxis is active
        if(_xAxis->sizeType()==Axis::Scaled) {
          xWidth=(_xAxis->maximum()-_xAxis->minimum())/_xAxis->sizeInfo()*100.0*dotpercm-xOffset;
        } else {
          xWidth=_xAxis->sizeInfo() * dotpercm - xOffset;
        }
        xHeight=_xAxis->printThickness(qRound(xWidth), dotpercm, p);
        yWidth=0;
        switch (_yAxis->sizeType()) {
        case Axis::TotalSize:
          yHeight=_yAxis->sizeInfo()*dotpercm-xHeight-yOffset;
          break;
        case Axis::Scaled:
          yHeight=(_yAxis->maximum()-_yAxis->minimum())/_yAxis->sizeInfo()*100.0*dotpercm-yOffset;
          break;
        case Axis::AxisSize:
          yHeight=_yAxis->sizeInfo()*dotpercm-yOffset;
          break;
        }
      }
    } else {
      if(_yAxis->isEnabled()) {
        // only yAxis is active
        if(_yAxis->sizeType()==Axis::Scaled) {
          yHeight=(_yAxis->maximum()-_yAxis->minimum())/_yAxis->sizeInfo()*100.0*dotpercm-yOffset;
        } else {
          yHeight=_yAxis->sizeInfo()*dotpercm-yOffset;
        }
        yWidth=_yAxis->printThickness(qRound(yHeight), dotpercm, p);
        xHeight=0;
        switch (_xAxis->sizeType()) {
        case Axis::TotalSize:
          xWidth=_xAxis->sizeInfo()*dotpercm-yWidth-xOffset;
          break;
        case Axis::Scaled:
          xWidth=(_xAxis->maximum()-_xAxis->minimum())/_xAxis->sizeInfo()*100.0*dotpercm-xOffset;
          break;
        case Axis::AxisSize:
          xWidth=_xAxis->sizeInfo()*dotpercm-xOffset;
          break;
        }
      } else {
        // Neither xAxis nor yAxis is active
        if(_yAxis->sizeType()==Axis::Scaled) {
          yHeight=(_yAxis->maximum()-_yAxis->minimum())/_yAxis->sizeInfo()*100.0*dotpercm-yOffset;
        } else {
          yHeight=_yAxis->sizeInfo()*dotpercm-yOffset;
        }
        if(_xAxis->sizeType()==Axis::Scaled)
          xWidth=(_xAxis->maximum()-_xAxis->minimum())/_xAxis->sizeInfo()*100.0*dotpercm-xOffset;
        else xWidth=_xAxis->sizeInfo()*dotpercm-xOffset;
        yWidth=0;
        xHeight=0;
      }
    }
  }

  void AxisWindow::setAxisGeometry(int xWidth, int yWidth, int xHeight, int yHeight)
  {
    TRACE;
    int plotWidth=xWidth+yWidth;
    int plotHeight=xHeight+yHeight;
    if(_xSBar->isVisible()) _xSBar->setGeometry(0, plotHeight, plotWidth, 16);
    if(_ySBar->isVisible()) _ySBar->setGeometry(plotWidth, 0, 16, plotHeight);
    if(_xAxis->orientation()==Axis::North) {
      if(_yAxis->orientation()==Axis::East) {
        _xAxis->setGeometry(yWidth , yHeight, xWidth, xHeight);
        _yAxis->setGeometry(0, 0, yWidth, yHeight);
        if(_content) _content->setGeometry(yWidth, 0, xWidth, yHeight);
      } else {
        _xAxis->setGeometry(0, yHeight, xWidth, xHeight);
        _yAxis->setGeometry(xWidth, 0, yWidth, yHeight);
        if(_content) _content->setGeometry(0, 0, xWidth, yHeight);
      }
    } else {
      if(_yAxis->orientation()==Axis::East) {
        _xAxis->setGeometry(yWidth , 0, xWidth, xHeight);
        _yAxis->setGeometry(0, xHeight, yWidth, yHeight);
        if(_content) _content->setGeometry(yWidth, xHeight, xWidth, yHeight);
      } else {
        _xAxis->setGeometry(0, 0, xWidth, xHeight);
        _yAxis->setGeometry(xWidth, xHeight, yWidth, yHeight);
        if(_content) _content->setGeometry(0, xHeight, xWidth, yHeight);
      }
    }
  }

  void AxisWindow::setAxisGeometry(double dotpercm)
  {
    TRACE;
    double xWidth, yHeight;
    if(_xAxis->isEnabled()) {
      if(_yAxis->isEnabled()) {
        if(_xAxis->sizeType()==Axis::TotalSize && _yAxis->sizeType()==Axis::TotalSize) {
          _xAxis->setSizeInfo(_printWidth);
          _yAxis->setSizeInfo(_printHeight);
          return ;
        } else calcFromTotalSize(dotpercm, _printWidth * dotpercm, _printHeight * dotpercm, xWidth, yHeight);
      } else {
        // only xAxis is active
        xWidth=_printWidth * dotpercm;
        yHeight=_printHeight * dotpercm - _xAxis->printThickness(qRound(xWidth), dotpercm);
      }
    } else {
      if(_yAxis->isEnabled()) {
        // only yAxis is active
        yHeight=_printHeight * dotpercm;
        xWidth=_printWidth * dotpercm - _yAxis->printThickness(qRound(yHeight), dotpercm);
      } else {
        // Neither xAxis nor yAxis is active
        yHeight=_printHeight * dotpercm;
        xWidth=_printWidth * dotpercm;
      }
    }
    switch (_xAxis->sizeType()) {
    case Axis::TotalSize:
      _xAxis->setSizeInfo(_printWidth);
      break;
    case Axis::Scaled:
      if(_printWidth==300.0) { // Widget was found too big, change the scale rather thant the axis limits
        _xAxis->setSizeInfo((_xAxis->maximum()-_xAxis->minimum() )/xWidth*(100.0*dotpercm) );
      } else {
        _xAxis->setMaximum(_xAxis->minimum()+xWidth/(100.0*dotpercm)*_xAxis->sizeInfo());
      }
      break;
    case Axis::AxisSize:
      _xAxis->setSizeInfo(xWidth/dotpercm);
      break;
    }
    switch (_yAxis->sizeType()) {
    case Axis::TotalSize:
      _yAxis->setSizeInfo(_printHeight);
      break;
    case Axis::Scaled:
      if(_printHeight==300.0) { // Widget was found too big, change the scale rather thant the axis limits
        _yAxis->setSizeInfo((_yAxis->maximum()-_yAxis->minimum())/yHeight*(100.0*dotpercm));
      } else {
        _yAxis->setMinimum(_yAxis->maximum()-yHeight/(100.0*dotpercm)*_yAxis->sizeInfo());
      }
      break;
    case Axis::AxisSize:
      _yAxis->setSizeInfo(yHeight/dotpercm);
      break;
    }
  }

  /*!
    Calculate size of children for diplay on screen when screen size is not linked to the print size
  */
  void AxisWindow::calcScreenSize(double dotpercm, int& xWidth, int& yHeight, int xOffset, int yOffset)
  {
    TRACE;
    if(_xAxis->isEnabled()) {
      if(_yAxis->isEnabled()) {
        double dxWidth=xWidth;
        double dyHeight=yHeight;
        calcFromTotalSize(dotpercm, width()-xOffset, height()-yOffset, dxWidth, dyHeight);
        xWidth=qRound(dxWidth);
        yHeight=qRound(dyHeight);
      } else {
        // only xAxis is active
        xWidth=width()-xOffset;
        yHeight=height()-yOffset-_xAxis->printThickness(xWidth, dotpercm);
      }
    } else {
      if(_yAxis->isEnabled()) {
        // only yAxis is active
        yHeight=height()-yOffset;
        xWidth=width()-xOffset-_yAxis->printThickness(yHeight, dotpercm);
      } else {
        // Neither xAxis nor yAxis is active
        yHeight=height()-yOffset;
        xWidth=width()-xOffset;
      }
    }
    if(_xAxis->sizeType()==Axis::Scaled) {
      bool zoomed=_xAxis->isZoomed();
      if(_printWidth==300.0) { // Widget was found too big, change the scale rather thant the axis limits
        _xAxis->setSizeInfo((_xAxis->maximum()-_xAxis->minimum())/xWidth*(100.0*dotpercm));
      } else {
        _xAxis->setMaximum(_xAxis->minimum()+xWidth/(100.0*dotpercm)*_xAxis->sizeInfo());
      }
      _xAxis->setVisibleRange(zoomed);
    }
    if(_yAxis->sizeType()==Axis::Scaled) {
      bool zoomed=_yAxis->isZoomed();
      if(_printHeight==300.0) { // Widget was found too big, change the scale rather thant the axis limits
        _yAxis->setSizeInfo((_yAxis->maximum()-_yAxis->minimum())/yHeight*(100.0*dotpercm));
      } else {
        _yAxis->setMinimum(_yAxis->maximum()-yHeight/(100.0*dotpercm)*_yAxis->sizeInfo());
      }
      _yAxis->setVisibleRange(zoomed);
    }
  }

  void AxisWindow::calcFromTotalSize(double dotpercm, double totalWidth,
                                     double totalHeight, double& xWidth, double& yHeight,
                                     QPainter * p) const
  {
    TRACE;
    if(totalWidth<=0.0 || totalHeight<=0.0) {
      return;
    }
    double xWidthNew=totalWidth;
    do {
      xWidth=xWidthNew;
      yHeight=totalHeight-_xAxis->printThickness(qRound(xWidth), dotpercm, p);
      if(yHeight<10.0) {
        xWidth=10.0;
        yHeight=10.0;
        return;
      }
      xWidthNew=totalWidth-_yAxis->printThickness(qRound(yHeight), dotpercm, p);
      if(xWidthNew<10.0) {
        xWidth=10.0;
        yHeight=10.0;
        return;
      }
    } while(xWidth!=xWidthNew);
  }

  /*!
    Automatically adjust the scale for maps

    Set the scale so that the given limits will fit in the current widget size. Limits are usually given in metres
  */
  void AxisWindow::setMapScale(double rx1, double ry1, double rx2, double ry2)
  {
    TRACE;
    if(_xAxis->sizeType()==Axis::Scaled) {
      double scaleX=100.0 * (rx2 - rx1)/(_content->width()/SciFigsGlobal::screenResolution());
      if(_yAxis->sizeType()==Axis::Scaled) {
        double scaleY=100.0 * (ry2 - ry1)/(_content->height()/SciFigsGlobal::screenResolution());
        if(scaleY > scaleX) scaleX=scaleY;
        if(fabs(_yAxis->sizeInfo()-scaleX)>0.1*scaleX) {
          scaleX=commonScale(scaleX);
        } else {
          scaleX=_yAxis->sizeInfo();
        }
        _yAxis->setSizeInfo(scaleX);
      } else {
        if(fabs(_xAxis->sizeInfo()-scaleX)>0.1*scaleX) {
          scaleX=commonScale(scaleX);
        } else {
          scaleX=_xAxis->sizeInfo();
        }
      }
      _xAxis->setSizeInfo(scaleX);
    } else if(_yAxis->sizeType()==Axis::Scaled) {
      double scaleY=100 * (ry2 - ry1)/(_content->height()/SciFigsGlobal::screenResolution());
      if(fabs(_yAxis->sizeInfo()-scaleY)>0.1*scaleY) {
        scaleY=commonScale(scaleY);
      } else {
        scaleY=_yAxis->sizeInfo();
      }
      _yAxis->setSizeInfo(scaleY);
    }
    bool isXZoomed=_xAxis->isZoomed();
    bool isYZoomed=_yAxis->isZoomed();
    _xAxis->setMinimum(rx1);
    _xAxis->setMaximum(rx2);
    _yAxis->setMinimum(ry1);
    _yAxis->setMaximum(ry2);
    _xAxis->setVisibleRange(isXZoomed);
    _yAxis->setVisibleRange(isYZoomed);
    updateExternalGeometry();
  }

  double AxisWindow::commonScale(double startAt)
  {
    TRACE;
  #define N_BASESCALES 13
    static const double baseScales[ N_BASESCALES ]={
          1, 1.25, 1.5, 1.75, 2.0, 2.5, 4.0, 5.0, 6.0, 7.5, 8.0, 9.0, 10.0
        };
    // The true scale= 1.0/ (baseScaleFactor*baseScales[ibaseScale])
    double baseScaleFactor=pow(10.0, floor(log10( startAt) ));
    int ibaseScale=0;
    while(baseScaleFactor * baseScales[ ibaseScale ] < startAt &&
            ibaseScale < N_BASESCALES) ibaseScale++;
    return baseScaleFactor*baseScales[ ibaseScale ];
  }

  /*!
    Align horizontal scales of two graphics

  */
  void AxisWindow::alignHScales(AxisWindow * ref, double at)
  {
    TRACE;
    QPoint pRef(ref->_content->options().xr2s(at), 0);
    pRef=ref->_xAxis->mapToGlobal(pRef);
    QPoint pThis(_content->options().xr2s(at), 0);
    pThis=_xAxis->mapToGlobal(pThis);
    _printAnchor.setX(_printAnchor.x()-(pThis.x()-pRef.x())/SciFigsGlobal::screenResolution());
    emit positionChanged();
  }

  /*!
    Align vertical scales of two graphics

  */
  void AxisWindow::alignVScales(AxisWindow * ref, double at)
  {
    TRACE;
    QPoint pRef(0, ref->_content->options().yr2s(at) );
    pRef=ref->_xAxis->mapToGlobal(pRef);
    QPoint pThis(0,  _content->options().yr2s(at) );
    pThis=_yAxis->mapToGlobal(pThis);
    _printAnchor.setY(_printAnchor.y()-(pThis.y()-pRef.y())/SciFigsGlobal::screenResolution());
    emit positionChanged();
  }

  bool AxisWindow::xml_polish(XML_POLISH_ARGS)
  {
    TRACE;
    _xAxis->setVisibleRange(_xAxis->minimum(), _xAxis->maximum());
    _yAxis->setVisibleRange(_yAxis->minimum(), _yAxis->maximum());
    deepUpdate();
    updateExternalGeometry();
    GraphicObject::xml_polish(context);
    return true;
  }

  void AxisWindow::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
  {
    TRACE;
    static const QString key("orientation"), valueVertical("Vertical"), valueHorizontal("Horizontal");
    GraphicObject::xml_writeChildren(s, context);
    XMLSaveAttributes att;
    QString& value=att.add(key);
    value=valueHorizontal;
    _xAxis->xml_save(s, context, att);
    value=valueVertical;
    _yAxis->xml_save(s, context, att);
    graphContent()->xml_save(s, context);
  }

  XMLMember AxisWindow::xml_member(XML_MEMBER_ARGS)
  {
    TRACE;
    if(tag=="Axis") {
      static const QString tmp("orientation");
      XMLRestoreAttributeIterator it=attributes.find(tmp);
      if(it!=attributes.end()) {
        if(it.value()=="Horizontal")
          return XMLMember(_xAxis);
        else if(it.value()=="Vertical")
          return XMLMember(_yAxis);
        else {
          App::log(tr("Bad axis orientation: %1\n").arg(it.value().toString()));
          return XMLMember(XMLMember::Unknown);
        }
      } else {
        App::log(tr("Attribute orientation not found\n"));
        return XMLMember(XMLMember::Unknown);
      }
    } else if(tag=="GraphContent") {
      // Lock any redraw of this graph
      graphContent()->delayPainting();
      return XMLMember(graphContent());
    }
    return GraphicObject::xml_member(tag, attributes, context);
  }

  uint AxisWindow::categoryXAxis=PropertyProxy::uniqueId();
  uint AxisWindow::categoryYAxis=PropertyProxy::uniqueId();

  /*!

  */
  void AxisWindow::addProperties(PropertyProxy * pp)
  {
    TRACE;
    GraphicObject::addProperties(pp);
    if(!pp->setCurrentCategory(categoryXAxis)) {
      pp->addCategory(categoryXAxis, tr("X Axis"), QIcon(":AxisX.png"));
    }
    xAxis()->addProperties(pp);
    if(!pp->setCurrentCategory(categoryYAxis)) {
      pp->addCategory(categoryYAxis, tr("Y Axis"), QIcon(":AxisY.png"));
    }
    yAxis()->addProperties(pp);
    _content->addProperties(pp);
  }

  void AxisWindow::removeProperties(PropertyProxy * pp)
  {
    TRACE;
    GraphicObject::removeProperties(pp);
    if(pp->setCurrentCategory(categoryXAxis) ) {
      xAxis()->removeProperties(pp);
    }
    if(pp->setCurrentCategory(categoryYAxis) ) {
      yAxis()->removeProperties(pp);
    }
    _content->removeProperties(pp);
  }

  /*!
    Swap axis pointers (used by ColorMapWidget to change orientation)
  */
  void AxisWindow::swapAxes()
  {
    Axis * tmp=_yAxis;
    _yAxis=_xAxis;
    _xAxis=tmp;
    _content->swapAxes();
  }

  void AxisWindow::setGeometry(double x, double y, double dx, double dy, Axis::SizeType type)
  {
    TRACE;
    _xAxis->setSizeType(type);
    _xAxis->setSizeInfo(dx);
    setPrintXAnchor(x);
    _yAxis->setSizeType(type);
    _yAxis->setSizeInfo(dy);
    setPrintYAnchor(y);
    updateExternalGeometry();
    updateGeometry();
  }

} // namespace SciFigs
