LCOV - code coverage report
Current view: top level - src/notationscene/widgets - editstringdata.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 162 0
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 "editstringdata.h"
      24              : 
      25              : #include <QKeyEvent>
      26              : 
      27              : #include "translation.h"
      28              : #include "global/utils.h"
      29              : 
      30              : #include "editpitch.h"
      31              : 
      32              : #include "engraving/dom/staff.h"
      33              : #include "engraving/dom/stringdata.h"
      34              : #include "engraving/dom/stringtunings.h"
      35              : #include "engraving/editing/editpart.h"
      36              : 
      37              : #include "notation/inotation.h"
      38              : #include "notation/inotationinteraction.h"
      39              : #include "notation/inotationselection.h"
      40              : 
      41              : #include "ui/view/widgetstatestore.h"
      42              : #include "ui/view/widgetnavigationfix.h"
      43              : 
      44              : static const int OPEN_ACCESSIBLE_TITLE_ROLE = Qt::UserRole + 1;
      45              : 
      46              : using namespace mu::engraving;
      47              : using namespace mu::notation;
      48              : using namespace muse::ui;
      49              : 
      50              : //---------------------------------------------------------
      51              : //   EditStringData
      52              : //    To edit the string data (tuning and number of frets) for an instrument
      53              : //---------------------------------------------------------
      54              : 
      55            0 : EditStringData::EditStringData(QWidget* parent, const std::vector<instrString>& strings, int frets)
      56            0 :     : muse::ui::WidgetDialog(parent)
      57              : {
      58            0 :     setObjectName("EditStringData");
      59            0 :     setupUi(this);
      60            0 :     setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
      61            0 :     setWindowModality(Qt::WindowModal);
      62              : 
      63            0 :     _strings = strings;
      64            0 :     _frets = frets;
      65              : 
      66            0 :     qApp->installEventFilter(this);
      67            0 : }
      68              : 
      69            0 : void EditStringData::componentComplete()
      70              : {
      71            0 :     if (_strings.empty()) {
      72            0 :         initStringsData();
      73              :     }
      74              : 
      75            0 :     init();
      76              : 
      77              :     //! NOTE: It is necessary for the correct start of navigation in the dialog
      78            0 :     setFocus();
      79            0 : }
      80              : 
      81            0 : std::vector<instrString> EditStringData::strings() const
      82              : {
      83            0 :     return _strings;
      84              : }
      85              : 
      86            0 : int EditStringData::frets() const
      87              : {
      88            0 :     return _frets;
      89              : }
      90              : 
      91            0 : void EditStringData::showEvent(QShowEvent* event)
      92              : {
      93            0 :     WidgetStateStore::restoreGeometry(this);
      94            0 :     QDialog::showEvent(event);
      95            0 : }
      96              : 
      97            0 : void EditStringData::hideEvent(QHideEvent* ev)
      98              : {
      99            0 :     WidgetStateStore::saveGeometry(this);
     100            0 :     QWidget::hideEvent(ev);
     101            0 : }
     102              : 
     103            0 : bool EditStringData::eventFilter(QObject* obj, QEvent* event)
     104              : {
     105            0 :     if (event->type() == QEvent::KeyPress) {
     106            0 :         QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>(event);
     107            0 :         if (keyEvent
     108            0 :             && WidgetNavigationFix::fixNavigationForTableWidget(
     109            0 :                 WidgetNavigationFix::NavigationChain { stringList, newString, buttonBox },
     110              :                 keyEvent->key())) {
     111            0 :             return true;
     112              :         }
     113              :     }
     114              : 
     115            0 :     return QDialog::eventFilter(obj, event);
     116              : }
     117              : 
     118            0 : QString EditStringData::openColumnAccessibleText(const QTableWidgetItem* item) const
     119              : {
     120            0 :     return item->data(OPEN_ACCESSIBLE_TITLE_ROLE).toString() + ": "
     121            0 :            + (item->checkState() == Qt::Checked ? muse::qtrc("ui", "checked", "checkstate") : muse::qtrc("ui", "unchecked", "checkstate"));
     122              : }
     123              : 
     124            0 : INotationSelectionPtr EditStringData::currentNotationSelection() const
     125              : {
     126            0 :     notation::INotationPtr currentNotation = projectSession()->currentNotation();
     127            0 :     return currentNotation ? currentNotation->interaction()->selection() : nullptr;
     128            0 : }
     129              : 
     130              : //---------------------------------------------------------
     131              : //   deleteStringClicked
     132              : //---------------------------------------------------------
     133              : 
     134            0 : void EditStringData::deleteStringClicked()
     135              : {
     136            0 :     int i = stringList->currentRow();
     137              : 
     138              :     // remove item from local string list and from dlg list control
     139            0 :     _stringsLoc.erase(_stringsLoc.begin() + i);
     140            0 :     stringList->model()->removeRow(i);
     141              :     // if no more items, disable buttons acting on individual string
     142            0 :     if (stringList->rowCount() == 0) {
     143            0 :         editString->setEnabled(false);
     144            0 :         deleteString->setEnabled(false);
     145              :     }
     146            0 :     _modified = true;
     147            0 : }
     148              : 
     149              : //---------------------------------------------------------
     150              : //   editStringClicked
     151              : //---------------------------------------------------------
     152              : 
     153            0 : void EditStringData::editStringClicked()
     154              : {
     155            0 :     int i = stringList->currentRow();
     156              :     int newCode;
     157              : 
     158            0 :     EditPitch* ep = new EditPitch(this, _stringsLoc[i].pitch);
     159            0 :     if ((newCode=ep->exec()) != -1) {
     160              :         // update item value in local string list and item text in dlg list control
     161            0 :         _stringsLoc[i].pitch = newCode;
     162            0 :         QTableWidgetItem* item = stringList->item(i, 1);
     163            0 :         item->setText(midiCodeToStr(newCode));
     164            0 :         _modified = true;
     165              :     }
     166            0 : }
     167              : 
     168              : //---------------------------------------------------------
     169              : //   listItemClicked
     170              : //---------------------------------------------------------
     171              : 
     172            0 : void EditStringData::listItemClicked(QTableWidgetItem* item)
     173              : {
     174            0 :     int col = item->column();
     175            0 :     if (col != 0) {                 // ignore clicks not on check boxes
     176            0 :         return;
     177              :     }
     178            0 :     int row = item->row();
     179              : 
     180              :     // flip openness in local string list, then sync dlg list ctrl
     181            0 :     bool open = !_stringsLoc[row].open;
     182            0 :     _stringsLoc[row].open = open;
     183            0 :     stringList->item(row, col)->setCheckState(open ? Qt::Checked : Qt::Unchecked);
     184            0 :     _modified = true;
     185              : }
     186              : 
     187              : //---------------------------------------------------------
     188              : //   newStringClicked
     189              : //---------------------------------------------------------
     190              : 
     191            0 : void EditStringData::newStringClicked()
     192              : {
     193              :     int i, newCode;
     194              : 
     195            0 :     EditPitch* ep = new EditPitch(this);
     196            0 :     if ((newCode=ep->exec()) != -1) {
     197              :         // add below selected string or at the end if no selected string
     198            0 :         i = stringList->currentRow() + 1;
     199            0 :         if (i <= 0) {
     200            0 :             i = stringList->rowCount();
     201              :         }
     202              : 
     203              :         // insert in local string list and in dlg list control
     204            0 :         mu::engraving::instrString strg = { newCode, 0 };
     205            0 :         _stringsLoc.insert(_stringsLoc.begin() + i, strg);
     206            0 :         stringList->insertRow(i);
     207              : 
     208            0 :         QTableWidgetItem* newCheck = new QTableWidgetItem();
     209            0 :         newCheck->setFlags(Qt::ItemFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled));
     210            0 :         newCheck->setCheckState(strg.open ? Qt::Checked : Qt::Unchecked);
     211            0 :         newCheck->setData(Qt::AccessibleTextRole, openColumnAccessibleText(newCheck));
     212            0 :         stringList->setItem(i, 0, newCheck);
     213              : 
     214            0 :         QTableWidgetItem* newPitch = new QTableWidgetItem(midiCodeToStr(strg.pitch));
     215            0 :         stringList->setItem(i, 1, newPitch);
     216              : 
     217              :         // select last added item and ensure buttons are active
     218            0 :         stringList->setCurrentCell(i, 1);
     219            0 :         editString->setEnabled(true);
     220            0 :         deleteString->setEnabled(true);
     221            0 :         _modified = true;
     222              :     }
     223            0 : }
     224              : 
     225            0 : void EditStringData::init()
     226              : {
     227            0 :     numOfFrets->setValue(_frets);
     228              : 
     229            0 :     stringList->setHorizontalHeaderLabels({ muse::qtrc("notation/editstringdata", "Always open"),
     230              :                                             muse::qtrc("notation/editstringdata", "Pitch") });
     231              :     QString toolTip = muse::qtrc("notation/editstringdata",
     232            0 :                                  "<b>Always open</b><br>On tablature staves, fret positions other than ‘0’ cannot be entered on strings marked ‘always open’. Useful for instruments with strings that are not on the fretboard, such as the theorbo.");
     233            0 :     stringList->horizontalHeaderItem(0)->setToolTip(toolTip);
     234            0 :     int numOfStrings = static_cast<int>(_strings.size());
     235            0 :     stringList->setRowCount(numOfStrings);
     236              :     // if any string, insert into string list control and select the first one
     237              : 
     238            0 :     if (numOfStrings > 0) {
     239            0 :         mu::engraving::instrString strg;
     240              :         // insert into local working copy and into string list dlg control
     241              :         // IN REVERSED ORDER
     242            0 :         for (int i = 0; i < numOfStrings; i++) {
     243            0 :             strg = _strings[numOfStrings - i - 1];
     244            0 :             _stringsLoc.push_back(strg);
     245            0 :             QTableWidgetItem* newCheck = new QTableWidgetItem();
     246            0 :             newCheck->setFlags(Qt::ItemFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled));
     247            0 :             newCheck->setCheckState(strg.open ? Qt::Checked : Qt::Unchecked);
     248              : 
     249            0 :             newCheck->setData(OPEN_ACCESSIBLE_TITLE_ROLE, stringList->horizontalHeaderItem(0)->text());
     250            0 :             newCheck->setToolTip(toolTip);
     251            0 :             newCheck->setData(Qt::AccessibleTextRole, openColumnAccessibleText(newCheck));
     252              : 
     253            0 :             stringList->setItem(i, 0, newCheck);
     254            0 :             QTableWidgetItem* newPitch = new QTableWidgetItem(midiCodeToStr(strg.pitch));
     255            0 :             stringList->setItem(i, 1, newPitch);
     256              :         }
     257            0 :         stringList->setCurrentCell(0, 1);
     258              :     }
     259              :     // if no string yet, disable buttons acting on individual string
     260              :     else {
     261            0 :         editString->setEnabled(false);
     262            0 :         deleteString->setEnabled(false);
     263              :     }
     264              : 
     265            0 :     connect(stringList, &QTableWidget::itemChanged, this, [this](QTableWidgetItem* item){
     266            0 :         if (item->column() == 0) {
     267            0 :             item->setData(Qt::AccessibleTextRole, openColumnAccessibleText(item));
     268              :         }
     269            0 :     });
     270              : 
     271            0 :     connect(deleteString, &QPushButton::clicked, this, &EditStringData::deleteStringClicked);
     272            0 :     connect(editString,   &QPushButton::clicked, this, &EditStringData::editStringClicked);
     273            0 :     connect(newString,    &QPushButton::clicked, this, &EditStringData::newStringClicked);
     274              : 
     275            0 :     connect(stringList,   &QTableWidget::itemClicked,       this, &EditStringData::listItemClicked);
     276            0 :     connect(stringList,   &QTableWidget::itemDoubleClicked, this, &EditStringData::editStringClicked);
     277              : 
     278            0 :     _modified = false;
     279            0 : }
     280              : 
     281            0 : void EditStringData::initStringsData()
     282              : {
     283            0 :     engraving::StringTunings* stringTunings = nullptr;
     284            0 :     for (EngravingItem* element : currentNotationSelection()->elements()) {
     285            0 :         if (element && element->isStringTunings()) {
     286            0 :             stringTunings = toStringTunings(element);
     287            0 :             break;
     288              :         }
     289              :     }
     290              : 
     291            0 :     if (!stringTunings) {
     292            0 :         return;
     293              :     }
     294              : 
     295            0 :     Part* part = stringTunings->staff()->part();
     296              : 
     297            0 :     auto it = muse::findLessOrEqual(part->instruments(), stringTunings->tick().ticks());
     298            0 :     if (it == part->instruments().cend()) {
     299            0 :         return;
     300              :     }
     301              : 
     302            0 :     m_instrument = it->second;
     303            0 :     _strings = m_instrument->stringData()->stringList();
     304            0 :     _frets = m_instrument->stringData()->frets();
     305              : 
     306            0 :     m_updateOnExit = true;
     307              : }
     308              : 
     309              : //---------------------------------------------------------
     310              : //   accept
     311              : //---------------------------------------------------------
     312              : 
     313            0 : void EditStringData::accept()
     314              : {
     315              :     // store data back into original variables
     316              :     // string tunings are copied in reversed order (from lowest to highest)
     317            0 :     if (_modified) {
     318            0 :         _strings.clear();
     319            0 :         for (int i = static_cast<int>(_stringsLoc.size()) - 1; i >= 0; i--) {
     320            0 :             _strings.push_back(_stringsLoc[i]);
     321              :         }
     322              :     }
     323            0 :     if (_frets != numOfFrets->value()) {
     324            0 :         _frets = numOfFrets->value();
     325            0 :         _modified = true;
     326              :     }
     327              : 
     328            0 :     if (_modified) {
     329            0 :         if (m_updateOnExit) {
     330            0 :             StringData newStringData(_frets, _strings);
     331            0 :             mu::engraving::EngravingItem* lastHit = currentNotationSelection()->lastElementHit();
     332            0 :             if (lastHit) {
     333            0 :                 lastHit->score()->undo(new ChangeStringData(m_instrument, newStringData));
     334              :             }
     335            0 :         }
     336              : 
     337            0 :         QDialog::accept();
     338              :     } else {
     339            0 :         QDialog::reject();                // if no data change, no need to trigger changes downward the caller chain
     340              :     }
     341            0 : }
     342              : 
     343            0 : QString EditStringData::midiCodeToStr(int midiCode)
     344              : {
     345            0 :     return QString::fromStdString(muse::pitchToString(midiCode));
     346              : }
        

Generated by: LCOV version 2.0-1