/***************************************************************************
**
**  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: 2007-05-30
**  Copyright: 2007-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "LineLayer.h"
#include "GraphContentLayerFactory.h"
#include "GraphContent.h"
#include "GraphicObject.h"
#include "XMLSciFigs.h"
#include "LayerMouseTracking.h"
#include "LineLayerProperties.h"
#include "LineFactory.h"
#include "LayerPainterRequest.h"
#include "LayerLocker.h"
#include "AxisWindow.h"

namespace SciFigs {

/*!
  \class LineLayer LineLayer.h
  \brief An abstract layer to plot AbstractLine

  Full description of class still missing
*/

const QString LineLayer::xmlLineLayerTag="LineLayer";

REGISTER_GRAPHCONTENTLAYER(LineLayer, "LineLayer")
// Compatibility
SYNONYM_GRAPHCONTENTLAYER(DynXYColorLines, "LineLayer")
SYNONYM_GRAPHCONTENTLAYER(DynLine2DLayer, "LineLayer")

LineLayer::LineLayer(AxisWindow * parent) :
    GraphContentLayer(parent)
{
  TRACE;
  _currentLine=-1;
  _errorBar=VerticalBar;
  _lineEditor=nullptr;
  _referenceLine=nullptr;
  _pointOptions=nullptr;
}

LineLayer::~LineLayer()
{
  TRACE;
  LayerLocker ll(this); // Do not use clear() to avoid properties update
  qDeleteAll(_lines);
  delete _lineEditor;
  delete _referenceLine;
  delete _pointOptions;
}

/*!
  Defines the type of line implementation to use with this object
*/
void LineLayer::setReferenceLine (AbstractLine * l)
{
  TRACE;
  ASSERT(!_referenceLine);
  _referenceLine=l;
}

void LineLayer::setReferencePen(const Pen& p)
{
  TRACE;
  _referenceLine->setPen(p);
}

void LineLayer::setReferenceSymbol(const Symbol& s)
{
  TRACE;
  _referenceLine->setSymbol(s);
}

/*!
  Return a clone of its internal list of lines. Mainly used by DynLineProperties
*/
QList<AbstractLine *> LineLayer::lines() const
{
  TRACE;
  QList<AbstractLine *> llist;
  for(QList<AbstractLine *>::const_iterator it=begin() ;it!=end();++it) {
    llist.append((*it)->clone());
  }
  return llist;
}

void LineLayer::clear()
{
  TRACE;
  delete _lineEditor;
  _lineEditor=nullptr;
  _currentLine=-1;
  LayerLocker ll(this);
  qDeleteAll(_lines);
  _lines.clear();
  resetLayerProperties();
}

void LineLayer::removeLine(int i)
{
  TRACE;
  delete _lineEditor;
  _lineEditor=nullptr;
  AbstractLine * l=_lines.at(i);
  if(i==_currentLine) _currentLine=-1;
  LayerLocker ll(this);
  delete l;
  _lines.removeAt(i);
  resetLayerProperties();
}

void LineLayer::removeLine(AbstractLine * l)
{
  TRACE;
  delete _lineEditor;
  _lineEditor=nullptr;
  int i=_lines.indexOf(l);
  if(i==_currentLine) _currentLine=-1;
  LayerLocker ll(this);
  delete l;
  _lines.removeAt(i);
  resetLayerProperties();
}

void LineLayer::removeEmptyLines()
{
  TRACE;
  delete _lineEditor;
  _lineEditor=nullptr;
  LayerLocker ll(this);
  for(int i=count() - 1;i >= 0;i-- ) {
    AbstractLine * l=_lines.at(i);
    if(l->count()==0) {
      delete l;
      _lines.removeAt(i);
      if(i==_currentLine) _currentLine=-1;
    }
  }
  resetLayerProperties();
}

/*!
  Adds one new line with default pen and symbol.
*/
AbstractLine * LineLayer::addLine()
{
  TRACE;
  AbstractLine * line=_referenceLine->clone();
  LayerLocker ll(this);
  _lines.append(line);
  resetLayerProperties();
  return line;
}

