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

#ifndef NO_SVG
#include <QtSvg>
#endif
#if(QT_VERSION > QT_VERSION_CHECK(5, 0, 0))
#include <QtPrintSupport>
#endif

#include "GraphicSheet.h"
#include "SelectionWidget.h"
#include "GraphicObject.h"
#include "GraphicObjectGroup.h"
#include "OrderTip.h"
#include "GraphicObjectIdProperties.h"
#include "GraphicObjectGeometryProperties.h"
#include "GraphicObjectPrintProperties.h"
#include "XMLSciFigs.h"
#include "MakeUpFilter.h"
#include "AxisWindow.h"
#include "SciFigsGlobal.h"

namespace SciFigs {

  const QString GraphicObject::xmlGraphicObjectTag="GraphicObject";

  bool GraphicObject::_draggingOn=false;
  bool GraphicObject::_draggingStarted=false;
  QPoint GraphicObject::_draggingPos;

  const QString GraphicObject::pixelImageFilter =
    "BMP (*.bmp);;JPEG (*.jpg *.jpeg);;"
    "PNG (*.png);;PPM (*.ppm);;XBM (*.xbm);;XPM (*.xpm)";

  #ifndef NO_SVG
  const QString GraphicObject::vectorialImageFilter =
    "PDF (*.pdf);;SVG (*.svg)";
  #else
  const QString GraphicObject::vectorialImageFilter =
    "PDF (*.pdf)";
  #endif

  const QString GraphicObject::allImageFilter =
    GraphicObject::vectorialImageFilter + ";;" + GraphicObject::pixelImageFilter;

  /*!
    \class GraphicObject GraphicObject.h
    \brief The GraphicObject is the base class for all children of a GraphicSheet

    GraphicObject handles main geometric properties of objects (position, size, margins, printing resolution and user name). It works with two modes:
    \li Stand-alone, without a GraphicSheet
    \li Docked inside a GraphicSheet.

    In the first mode, it responds only to mouse click events to display its general context menu containing (also available through CTRL+M):
    \li Properties: show dialog box to let the user adjust all properties
    \li Print: draw the object in its current state to a printer of a postcript file
    \li Export image: draw the object in its current state to an image file (PNG, JPEG, ...)
    \li Save and Restore make-up: to retrieve and store the current properties to a XML ".mkup" file.

    In the second mode, the same actions are supported, but the following menu items are added:
    \li Order submenu: to change the order of plotting objets
    \li Align submenu: change relative positions of selected objects
    \li Delete: remove the object from the sheet (deleting its data in memory as well)

    Moreover, the mouse clicks are handled to move and to select the object in the sheet. Multiple selections are supported with the SHIFT key.

    The selected object is marked by a SelectionWidget that is slightly larger than this object, showing a black (active selection) or grey (passive selection) border. The mouse events on the border are redirected to this object. Usually, it is not necessary to use it explicitely. See setSelected(), selected(), active(), activeObject().

    The margins are useful when exporting to bitmaps or when the object is located close to the edge of a GraphicSheet. It extends the printing or image size by a few millimeters. Units for margins are always cm. See printLeftMargin(), printRightMargin(), printTopMargin(), printBottomMargin(), and setPrintLeftMargin(), setPrintRightMargin(), setPrintTopMargin(), setPrintBottomMargin().
  */

  /*!
    Construct a generic graphical object.
    This function is generally not called directly.
    Use one of the derived objects (AxisWindow, TextEdit, ImageWidget, ColorMapWidget).
  */
  GraphicObject::GraphicObject(QWidget * parent, Qt::WindowFlags f) :
      QWidget(parent, f)
  {
    TRACE;
    _selected=0;
    _sheet=0;
    _orderTip=0;
    _isRemovable=false;
    _isSelectable=true;
    new QShortcut(tr("Ctrl+M"), this, SLOT(keyPopupMenu()));

    setAttribute(Qt::WA_OpaquePaintEvent, true);
    setAttribute(Qt::WA_DeleteOnClose, true);
    // Just a test to check if mask still necessary or directly implemented in Qt
    // setAttribute(Qt::WA_OpaquePaintEvent);
    _printAnchor=Point2D(1.0, 1.0);
    _anchor=TopLeft;
    _printWidth=5.0;
    _printHeight=5.0;
    _constantWidthHeightRatio=false;
    _printResolution=300;
    _printLeftMargin=0.5;
    _printRightMargin=0.5;
    _printTopMargin=0.5;
    _printBottomMargin=0.5;
    _transparency=255;
    _mask=false;
  }

  /*!
    Destructor
  */
  GraphicObject::~GraphicObject()
  {
    TRACE;
    delete _orderTip;
  }

  /*!
    \fn double GraphicObject::printWidth() const
    Return the printing width of the object in cm
  */

  /*!
    \fn double GraphicObject::printHeight() const
    Return the printing height of the object in cm
  */

  /*!
    \fn int GraphicObject::printResolution() const
    Return the printing resolution of the object in dot per inch
  */

  /*!
    \fn double GraphicObject::printLeftMargin() const
    Return the left margin in cm.
  */

  /*!
    \fn double GraphicObject::printRightMargin() const
    Return the right margin in cm.
  */

  /*!
    \fn double GraphicObject::printTopMargin() const
    Return the top margin in cm.
  */

  /*!
    \fn double GraphicObject::printBottomMargin() const
    Return the bottom margin in cm.
  */

  /*!
    Set the printing X position of the object's anchor in cm. To update the display call updateGeometry().
  */
  void GraphicObject::setPrintXAnchor(double val)
  {
    TRACE;
    if(val<0.0 && _isSelectable) { // Allowed temporarily in a group
      val=0.0;
    }
    _printAnchor.setX(val);
  }

  /*!
    Set the printing Y position of the object's anchor in cm. To update the display call updateGeometry().
  */
  void GraphicObject::setPrintYAnchor(double val)
  {
    TRACE;
    if(val<0.0 && _isSelectable) { // Allowed temporarily in a group
      val=0.0;
    }
    _printAnchor.setY(val);
  }

  /*!
    Set the printing positions of the object in cm. To update the display call updateGeometry().
  */
  void GraphicObject::setPrintAnchor(Point2D o)
  {
    TRACE;
    _printAnchor=o;
    if(_printAnchor.x()<0.0 && _isSelectable) {
      _printAnchor.setX(0.0);
    }
    if(_printAnchor.y()<0.0 && _isSelectable) {
      _printAnchor.setY(0.0);
    }
  }

  /*!

  */
  void GraphicObject::setPrintLeft(double val)
  {
    TRACE;
    if(val<0.0 && _isSelectable) { // Allowed temporarily in a group
      val=0.0;
    }
    switch(_anchor) {
    case TopLeft:
    case CenterLeft:
    case BottomLeft:
      _printAnchor.setX(val);
      break;
    case TopCenter:
    case Center:
    case BottomCenter:
      _printAnchor.setX(val+_printWidth*0.5);
      break;
    default:
      _printAnchor.setX(val+_printWidth);
      break;
    }
  }

