/***************************************************************************
**
**  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-18
**  Copyright: 2002-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "GraphContentFormatProperties.h"
#include "GraphContentLayerProperties.h"
#include "GraphContentLayer.h"
#include "GraphContent.h"
#include "GraphicSheet.h"
#include "LayerPainter.h"
#include "AxisWindow.h"
#include "CoordTip.h"
#include "XMLSciFigs.h"
#include "MouseTracking.h"
#include "GraphContentLayerFactory.h"
#include "LayerMouseTracking.h"
#include "LayerLocker.h"

namespace SciFigs {

//#define GRAPHCONTENT_PAINT_CHRONO

/*!
  \class GraphContent GraphContent.h
  \brief GraphContent is the plot area of a AxisWindow.

  GraphContent manages the X and Y scales to transform double precision coordinates
  into integer screen coordinates. The graph content is redrawn by the children
  which must be of type GraphContentLayer. It is done only when strictly necessary
  (forced by deepUpdate() or resizeEvent()). The resulting bitmap is saved and transfered to
  the screen on all refresh events.

  The following types of layers are implemented in SciFigs (there are all deriving GraphContentLayer):
  \li CircleViewer plot colored circles
  \li LineLayer plot lines with symbols (dynamic adding and removing of points)
  \li GridPlot base class for all layers that draw colored grids
  \li GridViewer plot a 2D regular grid
  \li ImageLayer plot a scaled bitmap
  \li IrregularGrid2DPlot plot a 2D grid whose cells have variable widths and heigths
  \li ParallelBands plot infinite parallel bands (along X or Y)
  \li PolarGridPlot draw polar colored grids
  \li XYColorLines draw polylines of color (static and optimized for large number of curve)
  \li XYColorPlot draw dots of color (static and optimized for large number of points)
  \li XYNamePlot same as LineLayer with names at each point
  \li XYPlot draw black dots (static and optimized for large number of points)

*/

const QString GraphContent::xmlGraphContentTag="GraphContent";
CoordTip * GraphContent::_coordTip=nullptr;
QCursor * GraphContent::_defaultCursor=nullptr;
LayerPainter * GraphContent::_layerPainter=nullptr;

void GraphContent::init()
{
  TRACE;
  _coordTip= new CoordTip;
  _defaultCursor=new QCursor(QPixmap(":cross.png"), 9, 9);
  // Needed to pass LayerImages as an argument of a slot/signal
  qRegisterMetaType<LayerImages>("LayerImages");
  _layerPainter=new LayerPainter;
}

void GraphContent::clear()
{
  TRACE;
  delete _coordTip;
  _coordTip=nullptr;
  delete _defaultCursor;
  _defaultCursor=nullptr;
  delete _layerPainter;
  _layerPainter=nullptr;
}

GraphContent::GraphContent(AxisWindow * parent)
  : QWidget(parent)
{
  TRACE;
  ::setWidgetColor(this, Qt::white);
  /*
    Avoid erase before painting
  */
  setAttribute(Qt::WA_OpaquePaintEvent);

  addActions();

  _paintDepth=0;
  _mouseTracking=nullptr;
  setFocusPolicy(Qt::ClickFocus);
  connect(this,SIGNAL(destroyed()), _coordTip,SLOT(hideTip()));
  setDefaultCursor();
  _mouseMoveDelay.setSingleShot(true);
  _mouseMoveDelay.setInterval(50);
  connect(&_mouseMoveDelay, SIGNAL(timeout()), this, SLOT(delayedMouseMoveEvent()));
  _progress=new PaintProgress(this);
  connect(_progress, SIGNAL(repaint()), this, SLOT(paintProgress()), Qt::QueuedConnection);

  _paintEventTiming.start();
  connect(&_paintEventDelay, SIGNAL(timeout()), this, SLOT(update()));
  _paintEventDelay.setInterval(200);
  _paintEventDelay.setSingleShot(true);
}

GraphContent::~GraphContent()
{
  TRACE;
  // Clear all painting requests
  _layerPainter->clear(this);
  _layerPainter->terminate(this);
  QList<GraphContentLayer *>::iterator it;
  for(it=_layers.begin();it!=_layers.end();++it) delete *it;
  delete _mouseTracking;
}


/*!
  \fn GraphContent::setGridLines(bool gl)
  Show or hide grid lines
*/

/*!
  \fn GraphContent::setTransparentMask(bool tm)
  Use a mask when printing an image made of non blank pixels
*/

/*!
  \fn GraphContent::setPrintLineWeight(double lw)
  Line weight for printing (in mm)
*/

/*!
  \fn GraphContent::setPrintBitmap(bool p)
  True to print in a bitmap rather than directly. Effective only for vectorial ouptut (ps, pdf,..)
*/

void GraphContent::setDefaultCursor()
{
  setCursor( *_defaultCursor);
}

void GraphContent::polish()
{
  TRACE;
  setMouseTracking(true);
}

/*!
  Called from GraphContentLayer's constructor
*/
void GraphContent::appendLayer(GraphContentLayer * newlayer)
{
  TRACE;
  newlayer->setParent(this);
  QList<GraphContentLayer *>::iterator it;
  for(it=_layers.begin();it!=_layers.end();++it) {
    ASSERT(*it!=newlayer);
  }
  QString layerName("layer%1");
  newlayer->setObjectName(layerName.arg(_layers.count()));
  _layers.append(newlayer); // We can add layers while painting because painter caches the list of layers
  _paintDepth=0;
}

/*!
  Called from GraphContentLayer's constructor
*/
void GraphContent::prependLayer(GraphContentLayer * newlayer)
{
  TRACE;
  newlayer->setParent(this);
  QList<GraphContentLayer *>::iterator it;
  for(it=_layers.begin(); it!=_layers.end(); ++it) {
    ASSERT(*it!=newlayer);
  }
  QString layerName("layer%1");
  newlayer->setObjectName(layerName.arg(_layers.count()));
  _layers.prepend(newlayer); // We can add layers while painting because painter caches the list of layers
  _paintDepth=0;
}