/*!
  Adds one new line with pen \a pen and symbol \a sym.
*/
AbstractLine * LineLayer::addLine(const Pen& pen, const Symbol& sym)
{
  TRACE;
  AbstractLine * line=_referenceLine->clone();
  line->setPen(pen);
  line->setSymbol(sym);
  LayerLocker ll(this);
  _lines.append(line);
  resetLayerProperties();
  return line;
}

void LineLayer::setVisible(int i, bool v)
{
  LayerLocker ll(this);
  _lines.at(i)->setVisible(v);
}

void LineLayer::setSelected(int i, bool s)
{
  LayerLocker ll(this);
  _lines.at(i)->setSelected(s);
}

Legend LineLayer::legend() const
{
  TRACE;
  int n=count();
  Legend leg(n);
  for(int i=0;i < n;i++ ) {
    const AbstractLine& l=* line(i);
    leg.setText(i , QString::number(i));
    leg.setPen(i, l.pen());
    leg.setSymbol(i, l.symbol());
  }
  return leg;
}

void LineLayer::setLegend(Legend legend)
{
  TRACE;
  LayerLocker ll(this);
  int n=count();
  for(int i=0; i< n; i++ ) {
    AbstractLine * l=_lines.at(i);
    l->setPen(legend.pen(i) );
    l->setSymbol(legend.symbol(i) );
  }
  deepUpdate();
}

Rect LineLayer::boundingRect() const
{
  TRACE;
  QList<AbstractLine *>::const_iterator it;
  // Find the first non null curve with valid points
  AbstractLine * line=nullptr;
  int j;
  SAFE_UNINITIALIZED(j, 0)
  for(it=begin() ;it!=end();++it) {
    line=*it;
    int n=line->count();
    for(j=0;j < n;j++ ) {
      if(line->isValid(j)) break;
    }
    if(j<n) break;
  }
  if(it==end()) {
    return Rect();
  }
  Point p=line->point(j, _pointOptions);
  Rect r(p.x(), p.y(), p.x(), p.y());
  for( ;it!=end();++it) {
    AbstractLine& line=**it;
    int n=line.count();
    for(int j=0;j < n;j++ ) {
      if(line.isValid(j))
        r.add(line.point(j, _pointOptions));
    }
  }
  return r;
}

inline void LineLayer::drawVerticalErrorBar(double x, double minY, double maxY, const GraphContentOptions& gc,
                                            QPainter& p, Rect& r, int tickLength) const
{
  TRACE;
  QPoint pscreen1, pscreen2;
  Point2D minPoint(x, minY);
  Point2D maxPoint(x, maxY);
  if(gc.r2s(maxPoint, minPoint, pscreen1, pscreen2) )
    p.drawLine(pscreen1, pscreen2);
  int centerx=gc.xr2s(x);
  if(r.includes(maxPoint) ) {
    int maxLim=pscreen1.y();
    p.drawLine(centerx - tickLength, maxLim, centerx + tickLength, maxLim);
  }
  if(r.includes(minPoint) ) {
    int minLim=pscreen2.y();
    p.drawLine(centerx - tickLength, minLim, centerx + tickLength, minLim);
  }
}

inline void LineLayer::drawHorizontalErrorBar(double y, double minX, double maxX, const GraphContentOptions& gc,
                                              QPainter& p, Rect& r, int tickLength) const
{
  TRACE;
  QPoint pscreen1, pscreen2;
  Point2D minPoint(minX, y);
  Point2D maxPoint(maxX, y);
  if(gc.r2s(maxPoint, minPoint, pscreen1, pscreen2) )
    p.drawLine(pscreen1, pscreen2);
  int centery=gc.yr2s(y);
  if(r.includes(maxPoint) ) {
    int maxLim=pscreen1.x();
    p.drawLine(maxLim, centery - tickLength, maxLim, centery + tickLength);
  }
  if(r.includes(minPoint) ) {
    int minLim=pscreen2.x();
    p.drawLine(minLim, centery - tickLength, minLim, centery + tickLength);
  }
}