  /*!

  */
  void GraphicObject::setPrintTop(double val)
  {
    TRACE;
    if(val<0.0 && _isSelectable) { // Allowed temporarily in a group
      val=0.0;
    }
    switch(_anchor) {
    case TopLeft:
    case TopCenter:
    case TopRight:
      _printAnchor.setY(val);
      break;
    case CenterLeft:
    case Center:
    case CenterRight:
      _printAnchor.setY(val+_printHeight*0.5);
      break;
    default:
      _printAnchor.setY(val+_printHeight);
      break;
    }
  }
  /*!

  */
  void GraphicObject::setPrintRight(double val)
  {
    TRACE;
    if(val<0.0 && _isSelectable) { // Allowed temporarily in a group
      val=0.0;
    }
    switch(_anchor) {
    case TopLeft:
    case CenterLeft:
    case BottomLeft:
      _printAnchor.setX(val-_printWidth);
      break;
    case TopCenter:
    case Center:
    case BottomCenter:
      _printAnchor.setX(val-_printWidth*0.5);
      break;
    default:
      _printAnchor.setX(val);
      break;
    }
  }

  /*!

  */
  void GraphicObject::setPrintBottom(double val)
  {
    TRACE;
    if(val<0.0 && _isSelectable) { // Allowed temporarily in a group
      val=0.0;
    }
    switch(_anchor) {
    case TopLeft:
    case TopCenter:
    case TopRight:
      _printAnchor.setY(val-_printHeight);
      break;
    case CenterLeft:
    case Center:
    case CenterRight:
      _printAnchor.setY(val-_printHeight*0.5);
      break;
    default:
      _printAnchor.setY(val);
      break;
    }
  }

  /*!
    \fn void GraphicObject::setPrintWidth(double val)
    Set the printing width of the object in cm. To update the display call updateGeometry().
  */

  /*!
    \fn void GraphicObject::setPrintHeight(double val)
    Set the printing height of the object in cm. To update the display call updateGeometry().
  */

  /*!
    \fn void GraphicObject::setPrintResolution(int val)
    Set the resolution of the object for printing tasks (dot per inch). 300 or 600 dpi is common values for classical printers. For html or slide presentations, it is better to set it as close as possible to the final screen resolution (e.g. 100 dpi). Large resizing in other softwares may reduce the quality of the image.

    When printing a whole GraphicSheet, this resolution is just a minimum, it might be greater if other objects require higher resolution. To avoid surprise, select all objects and set resolution to be the same for all objects.
  */

  /*!
    \fn void GraphicObject::setPrintLeftMargin(double val)
    Set the left margin in cm.
  */

  /*!
    \fn void GraphicObject::setPrintRightMargin(double val)
    Set the right margin in cm.
  */

  /*!
    \fn void GraphicObject::setPrintTopMargin(double val)
    Set the top margin in cm.
  */

  /*!
    \fn void GraphicObject::setPrintBottomMargin(double val)
    Set the bottom margin in cm.
  */

  /*!
    \fn SelectionWidget * GraphicObject::selected() const
    Tells whether the object is selected or not (passive or active selection is not considered)
  */

  /*!
    \fn void GraphicObject::setSelected(SelectionWidget *s)
    Select the object by attribuing a SelectionWidget.
  */

  GraphicObject& GraphicObject::operator=(const GraphicObject& o)
  {
    TRACE;
    _printAnchor=o._printAnchor;
    _anchor=o._anchor;
    _printWidth=o._printWidth;
    _printHeight=o._printHeight;
    _constantWidthHeightRatio=o._constantWidthHeightRatio;
    _printResolution=o._printResolution;
    _printLeftMargin=o._printLeftMargin;
    _printRightMargin=o._printRightMargin;
    _printTopMargin=o._printTopMargin;
    _printBottomMargin=o._printBottomMargin;
    _transparency=o._transparency;
    _mask=o._mask;
    setFont(o.font());
    return *this;
  }

  /*!
    Ensure that the widget is at least updated once.
  */
  void GraphicObject::polish()
  {
    TRACE;
    if(actions().isEmpty()) {
      addActions();
      setRemovable(isRemovable());
    }
    update();
  }

  /*!
    Return the printing X position (Left) of the object in cm, taking anchor into account.

    If refWidth is greater than 0.0, it is used rather than the current width.
  */
  double GraphicObject::printLeft(double refWidth) const
  {
    TRACE;
    switch(_anchor & XMask) {
    case HCenter:
      if(refWidth>0.0)
        return _printAnchor.x()-0.5*refWidth;
      else
        return _printAnchor.x()-0.5*printWidth();
    case Right:
      if(refWidth>0.0)
        return _printAnchor.x()-refWidth;
      else
        return _printAnchor.x()-printWidth();
    default:
      return _printAnchor.x();
    }
  }

  /*!
    Return the printing Y position (Top) of the object in cm, taking anchor into account.

    If refHeight is greater than 0.0, it is used rather than the current height.
  */
  double GraphicObject::printTop(double refHeight) const
  {
    TRACE;
    switch(_anchor & YMask) {
    case VCenter:
      if(refHeight>0.0)
        return _printAnchor.y()-0.5*refHeight;
      else
        return _printAnchor.y()-0.5*printHeight();
    case Bottom:
      if(refHeight>0.0)
        return _printAnchor.y()-refHeight;
      else
        return _printAnchor.y()-printHeight();
    default:
      return _printAnchor.y();
    }
  }

  /*!
    Return the printing X position (Right) of the object in cm, taking anchor into account.

    If refWidth is greater than 0.0, it is used rather than the current width.
  */
  double GraphicObject::printRight(double refWidth) const
  {
    TRACE;
    switch(_anchor & XMask) {
    case HCenter:
      if(refWidth>0.0)
        return _printAnchor.x()+0.5*refWidth;
      else
        return _printAnchor.x()+0.5*printWidth();
    case Right:
      return _printAnchor.x();
    default:
      if(refWidth>0.0)
        return _printAnchor.x()+refWidth;
      else
        return _printAnchor.x()+printWidth();
    }
  }

  /*!
    Return the printing Y position (Bottom) of the object in cm, taking anchor into account.

    If refHeight is greater than 0.0, it is used rather than the current height.
  */
  double GraphicObject::printBottom(double refHeight) const
  {
    TRACE;
    switch(_anchor & YMask) {
    case VCenter:
      if(refHeight>0.0)
        return _printAnchor.y()+0.5*refHeight;
      else
        return _printAnchor.y()+0.5*printHeight();
    case Bottom:
      return _printAnchor.y();
    default:
      if(refHeight>0.0)
        return _printAnchor.y()+refHeight;
      else
        return _printAnchor.y()+printHeight();
    }
  }