/*!
  Remove \a layer from layer stack and delete it.
*/
void GraphContent::removeLayer(GraphContentLayer * layer)
{
  TRACE;
  ASSERT(layer->isRemovable());
  layer->lockDelayPainting(); // Force termination of all paint requests and wait for layer unlock
  _layers.removeAt(indexOf(layer));
  _layerImages.remove(layer);
  layer->unlock();
  PropertyProxy * pp=graph()->proxy();
  if(pp) {
    if(pp->setCurrentCategory(_categoryLayers)) {
      layer->removeProperties(pp);
    }
  }
  delete layer;
  if(_layers.isEmpty()) {
#ifdef GRAPHCONTENT_PIXMAP
    _pixmap=QPixmap();
#else
    _pixmap=QImage();
#endif
    update();
  } else {
    deepUpdate();
  }
}

void GraphContent::moveLayerUp(GraphContentLayer * layer)
{
  TRACE;
  int i=indexOf(layer);
  ASSERT(i>-1);
  if(i<layerCount()-1) {
    layer->lockDelayPainting(); // Force termination of all paint requests and wait for layer unlock
#if(QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
    _layers.swapItemsAt(i, i+1);
#else
    _layers.swap(i, i+1);
#endif
    layer->unlock();
    deepUpdate();
  }
}

void GraphContent::moveLayerDown(GraphContentLayer * layer)
{
  TRACE;
  int i=indexOf(layer);
  if(i>0) {
    layer->lockDelayPainting(); // Force termination of all paint requests and wait for layer unlock
#if(QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
    _layers.swapItemsAt(i-1, i);
#else
    _layers.swap(i-1, i);
#endif
    layer->unlock();
    deepUpdate();
  }
}

/*!
  Returns the first layer of type \a tagName and named \a layerName. If \a layerName
  is empty the layer name is ignored. Search is started at \a startIndex.
*/
GraphContentLayer * GraphContent::findLayer(QString tagName, QString layerName, int startIndex)
{
  TRACE;
  int nLayers=_layers.count();
  for(int i=startIndex; i<nLayers; i++) {
    GraphContentLayer * layer=_layers.at(i);
    if(layer->xml_tagName()==tagName &&
       (layerName.isEmpty() || layer->objectName()==layerName)) {
      return layer;
    }
  }
  return nullptr;
}

void GraphContent::resizeEvent(QResizeEvent *)
{
  TRACE;
  if(!graph()->xAxis()->isEnabled()) _options.scaleX().setHorizontalTransformation(width());
  if(!graph()->yAxis()->isEnabled()) _options.scaleY().setVerticalTransformation(height());
}

void GraphContent::resizePixmap()
{
  TRACE;
  if(_pixmap.isNull()) {
#ifdef GRAPHCONTENT_PIXMAP
    _pixmap=QPixmap(size());
#else
    _pixmap=QImage(size(), QImage::Format_ARGB32_Premultiplied);
#endif
    QPainter p;
    p.begin(&_pixmap);
    p.fillRect(0, 0, width(), height(), Qt::white);
    p.end();
    _layerImages.clear();
  } else {
#ifdef GRAPHCONTENT_PAINT_CHRONO
    QTime chrono;
    chrono.start();
#endif
    if(graph()->xAxis()->sizeType()!=Axis::Scaled &&
       graph()->yAxis()->sizeType()!=Axis::Scaled) {
      _pixmap=_pixmap.scaled(size(), Qt::IgnoreAspectRatio, Qt::FastTransformation);
    } else {
      // For scaled axis, avoid uggly resizing
      QImage tmp(size(), QImage::Format_ARGB32_Premultiplied);
      QPainter p;
      p.begin(&tmp);
      p.eraseRect(QRectF(QPointF(0, 0), size()));
      p.drawImage(0, 0, _pixmap);
      p.end();
      _pixmap=tmp;
    }
    LayerImages::Iterator it;
    for(it=_layerImages.begin();it!=_layerImages.end();++it) {
      it.value()=QImage(size(), QImage::Format_ARGB32_Premultiplied);
    }
#ifdef GRAPHCONTENT_PAINT_CHRONO
    printf("Resize %i\n",chrono.elapsed());
#endif
    _paintDepth=0;
  }
}

void GraphContent::setPixmap(QImage image, LayerImages layerImages)
{
  if(_paintEventDelay.isActive()) {
    _paintEventDelay.stop();
  }
  if(image.size()==_pixmap.size()) {
#ifdef GRAPHCONTENT_PAINT_CHRONO
    QTime chrono;
    chrono.start();
#endif
#ifdef GRAPHCONTENT_PIXMAP
    _pixmap=QPixmap::fromImage(image, Qt::ColorOnly | Qt::ThresholdDither | Qt::OrderedAlphaDither);
#else
    _pixmap=image;
#endif
    _layerImages=layerImages;
    _progress->setActive(false);
    // effectively commit to screen with a delay or not
    if(_paintEventTiming.elapsed()<100) {
      _paintEventDelay.start();
    } else {
      update();
    }
    //deepUpdate(); // debug, infinite loop over refreshments
#ifdef GRAPHCONTENT_PAINT_CHRONO
    printf("setPixmap %i\n",chrono.elapsed());
#endif
  }
}

/*!
  Terminate all painting requests about this plot and generate a new painting request
  with the same depth. This is used internally by GraphContentLayer::lockDelayPainting().
*/
void GraphContent::delayPainting()
{
  int depth=_layerPainter->terminate(this);
  // Post a new paint request if something terminated
  if(depth>=0) deepUpdate(depth);
}

void GraphContent::paintProgress()
{
  _progress->setActive(true);
  update(_progress->boundingRect());
}

void GraphContent::setProgressEnd()
{
  _progress->setActive(false);
  update(_progress->boundingRect());
}

