LCOV - code coverage report
Current view: top level - src/engraving/dom - hammeronpulloff.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 61.3 % 173 106
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "hammeronpulloff.h"
      24              : 
      25              : #include "iengravingconfiguration.h" // IWYU pragma: keep
      26              : 
      27              : #include "mscore.h"
      28              : #include "note.h"
      29              : #include "score.h"
      30              : #include "stafftype.h"
      31              : #include "style/textstyle.h"
      32              : #include "system.h"
      33              : 
      34              : namespace mu::engraving {
      35           52 : HammerOnPullOff::HammerOnPullOff(DummyParentOr<EngravingItem> parent)
      36           52 :     : Slur(parent, ElementType::HAMMER_ON_PULL_OFF)
      37              : {
      38           52 : }
      39              : 
      40            5 : HammerOnPullOff::HammerOnPullOff(const HammerOnPullOff& other)
      41            5 :     : Slur(other)
      42              : {
      43            5 : }
      44              : 
      45           57 : HammerOnPullOffSegment::HammerOnPullOffSegment(HammerOnPullOff* sp)
      46           57 :     : SlurSegment(sp, ElementType::HAMMER_ON_PULL_OFF_SEGMENT)
      47              : {
      48           57 : }
      49              : 
      50            0 : HammerOnPullOffSegment::HammerOnPullOffSegment(const HammerOnPullOffSegment& other)
      51            0 :     : SlurSegment(other)
      52              : {
      53            0 : }
      54              : 
      55            0 : Color HammerOnPullOffSegment::curColor(const rendering::PaintOptions& opt) const
      56              : {
      57            0 :     if (!opt.isPrinting && MScore::warnGuitarBends && !isValid()) {
      58            0 :         return selected() ? configuration()->criticalSelectedColor() : configuration()->criticalColor();
      59              :     }
      60              : 
      61            0 :     return SlurSegment::curColor(opt);
      62              : }
      63              : 
      64            0 : void HammerOnPullOffSegment::scanElements(std::function<void(EngravingItem*)> func)
      65              : {
      66            0 :     for (HammerOnPullOffText* hopo : m_hopoText) {
      67            0 :         hopo->scanElements(func);
      68              :     }
      69              : 
      70            0 :     EngravingItem::scanElements(func);
      71            0 : }
      72              : 
      73          120 : void HammerOnPullOffSegment::setTrack(track_idx_t idx)
      74              : {
      75          120 :     m_track = idx;
      76          134 :     for (HammerOnPullOffText* hopo : m_hopoText) {
      77           14 :         hopo->setTrack(idx);
      78              :     }
      79          120 : }
      80              : 
      81           57 : void HammerOnPullOffSegment::setSelected(bool f)
      82              : {
      83           57 :     for (HammerOnPullOffText* text : m_hopoText) {
      84            0 :         text->setSelected(f);
      85              :     }
      86              : 
      87           57 :     SlurTieSegment::setSelected(f);
      88           57 : }
      89              : 
      90           61 : void HammerOnPullOffSegment::updateHopoText()
      91              : {
      92           61 :     Chord* startChord = nullptr;
      93           61 :     if (isSingleBeginType()) {
      94           61 :         EngravingItem* startEl = hammerOnPullOff()->startElement();
      95           61 :         startChord = startEl && startEl->isChord() ? toChord(startEl) : nullptr;
      96              :     } else {
      97            0 :         ChordRest* firstCR = system()->firstChordRest(track());
      98            0 :         startChord = firstCR && firstCR->isChord() ? toChord(firstCR) : nullptr;
      99              :     }
     100              : 
     101           61 :     Chord* endChord = nullptr;
     102           61 :     if (isSingleEndType()) {
     103           61 :         EngravingItem* endEl = hammerOnPullOff()->endElement();
     104           61 :         endChord = endEl && endEl->isChord() ? toChord(endEl) : nullptr;
     105              :     } else {
     106              :         // If the segment doesn't end in this system, the endChord is the first chord of next system
     107            0 :         ChordRest* lastCR = system()->lastChordRest(track());
     108            0 :         if (lastCR) {
     109            0 :             lastCR = toChordRest(lastCR->segment()->next1WithElemsOnTrack(track())->element(track()));
     110              :         }
     111            0 :         endChord = lastCR && lastCR->isChord() ? toChord(lastCR) : nullptr;
     112              :     }
     113              : 
     114           61 :     if (!startChord || !endChord) {
     115            0 :         muse::DeleteAll(m_hopoText);
     116            0 :         m_hopoText.clear();
     117            0 :         return;
     118              :     }
     119              : 
     120           61 :     std::vector<HopoTextRegion> hopoTextRegions = computeHopoTextRegions(startChord, endChord);
     121           61 :     size_t regionCount = hopoTextRegions.size();
     122              : 
     123           61 :     size_t curRegionIdx = 0;
     124          136 :     for (; curRegionIdx < regionCount; ++curRegionIdx) {
     125           75 :         HopoTextRegion curRegion = hopoTextRegions[curRegionIdx];
     126              : 
     127              :         HammerOnPullOffText* curHopoText;
     128           75 :         if (curRegionIdx < m_hopoText.size()) {
     129              :             // Reuse existing, if available
     130           10 :             curHopoText = m_hopoText[curRegionIdx];
     131              :         } else {
     132              :             // Create new
     133           65 :             curHopoText = new HammerOnPullOffText(this);
     134           65 :             m_hopoText.push_back(curHopoText);
     135              :         }
     136              : 
     137           75 :         curHopoText->setTrack(track());
     138           75 :         curHopoText->setIsValid(curRegion.isValid);
     139           75 :         curHopoText->setIsHammerOn(curRegion.isHammerOn);
     140           75 :         curHopoText->setXmlText(style().styleB(Sid::hopoUpperCase) ? (curRegion.isHammerOn ? "H" : "P") : (curRegion.isHammerOn ? "h" : "p"));
     141           75 :         curHopoText->setStartChord(curRegion.startChord);
     142           75 :         curHopoText->setEndChord(curRegion.endChord);
     143              :     }
     144              : 
     145              :     // Delete unused
     146           61 :     if (curRegionIdx < m_hopoText.size()) {
     147            0 :         size_t unusedHopoCount = m_hopoText.size() - curRegionIdx;
     148            0 :         for (size_t i = 0; i < unusedHopoCount; ++i) {
     149            0 :             delete m_hopoText.back();
     150            0 :             m_hopoText.pop_back();
     151              :         }
     152              :     }
     153           61 : }
     154              : 
     155           61 : std::vector<HammerOnPullOffSegment::HopoTextRegion> HammerOnPullOffSegment::computeHopoTextRegions(Chord* startChord, Chord* endChord)
     156              : {
     157           61 :     std::vector<HopoTextRegion> result;
     158           61 :     bool isTabStaff = staffType()->isTabStaff();
     159           61 :     if ((isTabStaff && !style().styleB(Sid::hopoShowOnTabStaves)) || (!isTabStaff && !style().styleB(Sid::hopoShowOnStandardStaves))) {
     160            0 :         return result;
     161              :     }
     162              : 
     163          136 :     for (Chord* curChord = startChord; curChord != endChord;) {
     164           75 :         IF_ASSERT_FAILED(curChord->tick() <= endChord->tick()) {
     165            0 :             break;
     166              :         }
     167              : 
     168           75 :         Chord* nextChord = curChord->next();
     169           75 :         if (!nextChord) {
     170            0 :             break;
     171              :         }
     172              : 
     173           75 :         Note* curNote = nullptr;
     174           75 :         Note* nextNote = nullptr;
     175           75 :         resolveStartEndNotes(&curNote, &nextNote, curChord, nextChord, isTabStaff);
     176              : 
     177           75 :         bool isValid = isTabStaff ? nextNote->string() == curNote->string() : nextNote->pitch() != curNote->pitch();
     178           75 :         bool isHammerOn = isTabStaff ? nextNote->fret() > curNote->fret() : nextNote->pitch() > curNote->pitch();
     179              : 
     180           75 :         bool startNewRegion = result.empty() || style().styleB(Sid::hopoShowAll) || result.back().isHammerOn != isHammerOn;
     181              : 
     182           75 :         if (startNewRegion) {
     183           75 :             HopoTextRegion region;
     184           75 :             region.startChord = curChord;
     185           75 :             region.endChord = nextChord;
     186           75 :             region.isValid = isValid;
     187           75 :             region.isHammerOn = isHammerOn;
     188           75 :             result.push_back(region);
     189              :         } else {
     190            0 :             HopoTextRegion& curRegion = result.back();
     191            0 :             curRegion.endChord = nextChord;
     192            0 :             curRegion.isValid = curRegion.isValid && isValid;
     193              :         }
     194              : 
     195           75 :         curChord = nextChord;
     196              :     }
     197              : 
     198           61 :     return result;
     199            0 : }
     200              : 
     201           75 : void HammerOnPullOffSegment::resolveStartEndNotes(Note** startNote, Note** endNote, Chord* startChord, Chord* endChord, bool isTabStaff)
     202              : {
     203              :     // In future we need the ability to draw slurs between individual notes. For now, slurs are
     204              :     // attached to chords so we just try to guess which notes to consider for this HOPO
     205              : 
     206           75 :     const std::vector<Note*>& startNotes = startChord->notes();
     207           75 :     const std::vector<Note*>& endNotes = endChord->notes();
     208           75 :     int startNotesSize = static_cast<int>(startNotes.size());
     209           75 :     int endNotesSize = static_cast<int>(endNotes.size());
     210           75 :     int largerNoteCount = startNotesSize > endNotesSize ? startNotesSize : endNotesSize;
     211              : 
     212           75 :     for (int i = largerNoteCount - 1; i >= 0; --i) {
     213           75 :         int startNoteIdx = std::min(i, startNotesSize - 1);
     214           75 :         int endNoteIdx = std::min(i, endNotesSize - 1);
     215           75 :         Note* sNote = startNotes[startNoteIdx];
     216           75 :         Note* eNote = endNotes[endNoteIdx];
     217           75 :         if (sNote->tieFor() && sNote->tieFor() == eNote->tieBack()) {
     218            0 :             continue;
     219              :         }
     220              : 
     221           75 :         bool usablePair = sNote->pitch() != eNote->pitch();
     222           75 :         if (isTabStaff) {
     223           12 :             usablePair &= sNote->string() == eNote->string();
     224              :         }
     225              : 
     226           75 :         if (usablePair) {
     227           75 :             *startNote = sNote;
     228           75 :             *endNote = eNote;
     229           75 :             return;
     230              :         }
     231              :     }
     232              : 
     233              :     // Couldn't find a usable pair
     234            0 :     *startNote = startChord->upNote();
     235            0 :     *endNote = endChord->upNote();
     236              : }
     237              : 
     238           48 : bool HammerOnPullOffSegment::isUserModified() const
     239              : {
     240           48 :     for (const HammerOnPullOffText* hopoText : m_hopoText) {
     241           48 :         if (hopoText->isUserModified()) {
     242           48 :             return true;
     243              :         }
     244              :     }
     245              : 
     246            0 :     return SlurSegment::isUserModified();
     247              : }
     248              : 
     249            0 : bool HammerOnPullOffSegment::isValid() const
     250              : {
     251            0 :     for (const HammerOnPullOffText* hopoText : m_hopoText) {
     252            0 :         if (!hopoText->isValid()) {
     253            0 :             return false;
     254              :         }
     255              :     }
     256              : 
     257            0 :     return true;
     258              : }
     259              : 
     260            0 : void HammerOnPullOffSegment::reset()
     261              : {
     262            0 :     for (HammerOnPullOffText* hopoText : m_hopoText) {
     263            0 :         hopoText->reset();
     264              :     }
     265              : 
     266            0 :     SlurTieSegment::reset();
     267            0 : }
     268              : 
     269              : static ElementStyle hopoStyle;
     270              : 
     271           69 : HammerOnPullOffText::HammerOnPullOffText(HammerOnPullOffSegment* parent)
     272            0 :     : TextBase(ElementType::HAMMER_ON_PULL_OFF_TEXT, parent, TextStyleType::HAMMER_ON_PULL_OFF,
     273           69 :                ElementFlag::MOVABLE | ElementFlag::GENERATED | ElementFlag::ON_STAFF)
     274              : {
     275           69 :     resetProperty(Pid::PLACEMENT);
     276           69 :     initElementStyle(&hopoStyle);
     277           69 : }
     278              : 
     279            0 : HammerOnPullOffText::HammerOnPullOffText(const HammerOnPullOffText& h)
     280            0 :     : TextBase(h)
     281              : {
     282            0 : }
     283              : 
     284            0 : std::vector<LineF> HammerOnPullOffText::dragAnchorLines() const
     285              : {
     286            0 :     std::vector<LineF> result;
     287              : 
     288            0 :     PointF p1 = canvasPos();
     289              : 
     290            0 :     HammerOnPullOffSegment* hopoSeg = hopoSegment();
     291            0 :     const Shape& hopoSegShape = hopoSeg->ldata()->shape();
     292            0 :     double x = ldata()->pos().x();// + hopoSegShape.bbox().x();
     293            0 :     double y = hopoSeg->hammerOnPullOff()->up() ? hopoSegShape.topAtX(x) : hopoSegShape.bottomAtX(x);
     294              : 
     295            0 :     PointF p2 = PointF(x, y) + hopoSeg->canvasPos();
     296              : 
     297            0 :     result.push_back(LineF(p1, p2));
     298              : 
     299            0 :     return result;
     300            0 : }
     301              : 
     302          103 : bool HammerOnPullOffText::isUserModified() const
     303              : {
     304          721 :     for (const TextStyleProperty& p : *textStyle(textStyleType())) {
     305          721 :         if (getProperty(p.pid) != propertyDefault(p.pid)) {
     306          103 :             return true;
     307              :         }
     308              :     }
     309              : 
     310            0 :     return TextBase::isUserModified();
     311              : }
     312              : 
     313            0 : Color HammerOnPullOffText::curColor(const rendering::PaintOptions& opt) const
     314              : {
     315            0 :     if (!isValid() && MScore::warnGuitarBends && !opt.isPrinting) {
     316            0 :         return selected() ? configuration()->criticalSelectedColor() : configuration()->criticalColor();
     317              :     }
     318              : 
     319            0 :     return TextBase::curColor(opt);
     320              : }
     321              : 
     322         1340 : PropertyValue HammerOnPullOffText::propertyDefault(Pid id) const
     323              : {
     324         1340 :     switch (id) {
     325          124 :     case Pid::PLACEMENT:
     326          124 :         return PlacementV::ABOVE;
     327         1216 :     default:
     328         1216 :         return TextBase::propertyDefault(id);
     329              :     }
     330              : }
     331              : } // namespace mu::engraving
        

Generated by: LCOV version 2.0-1