void LineLayer::drawCurve(const GraphContentOptions& gc, QPainter& p, double dotpercm, int h,
                          Rect& r, const AbstractLine& line, int startAt, int endAt) const
{
  TRACE;
  if(!line.isVisible() || line.count()==0) {
    return;
  }
  if(line.isSorted()) {
    line.indexRange(r.x1(), r.x2(), startAt, endAt);
  }
  Point p1;
  while(startAt<endAt) {
    if(line.isValid(startAt)) {
      p1=line.point(startAt, _pointOptions);
      break;
    }
    startAt++;
  }
  if(endAt<=startAt) {
    return;
  }
  QPoint pscreen1, pscreen2;
  QPolygon ap(endAt-startAt);
  Pen pen=line.pen();
  int j;
  if(pen.style()!=Qt::NoPen) {
    if(line.isSelected()) { // set selection mark
      pen.setColor(Qt::red);
      double s=pen.width();
      if(s==0.0) {
        s=0.1;
      }
      pen.setWidth(s*2);
    }
    p.setPen(pen.qpen(dotpercm));
    int subCurveCount=0;
    for(j=startAt+1; j<endAt; j++) {
      if(!line.isValid(j)) {
        continue;
      }
      Point p2=line.point(j, _pointOptions);
      if(gc.r2s(p1, p2, pscreen1, pscreen2)) {
        gc.checkOrigin(pscreen1, pscreen2, h);
        if(subCurveCount==0) {
          // Still no point on the sub curve? Either very first point of curve or entering back into visible area
          ap.setPoint(0, pscreen1);
          subCurveCount=1;
        } else if(ap.point(subCurveCount-1)!=pscreen1) {
          // Gap in the curve because it extends out of the plot area
          p.drawPolyline (ap.data(), subCurveCount);
          ap.setPoint(0, pscreen1);
          subCurveCount=1;
        }
        if(ap.point(subCurveCount-1)!=pscreen2) {
          // draw a line only if distance between p1 and p2 is not null
          ap.setPoint(subCurveCount, pscreen2);
          subCurveCount++;
        }
      } else if(subCurveCount>1) {
        p.drawPolyline (ap.data(), subCurveCount);
        subCurveCount=0;
      }
      p1=p2;
    }
    if(subCurveCount>1) {
      p.drawPolyline (ap.data(), subCurveCount);
    }
  }
  Symbol sym=line.symbol();
  pen=sym.pen();
  if(line.isSelected()) { // set selection mark
    double symSize=sym.size();
    symSize=symSize==0.0 ? 0.2 : symSize*2.0;
    double penWidth=sym.pen().width();
    penWidth=penWidth==0.0 ? 0.2 : penWidth*2.0;
    sym=Symbol(sym.type(), symSize,
                     Pen(Qt::red, penWidth, sym.pen().style()),
                     Brush(Qt::red, sym.brush().style()) );
  }
  if(_errorBar!=NoBar) {
    int tickLength=qRound(0.1*sym.size()*dotpercm);
    p.setPen(pen.qpen(dotpercm));
    for(j=startAt; j<endAt; j++) {
      if(!line.isValid(j)) {
        continue;
      }
      Point p1=line.point(j, _pointOptions);
      if(p1.z()>0) {
        switch (_errorBar) {
        case NoBar:
          break;
        case VerticalBar:
          drawVerticalErrorBar(p1.x(), p1.y() - p1.z(), p1.y() + p1.z(), gc, p, r, tickLength);
          break;
        case VerticalLogBar:
          drawVerticalErrorBar(p1.x(), p1.y()/p1.z(), p1.y() * p1.z(), gc, p, r , tickLength);
          break;
        case HorizontalBar:
          drawHorizontalErrorBar(p1.y(), p1.x() - p1.z(), p1.x() + p1.z(), gc, p, r, tickLength);
          break;
        case HorizontalLogBar:
          drawHorizontalErrorBar(p1.y(), p1.x()/p1.z(), p1.x() * p1.z(), gc, p, r, tickLength);
          break;
        }
      }
    }
  }
  if(sym.type()!=Symbol::NoSymbol) {
    for(j=startAt; j<endAt; j++) {
      if(!line.isValid(j)) {
        continue;
      }
      if(r.includes(line.point(j, _pointOptions))) {
        sym.paint(p, gc.r2sF(line.point(j, _pointOptions)), dotpercm);
      }
    }
  }
}