void GraphContent::paintEvent(QPaintEvent *e)
{
  TRACE;
  // Stretch current pixmaps if necessary: highest time consumption
  if(width()!=_pixmap.width() || height()!=_pixmap.height()) {
    resizePixmap();
  }
  if(_paintDepth<layerCount()) {
    LayerPainterRequest * request=new LayerPainterRequest;
    request->setGraphContent(this);
    request->setLayers(_layers);
    request->setSize(size());
    request->setOptions(_options);
    request->setLayerImages(_layerImages);
    request->setDepth(_paintDepth);
    connect(request, SIGNAL(finished(QImage, LayerImages)),
            this, SLOT(setPixmap(QImage, LayerImages)), Qt::QueuedConnection);
    _layerPainter->paint(request);
    _paintDepth=layerCount();
  }
#ifdef GRAPHCONTENT_PAINT_CHRONO
  QTime chrono;
  chrono.start();
#endif
  QPainter p;
  p.begin(this);
  if(!_pixmap.isNull()) {
    QRect r=e->rect();
#ifdef GRAPHCONTENT_PIXMAP
    p.drawPixmap(r.left(),r.top(), _pixmap, r.left(),r.top(),r.width(),r.height());
#else
    p.drawImage(r.left(),r.top(), _pixmap, r.left(),r.top(),r.width(),r.height());
#endif
  }
  // Add mouse rectange if necessary
  if(_mouseTracking &&
      _mouseTracking->rectangle() &&
      _mouseTracking->isRectangleVisible()) {
    p.setBrush(_mouseTracking->brush());
    p.setPen(_mouseTracking->pen());
    p.drawRect(*_mouseTracking->rectangle());
  }
  if(_progress->isActive()) {
    _progress->paint(p, width(), height());
  }
  p.end();
  _paintEventTiming.restart();
#ifdef GRAPHCONTENT_PAINT_CHRONO
  printf("Draw %i\n",chrono.elapsed());
#endif
}

void GraphContent::paintMask(QPainter& p)
{
  TRACE;
  if(transparentMask())
#ifdef GRAPHCONTENT_PIXMAP
    p.drawPixmap(x(), y(), _pixmap.createHeuristicMask());
#else
    p.drawImage(x(), y(), _pixmap.createHeuristicMask());
#endif
  else
    p.fillRect(x(),y(),width(), height(), Qt::color1);
}

/*!
  \a fromLayer can vary from 0 to the index of the last layer. 0 to force a complete redraw.
*/
void GraphContent::deepUpdate(int fromLayer)
{
  TRACE;
  if(fromLayer<_layers.count() && fromLayer>=0) {
    if(fromLayer<_paintDepth) _paintDepth=fromLayer;
  }
  update();
}

void GraphContent::print(QPainter& p, double dotpercm, int w, int h, bool mask)
{
  TRACE;
  if(w<=0 || h<=0) {
    return;
  }
  if(mask && !transparentMask()) {
    p.fillRect(0, 0, w, h, Qt::color1);
    return;
  }
  p.save();
  LayerPainterRequest lp;
  lp.setGraphContent(this);
  lp.setSize(w, h);
  lp.setOptions(_options);
  int nLayers=_layers.count();
  for(int i=0;i<nLayers;i++) {
    _layers.at(i)->paintText(lp, dotpercm);
  }
  if(printBitmap()) { // Depends upon media support: for bitmap, printBitmap is force to true
                      // in scaleFonts().
    QImage * printerImage=new QImage(w,h,QImage::Format_ARGB32_Premultiplied);
    QPainter ppx;
    ppx.begin(printerImage);
    ppx.fillRect(0,0,w,h,Qt::white);
    if (gridLines()) {
      _options.paintGridLines(ppx, dotpercm, w, h);
    }
    for (int i=0;i<nLayers;i++) {
      _layers.at(i)->paint(lp, ppx, dotpercm);
    }
    ppx.end();
    p.drawImage(0,0,*printerImage);
    delete printerImage;
  } else {
    p.fillRect(0,0,w,h,Qt::white);
    if(gridLines()) {
      _options.paintGridLines(p, dotpercm, w, h);
    }
    for (int i=0;i<nLayers;i++) {
      _layers.at(i)->paint(lp, p, dotpercm);
    }
  }
  if(contourWeight()>0) {
    _options.paintContour(p, dotpercm, w, h);
  }
  p.restore();
}

bool GraphContent::event (QEvent * e)
{
  // TRACE deactivated because leave a lot of garbage on the stack... don't know why.
  if(e->type()==QEvent::WindowDeactivate) {
    if(_coordTip) _coordTip->hideTip();
    return true;
  } else return QWidget::event(e);
}

/*!
  Can be called by any layer to request a rectangle defined by the user for any task (selection, zoom rect,...).

  If \a layer is 0, GraphContent actions will be triggered (Zoom and Browse).
  Various layers can rely on the same tracking rectangle.
*/
void GraphContent::beginMouseTracking(const LayerMouseTracking& layer)
{
  TRACE;
  if(!_mouseTracking) {
    _mouseTracking=new MouseTracking;
    emit mouseTrackingBegin();
  }
  if(!_mouseTracking->contains(layer.layer(), layer.id())) {
    _mouseTracking->add(layer);
    if(layer.idleCursor()) {
      setCursor(*layer.idleCursor());
    } else {
      setDefaultCursor();
    }
  }
}

/*!
  Remove \a layer from track rectangle, and remove if if empty
*/
void GraphContent::endMouseTracking(GraphContentLayer * layer, int id)
{
  TRACE;
  if(_mouseTracking) {
    int i=_mouseTracking->indexOf(layer, id);
    if(i>-1) {
      _mouseTracking->remove(i);
      update();
      if(_mouseTracking->isEmpty()) {
        delete _mouseTracking;
        _mouseTracking=nullptr;
        emit mouseTrackingEnd();
        setDefaultCursor();
      } else if(i==0) { // active tracking changed
        const LayerMouseTracking& l=_mouseTracking->activeLayer();
        if(l.idleCursor()) {
          setCursor(*l.idleCursor());
        }
      }
    }
  }
}

/*!
  Return true if current tracking info has layer \a layer
*/
bool GraphContent::isMouseTracking(const GraphContentLayer * layer, int id) const
{
  TRACE;
  if(_mouseTracking) {
    return _mouseTracking->contains(layer, id);
  } else return false;
}

void GraphContent::showProperties()
{
  TRACE;
  graph()->showProperties(_categoryLayers);
}

void GraphContent::mouseDoubleClickEvent(QMouseEvent * e)
{
  if(e->buttons() & Qt::LeftButton) {
    graph()->setActive(e->modifiers());
    showProperties();
  } else {
    e->ignore();
  }
}

