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 : }
|