void LineLayer::paintData(const LayerPainterRequest& lp, QPainter & p, double dotpercm) const
{
  TRACE;
  QList<AbstractLine *>::const_iterator it;
  const GraphContentOptions& gc=lp.options();
  int h=lp.size().height();
  Rect r(gc.xVisMin(), gc.yVisMin(), gc.xVisMax(), gc.yVisMax());
  for(it=begin() ;it!=end(); ++it) {
    if(lp.terminated()) return;
    AbstractLine& line=**it;
    drawCurve(gc, p, dotpercm, h, r, line, 0, line.count());
  }
}

void LineLayer::toggleTrackingAction(bool checked, int id)
{
  TRACE;
  if(id==-1) {
    QAction * a=qobject_cast<QAction *>(sender());
    if(!a) return;
    id=a->data().toInt();
  }
  if(checked) {
    if(!isMouseTracking(id)) {
      LayerMouseTracking mt(this);
      mt.setId(id);
      switch (id) {
      case Select:
        mt.setIdleCursor(QPixmap(":selectcursor.png"), 4, 4);
        mt.setRectangle(true);
        mt.showRectangle();
        break;
      case Pick: {
          mt.setIdleCursor(QPixmap(":pickcursor.png"), 4, 4);
          _currentLine=count();
          emit pickLine(addLine());
          break;
        }
      case PickOrdered: {
          mt.setIdleCursor(QPixmap(":pickorderedcursor.png"), 4, 4);
          _currentLine=count();
          emit pickLine(addLine());
          break;
        }
      case Edit:
        mt.setIdleCursor(QPixmap(":editcursor.png"), 4, 4);
        break;
      default:
        return; // Do not start mouse tracking
      }
      beginMouseTracking(mt);
    }
  } else {
    if(isMouseTracking(id)) {
      endMouseTracking(id);
    }
  }
}

bool LineLayer::keyPressEvent (QKeyEvent* e)
{
  TRACE;
  if(_lineEditor && e->key()==Qt::Key_Escape) {
    delete _lineEditor;
    _lineEditor=nullptr;
    deepUpdate();
    return true;
  }
  return false;
}

void LineLayer::mouseMoveEvent(const QPoint& pt, Qt::MouseButtons, Qt::KeyboardModifiers)
{
  TRACE;
  if(_lineEditor) {
    _lineEditor->mouseMoveEvent(pt);
    emit lineEdited(_lineEditor->lineIndex());
    deepUpdate();
  }
}

bool LineLayer::wheelEvent (QWheelEvent * e)
{
  TRACE;
  if(_lineEditor) {
    _lineEditor->wheelEvent(e);
    deepUpdate();
    return false;
  }
  return true;
}

bool LineLayer::mousePressEvent (QMouseEvent * e, int id)
{
  TRACE;
  if(e->button()==Qt::LeftButton) {
    if(id==Edit) {
      if(_lineEditor) {
        if(_lineEditor->mousePressEvent(e)) { // If return true, means that the user accepts the modification
          delete _lineEditor;
          _lineEditor=nullptr;
          deepUpdate();
        }
        return false;
      } else {
        _lineEditor=new LineEditor(this);
        if(_lineEditor->mousePressEvent(e)) {
          delete _lineEditor;
          _lineEditor=nullptr;
          deepUpdate();
        }
        return false;
      }
    }
  }
  return true;
}

bool LineLayer::mouseReleaseEvent (QMouseEvent * e, int id)
{
  TRACE;
  if(e->button()==Qt::LeftButton) {
    switch(id) {
    case Select:    // This mode uses rectangle, hence directly handled by GraphContent
      return false;
    case Pick:
      if(_currentLine>-1 && _currentLine<count()) {
        AbstractLine * l=_lines.at(_currentLine);
        int i=l->count();
        LayerLocker ll(this);
        emit beginPointAdd(l, i);
        l->append();
        l->setX(i, graphContent()->options().xs2r(e->pos().x()) );
        l->setY(i, graphContent()->options().ys2r(e->pos().y()), _pointOptions);
        emit endPointAdd(l, i);
        deepUpdate();
      }
      return true;
    case PickOrdered:
      if(_currentLine>-1 && _currentLine<count()) {
        AbstractLine * l=_lines.at(_currentLine);
        double x=graphContent()->options().xs2r(e->pos().x());
        int i, n=l->count();
        for(i=0; i<n; i++ ) {
          if(l->point(i, _pointOptions).x() > x) break;
        }
        LayerLocker ll(this);
        emit beginPointAdd(l, i);
        l->insert(i);
        l->setX(i, x);
        l->setY(i, graphContent()->options().ys2r(e->pos().y()), _pointOptions);
        emit endPointAdd(l, i);
        deepUpdate();
      }
      return true;
    default:
      break;
    }
  }
  return false;
}