/*!
  Forward press event to layers if no mouse tracking active
*/
void GraphContent::mousePressEvent(QMouseEvent * e)
{
  _coordTip->hideTip();
  switch(e->buttons()) {
  case Qt::LeftButton:
    if (_mouseTracking && _mouseTracking->isActive()) {
      if (_mouseTracking->isRectangle()) {
        _mouseTracking->initRectangle(e->pos());
      }
      const LayerMouseTracking& l=_mouseTracking->activeLayer();
      if(l.actionCursor()) {
        setCursor(*l.actionCursor());
      }
      if(l.layer()) {
        if(!l.layer()->mousePressEvent(e, l.id())) {
          return;
        }
      } else {
        return; // Zoom or Browse tracking are exclusive
      }
    }
    // No tracking currently active or active not exclusive, hence distribute to first layer that accepts this event
    for(int i=0; i<_layers.count(); i++) {
      if(!_layers.at(i)->mousePressEvent(e)) {
        return;
      }
    }
    e->ignore();
    break;
  case Qt::RightButton: {
      graph()->setActive(e->modifiers()); // required for properties
      QMenu m;
      m.addActions(actions());
      m.exec(mapToGlobal(e->pos()));
    }
    break;
  }
}

void GraphContent::mouseReleaseEvent(QMouseEvent * e)
{
  TRACE;
  bool exclusive=false;
  if(_mouseTracking && _mouseTracking->isActive()) {
    const LayerMouseTracking& l=_mouseTracking->activeLayer();
    if(_mouseTracking->rectangle()) {
      if(!_mouseTracking->isEmptyRectangle()) {
        QRect& r=*_mouseTracking->rectangle();
        double rx1=_options.xs2r(r.left());
        double ry1=_options.ys2r(r.top());
        double rx2=_options.xs2r(r.right());
        double ry2=_options.ys2r(r.bottom());
        if(rx1>rx2) {
          qSwap(rx1, rx2);
        }
        if(ry1>ry2) {
          qSwap(ry1, ry2);
        }
        _mouseTracking->resetRectangle();
        update();
        if(l.layer()) {
          exclusive=l.layer()->trackRectangle(l.id(), rx1,ry1,rx2,ry2, e->modifiers());
        } else {
          switch(l.id()) {
          case Zoom:
            emit(zoomIn(rx1,ry1,rx2,ry2));
            break;
          default:
            break;
          }
          exclusive=true;
        }
      } else {
        _mouseTracking->resetRectangle();
        update();
      }
    } else { // Give priority to layer with active tracking
      if(l.layer()) {
        exclusive=l.layer()->mouseReleaseEvent(e, l.id());
      } // Currently all tracking modes of graphcontent have a rectangle
    }
    if(l.actionCursor()) {
      if(l.idleCursor()) {
        setCursor(*l.idleCursor());
      } else {
        setDefaultCursor();
      }
    }
  }
  if(exclusive) {
    return;
  }
  // No tracking currently active or active not exclusive, hence distribute to first layer that accepts this event
  for(int i=0;i<_layers.count();i++) {
    if(!_layers.at(i)->mouseReleaseEvent(e)) {
      return;
    }
  }
  e->ignore();
}

void GraphContent::mouseMoveEvent(QMouseEvent * e)
{
  TRACE;
  _mouseMovePoint=e->pos();
  _mouseMoveButtons=e->buttons();
  _mouseMoveModifiers=e->modifiers();
  _coordTip->setEnabled(true);
  if(!_mouseMoveDelay.isActive()) _mouseMoveDelay.start();
  if(!_mouseTracking || !_mouseTracking->isActive()) {
    e->ignore();
  }
}

/*!
  \a e->delta() returns eights of degrees and usual mouses have steps of 15 degrees, hence the value
  returned by \a e->delta() is divided by 120. Positive upwards
*/
void GraphContent::wheelEvent (QWheelEvent * e)
{
  TRACE;
  for(int i=0;i<_layers.count();i++) {
    if(!_layers.at(i)->wheelEvent(e)) return;
  }
  if(e->modifiers() & Qt::ControlModifier) {
    Point2D mousePosReal=_options.s2r(e->pos()); // Must be before any zoom in/out to keep the original real coordinates
                                            // the mouse is currently pointing.
    if(e->delta()<0) {
      emit zoomOut();
    } else {
      zoomIn(e->pos());
    }
    QPoint mp=_options.r2s(mousePosReal);
    if(rect().contains(mp) && QGuiApplication::screens().count()==1) {
      // On multi-screen environments, we must provide the "current" screen to setPos
      //QWidget * w=this;
      //while(!w->windowHandle()) {
      //  w=w->parentWidget();
      //}
      //QCursor::setPos(w->windowHandle()->screen(), mapToGlobal(mp));
      // With Qt 5.5.1, this option does not work, bug to be submitted to qt-project.org
      QCursor::setPos(mapToGlobal(mp));
    }
    e->accept();
  } else if(graph()->xAxis()->isZoomed() && e->modifiers() & Qt::AltModifier) {  // Horizontal scroll
    graph()->setHorizontalLine(graph()->xAxis()->currentLine()-e->delta()/120);
    e->accept();
  } else if(graph()->yAxis()->isZoomed()) {                                         // Vertical scroll
    graph()->setVerticalLine(graph()->yAxis()->currentLine()-e->delta()/120);
    e->accept();
  } else {
    e->ignore();
  }
}

void GraphContent::leaveEvent (QEvent * )
{
  TRACE;
  _coordTip->setEnabled(false);
  emit mouseInside(false);
  if(_mouseTracking && !_mouseTracking->isActive()) {
    _mouseTracking->setActive(true);
    QCursor * c=_mouseTracking->activeLayer().idleCursor();
    if(c) {
      setCursor(*c);
    } else {
      unsetCursor();
    }
    emit mouseTrackingBegin();
  }
}

void GraphContent::enterEvent (QEvent * )
{
  TRACE;
  _coordTip->setEnabled(true);
  emit mouseInside(true);
}

