    //=============================================================================
    //  MuseScore
    //  Music Composition & Notation
    //
    //  Copyright (C) 2002-2011 Werner Schweer
    //
    //  This program is free software; you can redistribute it and/or modify
    //  it under the terms of the GNU General Public License version 2
    //  as published by the Free Software Foundation and appearing in
    //  the file LICENCE.GPL
    //=============================================================================

    #include "tuplet.h"
    #include "score.h"
    #include "chord.h"
    #include "note.h"
    #include "xml.h"
    #include "staff.h"
    #include "style.h"
    #include "text.h"
    #include "element.h"
    #include "undo.h"
    #include "stem.h"
    #include "beam.h"
    #include "measure.h"

    namespace Ms {

    //---------------------------------------------------------
    //   Tuplet
    //---------------------------------------------------------

    Tuplet::Tuplet(Score* s)
      : DurationElement(s)
          {
          setFlags(ElementFlag::MOVABLE | ElementFlag::SELECTABLE);

          _direction    = score()->styleV(StyleIdx::tupletDirection).value<Direction>();
          _numberType   = NumberType(score()->styleI(StyleIdx::tupletNumberType));
          _bracketType  = BracketType(score()->styleI(StyleIdx::tupletBracketType));

          _ratio        = Fraction(1, 1);
          _number       = 0;
          _hasBracket   = false;
          _isUp         = true;
          }

    Tuplet::Tuplet(const Tuplet& t)
       : DurationElement(t)
          {
          _tick         = t._tick;
          _hasBracket   = t._hasBracket;
          _ratio        = t._ratio;
          _baseLen      = t._baseLen;

          _direction    = t._direction;
          _numberType   = t._numberType;
          _bracketType  = t._bracketType;

          directionStyle = t.directionStyle;
          numberStyle    = t.numberStyle;
          bracketStyle   = t.bracketStyle;

          _isUp          = t._isUp;

          p1             = t.p1;
          p2             = t.p2;
          _p1            = t._p1;
          _p2            = t._p2;

          if (t._number)
                _number = new Text(*t._number);
          else
                _number = 0;
          }

    //---------------------------------------------------------
    //   ~Tuplet
    //---------------------------------------------------------

    Tuplet::~Tuplet()
          {
          delete _number;
          }

    //---------------------------------------------------------
    //   setSelected
    //---------------------------------------------------------

    void Tuplet::setSelected(bool f)
          {
          Element::setSelected(f);
          if (_number)
                _number->setSelected(f);
          }

    //---------------------------------------------------------
    //   setVisible
    //---------------------------------------------------------

    void Tuplet::setVisible(bool f)
          {
          Element::setVisible(f);
          if (_number)
                _number->setVisible(f);
          }

    //---------------------------------------------------------
    //   layout
    //---------------------------------------------------------

    void Tuplet::layout()
          {
          if (_elements.empty()) {
                qDebug("Tuplet::layout(): tuplet is empty");
                return;
                }
          // is in a TAB without stems, skip any format: tuplets are not shown
          if (staff() && staff()->isTabStaff(tick()) && staff()->staffType(tick())->slashStyle())
                return;

          qreal _spatium = spatium();
          if (_numberType != NumberType::NO_TEXT) {
                if (_number == 0) {
                      _number = new Text(SubStyle::TUPLET, score());
                      _number->setTrack(track());
                      _number->setParent(this);
                      _number->setVisible(visible());
                      }
                if (_numberType == NumberType::SHOW_NUMBER)
                      _number->setXmlText(QString("%1").arg(_ratio.numerator()));
                else
                      _number->setXmlText(QString("%1:%2").arg(_ratio.numerator()).arg(_ratio.denominator()));
                }
          else {
                if (_number) {
                      if (_number->selected())
                            score()->deselect(_number);
                      delete _number;
                      _number = 0;
                      }
                }
          //
          // find out main direction
          //
          if (_direction == Direction::AUTO) {
                int up = 1;
                for (const DurationElement* e : _elements) {
                      if (e->isChord()) {
                            const Chord* c = toChord(e);
                            if (c->stemDirection() != Direction::AUTO)
                                  up += c->stemDirection() == Direction::UP ? 1000 : -1000;
                            else
                                  up += c->up() ? 1 : -1;
                            }
                      else if (e->isTuplet()) {
                            // TODO
                            }
                      }
                _isUp = up > 0;
                }
          else
                _isUp = _direction == Direction::UP;

          const DurationElement* cr1 = _elements.front();
          while (cr1->isTuplet()) {
                const Tuplet* t = toTuplet(cr1);
                if (t->elements().empty())
                      break;
                cr1 = t->elements().front();
                }
          const DurationElement* cr2 = _elements.back();
          while (cr2->isTuplet()) {
                const Tuplet* t = toTuplet(cr2);
                if (t->elements().empty())
                      break;
                cr2 = t->elements().back();
                }

          //
          //   shall we draw a bracket?
          //
          if (_bracketType == BracketType::AUTO_BRACKET) {
                _hasBracket = false;
                foreach (DurationElement* e, _elements) {
                      if (e->isTuplet() || e->isRest()) {
                            _hasBracket = true;
                            break;
                            }
                      else if (e->isChordRest()) {
                            ChordRest* cr = toChordRest(e);
                            //
                            // maybe we should check for more than one beam
                            //
                            if (cr->beam() == 0) {
                                  _hasBracket = true;
                                  break;
                                  }
                            }
                      }
                }
          else
                _hasBracket = _bracketType != BracketType::SHOW_NO_BRACKET;

          //
          //    calculate bracket start and end point p1 p2
          //
          qreal maxSlope = score()->styleD(StyleIdx::tupletMaxSlope);
          bool outOfStaff = score()->styleB(StyleIdx::tupletOufOfStaff);
          qreal vHeadDistance = score()->styleP(StyleIdx::tupletVHeadDistance);
          qreal vStemDistance = score()->styleP(StyleIdx::tupletVStemDistance);
          qreal stemLeft = score()->styleP(StyleIdx::tupletStemLeftDistance);
          qreal stemRight = score()->styleP(StyleIdx::tupletStemRightDistance);
          qreal noteLeft = score()->styleP(StyleIdx::tupletNoteLeftDistance);
          qreal noteRight = score()->styleP(StyleIdx::tupletNoteRightDistance);

          int move = 0;
          if (outOfStaff && cr1->isChordRest() && cr2->isChordRest()) {
                // account for staff move when adjusting bracket to avoid staff
                // but don't attempt adjustment unless both endpoints are in same staff
                if (toChordRest(cr1)->staffMove() == toChordRest(cr2)->staffMove())
                      move = toChordRest(cr1)->staffMove();
                else
                      outOfStaff = false;
                }

          qreal l1 = _spatium;          // bracket tip height
          qreal l2l = vHeadDistance;    // left bracket vertical distance
          qreal l2r = vHeadDistance;    // right bracket vertical distance right

          if (_isUp)
                vHeadDistance = -vHeadDistance;

          p1      = cr1->pagePos();
          p2      = cr2->pagePos();
          p1.rx() -= noteLeft;
          p2.rx() += score()->noteHeadWidth() + noteRight;
          p1.ry() += vHeadDistance;
          p2.ry() += vHeadDistance;

          qreal xx1 = p1.x(); // use to center the number on the beam

          // follow beam angle if one beam extends over entire tuplet
          bool followBeam = false;
          qreal beamAdjust = 0.0;
          if (cr1->beam() && cr1->beam() == cr2->beam()) {
                followBeam = true;
                beamAdjust = score()->styleP(StyleIdx::beamWidth) * 0.5 * mag();
                }

          if (_isUp) {
                if (cr1->isChord()) {
                      const Chord* chord1 = toChord(cr1);
                      Stem* stem = chord1->stem();
                      if (stem)
                            xx1 = stem->abbox().x();
                      if (chord1->up()) {
                            if (stem) {
                                  if (followBeam)
                                        p1.ry() = stem->abbox().y() - beamAdjust;
                                  else if (chord1->beam())
                                        p1.ry() = chord1->beam()->abbox().y();
                                  else
                                        p1.ry() = stem->abbox().y();
                                  l2l = vStemDistance;
                                  }
                            else {
                                  p1.ry() = chord1->upNote()->abbox().top(); // whole note
                                  }
                            }
                      else if (!chord1->up()) {
                            p1.ry() = chord1->upNote()->abbox().top();
                            if (stem)
                                  p1.rx() = cr1->pagePos().x() - stemLeft;
                            }
                      }

                if (cr2->isChord()) {
                      const Chord* chord2 = toChord(cr2);
                      Stem* stem = chord2->stem();
                      if (stem && chord2->up()) {
                            if (followBeam)
                                  p2.ry() = stem->abbox().top() - beamAdjust;
                            else if (chord2->beam())
                                  p2.ry() = chord2->beam()->abbox().top();
                            else
                                  p2.ry() = stem->abbox().top();
                            l2r = vStemDistance;
                            p2.rx() = chord2->pagePos().x() + chord2->maxHeadWidth() + stemRight;
                            }
                      else {
                            p2.ry() = chord2->upNote()->abbox().top();
                            }
                      }
                //
                // special case: one of the bracket endpoints is
                // a rest
                //
                if (cr1->isChord() && cr2->isChord()) {
                      if (p2.y() < p1.y())
                            p1.setY(p2.y());
                      else
                            p2.setY(p1.y());
                      }
                else if (cr1->isChord() && !cr2->isChord()) {
                      if (p1.y() < p2.y())
                            p2.setY(p1.y());
                      else
                            p1.setY(p2.y());
                      }

                // outOfStaff
                if (outOfStaff) {
                      qreal min = cr1->measure()->staffabbox(cr1->staffIdx() + move).y();
                      if (min < p1.y()) {
                            p1.ry() = min;
                            l2l = vStemDistance;
                            }
                      min = cr2->measure()->staffabbox(cr2->staffIdx() + move).y();
                      if (min < p2.y()) {
                            p2.ry() = min;
                            l2r = vStemDistance;
                            }
                      }

                // check that slope is no more than max
                qreal d = (p2.y() - p1.y())/(p2.x() - p1.x());
                if (d  < -maxSlope) {
                      // move p1 y up
                      p1.ry() = p2.y() + maxSlope * (p2.x() - p1.x());
                      }
                else if (d  > maxSlope) {
                      // move p2 y up
                      p2.ry() = p1.ry() + maxSlope * (p2.x() - p1.x());
                      }

                // check for collisions
                int n = _elements.size();
                if (n >= 3) {
                      d = (p2.y() - p1.y())/(p2.x() - p1.x());
                      for (int i = 1; i < (n-1); ++i) {
                            Element* e = _elements[i];
                            if (e->isChord()) {
                                  const Chord* chord = toChord(e);
                                  const Stem* stem = chord->stem();
                                  if (stem) {
                                        QRectF r(chord->up() ? stem->abbox() : chord->upNote()->abbox());
                                        qreal y3 = r.top();
                                        qreal x3 = r.x() + r.width() * .5;
                                        qreal y0 = p1.y() + (x3 - p1.x()) * d;
                                        qreal c  = y0 - y3;
                                        if (c > 0) {
                                              p1.ry() -= c;
                                              p2.ry() -= c;
                                              }
                                        }
                                  }
                            }
                      }
                }
          else {
                if (cr1->isChord()) {
                      const Chord* chord1 = toChord(cr1);
                      Stem* stem = chord1->stem();
                      if (stem)
                            xx1 = stem->abbox().x();
                      if (!chord1->up()) {
                            if (stem) {
                                  if (followBeam)
                                        p1.ry() = stem->abbox().bottom() + beamAdjust;
                                  else if (chord1->beam())
                                        p1.ry() = chord1->beam()->abbox().bottom();
                                  else
                                        p1.ry() = stem->abbox().bottom();
                                  l2l = vStemDistance;
                                  p1.rx() = cr1->pagePos().x() - stemLeft;
                                  }
                            else {
                                  p1.ry() = chord1->downNote()->abbox().bottom(); // whole note
                                  }
                            }
                      else if (chord1->up()) {
                            p1.ry() = chord1->downNote()->abbox().bottom();
                            }
                      }

                if (cr2->isChord()) {
                      const Chord* chord2 = toChord(cr2);
                      Stem* stem = chord2->stem();
                      if (stem && !chord2->up()) {
                            // if (chord2->beam())
                            //      p2.setX(stem->abbox().x());
                            if (followBeam)
                                  p2.ry() = stem->abbox().bottom() + beamAdjust;
                            if (chord2->beam())
                                  p2.ry() = chord2->beam()->abbox().bottom();
                            else
                                  p2.ry() = stem->abbox().bottom();
                            l2r = vStemDistance;
                            }
                      else {
                            p2.ry() = chord2->downNote()->abbox().bottom();
                            if (stem)
                                  p2.rx() = chord2->pagePos().x() + chord2->maxHeadWidth() + stemRight;
                            }
                      }
                //
                // special case: one of the bracket endpoints is
                // a rest
                //
                if (!cr1->isChord() && cr2->isChord()) {
                      if (p2.y() > p1.y())
                            p1.setY(p2.y());
                      else
                            p2.setY(p1.y());
                      }
                else if (cr1->isChord() && !cr2->isChord()) {
                      if (p1.y() > p2.y())
                            p2.setY(p1.y());
                      else
                            p1.setY(p2.y());
                      }
                // outOfStaff
                if (outOfStaff) {
                      qreal max = cr1->measure()->staffabbox(cr1->staffIdx() + move).bottom();
                      if (max > p1.y()) {
                            p1.ry() = max;
                            l2l = vStemDistance;
                            }
                      max = cr2->measure()->staffabbox(cr2->staffIdx() + move).bottom();
                      if (max > p2.y()) {
                            p2.ry() = max;
                            l2r = vStemDistance;
                            }
                      }
                // check that slope is no more than max
                qreal d = (p2.y() - p1.y())/(p2.x() - p1.x());
                if (d  < -maxSlope) {
                      // move p1 y up
                      p2.ry() = p1.y() - maxSlope * (p2.x() - p1.x());
                      }
                else if (d  > maxSlope) {
                      // move p2 y up
                      p1.ry() = p2.ry() - maxSlope * (p2.x() - p1.x());
                      }

                // check for collisions
                int n = _elements.size();
                if (n >= 3) {
                      qreal d  = (p2.y() - p1.y())/(p2.x() - p1.x());
                      for (int i = 1; i < (n-1); ++i) {
                            Element* e = _elements[i];
                            if (e->isChord()) {
                                  const Chord* chord = toChord(e);
                                  const Stem* stem = chord->stem();
                                  if (stem) {
                                        QRectF r(chord->up() ? chord->downNote()->abbox() : stem->abbox());
                                        qreal y3 = r.bottom();
                                        qreal x3 = r.x() + r.width() * .5;
                                        qreal y0 = p1.y() + (x3 - p1.x()) * d;
                                        qreal c  = y0 - y3;
                                        if (c < 0) {
                                              p1.ry() -= c;
                                              p2.ry() -= c;
                                              }
                                        }
                                  }
                            }
                      }
                }

          setPos(0.0, 0.0);
          QPointF mp(parent()->pagePos());
          p1 -= mp;
          p2 -= mp;

          p1 += _p1;
          p2 += _p2;
          xx1 -= mp.x();

          p1.ry() -= l2l * (_isUp ? 1.0 : -1.0);
          p2.ry() -= l2r * (_isUp ? 1.0 : -1.0);

          // center number
          qreal x3 = 0.0;
          qreal numberWidth = 0.0;
          if (_number) {
                _number->layout();
                numberWidth = _number->bbox().width();
                //
                // for beamed tuplets, center number on beam
                //
                if (cr1->beam() && cr2->beam() && cr1->beam() == cr2->beam()) {
                      const ChordRest* crr = toChordRest(cr1);
                      if(_isUp == crr->up()) {
                            qreal deltax = cr2->pagePos().x() - cr1->pagePos().x();
                            x3 = xx1 + deltax * .5;
                            }
                      else {
                            qreal deltax = p2.x() - p1.x();
                            x3 = p1.x() + deltax * .5;
                            }
                      }
                else {
                      qreal deltax = p2.x() - p1.x();
                      x3 = p1.x() + deltax * .5;
                      }

                qreal y3 = p1.y() + (p2.y() - p1.y()) * .5 - l1 * (_isUp ? 1.0 : -1.0);
                _number->setPos(QPointF(x3, y3) - ipos());
                }

          if (_hasBracket) {
                qreal slope = (p2.y() - p1.y()) / (p2.x() - p1.x());

                if (_isUp) {
                      if (_number) {
                            bracketL[0] = QPointF(p1.x(), p1.y());
                            bracketL[1] = QPointF(p1.x(), p1.y() - l1);
                            qreal x     = x3 - numberWidth * .5 - _spatium * .5;
                            qreal y     = p1.y() + (x - p1.x()) * slope;
                            bracketL[2] = QPointF(x,   y - l1);

                            x           = x3 + numberWidth * .5 + _spatium * .5;
                            y           = p1.y() + (x - p1.x()) * slope;
                            bracketR[0] = QPointF(x,   y - l1);
                            bracketR[1] = QPointF(p2.x(), p2.y() - l1);
                            bracketR[2] = QPointF(p2.x(), p2.y());
                            }
                      else {
                            bracketL[0] = QPointF(p1.x(), p1.y());
                            bracketL[1] = QPointF(p1.x(), p1.y() - l1);
                            bracketL[2] = QPointF(p2.x(), p2.y() - l1);
                            bracketL[3] = QPointF(p2.x(), p2.y());
                            }
                      }
                else {
                      if (_number) {
                            bracketL[0] = QPointF(p1.x(), p1.y());
                            bracketL[1] = QPointF(p1.x(), p1.y() + l1);
                            qreal x     = x3 - numberWidth * .5 - _spatium * .5;
                            qreal y     = p1.y() + (x - p1.x()) * slope;
                            bracketL[2] = QPointF(x,   y + l1);

                            x           = x3 + numberWidth * .5 + _spatium * .5;
                            y           = p1.y() + (x - p1.x()) * slope;
                            bracketR[0] = QPointF(x,   y + l1);
                            bracketR[1] = QPointF(p2.x(), p2.y() + l1);
                            bracketR[2] = QPointF(p2.x(), p2.y());
                            }
                      else {
                            bracketL[0] = QPointF(p1.x(), p1.y());
                            bracketL[1] = QPointF(p1.x(), p1.y() + l1);
                            bracketL[2] = QPointF(p2.x(), p2.y() + l1);
                            bracketL[3] = QPointF(p2.x(), p2.y());
                            }
                      }
                }
          QRectF r;
          if (_number) {
                r |= _number->bbox().translated(_number->pos());
                if (_hasBracket) {
                      QRectF b;
                      b.setCoords(bracketL[1].x(), bracketL[1].y(), bracketR[2].x(), bracketR[2].y());
                      r |= b;
                      }
                }
          else if (_hasBracket) {
                QRectF b;
                b.setCoords(bracketL[1].x(), bracketL[1].y(), bracketL[3].x(), bracketL[3].y());
                r |= b;
                }
          setbbox(r);
          }

    //---------------------------------------------------------
    //   draw
    //---------------------------------------------------------

    void Tuplet::draw(QPainter* painter) const
          {
          // if in a TAB without stems, tuplets are not shown
          if (staff() && staff()->isTabStaff(tick()) && staff()->staffType(tick())->slashStyle())
                return;

          QColor color(curColor());
          if (_number) {
                painter->setPen(color);
                QPointF pos(_number->pos());
                painter->translate(pos);
                _number->draw(painter);
                painter->translate(-pos);
                }
          if (_hasBracket) {
                painter->setPen(QPen(color, mag() * score()->styleP(StyleIdx::tupletBracketWidth)));
                if (!_number)
                      painter->drawPolyline(bracketL, 4);
                else {
                      painter->drawPolyline(bracketL, 3);
                      painter->drawPolyline(bracketR, 3);
                      }
                }
          }

    //---------------------------------------------------------
    //   scanElements
    //---------------------------------------------------------

    void Tuplet::scanElements(void* data, void (*func)(void*, Element*), bool all)
          {
          func(data, this);
          if (_number && all)
                func(data, _number);
          }

    //---------------------------------------------------------
    //   write
    //---------------------------------------------------------

    void Tuplet::write(XmlWriter& xml) const
          {
          xml.stag(QString("Tuplet id=\"%1\"").arg(_id));
          if (tuplet())
                xml.tag("Tuplet", tuplet()->id());
          Element::writeProperties(xml);
          writeProperty(xml, P_ID::DIRECTION);
          writeProperty(xml, P_ID::NUMBER_TYPE);
          writeProperty(xml, P_ID::BRACKET_TYPE);
          writeProperty(xml, P_ID::NORMAL_NOTES);
          writeProperty(xml, P_ID::ACTUAL_NOTES);
          writeProperty(xml, P_ID::P1);
          writeProperty(xml, P_ID::P2);

          xml.tag("baseNote", _baseLen.name());

          if (_number) {
                xml.stag("Number");
                _number->writeProperties(xml);
                xml.etag();
                }
          if (!userOff().isNull())
                xml.tag("offset", userOff() / spatium());
          xml.etag();
          }

    //---------------------------------------------------------
    //   read
    //---------------------------------------------------------

    void Tuplet::read(XmlReader& e)
          {
          _id    = e.intAttribute("id", 0);
          while (e.readNextStartElement()) {
                if (readProperties(e))
                      ;
                else
                      e.unknown();
                }
          Fraction f(_ratio.denominator(), _baseLen.fraction().denominator());
          setDuration(f.reduced());
          }

    //---------------------------------------------------------
    //   readProperties
    //---------------------------------------------------------

    bool Tuplet::readProperties(XmlReader& e)
          {
          const QStringRef& tag(e.name());
          if (readProperty(tag, e, P_ID::DIRECTION))
                ;
          else if (tag == "numberType")
                _numberType = NumberType(e.readInt());
          else if (tag == "bracketType")
                _bracketType = BracketType(e.readInt());
          else if (tag == "normalNotes")
                _ratio.setDenominator(e.readInt());
          else if (tag == "actualNotes")
                _ratio.setNumerator(e.readInt());
          else if (tag == "p1")
                _p1 = e.readPoint();
          else if (tag == "p2")
                _p2 = e.readPoint();
          else if (tag == "baseNote")
                _baseLen = TDuration(e.readElementText());
          else if (tag == "Number") {
                _number = new Text(SubStyle::TUPLET, score());
                _number->setParent(this);
                _number->read(e);
                _number->setVisible(visible());     //?? override saved property
                _number->setTrack(track());
                }
          else if (!DurationElement::readProperties(e))
                return false;
          return true;
          }

    //---------------------------------------------------------
    //   add
    //---------------------------------------------------------

    void Tuplet::add(Element* e)
          {
    #ifndef NDEBUG
          for(DurationElement* el : _elements) {
                if (el == e) {
                      qDebug("%p: %p %s already there", this, e, e->name());
                      return;
                      }
                }
    #endif

          switch (e->type()) {
                case ElementType::TEXT:
                      _number = toText(e);
                      break;
                case ElementType::CHORD:
                case ElementType::REST:
                case ElementType::TUPLET: {
                      bool found = false;
                      DurationElement* de = toDurationElement(e);
                      int tick = de->tick();
                      if (tick != -1) {
                            for (unsigned int i = 0; i < _elements.size(); ++i) {
                                  if (_elements[i]->tick() > tick) {
                                        _elements.insert(_elements.begin() + i, de);
                                        found = true;
                                        break;
                                        }
                                  }
                            }
                      if (!found)
                            _elements.push_back(de);
                      de->setTuplet(this);
                      }
                      break;

                default:
                      qDebug("Tuplet::add() unknown element");
                      break;
                }
          }

    //---------------------------------------------------------
    //   remove
    //---------------------------------------------------------

    void Tuplet::remove(Element* e)
          {
          switch (e->type()) {
                case ElementType::TEXT:
                      if (e == _number)
                            _number = 0;
                      break;
                case ElementType::CHORD:
                case ElementType::REST:
                case ElementType::TUPLET: {
                      auto i = std::find(_elements.begin(), _elements.end(), static_cast<DurationElement*>(e));
                      if (i == _elements.end()) {
                            qDebug("Tuplet::remove: cannot find element <%s>", e->name());
                            qDebug("  elements %zu", _elements.size());
                            }
                      else
                            _elements.erase(i);
                      }
                      break;
                default:
                      qDebug("Tuplet::remove: unknown element");
                      break;
                }
          }

    //---------------------------------------------------------
    //   isEditable
    //---------------------------------------------------------

    bool Tuplet::isEditable() const
          {
          return _hasBracket;
          }

    //---------------------------------------------------------
    //   startEdit
    //---------------------------------------------------------

    void Tuplet::startEdit(EditData& ed)
          {
          ed.grips   = 2;
          ed.curGrip = Grip::END;
          }

    //---------------------------------------------------------
    //   editDrag
    //---------------------------------------------------------

    void Tuplet::editDrag(EditData& ed)
          {
          if (ed.curGrip == Grip::START)
                _p1 += ed.delta;
          else
                _p2 += ed.delta;
          setGenerated(false);
          layout();
          score()->setUpdateAll();
          }

    //---------------------------------------------------------
    //   updateGrips
    //---------------------------------------------------------

    void Tuplet::updateGrips(EditData& ed) const
          {
          ed.grip[0].translate(pagePos() + p1);
          ed.grip[1].translate(pagePos() + p2);
          }

    //---------------------------------------------------------
    //   reset
    //---------------------------------------------------------

    void Tuplet::reset()
          {
          if (directionStyle == PropertyFlags::UNSTYLED)
                undoChangeProperty(P_ID::DIRECTION, propertyDefault(P_ID::DIRECTION), PropertyFlags::STYLED);
          if (numberStyle == PropertyFlags::UNSTYLED)
                undoChangeProperty(P_ID::NUMBER_TYPE, propertyDefault(P_ID::NUMBER_TYPE), PropertyFlags::STYLED);
          if (bracketStyle == PropertyFlags::UNSTYLED)
                undoChangeProperty(P_ID::BRACKET_TYPE, propertyDefault(P_ID::BRACKET_TYPE), PropertyFlags::STYLED);

          score()->addRefresh(canvasBoundingRect());

          undoChangeProperty(P_ID::P1,        QPointF());
          undoChangeProperty(P_ID::P2,        QPointF());
          undoChangeProperty(P_ID::DIRECTION, propertyDefault(P_ID::DIRECTION));

          Element::reset();
          layout();
          score()->addRefresh(canvasBoundingRect());
          }

    //---------------------------------------------------------
    //   dump
    //---------------------------------------------------------

    void Tuplet::dump() const
          {
          Element::dump();
          qDebug("ratio %s", qPrintable(_ratio.print()));
          }

    //---------------------------------------------------------
    //   setTrack
    //---------------------------------------------------------

    void Tuplet::setTrack(int val)
          {
          if (_number)
                _number->setTrack(val);
          Element::setTrack(val);
          }

    //---------------------------------------------------------
    //   tickGreater
    //---------------------------------------------------------

    static bool tickGreater(const DurationElement* a, const DurationElement* b)
          {
          return a->tick() < b->tick();
          }

    //---------------------------------------------------------
    //   sortElements
    //---------------------------------------------------------

    void Tuplet::sortElements()
          {
          qSort(_elements.begin(), _elements.end(), tickGreater);
          }

    //---------------------------------------------------------
    //   elementsDuration
    ///  Get the sum of the element fraction in the tuplet,
    ///  even if the tuplet is not complete yet
    //---------------------------------------------------------

    Fraction Tuplet::elementsDuration()
          {
          Fraction f;
          for (DurationElement* el : _elements)
                f += el->duration();
          return f;
          }

    //---------------------------------------------------------
    //   getProperty
    //---------------------------------------------------------

    QVariant Tuplet::getProperty(P_ID propertyId) const
          {
          switch (propertyId) {
                case P_ID::DIRECTION:
                      return QVariant::fromValue<Direction>(_direction);
                case P_ID::NUMBER_TYPE:
                      return int(_numberType);
                case P_ID::BRACKET_TYPE:
                      return int(_bracketType);
                case P_ID::NORMAL_NOTES:
                      return _ratio.denominator();
                case P_ID::ACTUAL_NOTES:
                      return _ratio.numerator();
                case P_ID::P1:
                      return _p1;
                case P_ID::P2:
                      return _p2;
                case P_ID::FONT_SIZE:
                case P_ID::FONT_FACE:
                case P_ID::FONT_BOLD:
                case P_ID::FONT_ITALIC:
                case P_ID::FONT_UNDERLINE:
                case P_ID::ALIGN:
                      return _number ? _number->getProperty(propertyId) : QVariant();
                default:
                      break;
                }
          return DurationElement::getProperty(propertyId);
          }

    //---------------------------------------------------------
    //   setProperty
    //---------------------------------------------------------

    bool Tuplet::setProperty(P_ID propertyId, const QVariant& v)
          {
          switch (propertyId) {
                case P_ID::DIRECTION:
                      directionStyle = PropertyFlags::UNSTYLED;
                      setDirection(v.value<Direction>());
                      break;
                case P_ID::NUMBER_TYPE:
                      numberStyle = PropertyFlags::UNSTYLED;
                      setNumberType(NumberType(v.toInt()));
                      break;
                case P_ID::BRACKET_TYPE:
                      bracketStyle = PropertyFlags::UNSTYLED;
                      setBracketType(BracketType(v.toInt()));
                      break;
                case P_ID::NORMAL_NOTES:
                      _ratio.setDenominator(v.toInt());
                      break;
                case P_ID::ACTUAL_NOTES:
                      _ratio.setNumerator(v.toInt());
                      break;
                case P_ID::P1:
                      _p1 = v.toPointF();
                      break;
                case P_ID::P2:
                      _p2 = v.toPointF();
                      break;
                case P_ID::FONT_SIZE:
                case P_ID::FONT_FACE:
                case P_ID::FONT_BOLD:
                case P_ID::FONT_ITALIC:
                case P_ID::FONT_UNDERLINE:
                case P_ID::ALIGN:
                      if (_number)
                            _number->setProperty(propertyId, v);
                      break;
                default:
                      return DurationElement::setProperty(propertyId, v);
                }
          if (!_elements.empty()) {
                _elements.front()->triggerLayout();
                _elements.back()->triggerLayout();
                }
          return true;
          }

    //---------------------------------------------------------
    //   propertyDefault
    //---------------------------------------------------------

    QVariant Tuplet::propertyDefault(P_ID id) const
          {
          switch(id) {
                case P_ID::DIRECTION:
                      return score()->styleV(StyleIdx::tupletDirection);
                case P_ID::NUMBER_TYPE:
                      return score()->styleV(StyleIdx::tupletNumberType);
                case P_ID::BRACKET_TYPE:
                      return score()->styleV(StyleIdx::tupletBracketType);
                case P_ID::NORMAL_NOTES:
                case P_ID::ACTUAL_NOTES:
                      return 0;
                case P_ID::FONT_SIZE:
                case P_ID::FONT_FACE:
                case P_ID::FONT_BOLD:
                case P_ID::FONT_ITALIC:
                case P_ID::FONT_UNDERLINE:
                case P_ID::ALIGN:
                      return score()->styleV(getPropertyStyle(id));
                case P_ID::P1:
                case P_ID::P2:
                      return QPointF();
                default:
                      return DurationElement::propertyDefault(id);
                }
          }

    //---------------------------------------------------------
    //   propertyFlags
    //---------------------------------------------------------

    PropertyFlags Tuplet::propertyFlags(P_ID id) const
          {
          switch (id) {
                case P_ID::DIRECTION:
                      return directionStyle;
                case P_ID::NUMBER_TYPE:
                      return numberStyle;
                case P_ID::BRACKET_TYPE:
                      return bracketStyle;
                case P_ID::NORMAL_NOTES:
                case P_ID::ACTUAL_NOTES:
                case P_ID::P1:
                case P_ID::P2:
                      return PropertyFlags::NOSTYLE;
                case P_ID::FONT_FACE:
                case P_ID::FONT_SIZE:
                case P_ID::FONT_BOLD:
                case P_ID::FONT_ITALIC:
                case P_ID::FONT_UNDERLINE:
                case P_ID::ALIGN:
                      return _number ? _number->propertyFlags(id) : PropertyFlags::NOSTYLE;
                default:
                      return DurationElement::propertyFlags(id);
                }
          }

    //---------------------------------------------------------
    //   getPropertyStyle
    //---------------------------------------------------------

    StyleIdx Tuplet::getPropertyStyle(P_ID id) const
          {
          switch (id) {
                case P_ID::DIRECTION:
                      return StyleIdx::tupletDirection;
                case P_ID::NUMBER_TYPE:
                      return StyleIdx::tupletNumberType;
                case P_ID::BRACKET_TYPE:
                      return StyleIdx::tupletBracketType;
                case P_ID::FONT_FACE:
                      return StyleIdx::tupletFontFace;
                case P_ID::FONT_SIZE:
                      return StyleIdx::tupletFontSize;
                case P_ID::FONT_BOLD:
                      return StyleIdx::tupletFontBold;
                case P_ID::FONT_ITALIC:
                      return StyleIdx::tupletFontItalic;
                case P_ID::FONT_UNDERLINE:
                      return StyleIdx::tupletFontUnderline;
                case P_ID::ALIGN:
                      return StyleIdx::tupletAlign;
                default:
                      break;
                }
          return StyleIdx::NOSTYLE;
          }

    //---------------------------------------------------------
    //   resetProperty
    //---------------------------------------------------------

    void Tuplet::resetProperty(P_ID id)
          {
    printf("reset property %s\n", propertyQmlName(id));
          switch (id) {
                case P_ID::DIRECTION:
                      setProperty(id, propertyDefault(id));
                      directionStyle = PropertyFlags::STYLED;
                      break;
                case P_ID::NUMBER_TYPE:
                      setProperty(id, propertyDefault(id));
                      numberStyle = PropertyFlags::STYLED;
                      break;
                case P_ID::BRACKET_TYPE:
                      setProperty(id, propertyDefault(id));
                      bracketStyle = PropertyFlags::STYLED;
                      break;
                case P_ID::NORMAL_NOTES:
                case P_ID::ACTUAL_NOTES:
                      break;
                case P_ID::P1:
                case P_ID::P2:
                      setProperty(id, propertyDefault(id));
                      break;
                case P_ID::FONT_FACE:
                case P_ID::FONT_SIZE:
                case P_ID::FONT_BOLD:
                case P_ID::FONT_ITALIC:
                case P_ID::FONT_UNDERLINE:
                case P_ID::ALIGN:
                      if (_number)
                            _number->resetProperty(id);
                      break;
                default:
                      return DurationElement::resetProperty(id);
                }
          }

    //---------------------------------------------------------
    //   setPropertyFlags
    //---------------------------------------------------------

    void Tuplet::setPropertyFlags(P_ID id, PropertyFlags f)
          {
          switch (id) {
                case P_ID::DIRECTION:
                      directionStyle = f;
                      break;
                case P_ID::NUMBER_TYPE:
                      numberStyle = f;
                      break;
                case P_ID::BRACKET_TYPE:
                      bracketStyle = f;
                      break;
                case P_ID::NORMAL_NOTES:
                case P_ID::ACTUAL_NOTES:
                case P_ID::P1:
                case P_ID::P2:
                      break;
                case P_ID::FONT_FACE:
                case P_ID::FONT_SIZE:
                case P_ID::FONT_BOLD:
                case P_ID::FONT_ITALIC:
                case P_ID::FONT_UNDERLINE:
                case P_ID::ALIGN:
                      if (_number)
                            _number->setPropertyFlags(id, f);
                      break;
                default:
                      return DurationElement::setPropertyFlags(id, f);
                }
          }

    //---------------------------------------------------------
    //   styleChanged
    //    reset all styled values to actual style
    //---------------------------------------------------------

    void Tuplet::styleChanged()
          {
          if (directionStyle == PropertyFlags::STYLED)
                setDirection(score()->styleV(StyleIdx::tupletDirection).value<Direction>());
          if (numberStyle == PropertyFlags::STYLED)
                setNumberType(NumberType(score()->styleI(StyleIdx::tupletNumberType)));
          if (bracketStyle == PropertyFlags::STYLED)
                setBracketType(BracketType(score()->styleI(StyleIdx::tupletBracketType)));
          if (!_elements.empty()) {
                _elements.front()->triggerLayout();
                _elements.back()->triggerLayout();
                }
          if (_number)
                _number->styleChanged();
          }

    //---------------------------------------------------------
    //   sanitizeTuplet
    ///    Check validity of tuplets and coherence between duration
    ///    and baselength. Needed for importing old files due to a bug
    ///    in the released version for corner-case tuplets.
    ///    See issue #136406 and Pull request #2881
    //---------------------------------------------------------

    void Tuplet::sanitizeTuplet()
          {
          Fraction tupletDuration = duration().reduced();
          Fraction baseLenDuration = (Fraction(ratio().denominator(),1) * baseLen().fraction()).reduced();
          if ((tupletDuration - baseLenDuration).reduced().numerator() == 0)
                return;
          // Mismatch between the duration and the duration computed from the base length.
          // A tentative will now be made to retrieve the correct duration by summing up all the
          // durations of the elements constituting the tuplet. This does not work for
          // not-completely filled tuplets, such as tuplets in voices > 0 with
          // gaps (for example, a tuplet in second voice with a deleted chordrest element)
          Fraction testDuration(0,1);
          for (DurationElement* de : elements()) {
                if (de == 0)
                      continue;
                Fraction elementDuration(0,1);
                if (de->isTuplet()){
                      Tuplet* t = toTuplet(de);
                      t->sanitizeTuplet();
                      elementDuration = t->duration();
                      }
                else {
                      elementDuration = de->duration();
                      }
                testDuration += elementDuration;
                }
          testDuration = testDuration / ratio();
          testDuration.reduce();
          if (((testDuration - tupletDuration).reduced().numerator() != 0) || ((testDuration - baseLenDuration).reduced().numerator() != 0)) {
                Fraction f = testDuration * Fraction(1, ratio().denominator());
                f.reduce();
                Fraction fbl(1, f.denominator());
                if (TDuration::isValid(fbl)) {
                      setDuration(testDuration);
                      setBaseLen(fbl);
                      qDebug("Tuplet %p sanitized",this);
                      }
                else {
                      qDebug("Impossible to sanitize the tuplet");
                      }
                }
          }
    }  // namespace Ms

↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: _tick, _id.

↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: _id.