void LineLayer::mulYbyX(double constant)
{
  TRACE;
  QList<AbstractLine *>::const_iterator it;
  LayerLocker ll(this);
  for(it=begin() ;it!=end();++it) {
    AbstractLine& line=**it;
    int n=line.count();
    for(int i =0; i<n; i++) {
      Point p=line.point(i, _pointOptions);
      line.setY(i, p.y() * constant * p.x(), _pointOptions);
    }
  }
}

void LineLayer::divYbyX(double constant)
{
  TRACE;
  QList<AbstractLine *>::const_iterator it;
  LayerLocker ll(this);
  for(it=begin() ;it!=end();++it) {
    AbstractLine& line=**it;
    int n=line.count();
    for(int i =0; i<n; i++) {
      Point p=line.point(i, _pointOptions);
      line.setY(i, p.y()/(constant * p.x()), _pointOptions);
    }
  }
}

bool LineLayer::trackRectangle(int, double rx1, double ry1, double rx2, double ry2, Qt::KeyboardModifiers m)
{
  TRACE;
  Rect r(rx1, ry1, rx2, ry2);
  QList<AbstractLine *>::const_iterator it;
  Point2D p;
  for(it=begin() ;it!=end();++it) {
    AbstractLine& line=**it;
    int n=line.count();
    bool hit=false;
    for(int j=0;j < n;j++ ) {
      p=line.point(j, _pointOptions);
      if(r.includes(p, Rect::AllRect) ) {
        hit=true;
        break;
      }
    }
    if(hit) {
      line.setSelected(!line.isSelected());
    } else if(!(m & Qt::SHIFT)) {
      line.setSelected(false);
    }
  }
  graphContent() ->deepUpdate();
  return true;
}

void LineLayer::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  int n=count();
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  LineLayerContext lineContext(scifigsContext, _pointOptions);
  static const QString key("index");
  XMLSaveAttributes att;
  QString& value=att.add(key);
  if(_referenceLine) {
    value="reference";
    _referenceLine->xml_save(s, &lineContext, att);
  }
  for(int i=0; i< n ; i++) {
    if(line(i)->count()>0) {
      value=QString::number(i);
      _lines.at(i)->xml_save(s, &lineContext, att);
    }
  }
}

XMLMember LineLayer::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  static const QString att="index";
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(_referenceLine) {
    bool correctType;
    if(tag==_referenceLine->xml_tagName()) {
      correctType=true;
    } else { // Recognition of synonyms when comparing to reference type
      AbstractLine * line=LineFactory::instance()->create(tag.toString());
      correctType=line ? (line->xml_tagName()==_referenceLine->xml_tagName()) : false;
      delete line;
    }
    if(correctType) {
      AbstractLine * line;
      XMLRestoreAttributeIterator it=attributes.find(att);
      if(it!=attributes.end() && it.value().toString()=="reference") {
        line=_referenceLine;
      } else {
        if(scifigsContext->data()) {
          line=_referenceLine->clone();
          _lines.append(line);
        } else {
          if(it==attributes.end()) {
            App::log(tr("Make-up for line: index attribute is mising\n") );
            return XMLMember(XMLMember::Skip);
          }
          int index=it.value().toInt();
          if(index<0 || index>=_lines.count()) {
            App::log(tr("Make-up for line: index is out of range\n") );
            return XMLMember(XMLMember::Skip);
          }
          line=_lines.at(index);
        }
      }
      return XMLMember(line, false, new LineLayerContext(scifigsContext, _pointOptions));
    }
  } else {
    AbstractLine * line=LineFactory::instance()->create(tag.toString());
    if(line) {
      XMLRestoreAttributeIterator it=attributes.find(att);
      if(it!=attributes.end() && it.value().toString()=="reference") {
        setReferenceLine(line);
      } else if(scifigsContext->data()) { // For compatibility if reference line is missing
        setReferenceLine(line);
        line=line->clone();
        _lines.append(line);
      }
      return XMLMember(line, false, new LineLayerContext(scifigsContext, _pointOptions));
    }
  }
  return GraphContentLayer::xml_member(tag, attributes, context);
}