/*!
  The number of mouseMoveEvent per second may be dramatic. Any mouse move forces a repaint to
  occur every time which is quite slow and CPU demanding.
  
  Effective repaint is always delayed by 50 milliseconds.If another event occur less than 50 milliseconds
  after a first one, the current point is modified to be as closed as possible to the current mouse position but
  nothing is processed. At end of the 50 milliseconds, delayedMouseMoveEvent() is called to effectively paint
  the mouse changes on screen.
*/
void GraphContent::delayedMouseMoveEvent ()
{
  TRACE;
  if(!_coordTip) return;
  _coordTip->setMousePos(this,_mouseMovePoint);
  // update other widgets depending on mouse position in graphcontent
  emit mouseMoved(_mouseMovePoint, _mouseMoveButtons, _mouseMoveModifiers);
  if(_mouseTracking && _mouseTracking->isActive()) {
    if(_mouseTracking->rectangle()) {
      QRect& r=*_mouseTracking->rectangle();
      r.setWidth(_mouseMovePoint.x()-r.left());
      r.setHeight(_mouseMovePoint.y()-r.top());
      update();
      const LayerMouseTracking& l=_mouseTracking->activeLayer();
      if(!l.layer()) {
        switch(l.id()) {
        case Browse:
          emit(browse(r));
          break;
        default:
          break;
        }
      }
    }
  }
  for(int i=0;i<_layers.count();i++) {
    _layers.at(i)->mouseMoveEvent(_mouseMovePoint, _mouseMoveButtons, _mouseMoveModifiers);
  }
}

void GraphContent::keyPressEvent (QKeyEvent * e)
{
  TRACE;
  if(e->key()==Qt::Key_Control) {
    if(_mouseTracking && _mouseTracking->isActive()) {
      _mouseTracking->setActive(false);
      setDefaultCursor();
      emit mouseTrackingEnd();
    }
  }
  for (int i=0; i<_layers.count(); i++) {
    if(!_layers.at(i)->keyPressEvent(e)) return;
  }
  QWidget::keyPressEvent(e);
}

void GraphContent::keyReleaseEvent (QKeyEvent * e)
{
  TRACE;
  if(e->key()==Qt::Key_Control) {
    if(_mouseTracking && !_mouseTracking->isActive()) {
      _mouseTracking->setActive(true);
      if(_mouseTracking->activeLayer().idleCursor()) {
        setCursor(*_mouseTracking->activeLayer().idleCursor());
      }
      emit mouseTrackingBegin();
    }
  }
  for (int i=0;i<_layers.count();i++) {
    if(!_layers.at(i)->keyReleaseEvent(e)) return;
  }
  QWidget::keyReleaseEvent(e);
}

/*!
  Set the line weight for contour and for grid lines. Mainly used for compatibility with old files.
  Grid line weight is set to one half of contour weight.
*/
void GraphContent::setLineWeights(double lw)  // kept for compatibility
{
  setContourWeight(lw);
  setGridLineWeight(lw * 0.5);
}

/*!
  Initialize basic context menu
*/
void GraphContent::addActions()
{
  TRACE;
  QAction * a;

  a=new QAction(tr( "&Properties" ), this);
  a->setObjectName( "Properties" );
  a->setStatusTip(tr("Set properties of object"));
  a->setShortcut(tr("Ctrl+Alt+P"));
  a->setShortcutContext(Qt::WidgetShortcut);
  connect(a, SIGNAL(triggered(bool)) , this, SLOT(showProperties()));
  QWidget::addAction(a);

  a=new QAction(this);
  a->setObjectName("ZoomSeparator");
  a->setSeparator(true);
  QWidget::addAction(a);

  a=new QAction(tr("&Zoom"),this);
  a->setObjectName("Zoom");
  a->setShortcut(tr("Ctrl+Alt+Z"));
  a->setShortcutContext(Qt::WidgetShortcut);
  a->setStatusTip(tr("Select rectangle to zoom.")+GraphContentLayer::trackingActionCtrlHelp());
  a->setCheckable(true);
  connect(a, SIGNAL(triggered(bool)) , this, SLOT(zoom()) );
  QWidget::addAction(a);

  a=new QAction(tr("&Zoom in"), this);
  a->setObjectName("ZoomIn");
  a->setShortcut(tr("Ctrl++"));
  a->setShortcutContext(Qt::WidgetShortcut);
  a->setStatusTip(tr("Zoom in by 50%"));
  connect(a, SIGNAL(triggered(bool)) , this, SLOT(zoomIn()) );
  QWidget::addAction(a);

  a=new QAction(tr("&Unzoom"), this);
  a->setObjectName("Unzoom");
  a->setShortcut(tr("Ctrl+-"));
  a->setShortcutContext(Qt::WidgetShortcut);
  a->setStatusTip(tr("Back to last unzoomed view"));
  a->setEnabled(false);
  connect(a, SIGNAL(triggered(bool)) , this, SIGNAL(zoomOut()));
  QWidget::addAction(a);

  a=new QAction(tr("&Browse"),this);
  a->setObjectName("Browse");
  a->setStatusTip(tr("Browse a zoomed view.")+GraphContentLayer::trackingActionCtrlHelp());
  a->setCheckable(true);
  a->setEnabled(false);
  connect(a, SIGNAL(triggered(bool)) , this, SLOT(browse()));
  QWidget::addAction(a);

  a=new QAction(this);
  a->setObjectName("LayersSeparator");
  a->setSeparator(true);
  QWidget::addAction(a);

  a=new QAction(tr("&Copy layers"), this);
  a->setObjectName("CopyLayers");
  a->setStatusTip(tr("Copy all layers to the clipboard"));
  connect(a, SIGNAL(triggered(bool)) , this, SLOT(copyLayers()));
  QWidget::addAction(a);

  a=new QAction(tr("&Paste layers"), this);
  a->setObjectName("PasteLayers");
  a->setStatusTip(tr("Add layers from the clipboard"));
  connect(a, SIGNAL(triggered(bool)) , this, SLOT(pasteLayers()));
  QWidget::addAction(a);

  a=new QAction(tr("&Save layers"), this);
  a->setObjectName("SaveLayers");
      a->setStatusTip(tr("Save all layers to a .layer file"));
  connect(a, SIGNAL(triggered(bool)) , this, SLOT(saveLayers()));
  QWidget::addAction(a);

  a=new QAction(tr("&Append layers"), this);
  a->setObjectName("AppendLayers");
  a->setStatusTip(tr("Append layers from a .layer file"));
  connect(a, SIGNAL(triggered(bool)) , this, SLOT(appendLayers()));
  QWidget::addAction(a);

  a=new QAction(tr("&Prepend layers"), this);
  a->setObjectName("PrependLayers");
  a->setStatusTip(tr("Prepend layers from a .layer file"));
  connect(a, SIGNAL(triggered(bool)) , this, SLOT(prependLayers()));
  QWidget::addAction(a);
}

