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 <QDir>
24 : #include <QAction>
25 : #include <QMouseEvent>
26 : #include <QDragEnterEvent>
27 : #include <QMimeData>
28 : #include <QVBoxLayout>
29 :
30 : #include "keyedit.h"
31 :
32 : #include "modularity/ioc.h"
33 : #include "translation.h"
34 :
35 : #include "engraving/dom/accidental.h"
36 : #include "engraving/dom/clef.h"
37 : #include "engraving/dom/dummyparent.h"
38 : #include "engraving/dom/factory.h"
39 : #include "engraving/dom/keysig.h"
40 : #include "engraving/dom/masterscore.h"
41 : #include "engraving/dom/mscore.h"
42 : #include "engraving/rendering/iscorerenderer.h"
43 : #include "engraving/rw/rwregister.h"
44 : #include "engraving/style/defaultstyle.h"
45 : #include "engraving/types/symnames.h"
46 :
47 : #include "keycanvas.h"
48 : #include "palettewidget.h"
49 : #include "internal/palettecreator.h"
50 :
51 : #include "log.h"
52 :
53 : using namespace mu;
54 : using namespace muse::draw;
55 : using namespace mu::engraving;
56 : using namespace mu::palette;
57 :
58 : //---------------------------------------------------------
59 : // KeyCanvas
60 : //---------------------------------------------------------
61 :
62 0 : KeyCanvas::KeyCanvas(QWidget* parent)
63 0 : : QFrame(parent), muse::Contextable(muse::iocCtxForQWidget(this))
64 : {
65 0 : setAcceptDrops(true);
66 0 : qreal mag = configuration()->paletteSpatium() * configuration()->paletteScaling()
67 0 : / paletteScoreProvider()->paletteScore()->style().spatium();
68 0 : _matrix = QTransform(mag, 0.0, 0.0, mag, 0.0, 0.0);
69 0 : imatrix = _matrix.inverted();
70 0 : dragElement = 0;
71 0 : setFocusPolicy(Qt::StrongFocus);
72 0 : QAction* a = new QAction("delete", this);
73 0 : a->setShortcut(Qt::Key_Delete);
74 0 : addAction(a);
75 0 : clef = Factory::createClef(paletteScoreProvider()->paletteScore()->dummy());
76 0 : clef->setClefType(ClefType::G);
77 0 : connect(a, &QAction::triggered, this, &KeyCanvas::deleteElement);
78 0 : }
79 :
80 : //---------------------------------------------------------
81 : // delete
82 : //---------------------------------------------------------
83 :
84 0 : void KeyCanvas::deleteElement()
85 : {
86 0 : foreach (Accidental* a, accidentals) {
87 0 : if (a->selected()) {
88 0 : accidentals.removeOne(a);
89 0 : delete a;
90 0 : update();
91 0 : break;
92 : }
93 0 : }
94 0 : }
95 :
96 : //---------------------------------------------------------
97 : // clear
98 : //---------------------------------------------------------
99 :
100 0 : void KeyCanvas::clear()
101 : {
102 0 : foreach (Accidental* a, accidentals) {
103 0 : delete a;
104 0 : }
105 0 : accidentals.clear();
106 0 : update();
107 0 : }
108 :
109 : //---------------------------------------------------------
110 : // paintEvent
111 : //---------------------------------------------------------
112 :
113 0 : void KeyCanvas::paintEvent(QPaintEvent*)
114 : {
115 0 : muse::draw::Painter painter(this, "keycanvas");
116 0 : painter.setAntialiasing(true);
117 0 : qreal wh = double(height());
118 0 : qreal ww = double(width());
119 0 : double y = wh * .5 - 2 * configuration()->paletteSpatium() * extraMag;
120 :
121 0 : const double paletteScoreSpatium = paletteScoreProvider()->paletteScore()->style().spatium();
122 0 : qreal mag = configuration()->paletteSpatium() * extraMag / paletteScoreSpatium;
123 0 : _matrix = QTransform(mag, 0.0, 0.0, mag, 0.0, y);
124 0 : imatrix = _matrix.inverted();
125 :
126 0 : qreal x = 3;
127 0 : qreal w = ww - 6;
128 :
129 0 : painter.setWorldTransform(Transform::fromQTransform(_matrix));
130 :
131 0 : QRectF r = imatrix.mapRect(QRectF(x, y, w, wh));
132 :
133 0 : RectF background = RectF::fromQRectF(imatrix.mapRect(QRectF(0, 0, ww, wh)));
134 0 : painter.fillRect(background, notationConfiguration()->foregroundColor());
135 :
136 0 : rendering::PaintOptions opt;
137 0 : opt.invertColors = notationConfiguration()->shouldInvertScore();
138 :
139 0 : muse::draw::Pen pen(engravingConfiguration()->displayedDefaultColor(opt.invertColors));
140 0 : pen.setWidthF(engraving::DefaultStyle::defaultStyle().styleS(
141 0 : Sid::staffLineWidth).val() * paletteScoreSpatium);
142 0 : painter.setPen(pen);
143 :
144 0 : for (int i = 0; i < 5; ++i) {
145 0 : qreal yy = r.y() + i * paletteScoreSpatium;
146 0 : painter.drawLine(LineF(r.x(), yy, r.x() + r.width(), yy));
147 : }
148 :
149 0 : const auto paletteRenderer = paletteScoreProvider()->paletteScore()->renderer();
150 0 : if (dragElement) {
151 0 : painter.save();
152 0 : painter.translate(dragElement->pagePos());
153 0 : paletteRenderer->drawItem(dragElement, &painter, opt);
154 0 : painter.restore();
155 : }
156 0 : foreach (Accidental* a, accidentals) {
157 0 : painter.save();
158 0 : painter.translate(a->pagePos());
159 0 : paletteRenderer->drawItem(a, &painter, opt);
160 0 : painter.restore();
161 0 : }
162 0 : clef->setPos(0.0, 0.0);
163 :
164 0 : engravingRender()->layoutItem(clef);
165 :
166 0 : painter.translate(clef->pagePos());
167 0 : paletteRenderer->drawItem(clef, &painter, opt);
168 0 : }
169 :
170 : //---------------------------------------------------------
171 : // mousePressEvent
172 : //---------------------------------------------------------
173 :
174 0 : void KeyCanvas::mousePressEvent(QMouseEvent* event)
175 : {
176 0 : startMove = imatrix.map(QPointF(event->pos() - base));
177 0 : moveElement = 0;
178 0 : foreach (Accidental* a, accidentals) {
179 0 : QRectF r = a->pageBoundingRect().toQRectF();
180 0 : if (r.contains(startMove)) {
181 0 : a->setSelected(true);
182 0 : moveElement = a;
183 : } else {
184 0 : a->setSelected(false);
185 : }
186 0 : }
187 0 : update();
188 0 : }
189 :
190 : //---------------------------------------------------------
191 : // mouseMoveEvent
192 : //---------------------------------------------------------
193 :
194 0 : void KeyCanvas::mouseMoveEvent(QMouseEvent* event)
195 : {
196 0 : if (moveElement == 0) {
197 0 : return;
198 : }
199 0 : QPointF p = imatrix.map(QPointF(event->pos()));
200 0 : QPointF delta = p - startMove;
201 0 : moveElement->move(PointF::fromQPointF(delta));
202 0 : startMove = p;
203 0 : update();
204 : }
205 :
206 : //---------------------------------------------------------
207 : // mouseReleaseEvent
208 : //---------------------------------------------------------
209 :
210 0 : void KeyCanvas::mouseReleaseEvent(QMouseEvent*)
211 : {
212 0 : if (moveElement == 0) {
213 0 : return;
214 : }
215 0 : snap(moveElement);
216 0 : update();
217 : }
218 :
219 : //---------------------------------------------------------
220 : // dragEnterEvent
221 : //---------------------------------------------------------
222 :
223 0 : void KeyCanvas::dragEnterEvent(QDragEnterEvent* event)
224 : {
225 0 : const QMimeData* dta = event->mimeData();
226 0 : if (dta->hasFormat(mimeSymbolFormat)) {
227 0 : QByteArray a = dta->data(mimeSymbolFormat);
228 0 : XmlReader e(a);
229 :
230 0 : PointF dragOffset;
231 0 : Fraction duration;
232 0 : ElementType type = EngravingItem::readType(e, &dragOffset, &duration);
233 0 : if (type != ElementType::ACCIDENTAL) {
234 0 : return;
235 : }
236 :
237 0 : event->acceptProposedAction();
238 0 : dragElement = static_cast<Accidental*>(Factory::createItem(type, paletteScoreProvider()->paletteScore()->dummy()));
239 :
240 0 : rw::RWRegister::reader()->readItem(dragElement, e);
241 0 : engravingRender()->layoutItem(dragElement);
242 0 : } else {
243 0 : if (MScore::debugMode) {
244 0 : LOGD("KeyCanvas::dragEnterEvent: formats:");
245 0 : foreach (const QString& s, event->mimeData()->formats()) {
246 0 : LOGD(" %s", qPrintable(s));
247 0 : }
248 : }
249 : }
250 : }
251 :
252 : //---------------------------------------------------------
253 : // dragMoveEvent
254 : //---------------------------------------------------------
255 :
256 0 : void KeyCanvas::dragMoveEvent(QDragMoveEvent* event)
257 : {
258 0 : if (dragElement) {
259 0 : event->acceptProposedAction();
260 0 : PointF pos = PointF::fromQPointF(imatrix.map(event->position()));
261 0 : dragElement->setPos(pos);
262 0 : update();
263 : }
264 0 : }
265 :
266 : //---------------------------------------------------------
267 : // dropEvent
268 : //---------------------------------------------------------
269 :
270 0 : void KeyCanvas::dropEvent(QDropEvent*)
271 : {
272 0 : for (Accidental* a : accidentals) {
273 0 : a->setSelected(false);
274 : }
275 0 : dragElement->setSelected(true);
276 0 : accidentals.append(dragElement);
277 0 : snap(dragElement);
278 0 : dragElement = 0;
279 0 : update();
280 0 : }
281 :
282 : //---------------------------------------------------------
283 : // snap
284 : //---------------------------------------------------------
285 :
286 0 : void KeyCanvas::snap(Accidental* a)
287 : {
288 0 : double _spatium = paletteScoreProvider()->paletteScore()->style().spatium();
289 0 : double spatium2 = _spatium * .5;
290 0 : double y = a->ldata()->pos().y();
291 0 : double x = KEYEDIT_ACC_ZERO_POINT * _spatium;
292 0 : int line = round(y / spatium2);
293 0 : y = line * spatium2;
294 0 : a->mutldata()->setPosY(y);
295 : // take default xposition unless Control is pressed
296 0 : int i = accidentals.indexOf(a);
297 0 : if (i > 0) {
298 0 : qreal accidentalGap = DefaultStyle::baseStyle().styleS(Sid::keysigAccidentalDistance).val() * _spatium;
299 0 : Accidental* prev = accidentals[i - 1];
300 0 : double prevX = prev->ldata()->pos().x();
301 0 : qreal prevWidth = prev->symWidth(prev->symId());
302 0 : x = prevX + prevWidth + accidentalGap;
303 : }
304 0 : if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) {
305 0 : a->mutldata()->setPosX(x);
306 : }
307 0 : }
308 :
309 : //---------------------------------------------------------
310 : // KeyEditor
311 : //---------------------------------------------------------
312 :
313 0 : KeyEditor::KeyEditor(QWidget* parent)
314 0 : : QWidget(parent), muse::Contextable(muse::iocCtxForQWidget(this))
315 : {
316 0 : setupUi(this);
317 :
318 0 : QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Expanding);
319 :
320 : // create key signature palette
321 :
322 0 : QLayout* layout = new QVBoxLayout();
323 0 : layout->setContentsMargins(0, 0, 0, 0);
324 0 : keySigframe->setLayout(layout);
325 :
326 0 : m_keySigPaletteWidget = new PaletteWidget(this, true /*setIocContext*/);
327 0 : m_keySigPaletteWidget->setPalette(PaletteCreator(iocContext()).newKeySigPalette());
328 0 : m_keySigPaletteWidget->setReadOnly(false);
329 :
330 0 : m_keySigArea = new PaletteScrollArea(m_keySigPaletteWidget);
331 0 : m_keySigArea->setSizePolicy(policy);
332 0 : m_keySigArea->setRestrictHeight(false);
333 0 : m_keySigArea->setFocusProxy(m_keySigPaletteWidget);
334 0 : m_keySigArea->setFocusPolicy(Qt::TabFocus);
335 :
336 0 : layout->addWidget(m_keySigArea);
337 :
338 : // create accidental palette
339 :
340 0 : layout = new QVBoxLayout();
341 0 : layout->setContentsMargins(0, 0, 0, 0);
342 0 : accidentalsFrame->setLayout(layout);
343 :
344 0 : m_accidentalsPaletteWidget = new PaletteWidget(this, true /*setIocContext*/);
345 0 : m_accidentalsPaletteWidget->setPalette(PaletteCreator(iocContext()).newAccidentalsPalette());
346 0 : qreal adj = m_accidentalsPaletteWidget->mag();
347 0 : m_accidentalsPaletteWidget->setGridSize(m_accidentalsPaletteWidget->gridWidth() / adj, m_accidentalsPaletteWidget->gridHeight() / adj);
348 0 : m_accidentalsPaletteWidget->setMag(1.0);
349 :
350 0 : PaletteScrollArea* accidentalsPaletteArea = new PaletteScrollArea(m_accidentalsPaletteWidget);
351 0 : accidentalsPaletteArea->setSizePolicy(policy);
352 0 : accidentalsPaletteArea->setRestrictHeight(false);
353 0 : accidentalsPaletteArea->setFocusProxy(m_accidentalsPaletteWidget);
354 0 : accidentalsPaletteArea->setFocusPolicy(Qt::TabFocus);
355 :
356 0 : layout->addWidget(accidentalsPaletteArea);
357 :
358 0 : connect(addButton, &QPushButton::clicked, this, &KeyEditor::addClicked);
359 0 : connect(clearButton, &QPushButton::clicked, this, &KeyEditor::clearClicked);
360 0 : connect(m_keySigPaletteWidget, &PaletteWidget::changed, this, &KeyEditor::setDirty);
361 :
362 : //
363 : // set all "builtin" key signatures to read only
364 : //
365 0 : int n = m_keySigPaletteWidget->actualCellCount();
366 0 : for (int i = 0; i < n; ++i) {
367 0 : m_keySigPaletteWidget->setCellReadOnly(i, true);
368 : }
369 :
370 0 : if (!configuration()->useFactorySettings()) {
371 0 : m_keySigPaletteWidget->readFromFile(configuration()->keySignaturesDirPath().toQString());
372 : }
373 :
374 : //! NOTE: It is necessary for the correct start of navigation in the dialog
375 0 : setFocus();
376 0 : }
377 :
378 : //---------------------------------------------------------
379 : // addClicked
380 : //---------------------------------------------------------
381 :
382 0 : void KeyEditor::addClicked()
383 : {
384 0 : const QList<Accidental*> al = canvas->getAccidentals();
385 0 : double spatium = paletteScoreProvider()->paletteScore()->style().spatium();
386 0 : double xoff = KEYEDIT_ACC_ZERO_POINT * spatium;
387 :
388 0 : KeySigEvent e;
389 0 : e.setCustom(true);
390 0 : Spatium accidentalGap = DefaultStyle::baseStyle().styleS(Sid::keysigAccidentalDistance);
391 0 : for (int i = 0; i < al.size(); ++i) {
392 0 : Accidental* a = al[i];
393 0 : CustDef c;
394 0 : c.sym = a->symId();
395 0 : PointF pos = a->ldata()->pos();
396 0 : c.xAlt = Spatium::fromAbsolute(pos.x() - xoff, spatium);
397 0 : if (i > 0) {
398 0 : Accidental* prev = al[i - 1];
399 0 : PointF prevPos = prev->ldata()->pos();
400 0 : qreal prevWidth = prev->symWidth(prev->symId());
401 0 : c.xAlt -= Spatium::fromAbsolute(prevPos.x() - xoff + prevWidth, spatium) + accidentalGap;
402 : }
403 0 : int line = static_cast<int>(round((pos.y() / spatium) * 2));
404 0 : bool flat = std::string(SymNames::nameForSymId(c.sym).ascii()).find("Flat") != std::string::npos;
405 0 : c.degree = (3 - line) % 7;
406 0 : c.degree += (c.degree < 0) ? 7 : 0;
407 0 : line += flat ? -1 : 1; // top accidentals in treble clef are gis (#), or es (b)
408 0 : c.octAlt = static_cast<int>((line - (line >= 0 ? 0 : 6)) / 7);
409 0 : e.customKeyDefs().push_back(c);
410 : }
411 0 : auto ks = Factory::makeKeySig(paletteScoreProvider()->paletteScore()->dummy());
412 0 : ks->setKeySigEvent(e);
413 0 : m_keySigPaletteWidget->appendElement(ks, "custom");
414 0 : m_dirty = true;
415 :
416 0 : paletteProvider()->addCustomItemRequested().send(ks);
417 0 : }
418 :
419 : //---------------------------------------------------------
420 : // clearClicked
421 : //---------------------------------------------------------
422 :
423 0 : void KeyEditor::clearClicked()
424 : {
425 0 : canvas->clear();
426 0 : }
427 :
428 : //---------------------------------------------------------
429 : // showKeyPalette
430 : //---------------------------------------------------------
431 :
432 0 : void KeyEditor::setShowKeyPalette(bool showKeyPalette)
433 : {
434 0 : m_keySigArea->setVisible(showKeyPalette);
435 0 : }
436 :
437 : //---------------------------------------------------------
438 : // save
439 : //---------------------------------------------------------
440 :
441 0 : void KeyEditor::save()
442 : {
443 0 : QDir dir;
444 0 : dir.mkpath(configuration()->keySignaturesDirPath().toQString());
445 0 : m_keySigPaletteWidget->writeToFile(configuration()->keySignaturesDirPath().toQString());
446 0 : }
447 :
448 0 : bool KeyEditor::showKeyPalette() const
449 : {
450 0 : return m_keySigArea->isVisible();
451 : }
452 :
453 0 : KeyEditorDialog::KeyEditorDialog(QWidget* parent)
454 0 : : muse::ui::WidgetDialog(parent)
455 : {
456 0 : setWindowTitle(muse::qtrc("palette", "Key signatures"));
457 0 : setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
458 0 : }
459 :
460 0 : void KeyEditorDialog::classBegin()
461 : {
462 0 : m_keyEditor = new KeyEditor(this);
463 0 : QVBoxLayout* layout = new QVBoxLayout(this);
464 0 : layout->setContentsMargins(0, 0, 0, 0);
465 0 : layout->setSizeConstraint(QLayout::SetFixedSize);
466 0 : layout->addWidget(m_keyEditor);
467 0 : }
468 :
469 0 : bool KeyEditorDialog::showKeyPalette() const
470 : {
471 0 : return m_keyEditor->showKeyPalette();
472 : }
473 :
474 0 : void KeyEditorDialog::setShowKeyPalette(bool showKeyPalette)
475 : {
476 0 : m_keyEditor->setShowKeyPalette(showKeyPalette);
477 0 : }
|