bool LineLayer::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  switch (memberID) {
  case 0: {
      AbstractLine * line=LineFactory::instance()->create(content.toString());
      if(line) {
        setReferenceLine(line);
        return true;
      } else {
        App::log(tr("Unknown line type: %1\n").arg(content.toString()) );
        return false;
      }
    }
  default:
    return GraphContentLayer::xml_setProperty(memberID, tag, attributes, content, context);
  }
}

void LineLayer::setPenColor(int curveIndex, int red, int green, int blue)
{
  TRACE;
  if(curveIndex<count()) {
    AbstractLine * l=_lines.at(curveIndex);
    Pen p=l->pen();
    p.setColor(QColor(red,green,blue));
    LayerLocker ll(this);
    l->setPen(p);
  }
}

void LineLayer::setPenStyle(int curveIndex, double width, QString lineStyle,
                                QString capStyle, QString joinStyle)
{
  TRACE;
  if(curveIndex>=count()) return;
  AbstractLine * l=_lines.at(curveIndex);
  Pen p=l->pen();
  p.setWidth(width);
  p.setStyle(Pen::styleValue(lineStyle) );
  p.setCapStyle(Pen::capStyleValue(capStyle) );
  p.setJoinStyle(Pen::joinStyleValue(joinStyle) );
  LayerLocker ll(this);
  l->setPen(p);
}

void LineLayer::setSymType(int curveIndex, QString type)
{
  TRACE;
  if(curveIndex>=count()) return;
  AbstractLine * l=_lines.at(curveIndex);
  Symbol s=l->symbol();
  s.setType(Symbol::symbolValue(type) );
  LayerLocker ll(this);
  l->setSymbol(s);
}

void LineLayer::setSymSize(int curveIndex, double size)
{
  TRACE;
  if(curveIndex>=count()) return;
  AbstractLine * l=_lines.at(curveIndex);
  Symbol s=l->symbol();
  s.setSize(size);
  LayerLocker ll(this);
  l->setSymbol(s);
}

void LineLayer::setSymPenColor(int curveIndex, int red, int green, int blue)
{
  TRACE;
  if(curveIndex>=count()) return;
  AbstractLine * l=_lines.at(curveIndex);
  Symbol s=l->symbol();
  Pen p=s.pen();
  p.setColor(QColor(red,green,blue));
  s.setPen(p);
  LayerLocker ll(this);
  l->setSymbol(s);
}

void LineLayer::setSymPenStyle(int curveIndex, double width,
                                             QString lineStyle, QString capStyle, QString joinStyle)
{
  TRACE;
  if(curveIndex>=count()) return;
  AbstractLine * l=_lines.at(curveIndex);
  Symbol s=l->symbol();
  Pen p=s.pen();
  p.setWidth(width);
  p.setStyle(Pen::styleValue(lineStyle) );
  p.setCapStyle(Pen::capStyleValue(capStyle) );
  p.setJoinStyle(Pen::joinStyleValue(joinStyle));
  s.setPen(p);
  LayerLocker ll(this);
  l->setSymbol(s);
}

void LineLayer::setSymBrushColor(int curveIndex, int red, int green, int blue)
{
  TRACE;
  if(curveIndex>=count()) return;
  AbstractLine * l=_lines.at(curveIndex);
  Symbol s=l->symbol();
  Brush b=s.brush();
  b.setColor(QColor(red,green,blue));
  s.setBrush(b);
  LayerLocker ll(this);
  l->setSymbol(s);
}

void LineLayer::setSymBrushStyle(int curveIndex, QString brushStyle)
{
  TRACE;
  if(curveIndex>=count()) return;
  AbstractLine * l=_lines.at(curveIndex);
  Symbol s=l->symbol();
  Brush b=s.brush();
  b.setStyle(Brush::styleValue(brushStyle) );
  s.setBrush(b);
  LayerLocker ll(this);
  l->setSymbol(s);
}