/*!
  Check that action \a actionName is not already added (by objectName).
  If action \a a is already added, it returns action named \a actionName. Else, a new action is created and added.
*/
QAction * GraphContent::addAction(QString actionName)
{
  TRACE;
  QAction * a=action(actionName);
  if(!a) {
    a=new QAction(this);
    a->setObjectName(actionName);
    QWidget::addAction(a);
  }
  return a;
}

/*!
  Return action named actionName
*/
QAction * GraphContent::action(QString actionName)
{
  TRACE;
  QList<QAction *> aList=actions();
  QList<QAction *>::iterator it;
  for(it=aList.begin();it!=aList.end(); ++it) {
    if((*it)->objectName()==actionName) return *it;
  }
  return nullptr;
}

/*!
  Zoom from center \a c.
*/
void GraphContent::zoomIn (const QPoint& c)
{
  TRACE;
  int w=width()/4;
  int h=height()/4;
  int x1=c.x()-w;
  int y1=c.y()-h;
  int x2=c.x()+w;
  int y2=c.y()+h;
  if(x1<0) {
    x2-=x1;
    x1=0;
  } else if(x2>width()) {
    x1-=x2-width();
    x2=width();
  }
  if(y1<0) {
    y2-=y1;
    y1=0;
  } else if(y2>height()) {
    y1-=y2-height();
    y2=height();
  }
  emit(zoomIn(_options.xs2r(x1), _options.ys2r(y1), _options.xs2r(x2), _options.ys2r(y2)));
}

/*!
  Zoom from center of content
*/
void GraphContent::zoomIn()
{
  TRACE;
  double rx1=_options.xs2r(width()/4);
  double ry1=_options.ys2r(3*height()/4);
  double rx2=_options.xs2r(3*width()/4);
  double ry2=_options.ys2r(height()/4);
  emit(zoomIn(rx1,ry1,rx2,ry2));
}

void GraphContent::zoom()
{
  TRACE;
  QAction * a=action("Zoom");
  if(a) {
    if(isMouseTracking(nullptr, Zoom)) {
      a->setChecked(false);
      endMouseTracking(nullptr, Zoom);
    } else {
      a->setChecked(true);
      LayerMouseTracking mt(nullptr);
      mt.setId(Zoom);
      mt.setIdleCursor(QPixmap(":zoomcursor.png"), 4, 4);
      mt.setRectangle(true);
      mt.showRectangle();
      beginMouseTracking(mt);
    }
  }
}

void GraphContent::browse()
{
  TRACE;
  QAction * a=action("Browse");
  if(a) {
    if(isMouseTracking(nullptr, Browse) || !graph()->isZoomed()) {
      a->setChecked(false);
      endMouseTracking(nullptr, Browse);
    } else {
      a->setChecked(true);
      LayerMouseTracking mt(nullptr);
      mt.setId(Browse);
      mt.setIdleCursor(Qt::OpenHandCursor);
      mt.setActionCursor(Qt::ClosedHandCursor);
      mt.setRectangle(true);
      beginMouseTracking(mt);
    }
  }
}

/*!
  Copy all layers to the clipboard
*/
void GraphContent::copyLayers()
{
  TRACE;
  QClipboard * cb=QApplication::clipboard();
  XMLSciFigs s;
  QByteArray layers=s.saveByteArray(this, XMLSciFigs::Layer);
  QMimeData * mime=new QMimeData;
  mime->setData("XMLSciFigs::Layers", layers);
  cb->setMimeData(mime, QClipboard::Clipboard);
}

/*!
  Paste layers from the clipboard if there are any
*/
void GraphContent::pasteLayers()
{
  TRACE;
  static QString title=tr("Paste layers");
  QClipboard * cb=QApplication::clipboard();
  const QMimeData * mime=cb->mimeData(QClipboard::Clipboard);
  if(mime->hasFormat("XMLSciFigs::Layers")) {
    QByteArray layers=mime->data("XMLSciFigs::Layers");
    XMLErrorReport xmler(XMLErrorReport::Read);
    xmler.setTitle(title);
    XMLSciFigs s;
    xmler.exec(s.restoreByteArray(layers, this, XMLSciFigs::Layer));
    deepUpdate();
  }
}

void GraphContent::saveLayers(QString fileName)
{
  TRACE;
  if(fileName.isEmpty()) {
    fileName=Message::getSaveFileName(tr("Save layers as ..."), tr("Graph layers (*.layer)"));
  }
  if(fileName.length()>0) {
    XMLSciFigs s;
    s.saveFile(fileName, this, XMLSciFigs::Layer);
  }
}

void GraphContent::appendLayers(QString fileName)
{
  TRACE;
  static QString title=tr("Append layers");
  if(fileName.isEmpty()) {
    fileName=Message::getOpenFileName(title, tr("Graph layers (*.layer)"));
  }
  if(fileName.length()>0) {
    XMLErrorReport xmler(XMLErrorReport::Read);
    xmler.setTitle(title);
    xmler.setFileName(fileName);
    XMLSciFigs s;
    s.setLayerMode(XMLSciFigs::AppendLayer);
    xmler.exec(s.restoreFile(fileName, this, XMLSciFigs::Layer));
    deepUpdate();
  }
}

void GraphContent::prependLayers(QString fileName)
{
  TRACE;
  static QString title=tr("Prepend layers");
  if(fileName.isEmpty()) {
    fileName=Message::getOpenFileName(title, tr("Graph layers (*.layer)"));
  }
  if(fileName.length()>0) {
    XMLErrorReport xmler(XMLErrorReport::Read);
    xmler.setTitle(title);
    xmler.setFileName(fileName);
    XMLSciFigs s;
    s.setLayerMode(XMLSciFigs::PrependLayer);
    xmler.exec(s.restoreFile(fileName, this, XMLSciFigs::Layer));
    deepUpdate();
  }
}

