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 "measureproperties.h"
24 :
25 : #include <QKeyEvent>
26 :
27 : #include "translation.h"
28 :
29 : #include "engraving/dom/masterscore.h"
30 : #include "engraving/dom/measure.h"
31 : #include "engraving/dom/measurerepeat.h"
32 : #include "engraving/dom/range.h"
33 : #include "engraving/dom/sig.h"
34 : #include "engraving/editing/editstaff.h"
35 :
36 : #include "notation/inotationelements.h" // IWYU pragma: keep
37 : #include "notation/inotationinteraction.h"
38 : #include "notation/inotationundostack.h"
39 :
40 : #include "ui/view/widgetnavigationfix.h"
41 : #include "ui/view/widgetstatestore.h"
42 : #include "ui/view/widgetutils.h"
43 :
44 : using namespace mu::notation;
45 : using namespace muse::ui;
46 :
47 : static const int ITEM_ACCESSIBLE_TITLE_ROLE = Qt::UserRole + 1;
48 :
49 0 : MeasurePropertiesDialog::MeasurePropertiesDialog(QWidget* parent)
50 0 : : muse::ui::WidgetDialog(parent)
51 : {
52 0 : setObjectName("MeasureProperties");
53 0 : setupUi(this);
54 0 : setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
55 :
56 0 : qApp->installEventFilter(this);
57 0 : }
58 :
59 0 : void MeasurePropertiesDialog::componentComplete()
60 : {
61 0 : m_notation = context()->currentNotation();
62 0 : initMeasure();
63 :
64 0 : staves->verticalHeader()->hide();
65 :
66 0 : nextButton->setText(iconCodeToChar(IconCode::Code::ARROW_RIGHT));
67 0 : previousButton->setText(iconCodeToChar(IconCode::Code::ARROW_LEFT));
68 :
69 0 : connect(buttonBox, &QDialogButtonBox::clicked, this, &MeasurePropertiesDialog::bboxClicked);
70 0 : connect(nextButton, &QToolButton::clicked, this, &MeasurePropertiesDialog::gotoNextMeasure);
71 0 : connect(previousButton, &QToolButton::clicked, this, &MeasurePropertiesDialog::gotoPreviousMeasure);
72 :
73 0 : if (qApp->layoutDirection() == Qt::LayoutDirection::RightToLeft) {
74 0 : horizontalLayout_2->removeWidget(nextButton);
75 0 : horizontalLayout_2->insertWidget(0, nextButton);
76 : }
77 :
78 0 : WidgetUtils::setWidgetIcon(previousButton, IconCode::Code::ARROW_LEFT);
79 0 : WidgetUtils::setWidgetIcon(nextButton, IconCode::Code::ARROW_RIGHT);
80 :
81 : //! NOTE: It is necessary for the correct start of navigation in the dialog
82 0 : setFocus();
83 0 : }
84 :
85 0 : void MeasurePropertiesDialog::initMeasure()
86 : {
87 0 : if (!m_notation) {
88 0 : return;
89 : }
90 :
91 0 : mu::engraving::Measure* measure = m_notation->interaction()->selectedMeasure();
92 :
93 0 : IF_ASSERT_FAILED(measure) {
94 0 : return;
95 : }
96 :
97 0 : setMeasure(measure);
98 : }
99 :
100 : //---------------------------------------------------------
101 : // getNextMeasure
102 : // skip multi measure rests
103 : //---------------------------------------------------------
104 :
105 0 : mu::engraving::Measure* getNextMeasure(mu::engraving::Measure* m)
106 : {
107 0 : mu::engraving::Measure* mm = m->nextMeasureMM();
108 0 : while (mm && mm->isMMRest()) {
109 0 : mm = mm->nextMeasureMM();
110 : }
111 0 : return mm;
112 : }
113 :
114 : //---------------------------------------------------------
115 : // getPrevMeasure
116 : // skip multi measure rests
117 : //---------------------------------------------------------
118 :
119 0 : mu::engraving::Measure* getPrevMeasure(mu::engraving::Measure* m)
120 : {
121 0 : mu::engraving::Measure* mm = m->prevMeasureMM();
122 0 : while (mm && mm->isMMRest()) {
123 0 : mm = mm->prevMeasureMM();
124 : }
125 0 : return mm;
126 : }
127 :
128 : //---------------------------------------------------------
129 : // gotoNextMeasure
130 : //---------------------------------------------------------
131 :
132 0 : void MeasurePropertiesDialog::gotoNextMeasure()
133 : {
134 0 : apply();
135 0 : if (getNextMeasure(m_measure)) {
136 0 : setMeasure(getNextMeasure(m_measure));
137 : }
138 0 : nextButton->setEnabled(getNextMeasure(m_measure));
139 0 : previousButton->setEnabled(getPrevMeasure(m_measure));
140 0 : m_notation->notationChanged().send(muse::RectF());
141 0 : }
142 :
143 : //---------------------------------------------------------
144 : // gotoPreviousMeasure
145 : //---------------------------------------------------------
146 :
147 0 : void MeasurePropertiesDialog::gotoPreviousMeasure()
148 : {
149 0 : apply();
150 0 : if (getPrevMeasure(m_measure)) {
151 0 : setMeasure(getPrevMeasure(m_measure));
152 : }
153 0 : nextButton->setEnabled(getNextMeasure(m_measure));
154 0 : previousButton->setEnabled(getPrevMeasure(m_measure));
155 0 : m_notation->notationChanged().send(muse::RectF());
156 0 : }
157 :
158 0 : bool MeasurePropertiesDialog::eventFilter(QObject* obj, QEvent* event)
159 : {
160 0 : if (event->type() == QEvent::KeyPress) {
161 0 : QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>(event);
162 0 : if (keyEvent
163 0 : && WidgetNavigationFix::fixNavigationForTableWidget(
164 0 : WidgetNavigationFix::NavigationChain { staves, actualZ, buttonBox },
165 : keyEvent->key())) {
166 0 : return true;
167 : }
168 : }
169 :
170 0 : return QDialog::eventFilter(obj, event);
171 : }
172 :
173 : //---------------------------------------------------------
174 : // setMeasure
175 : //---------------------------------------------------------
176 :
177 0 : void MeasurePropertiesDialog::setMeasure(mu::engraving::Measure* measure)
178 : {
179 0 : m_measure = measure;
180 0 : nextButton->setEnabled(m_measure->nextMeasure() != 0);
181 0 : previousButton->setEnabled(m_measure->prevMeasure() != 0);
182 :
183 0 : setWindowTitle(muse::qtrc("notation/measureproperties", "Properties for measure %1").arg(m_measure->measureNumber() + 1));
184 0 : m_notation->interaction()->clearSelection();
185 0 : m_notation->interaction()->select({ m_measure }, mu::engraving::SelectType::ADD, 0);
186 :
187 0 : actualZ->setValue(m_measure->ticks().numerator());
188 0 : int index = actualN->findText(QString::number(m_measure->ticks().denominator()));
189 0 : if (index == -1) {
190 0 : index = 2;
191 : }
192 0 : actualN->setCurrentIndex(index);
193 0 : nominalZ->setNum(m_measure->timesig().numerator());
194 0 : nominalN->setNum(m_measure->timesig().denominator());
195 :
196 0 : excludeFromNumbering->setChecked(m_measure->excludeFromNumbering());
197 0 : breakMultiMeasureRest->setChecked(m_measure->breakMultiMeasureRest());
198 0 : int n = m_measure->repeatCount();
199 0 : count->setValue(n);
200 0 : bool enableCount = m_measure->repeatEnd();
201 0 : count->setEnabled(enableCount);
202 0 : count->setVisible(enableCount);
203 0 : labelCount->setVisible(enableCount);
204 0 : layoutStretch->setValue(m_measure->userStretch());
205 0 : measureNumberMode->setCurrentIndex(int(m_measure->measureNumberMode()));
206 0 : measureNumberOffset->setValue(m_measure->measureNumberOffset());
207 :
208 0 : mu::engraving::Score* score = m_measure->score();
209 0 : size_t rows = score->nstaves();
210 0 : staves->setRowCount(static_cast<int>(rows));
211 0 : staves->setColumnCount(3);
212 :
213 0 : auto itemAccessibleText = [](const QTableWidgetItem* item){
214 0 : return item->data(ITEM_ACCESSIBLE_TITLE_ROLE).toString() + ": "
215 0 : + (item->checkState() == Qt::Checked ? muse::qtrc("ui", "checked", "checkstate") : muse::qtrc("ui", "unchecked",
216 0 : "checkstate"));
217 : };
218 :
219 0 : for (size_t staffIdx = 0; staffIdx < rows; ++staffIdx) {
220 0 : QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(staffIdx + 1));
221 0 : staves->setItem(static_cast<int>(staffIdx), 0, item);
222 :
223 0 : item = new QTableWidgetItem();
224 0 : item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
225 0 : item->setCheckState(m_measure->visible(staffIdx) ? Qt::Checked : Qt::Unchecked);
226 0 : if (rows == 1) { // cannot be invisible if only one row
227 0 : item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
228 : }
229 0 : item->setData(ITEM_ACCESSIBLE_TITLE_ROLE, muse::qtrc("notation/measureproperties", "Visible"));
230 0 : item->setData(Qt::AccessibleTextRole, itemAccessibleText(item));
231 0 : staves->setItem(static_cast<int>(staffIdx), 1, item);
232 :
233 0 : item = new QTableWidgetItem();
234 0 : item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
235 0 : item->setCheckState(m_measure->stemless(staffIdx) ? Qt::Checked : Qt::Unchecked);
236 0 : item->setData(ITEM_ACCESSIBLE_TITLE_ROLE, muse::qtrc("notation/measureproperties", "Stemless"));
237 0 : item->setData(Qt::AccessibleTextRole, itemAccessibleText(item));
238 0 : staves->setItem(static_cast<int>(staffIdx), 2, item);
239 : }
240 :
241 0 : connect(staves, &QTableWidget::itemChanged, this, [&itemAccessibleText](QTableWidgetItem* item){
242 0 : item->setData(Qt::AccessibleTextRole, itemAccessibleText(item));
243 0 : });
244 0 : }
245 :
246 : //---------------------------------------------------------
247 : // bboxClicked
248 : //---------------------------------------------------------
249 :
250 0 : void MeasurePropertiesDialog::bboxClicked(QAbstractButton* button)
251 : {
252 0 : switch (buttonBox->buttonRole(button)) {
253 0 : case QDialogButtonBox::ApplyRole:
254 : case QDialogButtonBox::AcceptRole:
255 0 : apply();
256 0 : break;
257 0 : default:
258 0 : break;
259 : }
260 0 : }
261 :
262 : //---------------------------------------------------------
263 : // visible
264 : //---------------------------------------------------------
265 :
266 0 : bool MeasurePropertiesDialog::visible(int staffIdx)
267 : {
268 0 : QTableWidgetItem* item = staves->item(staffIdx, 1);
269 0 : return item->checkState() == Qt::Checked;
270 : }
271 :
272 : //---------------------------------------------------------
273 : // stemless
274 : //---------------------------------------------------------
275 :
276 0 : bool MeasurePropertiesDialog::stemless(int staffIdx)
277 : {
278 0 : QTableWidgetItem* item = staves->item(staffIdx, 2);
279 0 : return item->checkState() == Qt::Checked;
280 : }
281 :
282 : //---------------------------------------------------------
283 : // sig
284 : //---------------------------------------------------------
285 :
286 0 : mu::engraving::Fraction MeasurePropertiesDialog::len() const
287 : {
288 0 : return mu::engraving::Fraction(actualZ->value(), 1 << actualN->currentIndex());
289 : }
290 :
291 : //---------------------------------------------------------
292 : // repeatCount
293 : //---------------------------------------------------------
294 :
295 0 : int MeasurePropertiesDialog::repeatCount() const
296 : {
297 0 : return count->value();
298 : }
299 :
300 : //---------------------------------------------------------
301 : // apply
302 : //---------------------------------------------------------
303 :
304 0 : void MeasurePropertiesDialog::apply()
305 : {
306 0 : if (!m_notation || !m_measure) {
307 0 : return;
308 : }
309 :
310 0 : mu::engraving::Score* score = m_measure->score();
311 :
312 0 : m_notation->undoStack()->prepareChanges(muse::TranslatableString("undoableAction", "Edit measure properties"));
313 0 : bool propertiesChanged = false;
314 0 : for (size_t staffIdx = 0; staffIdx < score->nstaves(); ++staffIdx) {
315 0 : bool v = visible(static_cast<int>(staffIdx));
316 0 : bool s = stemless(static_cast<int>(staffIdx));
317 0 : if (m_measure->visible(staffIdx) != v || m_measure->stemless(staffIdx) != s) {
318 0 : score->undo(new mu::engraving::ChangeMStaffProperties(m_measure, static_cast<int>(staffIdx), v, s));
319 0 : propertiesChanged = true;
320 : }
321 : }
322 :
323 0 : m_measure->undoChangeProperty(mu::engraving::Pid::REPEAT_COUNT, repeatCount());
324 0 : m_measure->undoChangeProperty(mu::engraving::Pid::BREAK_MMR, breakMultiMeasureRest->isChecked());
325 0 : m_measure->undoChangeProperty(mu::engraving::Pid::USER_STRETCH, layoutStretch->value());
326 0 : m_measure->undoChangeProperty(mu::engraving::Pid::MEASURE_NUMBER_MODE, measureNumberMode->currentIndex());
327 0 : m_measure->undoChangeProperty(mu::engraving::Pid::MEASURE_NUMBER_OFFSET, measureNumberOffset->value());
328 0 : m_measure->undoChangeProperty(mu::engraving::Pid::EXCLUDE_FROM_NUMBERING, excludeFromNumbering->isChecked());
329 :
330 0 : if (m_measure->ticks() != len()) {
331 0 : mu::engraving::ScoreRange range;
332 0 : range.read(m_measure->first(), m_measure->last());
333 0 : m_measure->adjustToLen(len());
334 0 : }
335 :
336 0 : if (propertiesChanged) {
337 0 : m_measure->triggerLayout();
338 : }
339 0 : m_notation->undoStack()->commitChanges();
340 :
341 0 : m_notation->interaction()->select({ m_measure }, mu::engraving::SelectType::SINGLE, 0);
342 0 : m_notation->notationChanged().send(muse::RectF());
343 : }
344 :
345 0 : void MeasurePropertiesDialog::showEvent(QShowEvent* event)
346 : {
347 0 : WidgetStateStore::restoreGeometry(this);
348 0 : QDialog::showEvent(event);
349 0 : }
350 :
351 0 : void MeasurePropertiesDialog::hideEvent(QHideEvent* event)
352 : {
353 0 : WidgetStateStore::saveGeometry(this);
354 0 : QDialog::hideEvent(event);
355 0 : }
|