  /*!
    If index is not negative, create a new OrderTip if does not exist and
    set its value to index. If index is negative delete the tip.
  */
  bool GraphicObject::setOrderIndex(int index)
  {
    TRACE;
    bool ret=false;
    if(index >= 0) {
      if( !_orderTip) {
        _orderTip=new OrderTip(this);
        _orderTip->move(0, 0);
        _orderTip->show();
        ret=true;
      }
      _orderTip->setIndex(index);
    } else {
      delete _orderTip;
      _orderTip=0;
    }
    return ret;
  }

  /*!
    Initialize context menu
  */
  void GraphicObject::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);
    _propertiesAction=a;

    if(sheet()) {
      a=new QAction(this);
      a->setSeparator(true);
      QWidget::addAction(a);

      a=new QAction(tr("&Group"), this);
      a->setObjectName("Group");
      connect(a, SIGNAL(triggered()), sheet(), SLOT(groupObjects()));
      QWidget::addAction(a);

      QMenu * m=new QMenu(this);
      m->addAction(tr("Bring to &front"), sheet(), SLOT(raiseSelection()) );
      m->addAction(tr("Send to &back"), sheet(), SLOT(lowerSelection()) );

      a=new QAction(tr("&Order"), this);
      a->setObjectName("Order");
      a->setMenu(m);
      QWidget::addAction(a);

      m=new QMenu(this);
      m->addAction(tr("&Left"), sheet(), SLOT(alignLeft()));
      m->addAction(tr("&Centered"), sheet(), SLOT(alignHCenter()));
      m->addAction(tr("&Right"), sheet(), SLOT(alignRight()));
      m->addSeparator();
      m->addAction(tr("&Top"), sheet(), SLOT(alignTop()));
      m->addAction(tr("C&entered"), sheet(), SLOT(alignVCenter()));
      m->addAction(tr("&Bottom"), sheet(), SLOT(alignBottom()));
      m->addSeparator();
      m->addAction(tr("&Grid"), sheet(), SLOT(alignGrid()));

      a=new QAction(tr("&Alignment"), this);
      a->setObjectName("Alignment");
      a->setMenu(m);
      QWidget::addAction(a);

      a=new QAction(tr("&Delete"), this);
      a->setObjectName("Delete");
      connect(a, SIGNAL(triggered(bool)) , this, SLOT(deleteObject()));
      QWidget::addAction(a);
    }

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

    a=new QAction(tr("&Copy"), this);
    a->setStatusTip(tr("Copy this object to the clipboard (compatible with figue)"));
    connect(a, SIGNAL(triggered(bool)) , this, SLOT(copy()));
    QWidget::addAction(a);

    a=new QAction(tr("Copy &image"), this);
    a->setStatusTip(tr("Copy this object as an image to the clipboard (compatible with office suites)"));
    connect(a, SIGNAL(triggered(bool)) , this, SLOT(copyImage()));
    QWidget::addAction(a);

    a=new QAction(tr( "Save as a page" ), this);
    a->setObjectName( "Save as a page" );
    a->setStatusTip(tr("Save this object as a page file. Open and edit it with figue."));
    connect(a, SIGNAL(triggered(bool)), this, SLOT(savePage()));
    QWidget::addAction(a);

    a=new QAction(tr( "&Export image as ..." ), this);
    a->setObjectName( "Export image" );
    a->setStatusTip(tr("Export this object as an image (PNG, JPG, SVG, PDF,...). Adjust resolution in object properties."));
    connect( a, SIGNAL(triggered(bool)) , this, SLOT(exportImage()));
    QWidget::addAction(a);

    a=new QAction(tr( "&Print" ), this);
    a->setObjectName( "Print" );
    connect(a, SIGNAL(triggered( bool) ) , this, SLOT(print()) );
    QWidget::addAction(a);

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

    a=new QAction(tr("&Copy make-up"), this);
    a->setObjectName("Copy make-up");
    a->setStatusTip(tr("Copy object format to the clipboard"));
    connect(a, SIGNAL(triggered(bool)) , this, SLOT(copyMakeUp()));
    QWidget::addAction(a);

    a=new QAction(tr("&Paste make-up"), this);
    a->setObjectName("Paste make-up");
    a->setStatusTip(tr("Restore object format from the clipboard"));
    connect(a, SIGNAL(triggered(bool)) , this, SLOT(pasteMakeUp()));
    QWidget::addAction(a);

    a=new QAction(tr( "&Save make-up" ), this);
    a->setObjectName( "Save make-up" );
    a->setStatusTip(tr("Save object format to a file"));
    connect(a, SIGNAL(triggered( bool)) , this, SLOT(saveMakeUp()));
    QWidget::addAction(a);

    a=new QAction(tr( "&Restore make-up" ), this);
    a->setObjectName( "Restore make-up" );
    a->setStatusTip(tr("Restore object format from a file"));
    connect(a, SIGNAL(triggered(bool)), this, SLOT(restoreMakeUp()));
    QWidget::addAction(a);
  }

  /*!
    Re-implemetation of QWidget::setObjectName(const char * name)
    It is used to avoid duplicate names inside the same GraphicSheet
  */
  void GraphicObject::setObjectName(QString name)
  {
    TRACE;
    QString newUniqueName;
    if(_sheet) {
      newUniqueName=_sheet->objects().uniqueName(this, name);
    } else {
      newUniqueName=name;
    }
    QWidget::setObjectName(newUniqueName);
  }

  void GraphicObject::update()
  {
    TRACE;
    if(transparentMask()) {
      updateMask();
    }
    QWidget::update();
  }

  void GraphicObject::updateMask()
  {
    TRACE;
    //qDebug() << "update mask";
    // Repaint the widget in a pixmap (like it appear on the screen)
    if(width()==0 || height()==0) return;
    QPixmap pixmap(width(), height());
    QPainter p;
    p.begin(&pixmap);
    p.fillRect(0, 0, width(), height(), Qt::white);
    paint(p, SciFigsGlobal::screenResolution(), width(), height(), false);
    p.end();
    QBitmap mask=SciFigsGlobal::colorMask(pixmap, 0xFFFFFFFF);
    //clearMask();
    //repaint();     // Forces erase of widget paint area
    setMask(mask); // With the new mask some parts of the area where not erased properly.
  }

  void GraphicObject::setTransparentMask(bool val)
  {
    TRACE;
    _mask=val;
    if(!val) {
      clearMask();
    }
  }

  /*!
    Emit the signals positionChanged() and sizeChanged(). When docked in a sheet,
    it tells GraphicSheet to move or to resize the object together with its
    selection widget.

    Call this function every time you modify the position or the size of the object,
    e.g. with printX or printWidth properties.
  */
  void GraphicObject::updateGeometry()
  {
    TRACE;
    emit positionChanged();
    emit sizeChanged();
    if(transparentMask()) updateMask();
  }

  /*!
    Delete all selected object after confirmation from the user . It is not
    working if not docked in a sheet.
  */
  void GraphicObject::deleteObject()
  {
    TRACE;
    if(!_sheet) return;
    QList<GraphicObject *> tmpList=_sheet->selectedObjects<GraphicObject>();
    if(tmpList.isEmpty()) {
      Message::warning(MSG_ID, tr( "Deleting objects" ), tr( "Empty selection." ), Message::ok());
      return;
    }
    // Count removable objects
    int nToRemove=0;
    GraphicObject * obj;
    foreach(obj, tmpList) {
      if(obj->isRemovable()) nToRemove++;
    }
    if (tmpList.count()>nToRemove) {
      static QString title=tr("Deleting objects");
      switch(nToRemove) {
      case 0:
        if(tmpList.count()==1) {
          Message::warning(MSG_ID, title,
                           tr("This object is not removable.")
                                  .arg(tmpList.count()), Message::ok(), true);
        } else {
          Message::warning(MSG_ID, title,
                           tr("These %1 objects are not removable.")
                                  .arg(tmpList.count()), Message::ok(), true);
        }
        break;
      case 1:
        Message::warning(MSG_ID, title,
                         tr("Only 1 object out of %2 selected is removable.")
                                .arg(tmpList.count()), Message::ok(), true);
        break;
      default:
        Message::warning(MSG_ID, title,
                         tr("Only %1 objects out of %2 selected are removable.")
                                .arg(nToRemove).arg( tmpList.count()), Message::ok(), true);
        break;
      }

      if (nToRemove==1) {
        Message::warning(MSG_ID, tr("Deleting objects"),
                             tr("Only 1 object out of %2 selected are removable.")
                                .arg(tmpList.count()), Message::ok(), true);
      } else {
        Message::warning(MSG_ID, tr( "Deleting objects"),
                           tr( "Only %1 objects out of %2 selected are removable.")
                                .arg(nToRemove).arg( tmpList.count()), Message::ok(), true);
      }
    }
    switch(nToRemove) {
    case 0:
      break;
    case 1:
      if(Message::warning(MSG_ID, "Deleting objects",
                                tr( "Do you really want to delete 1 object?" ),
                                Message::no() , Message::yes())==Message::Answer0) {
        return;
      }
      break;
    default:
      if(Message::warning(MSG_ID, "Deleting objects",
                                tr( "Do you really want to delete %1 objects?" ).arg(nToRemove),
                                Message::no() , Message::yes())==Message::Answer0) {
        return;
      }
      break;
    }
    foreach(obj, tmpList) _sheet->removeObject(obj);
  }

  /*!
    Print the object to a Qpainter using relative coordinates. Relative coordinates
    are usefull for printing various pages. This basic implementation call the
    virtual function paint() to draw the object's content.
  */
  void GraphicObject::print(QPainter& p, double dotpercm, int x0Sheet, int y0Sheet, bool mask)
  {
    TRACE;
    int w=qRound(_printWidth*dotpercm);
    int h=qRound(_printHeight*dotpercm);
    int orX=qRound(printLeft()*dotpercm)-x0Sheet;
    int orY=qRound(printTop()*dotpercm)-y0Sheet;
    p.setWindow(0, 0, w, h);
    p.setViewport(orX, orY, w, h);
    if(mask && !transparentMask()) {
      p.fillRect(0, 0, w, h, Qt::color1);
    } else {
      paint(p, dotpercm, w, h, mask);
    }
  }

  /*!
    Called before and after printing to adjust the size of object.
    Normally what you see is what you get, but there might be some
    slight modification due to rounding error when scaling fonts.
    All object that offer options to automatically adjust size and
    if it depends upon fonts must re-implement this function.

    The basic implemetation does nothing.
  */
  void GraphicObject::setPrintSize(double /* dotpercm */ )
  {
  }

  /*!
    User slot for printing the object at a defined resolution, overridding the internal printResolution.

    If dpi is null, the internal printResolution is used.
  */
  void GraphicObject::print(int dpi)
  {
    TRACE;
    QPrinter printer(QPrinter::HighResolution);
    if(dpi==0) printer.setResolution(_printResolution);
    else printer.setResolution(dpi);
    if(Settings::printSetup(&printer) ) {
      print(printer);
    }
  }

  void GraphicObject::print(QPrinter& printer)
  {
    TRACE;
    QPainter p;
    if(p.begin(&printer)) {
      print(p, ((double)printer.resolution())/2.54, 0, 0, false);
      p.end();
    } else {
      Message::warning(MSG_ID, tr("Printing..."), tr("Error encountered while printing. If you are printing to a pdf file, "
                                                     "check file permissions."));
    }
  }

  /*!
    Slot to be used from a script to print using the current print settings (from QSA::getPrintInfo())
  */
  void GraphicObject::print(bool outputToFile, QString name, int dpi)
  {
    TRACE;
    QPrinter printer(QPrinter::HighResolution);
    if(dpi==0) printer.setResolution(printResolution());
    else printer.setResolution(dpi);
    if(outputToFile) printer.setOutputFileName(name);
    else printer.setPrinterName(name);
    QPainter p;
    p.begin(&printer);
    print(p,((double)printer.resolution())/2.54, 0, 0, false);
    p.end();
  }

  /*!
    Export the object to a file, \a fileFormat may be any of the format accepted by
    QImage, PDF, SVG, PAGE or LAYER.

    If \a dpi is different from 0, it override the resolution specified in object properties.
  */
  void GraphicObject::exportFile(QString fileName, QString fileFormat, int dpi)
  {
    TRACE;
    if(fileFormat.isEmpty()) {
      QFileInfo fi(fileName);
      fileFormat=fi.suffix().toUpper();
    }
    if(fileFormat.toLower()=="page") {
      savePage(fileName);
    } else if(fileFormat.toLower()=="layer") {
      AxisWindow * w=qobject_cast<AxisWindow *>(this);
      if(w) {
        w->graphContent()->saveLayers(fileName);
      }
    } else {
      exportImage(fileName, fileFormat, dpi);
    }
  }

  /*!
    Export the object to an image file, \a imageFormat may be any of the format accepted by
    QImage. Additionally, 'PDF' and SVG are accepted.

    If \a dpi is different from 0, it override the resolution specified in object properties.
  */
  void GraphicObject::exportImage(QString fileName, QString imageFormat, int dpi)
  {
    TRACE;
    if(imageFormat.isEmpty()) {
      QFileInfo fi(fileName);
      imageFormat=fi.suffix().toUpper();
    }
    if(fileName.isEmpty()) {
      fileName=Message::getSaveFileName(tr("Export image as ..." ), allImageFilter);
    }
    if(fileName.length() > 0) {
      if(imageFormat.isEmpty()) {
        imageFormat=Settings::getFilter("filter-"+QString::number(qHash( allImageFilter))).
                                        section(' ', 0, 0);
      }
      if(imageFormat=="PDF") {
        QPrinter printer(QPrinter::HighResolution);
        if(dpi==0) dpi=_printResolution;
        printer.setResolution(dpi);
        printer.setFullPage(true);
        printer.setOutputFileName(fileName);
        printer.setOutputFormat(QPrinter::PdfFormat);
        print(printer);
      } else if(imageFormat =="SVG") {
        printSvg(fileName, dpi);
      } else {
        QPixmap pixmap=image(dpi);
        if(!pixmap.isNull()) {
          // Save pixmap to disk
          if(!pixmap.save(fileName, imageFormat.toLatin1().data())) {
            Message::warning(MSG_ID, tr("Exporting as image"), tr("Failed to export image to file %1 with format %2")
                                .arg(fileName).arg(imageFormat.toLatin1().data()));
          }
        }
        if(transparentMask()) updateMask();
      }
    }
  }

  /*!
    Copy this object to the clipboard
  */
  void GraphicObject::copy()
  {
    TRACE;
    QClipboard * cb=QApplication::clipboard();
    XMLSciFigs s;
    QByteArray object=s.saveByteArray(this, XMLSciFigs::Page);
    QMimeData * mime=new QMimeData;
    mime->setData("XMLSciFigs::Page", object);
    cb->setMimeData(mime, QClipboard::Clipboard);
  }

  /*!
    Transform object into a pixmap and copy it to the clipboard
  */
  void GraphicObject::copyImage(int dpi)
  {
    TRACE;
    QPixmap pixmap=image(dpi);
    if(!pixmap.isNull()) {
      QClipboard * cb=QApplication::clipboard();
      cb->setPixmap(pixmap, QClipboard::Clipboard);
    }
  }

  QPixmap GraphicObject::image(int dpi)
  {
    TRACE;
    double resolution;
    if(dpi==0) resolution=_printResolution/2.54;
    else resolution=dpi/2.54;
    double factor=resolution/SciFigsGlobal::screenResolution();
    double hMargins=_printLeftMargin+_printRightMargin;
    double vMargins=_printTopMargin+_printBottomMargin;
    FontScales originalFonts;
    scaleFonts(originalFonts, factor);
    setPrintSize(resolution);
    int totalw=qRound((_printWidth+hMargins)*resolution);
    int totalh=qRound((_printHeight+vMargins)*resolution);
    QPixmap pixmap;
    if(totalw>0 && totalh>0) {
      QPainter p;
      pixmap=QPixmap(totalw, totalh);
      p.begin(&pixmap);
      p.eraseRect(0, 0, totalw, totalh);
      p.save();
      p.translate(-qRound((printLeft()-_printLeftMargin)*resolution),
                  -qRound((printTop()-_printTopMargin)*resolution));
      print(p, resolution, 0, 0, false);
      p.restore();
      //SciFigsGlobal::footprint(p, resolution, totalw, totalh);
      p.end();
      if(transparentMask()) {
        QBitmap mask=SciFigsGlobal::colorMask(pixmap, 0xFFFFFFFF);
        p.begin(&mask);
        p.translate(-qRound((printLeft()-_printLeftMargin)*resolution),
                    -qRound((printTop()-_printTopMargin)*resolution));
        print(p, resolution, 0, 0, true);
        p.end();
        pixmap.setMask(mask);
      }
      if(_transparency<255) {

      }
    } else {
      Message::warning(MSG_ID, tr("Drawing image"), tr("Null size for image of object %1 (%2, %3).")
                       .arg(objectName())
                       .arg(totalw)
                       .arg(totalh), Message::cancel());
    }
    restoreScaleFonts(originalFonts);
    setPrintSize(SciFigsGlobal::screenResolution());
    return pixmap;
  }

  void GraphicObject::printSvg(const QString& fileName, int dpi)
  {
    TRACE;
  #ifdef NO_SVG
    Q_UNUSED(fileName)
    Q_UNUSED(dpi)
    App::log(1, tr("SciFigs not compiled with SVG support.\n");
  #else
    QSvgGenerator gen;
    gen.setFileName(fileName);
    if(dpi==0) dpi=_printResolution;
    gen.setResolution(dpi);
    double resolution=dpi/2.54;
    double factor=resolution/SciFigsGlobal::screenResolution();
    double hMargins=_printLeftMargin + _printRightMargin;
    double vMargins=_printTopMargin + _printBottomMargin;
    FontScales originalFonts;
    scaleFonts(originalFonts, factor);
    setPrintSize(resolution);
    int totalw=(int) (( _printWidth + hMargins) * resolution);
    int totalh=(int) (( _printHeight + vMargins) * resolution);
    gen.setSize(QSize( totalw, totalh) );
    QPainter p;
    p.begin(&gen);
    if(!_mask) {
      p.eraseRect(0, 0, totalw, totalh);
    }
    p.save();
    p.translate( -(printLeft() - _printLeftMargin) * resolution, -(printTop() - _printTopMargin) * resolution);
    print(p, resolution, 0, 0, false);
    p.restore();
    p.end();
    restoreScaleFonts(originalFonts);
    setPrintSize(SciFigsGlobal::screenResolution());
  #endif
  }

  /*!
    Save object in a .page file
  */
  void GraphicObject::savePage(QString fileName)
  {
    TRACE;
    if(fileName.isEmpty()) {
      fileName=Message::getSaveFileName(tr("Save object as a page"), tr("Page file (*.page)"));
    }
    if(fileName.length() > 0) {
      XMLSciFigs s;
      s.saveFile(fileName, this, XMLSciFigs::Page);
    }
  }

  /*!
    Copy make-up to the clipboard
  */
  void GraphicObject::copyMakeUp()
  {
    TRACE;
    XMLSciFigs s;
    MakeUpFilter * d=new MakeUpFilter(this);
    d->setXml(s.saveString(this, XMLSciFigs::MakeUp));
    if(d->exec()==QDialog::Accepted) {
      QClipboard * cb=QApplication::clipboard();
      QMimeData * mime=new QMimeData;
      mime->setData("XMLSciFigs::ObjectMakeUp", d->saveByteArray());
      cb->setMimeData(mime, QClipboard::Clipboard);
    }
  }

  /*!
    Restore make-up from the clipboard if there are any
  */
  void GraphicObject::pasteMakeUp()
  {
    TRACE;
    static QString title=tr("Paste make-up");
    QClipboard * cb=QApplication::clipboard();
    const QMimeData * mime=cb->mimeData(QClipboard::Clipboard);
    if(mime->hasFormat("XMLSciFigs::ObjectMakeUp")) {
      QByteArray makeup=mime->data("XMLSciFigs::ObjectMakeUp");
      XMLErrorReport xmler(XMLErrorReport::Read);
      xmler.setTitle(title);
      XMLSciFigs s;
      xmler.exec(s.restoreByteArray(makeup, this, XMLSciFigs::MakeUp));
      deepUpdate();
    }
  }


  /*!
    Save make-up properties to fileName
  */
  void GraphicObject::saveMakeUp(QString fileName)
  {
    TRACE;
    XMLSciFigs s;
    MakeUpFilter * d=new MakeUpFilter(this);
    d->setXml(s.saveString(this, XMLSciFigs::MakeUp));
    if(d->exec()==QDialog::Accepted) {
      if(fileName.isEmpty()) {
        fileName=Message::getSaveFileName(tr( "Save make-up as ..." ),
                                                tr( "Object make-up (*.mkup)" ));
      }
      if(fileName.length() > 0) {
        d->saveFile(fileName);
      }
    }
    delete d;
  }

  /*!
    Restore make-up properties from fileName.
  */
  void GraphicObject::restoreMakeUp(QString fileName)
  {
    TRACE;
    static QString title=tr( "Open an exisiting make-up" );
    if(fileName.isEmpty()) {
      fileName=Message::getOpenFileName(title, tr( "Graph make-up (*.mkup)" ));
    }
    if(fileName.length() > 0) {
      XMLErrorReport xmler(XMLErrorReport::Read);
      xmler.setTitle(title);
      xmler.setFileName(fileName);
      QFileInfo fi(fileName);
      XMLSciFigs s;
      xmler.exec(s.restoreFile(fileName, this, XMLSciFigs::MakeUp));
    }
  }

  uint GraphicObject::_category=PropertyProxy::uniqueId();
  uint GraphicObject::_tabId=PropertyProxy::uniqueId();
  uint GraphicObject::_tabGeomery=PropertyProxy::uniqueId();
  uint GraphicObject::_tabPrint=PropertyProxy::uniqueId();

  PropertyProxy * GraphicObject::proxy() const
  {
    TRACE;
    return _sheet ? _sheet->proxy() : PropertyContext::proxy();
  }

  /*!

  */
  void GraphicObject::addProperties(PropertyProxy * pp)
  {
    TRACE;
    if( !pp->setCurrentCategory(_category) ) {
      pp->addCategory(_category, tr("Object"), QIcon( ":graphicobject.png"));
    }
    if(pp->setCurrentTab(_tabId)) {
      pp->addReference(this);
    } else {
      GraphicObjectIdProperties * w=new GraphicObjectIdProperties;
      pp->addTab(_tabId, tr("Identification"), w, this);
    }
    if(pp->setCurrentTab(_tabGeomery)) {
      pp->addReference(this);
    } else {
      GraphicObjectGeometryProperties * w=new GraphicObjectGeometryProperties;
      pp->addTab(_tabGeomery, tr("Geometry"), w, this);
    }
    if(pp->setCurrentTab(_tabPrint)) {
      pp->addReference(this);
    } else {
      GraphicObjectPrintProperties * w=new GraphicObjectPrintProperties;
      pp->addTab(_tabPrint, tr("Print"), w, this);
    }
  }

  /*!

  */
  void GraphicObject::removeProperties(PropertyProxy * pp)
  {
    TRACE;
    if(pp->setCurrentCategory(_category) ) {
      pp->removeTab(_tabId, this);
      pp->removeTab(_tabGeomery, this);
      pp->removeTab(_tabPrint, this);
    }
  }

  void GraphicObject::properties(PropertyWidget * w) const
  {
    TRACE;
    if(w->id()==_tabGeomery) {
      w->setValue(GraphicObjectGeometryProperties::X, printXAnchor());
      w->setValue(GraphicObjectGeometryProperties::Y, printYAnchor());
      w->setValue(GraphicObjectGeometryProperties::MoveSelection, isMoveSelection() ? 1 : 0);
      w->setValue(GraphicObjectGeometryProperties::Anchor, GraphicObjectGeometryProperties::anchor2item(anchor()) );
      w->setValue(GraphicObjectGeometryProperties::Width, printWidth());
      w->setValue(GraphicObjectGeometryProperties::Height, printHeight());
      w->setValue(GraphicObjectGeometryProperties::ConstantWidthHeightRatio, isConstantWidthHeightRatio());
      w->setValue(GraphicObjectGeometryProperties::WidthHeightRatio, printWidth()/printHeight());
    } else if(w->id()==_tabPrint) {
      w->setValue(GraphicObjectPrintProperties::LeftMargin, printLeftMargin());
      w->setValue(GraphicObjectPrintProperties::RightMargin, printRightMargin());
      w->setValue(GraphicObjectPrintProperties::TopMargin, printTopMargin());
      w->setValue(GraphicObjectPrintProperties::BottomMargin, printBottomMargin());
      w->setValue(GraphicObjectPrintProperties::Resolution, printResolution());
      w->setValue(GraphicObjectPrintProperties::Transparency, transparency());
      w->setValue(GraphicObjectPrintProperties::TransparentMask, transparentMask());
    } else if(w->id()==_tabId) {
      w->setValue(GraphicObjectIdProperties::Name, objectName());
      w->setValue(GraphicObjectIdProperties::Type, metaObject()->className());
    }
  }

  void GraphicObject::setProperty(uint wid, int pid, QVariant val)
  {
    TRACE;
    bool ok=true;
    double dVal;
    if(wid==_tabGeomery) {
      switch(pid) {
      case GraphicObjectGeometryProperties::X:
        dVal=Number::toDouble(val, ok);
        if(ok) {
          if(sheet() && isMoveSelection()) {
            if(isActive()) {
              sheet()->moveObjects(Point2D(dVal-printXAnchor(), 0.0));
            }
          } else {
            setPrintXAnchor(dVal);
            emit positionChanged();
          }
        }
        break;
      case GraphicObjectGeometryProperties::Y:
        dVal=Number::toDouble(val, ok);
        if(ok) {
          if(sheet() && isMoveSelection()) {
            if(isActive()) {
              sheet()->moveObjects(Point2D(0.0, dVal-printYAnchor()));
            }
          } else {
            setPrintYAnchor(dVal);
            emit positionChanged();
          }
        }
        break;
      case GraphicObjectGeometryProperties::MoveSelection:
        setMoveSelection(val.toInt()==1);
        break;
      case GraphicObjectGeometryProperties::Anchor:
        setAnchor(GraphicObjectGeometryProperties::item2anchor(val.toInt()), true);
        emit positionChanged();
        break;
      case GraphicObjectGeometryProperties::Width:
        dVal=Number::toDouble(val, ok);
        if(ok) {
          if(isConstantWidthHeightRatio()) {
            double rhw=printHeight()/printWidth();
            setPrintWidth(dVal);
            setPrintHeight(rhw * printWidth());
          } else {
            setPrintWidth(dVal);
          }
          emit sizeChanged();
        }
        break;
      case GraphicObjectGeometryProperties::Height:
        dVal=Number::toDouble(val, ok);
        if(ok) {
          if(isConstantWidthHeightRatio()) {
            double rwh=printWidth()/printHeight();
            setPrintHeight(dVal);
            setPrintWidth(rwh * printHeight());
          } else {
            setPrintHeight(dVal);
          }
          emit sizeChanged();
        }
        break;
      case GraphicObjectGeometryProperties::ConstantWidthHeightRatio:
        setConstantWidthHeightRatio(val.toBool());
        break;
      case GraphicObjectGeometryProperties::WidthHeightRatio:
        break;
      }
    } else if(wid==_tabPrint) {
      switch(pid) {
      case GraphicObjectPrintProperties::LeftMargin:
        dVal=Number::toDouble(val, ok);
        if(ok) {
          setPrintLeftMargin(dVal);
          emit sizeChanged();
        }
        break;
      case GraphicObjectPrintProperties::RightMargin:
        dVal=Number::toDouble(val, ok);
        if(ok) {
          setPrintRightMargin(dVal);
          emit sizeChanged();
        }
        break;
      case GraphicObjectPrintProperties::TopMargin:
        dVal=Number::toDouble(val, ok);
        if(ok) {
          setPrintTopMargin(dVal);
          emit sizeChanged();
        }
        break;
      case GraphicObjectPrintProperties::BottomMargin:
        dVal=Number::toDouble(val, ok);
        if(ok) {
          setPrintBottomMargin(dVal);
          emit sizeChanged();
        }
        break;
      case GraphicObjectPrintProperties::Resolution:
        setPrintResolution(val.toInt());
        break;
      case GraphicObjectPrintProperties::Transparency:
        setTransparency(val.toInt());
        break;
      case GraphicObjectPrintProperties::TransparentMask:
        setTransparentMask(val.toBool());
        break;
      }
    } else if(wid==_tabId) {
      switch(pid) {
      case GraphicObjectIdProperties::Name:
        setObjectName(val.toString());
        break;
      case GraphicObjectIdProperties::Type: // Read-only
        break;
      default:
        break;
      }
    }
  }

  void GraphicObject::updateGeometryProperties(PropertyProxy * pp)
  {
    TRACE;
    if(sheet() && isActive() && pp->setCurrentCategory(_category) && pp->setCurrentTab(_tabGeomery)) {
      GraphicObjectGeometryProperties * w=static_cast<GraphicObjectGeometryProperties *>(pp->currentTabWidget());
      pp->setCurrentTabValues();
      w->setWidgets();
    }
  }

  /*!

  */
  void GraphicObject::showProperties(uint category)
  {
    TRACE;
    if(_sheet) {
       _sheet->showProperties(category);
    } else {
      initProxy();
      proxy()->saveStates();
      addProperties(proxy());
      setPropertyTitle(this);
      raiseEditor(category);
      proxy()->restoreStates();
      proxy()->setValues();
    }
  }

  void GraphicObject::paintEvent(QPaintEvent *)
  {
    TRACE;
    QPainter p(this);
    p.fillRect(0,0, width(), height(), Qt::white);
    paint(p, SciFigsGlobal::screenResolution(), width(), height(), false);
  }

  /*!
    Virtual function to be re-implemented for each type of object to paint specific
    content. It is different than the classical QWidget function of the same type.
    Actually, the needed resolution is passed as argument as well as the screen
    width and height of the object. It is called by print().

    Nothing is done, calling it in re-implemented function is useless.
  */
  void GraphicObject::paint(QPainter& , double , int , int, bool)
  {
    TRACE;
  }

  /*!
    Select object by affecting a new selection widget \a s.
  */
  void GraphicObject::setSelected(SelectionWidget * s)
  {
    TRACE;
    _selected=s;
  }

  /*!
    Tells whether the object is selected
  */
  bool GraphicObject::isSelected() const
  {
    TRACE;
    return !_sheet || (_selected && (_selected->state()==SelectionWidget::Selected ||
                                     _selected->state()==SelectionWidget::Activated) );
  }

  /*!
    Tells whether the object is selected and is the active selection.
  */
  bool GraphicObject::isActive() const
  {
    TRACE;
    return !_sheet || (_selected && _selected->state()==SelectionWidget::Activated);
  }

  /*!
    Force the object to be selected and to be the active selection.
  */
  void GraphicObject::setActive(Qt::KeyboardModifiers s)
  {
    TRACE;
    if(!_selected || _selected->state()!=SelectionWidget::Activated) {
      emit wantToBeSelected(this, s);
    }
  }

  /*!
    Return the active object of the parent GraphicSheet.
  */
  GraphicObject * GraphicObject::activeObject(const char * className) const
  {
    TRACE;
    return _sheet->activeObject(className);
  }

  /*!
    Show context menu after CTRL+M request, or for other reason. The menu is displayed in the to left corner.

    TODO: it doesn't work...
  */
  void GraphicObject::keyPopupMenu()
  {
    TRACE;
    if(isActive()) {
      QMenu m;
      m.addActions(actions());
      m.exec(mapToGlobal( QPoint(0, 0) ));
    } else { // Insure that the menu will be displayed in the active object
      GraphicObject * obj=activeObject();
      if(obj) obj->keyPopupMenu();
    }
  }

  /*!
    Selection of object is not already selected and active.

    Start dragging the object, the initial position is recorded in static members.
  */
  void GraphicObject::mousePressEvent(QMouseEvent * e)
  {
    TRACE;
    switch (e->button()) {
    case Qt::LeftButton:
      if(_isSelectable) {
        setActive(e->modifiers());
        _draggingPos=mapToParent(e->pos());
        emit wantToStartDragging();
        _draggingOn=true;
        _draggingStarted=false;
      } else {
        e->ignore();
      }
      break;
    case Qt::RightButton: {
        GraphicObject * g=group();
        g->setActive(e->modifiers());
        QMenu m;
        QList<QAction *> l=g->actions();
        l.removeFirst();
        l.prepend(_propertiesAction);
        m.addActions(l);
        m.exec(mapToGlobal(e->pos()));
      }
      break;
    default:
      break;
    }
  }

  /*!
    Stop dragging the object.
  */
  void GraphicObject::mouseReleaseEvent(QMouseEvent * e)
  {
    TRACE;
    if(_isSelectable) {
      if(_draggingOn) {
        _draggingOn=false;
        if(sheet() && sheet()->isOrderIndex()) {
          sheet()->setOrderIndex(this);
        }
      }
    } else {
      e->ignore();
    }
  }

  /*!
    If dragging is on and if the mouse is at more than 10 pixels from its original position, true dragging is started. It only emits a dragged() signal to be interpreted by GraphicSheet which is in charge of moving all selected object at the same time.
  */
  void GraphicObject::mouseMoveEvent(QMouseEvent * e)
  {
    TRACE;
    if(_isSelectable) {
      if(_draggingOn) {
        QPoint d=mapToParent(e->pos());
        d-=_draggingPos;
        if(_draggingStarted || abs(d.x()) > 10 || abs(d.y()) > 10) {
          _draggingStarted=true;
          emit draggedTo(mapToGlobal(e->pos()));
        }
      }
    } else {
      e->ignore();
    }
  }

  void GraphicObject::setSelectable(bool s)
  {
    TRACE;
    _isSelectable=s;
    if(!_isSelectable && _selected) {
      _selected->deleteLater();
      _selected=0;
    }
  }

  // Dirty method to avoid crash under Mac
  static bool antiReccurFlag=true;

  void GraphicObject::enterEvent(QEvent *)
  {
    TRACE;
    if(antiReccurFlag) {
      antiReccurFlag=false;
      if(sheet() && !selectionWidget() && _isSelectable) {
        sheet()->addSelection(this);
        // Here and not in addSelection(), bug observed with Qt 4.6 under Mac, show() calls GraphicObject::enterEvent()
        ASSERT(_selected);
        _selected->setState(SelectionWidget::Hoover);
        _selected->show();
      }
      antiReccurFlag=true;
    }
  }

  void GraphicObject::leaveEvent(QEvent *)
  {
    TRACE;
    if(_selected && _selected->state()==SelectionWidget::Hoover) {
      ASSERT(sheet());
      QPoint p=sheet()->mapFromGlobal(QCursor::pos());
      QRect r=geometry();
      if(!r.contains(p)) {
        _selected->deleteLater();
        _selected=0;
      }
    }
  }

  void GraphicObject::drag(Point2D delta)
  {
    TRACE;
    setPrintAnchor(_oldPrintPos+delta);
  }

  /*!
    Select the object if not already selected and active.

    Open the property dialog box.
  */
  void GraphicObject::mouseDoubleClickEvent(QMouseEvent * e)
  {
    TRACE;
    setActive(e->modifiers());
    if(e->buttons() & Qt::LeftButton) {
      showProperties();
    }
  }

  /*!
    Ensures that the widget is polished when not docked into a GraphicSheet
  */
  void GraphicObject::showEvent(QShowEvent * e)
  {
    TRACE;
    polish();
    QWidget::showEvent(e);
  }

  /*!
    For some high resolution bitmap image, fonts must scaled
    A pointer to double is returned to allow storage of multiple fonts, for instance, in AxisWindow.
  */
  void GraphicObject::scaleFonts(FontScales& original, double scale)
  {
    TRACE;
    original.add(this, scaleWidgetFonts(this, scale));
  }

  /*!
    Scale the fonts of any widget, might be called by any re-implemetation of
    scaleFonts().

    Return the original pointSize.

    Used by scaleFonts(double scale) or by any re-implementation.
  */
  double GraphicObject::scaleWidgetFonts(QWidget * w, double scale)
  {
    TRACE;
    QFont f=w->font();
    bool changed;
    double originalfontSize=Font::scale(f, scale, &changed);
    if(changed) {
      // Recompute mask if font police was changed (for unscalable fonts)
      // If scaling does not require any family change, this operation
      // does not require any repaint after restoring.
      QFont fscreen=f;
      fscreen.setPointSizeF(originalfontSize);
      w->setFont(fscreen);
      update();
    }
    // Set new font size
    w->setFont(f);
    return originalfontSize;
  }

  /*!
    Restore a saved scale for fonts
  */
  void GraphicObject::restoreScaleFonts(const FontScales& original)
  {
    TRACE;
    QFont f=font();
    f.setPointSizeF(original.value(this));
    setFont(f);
  }

  void GraphicObject::setAnchor(PositionAnchor pa, bool preservePosition)
  {
    TRACE;
    // Changing the anchor should not change the position.
    if(_anchor!=pa) {
      if(preservePosition) {
        double x0=printLeft();
        double y0=printTop();
        _anchor=pa;
        setPrintLeft(x0);
        setPrintTop(y0);
      } else {
        _anchor=pa;
      }
    }
  }

  QString GraphicObject::anchorString() const
  {
    TRACE;
    switch (anchor()) {
    case TopCenter:
      return "TopCenter";
    case TopRight:
      return "TopRight";
    case CenterLeft:
      return "CenterLeft";
    case Center:
      return "Center";
    case CenterRight:
      return "CenterRight";
    case BottomLeft:
      return "BottomLeft";
    case BottomCenter:
      return "BottomCenter";
    case BottomRight:
      return "BottomRight";
    default:
      return "TopLeft";
    }
  }

  void GraphicObject::setAnchor(QString pa)
  {
    TRACE;
    if(pa.count()>=6) {
      switch(pa[0].unicode()) {
      case 'T':
        if(pa=="TopLeft") {
          setAnchor(TopLeft);
        } else if(pa=="TopCenter") {
          setAnchor(TopCenter);
        } else if(pa=="TopRight") {
          setAnchor(TopRight);
        }
        break;
      case 'C':
        if(pa=="Center") {
          setAnchor(Center);
        } else if(pa=="CenterLeft") {
          setAnchor(CenterLeft);
        } else if(pa=="CenterRight") {
          setAnchor(CenterRight);
        }
        break;
      case 'B':
        if(pa=="BottomLeft") {
          setAnchor(BottomLeft);
        } else if(pa=="BottomCenter") {
          setAnchor(BottomCenter);
        } else if(pa=="BottomRight") {
          setAnchor(BottomRight);
        }
        break;
      }
    }
  }

  bool GraphicObject::xml_polish(XML_POLISH_ARGS)
  {
    TRACE;
    Q_UNUSED(context)
    /* Used to re-order object according to make-up
    if(sheet()) {
      if(selectionWidget()) selectionWidget()->raise();
      raise();
    }*/
    if(printWidth()<=0.0 || printHeight()<=0.0) {
      setPrintSize(SciFigsGlobal::screenResolution());
    }
    updateGeometry();
    return true;
  }

  /*!
    Share tooltip with its selection widget if any.
  */
  void GraphicObject::setToolTip(const QString& t)
  {
    if(_selected) {
      _selected->setToolTip(t);
    }
    QWidget::setToolTip(t);
  }

  QAction * GraphicObject::action(const QString& name)
  {
    TRACE;
    QList<QAction *> l=actions();
    for(QList<QAction *>::iterator it=l.begin(); it!=l.end(); it++) {
      if((*it)->objectName()==name) {
        return *it;
      }
    }
    return nullptr;
  }

  void GraphicObject::setRemovable(bool r)
  {
    TRACE;
    _isRemovable=r;
    QAction * a=action("Delete");
    if(a) {
      a->setEnabled(_isRemovable);
    }
  }

  /*!
    If inside a group, return the base group.
    If not return this.
  */
  GraphicObject * GraphicObject::group()
  {
    TRACE;
    if(parentWidget()==_sheet || !_sheet) {
      return this;
    } else {
      GraphicObjectGroup * g=qobject_cast<GraphicObjectGroup *>(parentWidget());
      if(g) {
        return g->group();
      } else {
        return this;
      }
    }
  }

  void GraphicObject::setPos()
  {
    QWidget::move(qRound(printLeft()*SciFigsGlobal::screenResolution()),
                  qRound(printTop()*SciFigsGlobal::screenResolution()));
  }

  void GraphicObject::setSize()
  {
    resize(qRound(printWidth()*SciFigsGlobal::screenResolution()),
           qRound(printHeight()*SciFigsGlobal::screenResolution()));
  }

  void GraphicObject::setGeometry(double x, double y, double dx, double dy)
  {
    TRACE;
    setPrintXAnchor(x);
    setPrintYAnchor(y);
    setPrintWidth(dx);
    setPrintHeight(dy);
    updateGeometry();
  }


} // namespace SciFigs