Rect GraphContent::boundingRect() const
{
  TRACE;
  Rect r;
  QList<GraphContentLayer *>::const_iterator it=_layers.begin();
  // Find the first visible layer
  while(it!=_layers.end() && (*it)->opacity()==0.0) {
    it++;
  }
  if(it!=_layers.end()) {
    r=(*it)->boundingRect();
    for(it++;it!=_layers.end();++it)  {
      GraphContentLayer * layer=*it;
      if(layer->opacity()>0.0) {
        Rect lr=layer->boundingRect();
        if(!lr.isNull()) {
          r.add(lr);
        }
      }
    }
  }
  return r;
}

void GraphContent::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  QList<GraphContentLayer *>::const_iterator it;
  static const QString key("layer");
  XMLSaveAttributes att;
  QString& value=att.add(key);
  for(it=_layers.begin();it!=_layers.end();++it)  {
    GraphContentLayer * layer=*it;
    if(layer->isSelected()) {
      value=layer->objectName();
      layer->xml_save(s, context, att);
    }
  }
}

void GraphContent::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->makeUp()) {
    qobject_writeProperties(this, this, s, context);
  }
}

XMLMember GraphContent::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  static const QString tmp("layer");
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  XMLRestoreAttributeIterator it=attributes.find(tmp);
  if(it!=attributes.end()) {
    GraphContentLayer * layer;
    if(scifigsContext->data()) {
      GraphContentLayer * layer=GraphContentLayerFactory::instance()->create(tag.toString());
      if(layer) {
        switch(scifigsContext->layerMode()) {
        case XMLSciFigs::AppendLayer:
          appendLayer(layer);
          break;
        case XMLSciFigs::PrependLayer:
          prependLayer(layer);
          break;
        }
        layer->setObjectName(it.value().toString()); // In pure data XML, objectName is not set properly
        layer->removeReference(); // By default layers are not removable except when loaded from page and layer files
        return XMLMember(layer);
      } else {
        App::log(tr("Unknown layer type %1\n").arg(tag.toString()) );
        return XMLMember(XMLMember::Unknown);
      }
    } else {
      layer=findLayer(tag.toString(), it.value().toString());
      if(layer) return XMLMember(layer);
      else {
        layer=findLayer(tag.toString(), QString());
        if(layer) {
          if(Message::warning(MSG_ID,tr("Restore"),
                                   tr("Cannot find a layer of type '%1' and named '%2', "
                                      "but a layer of type '%3' and named '%4' has been found. "
                                      "Do you want restore properties to this layer?").
                                   arg(tag.toString()).arg(it.value().toString()).
                                   arg(tag.toString()).arg(layer->objectName()),
                                   Message::yes(),Message::no(), true)==Message::Answer0) {
            return XMLMember(layer);
          }
          else return XMLMember(XMLMember::Unknown);
        }
        else {
          App::log(tr("Cannot find a layer of type %1\n").arg(tag.toString()) );
          return XMLMember(XMLMember::Unknown);
        }
      }
    }
  } else if(scifigsContext->makeUp()) {
    return qobject_member(this, tag, attributes, context);
  }
  return XMLMember(XMLMember::Unknown);
}

void GraphContent::xml_polishChild(XML_POLISHCHILD_ARGS)
{
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->data()) {
    GraphContentLayer * layer=static_cast<GraphContentLayer *>(child);
    // Add layer dynamically to property editor if necessary
    addProperties(layer);
  }
}

bool GraphContent::xml_polish(XML_POLISH_ARGS)
{
  int n=_layers.count();
  for(int i=0; i<n; i++)  {
    _layers.at(i)->xml_layerPolish(context);
  }
  return true;
}

QString GraphContent::coordinateTipText(const QPoint& mousePos)
{
  TRACE;
  Point2D pReal=_options.s2r(mousePos);
  Point pUser(pReal);
  AxisWindow * w=graph();
  Axis * xAxis=w->xAxis();
  Axis * yAxis=w->yAxis();
  pUser*=Point2D(xAxis->unitFactor(), yAxis->unitFactor());
  QString xStr, yStr;
  switch(xAxis->scaleType()) {
  case Scale::Inversed:
  case Scale::InversedLog:
    pUser.setX(1.0/pUser.x());
    xStr=Number::toString(pUser.x(), xAxis->numberType(), xAxis->numberPrecisionInversedScale()+2);
    break;
  case Scale::Linear:
  case Scale::Log:
    xStr=Number::toString(pUser.x(), xAxis->numberType(), xAxis->numberPrecision()+2);
    break;
  case Scale::RelativeTime:
    xStr=Number::secondsToDuration(pUser.x(), xAxis->numberPrecision()+2);
    break;
  case Scale::AbsoluteTime:
    xStr=xAxis->time(pUser.x()).toString(DateTime::defaultUserFormat,
                                         xAxis->numberPrecision()+2);
    break;
  }
  switch(yAxis->scaleType()) {
  case Scale::Inversed:
  case Scale::InversedLog:
    pUser.setY(1.0/pUser.y());
    yStr=Number::toString(pUser.y(), yAxis->numberType(), yAxis->numberPrecisionInversedScale()+2);
    break;
  case Scale::Linear:
  case Scale::Log:
    yStr=Number::toString(pUser.y(), yAxis->numberType(), yAxis->numberPrecision()+2);
    break;
  case Scale::RelativeTime:
    yStr=Number::secondsToDuration(pUser.y(), yAxis->numberPrecision()+2);
    break;
  case Scale::AbsoluteTime:
    yStr=yAxis->time(pUser.y()).toString(DateTime::defaultUserFormat,
                                         yAxis->numberPrecision()+2);
    break;
  }

  // Set info from the visible top most layer
  QString msg;
  for(int i=_layers.count()-1; i >= 0; i--)  {
    if(_layers.at(i)->opacity()>0.0) {
      msg=_layers.at(i)->coordinateTipInfo(pUser, pReal);
      if(!msg.isEmpty()) {
        break;
      }
    }
  }
  if(msg.isEmpty()) {
    msg=QString("(%1 ; %2)");
  }
  if(msg.contains("%")) {
    msg=msg.arg(xStr);
    if(msg.contains("%")) {
      msg=msg.arg(yStr);
    }
  }
  return msg;
}

uint GraphContent::_categoryGraphic=PropertyProxy::uniqueId();
uint GraphContent::_categoryLayers=PropertyProxy::uniqueId();
uint GraphContent::_tabGraphicFormat=PropertyProxy::uniqueId();
uint GraphContent::_tabGraphicLayers=PropertyProxy::uniqueId();