void LineLayer::rmStdDev(int curveIndex)
{
  TRACE;
  if(curveIndex>=count()) return;
  AbstractLine& l=* _lines.at(curveIndex);
  int n=l.count();
  LayerLocker ll(this);
  for(int i =0; i<n; i++) {
    l.setZ(i, 0.0, _pointOptions);
  }
  resetLineProperties();
}

void LineLayer::setErrorBar(QString b)
{
  TRACE;
  if(b.count()>0) {
    switch(b[0].unicode()) {
    case 'N':
      setErrorBar(NoBar);
      break;
    case 'H':
      if(b=="HorizontalLogBar")
        setErrorBar(HorizontalLogBar);
      else
        setErrorBar(HorizontalBar);
      break;
    default:
      if(b=="VerticalLogBar")
        setErrorBar(VerticalLogBar);
      else
        setErrorBar(VerticalBar);
      break;
    }
  }
}

QString LineLayer::errorBarString() const
{
  TRACE;
  switch(_errorBar) {
  case NoBar:
    return "NoBar";
  case HorizontalBar:
    return "HorizontalBar";
  case HorizontalLogBar:
    return "HorizontalLogBar";
  case VerticalLogBar:
    return "VerticalLogBar";
  default:
    return "VerticalBar";
  }
}

void LineLayer::attributesHelp()
{
  TRACE;
  qDebug("SYMBOL TYPE: NoSymbol, Cross, Square, Circle, Triangle, "
         "Losange, Star\n"
         "PEN STYLE: NoPen, SolidLine, DashLine, DotLine, DashDotLine, "
         "DashDotDotLine\n"
         "PEN CAP STYLE: FlatCap, SquareCap, RoundCap\n"
         "PEN JOIN STYLE: MiterJoin, BevelJoin, RoundJoin\n"
         "BRUSH STYLES: NoBrush, SolidPattern, Dense1Pattern, Dense2Pattern, "
         "Dense3Pattern, Dense4Pattern, Dense5Pattern, Dense6Pattern, "
         "Dense7Pattern, HorPattern, VerPattern, CrossPattern, BDiagPattern, "
         "FDiagPattern, DiagCrossPattern\n");
}

uint LineLayer::_tabLineLayer=PropertyProxy::uniqueId();

/*!
  Setup property editor
*/
void LineLayer::addProperties(PropertyProxy * pp)
{
  TRACE;
  if(pp->setCurrentTab(_tabLineLayer)) {
    pp->addReference(this);
    LineLayerProperties * w=static_cast<LineLayerProperties *>(pp->currentTabWidget());
    w->addLayer(this);
  } else {
    LineLayerProperties * w=new LineLayerProperties;
    pp->addTab(_tabLineLayer, tr("Lines"), w, this);
    w->addLayer(this);
  }
}

void LineLayer::removeProperties(PropertyProxy * pp)
{
  TRACE;
  if(pp->setCurrentTab(_tabLineLayer)) {
    LineLayerProperties * w=static_cast<LineLayerProperties *>(pp->currentTabWidget());
    w->removeLayer(this);
    pp->removeTab(_tabLineLayer, this);
  }
}

/*!
  Update the property tab, if active.
  Called automatically by clear(), removeLine(int i), removeLine(AbstractLine * l), removeEmptyLines(),
  addLine(),addLine(const Pen& pen, const Symbol& sym).
*/
void LineLayer::resetLayerProperties()
{
  TRACE;
  PropertyProxy * pp=graph()->proxy();
  if(pp) {
    if(pp->setCurrentTab(_tabLineLayer)) {
      LineLayerProperties * w=static_cast<LineLayerProperties *>(pp->currentTabWidget());
      w->updateCurrentLayer();
    }
  }
}

/*!
  Update the property tab, if active.
  Call this function after changing the number of samples in a curve or changing its values.
*/
void LineLayer::resetLineProperties()
{
  TRACE;
  PropertyProxy * pp=graph()->proxy();
  if(pp) {
    if(pp->setCurrentTab(_tabLineLayer)) {
      LineLayerProperties * w=static_cast<LineLayerProperties *>(pp->currentTabWidget());
      w->updateCurrentLayer();
    }
  }
}

} // namespace SciFigs