/*!
  Setup property editor
*/
void GraphContent::addProperties(PropertyProxy * pp)
{
  TRACE;
  if( !pp->setCurrentCategory(_categoryGraphic) ) {
    pp->addCategory(_categoryGraphic, tr("Graphic"), QIcon( ":GraphContent.png"));
  }
  if(pp->setCurrentTab(_tabGraphicFormat)) {
      pp->addReference(this);
  } else {
    GraphContentFormatProperties * w=new GraphContentFormatProperties;
    pp->addTab(_tabGraphicFormat, tr("Format"), w, this);
  }
  if(pp->setCurrentTab(_tabGraphicLayers)) {
    pp->addReference(this);
    GraphContentLayerProperties * w=static_cast<GraphContentLayerProperties *>(pp->currentTabWidget());
    w->addGraph(this);
  } else {
    GraphContentLayerProperties * w=new GraphContentLayerProperties;
    w->addGraph(this);
    pp->addTab(_tabGraphicLayers, tr("Layer stack"), w, this);
  }
  // First check if there are layers with properties
  bool hasLayerProperties=false;
  QList<GraphContentLayer *>::iterator it;
  for(it=_layers.begin();it!=_layers.end();++it) {
    GraphContentLayer * l=*it;
    if(l->hasProperties() && l->isEditable()) {
      hasLayerProperties=true;
      break;
    }
  }
  if(hasLayerProperties) {
    if( !pp->setCurrentCategory(_categoryLayers) ) {
      pp->addCategory(_categoryLayers, tr("Layers"), QIcon( ":GraphContentLayer.png"));
    }
    QList<GraphContentLayer *>::iterator it;
    for(it=_layers.begin();it!=_layers.end();++it) {
      GraphContentLayer * l=*it;
      if(l->isEditable()) l->addProperties(pp);
    }
  }
}

/*!
  Dynamically add properties of \a layer to property editor. Cannot be integrated in addLayer() directly
  because addLayer() is called from contructor of layer object (use of virtual functions).
*/
void GraphContent::addProperties(GraphContentLayer * layer)
{
  AxisWindow * w=graph();
  PropertyProxy * pp=w->proxy();
  if(pp) {
    if(layer->hasProperties() && w->isSelected() && layer->isEditable()) {
      pp->saveStates();
      if(!pp->setCurrentCategory(_categoryLayers) ) {
        pp->addCategory(_categoryLayers, tr("Layers"), QIcon( ":GraphContentLayer.png"));
      }
      layer->addProperties(pp);
      pp->restoreStates();
      pp->setValues();
    }
    if(pp->setCurrentCategory(_categoryGraphic) ) {
      if(pp->setCurrentCategory(_tabGraphicLayers) ) {
        GraphContentLayerProperties * w=static_cast<GraphContentLayerProperties *>(pp->currentTabWidget());
        w->reset();
      }
    }
  }
}

/*!
  Cleanup property editor
*/
void GraphContent::removeProperties(PropertyProxy * pp)
{
  TRACE;
  if(pp->setCurrentCategory(_categoryGraphic)) {
    pp->removeTab(_tabGraphicFormat, this);
    if(pp->setCurrentTab(_tabGraphicLayers)) {
      GraphContentLayerProperties * w=static_cast<GraphContentLayerProperties *>(pp->currentTabWidget());
      w->removeGraph(this);
      pp->removeTab(_tabGraphicLayers, this);
    }
  }

  if(pp->setCurrentCategory(_categoryLayers)) {
    QList<GraphContentLayer *>::iterator it;
    for(it=_layers.begin(); it!=_layers.end(); ++it) {
      (*it)->removeProperties(pp);
    }
  }
}

void GraphContent::properties(PropertyWidget * w) const
{
  TRACE;

  if(w->id()==_tabGraphicFormat) {
    AxisWindow * g=graph();
    w->setValue(GraphContentFormatProperties::HorizontalAxis, g->xAxis()->isEnabled());
    w->setValue(GraphContentFormatProperties::VerticalAxis, g->yAxis()->isEnabled());
    w->setValue(GraphContentFormatProperties::GridLines, gridLines());
    w->setValue(GraphContentFormatProperties::GridLineColor, gridLineColor());
    w->setValue(GraphContentFormatProperties::TransparentMask, transparentMask());
    w->setValue(GraphContentFormatProperties::PrintBitmap, printBitmap());
    w->setValue(GraphContentFormatProperties::ContourWeight, contourWeight());
    w->setValue(GraphContentFormatProperties::GridLineWeight, gridLineWeight());
  }
}

/*!
  Set value \a val to property \a id, after a user action in property editor
*/
void GraphContent::setProperty(uint wid, int pid, QVariant val)
{
  TRACE;
  if(wid==_tabGraphicFormat) {
    AxisWindow * p=graph();
    switch(pid) {
    case GraphContentFormatProperties::HorizontalAxis:
      p->xAxis()->setEnabled(val.toBool());
      if(p->xAxis()->isEnabled()) p->xAxis()->show(); else p->xAxis()->hide();
      p->updateExternalGeometry();
      p->deepUpdate();
      break;
    case GraphContentFormatProperties::VerticalAxis:
      p->yAxis()->setEnabled(val.toBool());
      if(p->yAxis()->isEnabled()) p->yAxis()->show(); else p->yAxis()->hide();
      p->updateExternalGeometry();
      p->deepUpdate();
      break;
    case GraphContentFormatProperties::GridLines:
      setGridLines(val.toBool());
      p->deepUpdate();
      break;
    case GraphContentFormatProperties::GridLineColor:
      setGridLineColor(val.value<QColor>());
      p->deepUpdate();
      break;
    case GraphContentFormatProperties::TransparentMask:
      setTransparentMask(val.toBool());
      break;
    case GraphContentFormatProperties::PrintBitmap:
      setPrintBitmap(val.toBool());
      break;
    case GraphContentFormatProperties::ContourWeight:
      setContourWeight(val.toDouble());
      p->deepUpdate();
      break;
    case GraphContentFormatProperties::GridLineWeight:
      setGridLineWeight(val.toDouble());
      p->deepUpdate();
      break;
    default:
      break;
    }
  }
}


} // namespace SciFigs
