1 //=============================================================================
2 // MuseScore
3 // Music Composition & Notation
4 //
5 // Copyright (C) 2002-2011 Werner Schweer
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2
9 // as published by the Free Software Foundation and appearing in
10 // the file LICENCE.GPL
11 //=============================================================================
12
13 #include "libmscore/score.h"
14 #include "libmscore/element.h"
15 #include "libmscore/note.h"
16 #include "libmscore/rest.h"
17 #include "libmscore/measure.h"
18 #include "libmscore/system.h"
19 #include "libmscore/segment.h"
20 #include "libmscore/page.h"
21 #include "libmscore/image.h"
22 #include "libmscore/text.h"
23 #include "libmscore/spanner.h"
24 #include "libmscore/chord.h"
25 #include "libmscore/icon.h"
26 #include "libmscore/xml.h"
27 #include "libmscore/stafflines.h"
28 #include "musescore.h"
29 #include "scoreview.h"
30 #include "continuouspanel.h"
31 #include "tourhandler.h"
32
33 namespace Ms {
34
35 //---------------------------------------------------------
36 // setDropTarget
37 //---------------------------------------------------------
38
setDropTarget(const Element * el)39 void ScoreView::setDropTarget(const Element* el)
40 {
41 if (dropTarget != el) {
42 if (dropTarget) {
43 dropTarget->setDropTarget(false);
44 dropTarget = 0;
45 }
46 dropTarget = el;
47 if (dropTarget) {
48 dropTarget->setDropTarget(true);
49 }
50 }
51 if (!m_dropAnchorLines.isEmpty())
52 m_dropAnchorLines.clear();
53
54 if (dropRectangle.isValid()) {
55 dropRectangle = QRectF();
56 }
57 update();
58 }
59
60 //---------------------------------------------------------
61 // setDropRectangle
62 //---------------------------------------------------------
63
setDropRectangle(const QRectF & r)64 void ScoreView::setDropRectangle(const QRectF& r)
65 {
66 if (dropRectangle.isValid())
67 _score->addRefresh(dropRectangle);
68 dropRectangle = r;
69 if (dropTarget) {
70 dropTarget->setDropTarget(false);
71 _score->addRefresh(dropTarget->canvasBoundingRect());
72 dropTarget = 0;
73 }
74 else if (!m_dropAnchorLines.isEmpty()) {
75 QRectF rf;
76 rf.setTopLeft(m_dropAnchorLines.first().p1());
77 rf.setBottomRight(m_dropAnchorLines.first().p2());
78 _score->addRefresh(rf.normalized());
79 m_dropAnchorLines.clear();
80 }
81
82 update();
83 }
84
85 //---------------------------------------------------------
86 // setDropAnchorList
87 //---------------------------------------------------------
setDropAnchorLines(const QVector<QLineF> & anchorList)88 void ScoreView::setDropAnchorLines(const QVector<QLineF>& anchorList)
89 {
90 if (m_dropAnchorLines != anchorList)
91 m_dropAnchorLines = anchorList;
92
93 if (dropRectangle.isValid())
94 dropRectangle = QRectF();
95
96 update();
97 }
98
99 //---------------------------------------------------------
100 // setViewRect
101 //---------------------------------------------------------
102
setViewRect(const QRectF & r)103 void ScoreView::setViewRect(const QRectF& r)
104 {
105 QRectF rr = _matrix.mapRect(r);
106 QPoint d = rr.topLeft().toPoint();
107 int dx = -d.x();
108 int dy = -d.y();
109 QApplication::sendPostedEvents(this, 0);
110 _matrix.setMatrix(_matrix.m11(), _matrix.m12(), _matrix.m13(), _matrix.m21(),
111 _matrix.m22(), _matrix.m23(), _matrix.dx()+dx, _matrix.dy()+dy, _matrix.m33());
112 imatrix = _matrix.inverted();
113 scroll(dx, dy, QRect(0, 0, width(), height()));
114 emit offsetChanged(_matrix.dx(), _matrix.dy());
115 if (_continuousPanel->visible())
116 update();
117 }
118
119 //---------------------------------------------------------
120 // dragTimeAnchorElement
121 // pos is in canvas coordinates
122 // return true if there is a valid target
123 //---------------------------------------------------------
124
dragTimeAnchorElement(const QPointF & pos)125 bool ScoreView::dragTimeAnchorElement(const QPointF& pos)
126 {
127 int staffIdx;
128 Segment* seg;
129 MeasureBase* mb = _score->pos2measure(pos, &staffIdx, 0, &seg, 0);
130 int track = staffIdx * VOICES;
131
132 if (mb && mb->isMeasure() && seg->element(track)) {
133 Measure* m = toMeasure(mb);
134 System* s = m->system();
135 qreal y = s->staff(staffIdx)->y() + s->pos().y() + s->page()->pos().y();
136 QPointF anchor(seg->canvasBoundingRect().x(), y);
137 setDropAnchorLines({ QLineF(pos, anchor) });
138 editData.dropElement->score()->addRefresh(editData.dropElement->canvasBoundingRect());
139 editData.dropElement->setTrack(track);
140 editData.dropElement->score()->addRefresh(editData.dropElement->canvasBoundingRect());
141 return true;
142 }
143 editData.dropElement->score()->addRefresh(editData.dropElement->canvasBoundingRect());
144 setDropTarget(0);
145 return false;
146 }
147
148 //---------------------------------------------------------
149 // dragMeasureAnchorElement
150 //---------------------------------------------------------
151
dragMeasureAnchorElement(const QPointF & pos)152 bool ScoreView::dragMeasureAnchorElement(const QPointF& pos)
153 {
154 int staffIdx;
155 Segment* seg;
156 MeasureBase* mb = _score->pos2measure(pos, &staffIdx, 0, &seg, 0);
157 if (!(editData.modifiers & Qt::ControlModifier))
158 staffIdx = 0;
159 int track = staffIdx * VOICES;
160
161 if (mb && mb->isMeasure()) {
162 Measure* m = toMeasure(mb);
163 System* s = m->system();
164 qreal y = s->staff(staffIdx)->y() + s->pos().y() + s->page()->pos().y();
165 QRectF b(m->canvasBoundingRect());
166 if (pos.x() >= (b.x() + b.width() * .5) && m != _score->lastMeasureMM() && m->nextMeasure()->system() == m->system())
167 m = m->nextMeasure();
168 QPointF anchor(m->canvasBoundingRect().x(), y);
169 setDropAnchorLines({ QLineF(pos, anchor) });
170 editData.dropElement->score()->addRefresh(editData.dropElement->canvasBoundingRect());
171 editData.dropElement->setTrack(track);
172 editData.dropElement->score()->addRefresh(editData.dropElement->canvasBoundingRect());
173 return true;
174 }
175 editData.dropElement->score()->addRefresh(editData.dropElement->canvasBoundingRect());
176 setDropTarget(0);
177 return false;
178 }
179
180 //---------------------------------------------------------
181 // dragEnterEvent
182 //---------------------------------------------------------
183
dragEnterEvent(QDragEnterEvent * event)184 void ScoreView::dragEnterEvent(QDragEnterEvent* event)
185 {
186 double _spatium = score()->spatium();
187 editData.dropElement = 0;
188
189 const QMimeData* dta = event->mimeData();
190
191 if (dta->hasFormat(mimeSymbolListFormat) || dta->hasFormat(mimeStaffListFormat)) {
192 if (event->possibleActions() & Qt::CopyAction)
193 event->setDropAction(Qt::CopyAction);
194 if (event->dropAction() == Qt::CopyAction)
195 event->accept();
196 return;
197 }
198
199 if (dta->hasFormat(mimeSymbolFormat)) {
200 if (event->possibleActions() & Qt::CopyAction)
201 event->setDropAction(Qt::CopyAction);
202 if (event->dropAction() == Qt::CopyAction)
203 event->accept();
204
205 mscore->notifyElementDraggedToScoreView();
206
207 QByteArray a = dta->data(mimeSymbolFormat);
208
209 if (MScore::debugMode)
210 qDebug("ScoreView::dragEnterEvent Symbol: <%s>", a.data());
211
212 XmlReader e(a);
213 editData.dragOffset = QPoint();
214 Fraction duration; // dummy
215 ElementType type = Element::readType(e, &editData.dragOffset, &duration);
216
217 Element* el = Element::create(type, score());
218 if (el) {
219 if (type == ElementType::BAR_LINE || type == ElementType::ARPEGGIO || type == ElementType::BRACKET)
220 el->setHeight(_spatium * 5);
221 editData.dropElement = el;
222 editData.dropElement->setParent(0);
223 editData.dropElement->read(e);
224 editData.dropElement->layout();
225 }
226 return;
227 }
228
229 if (dta->hasUrls()) {
230 QList<QUrl>ul = dta->urls();
231 QUrl u = ul.front();
232
233 QMimeDatabase db;
234 if (!QImageReader::supportedMimeTypes().contains(db.mimeTypeForUrl(u).name().toLatin1())) {
235 event->ignore();
236 return;
237 }
238
239 Image* image = 0;
240 if (u.scheme() == "file") {
241 image = new Image(score());
242 QString str(u.toLocalFile());
243 image->load(str);
244 }
245 else if (u.scheme() == "http" || u.scheme() == "https") {
246 QNetworkAccessManager manager;
247 QNetworkReply* reply = manager.get(QNetworkRequest(u));
248
249 // TODO:
250 // feed progress bar in loop
251 // implement timeout/abort
252
253 QMutex mutex;
254 QWaitCondition wc;
255 while (!reply->isFinished()) {
256 mutex.lock();
257 wc.wait(&mutex, 100);
258 qApp->processEvents();
259 mutex.unlock();
260 }
261 QByteArray ba = reply->readAll();
262
263 image = new Image(score());
264 image->loadFromData(u.path(), ba);
265 delete reply;
266 }
267 if (image) {
268 editData.dropElement = image;
269 editData.dropElement->setParent(0);
270 editData.dropElement->layout();
271 event->accept();
272 }
273 return;
274 }
275 qDebug("unknown drop format: formats:");
276 for (const QString& s : dta->formats())
277 qDebug(" <%s>", qPrintable(s));
278 event->ignore();
279 }
280
281 //---------------------------------------------------------
282 // getDropTarget
283 //---------------------------------------------------------
284
getDropTarget(EditData & ed)285 Element* ScoreView::getDropTarget(EditData& ed)
286 {
287 QList<Element*> el = elementsAt(ed.pos);
288 setDropTarget(0);
289 for (Element* e : qAsConst(el)) {
290 if (e->isStaffLines()) {
291 if (el.size() > 2) // is not first class drop target
292 continue;
293 e = toStaffLines(e)->measure();
294 }
295 if (e->acceptDrop(ed)) {
296 if (!e->isMeasure())
297 setDropTarget(e);
298 return e;
299 }
300 }
301 return nullptr;
302 }
303
304 //---------------------------------------------------------
305 // dragMoveEvent
306 //---------------------------------------------------------
307
dragMoveEvent(QDragMoveEvent * event)308 void ScoreView::dragMoveEvent(QDragMoveEvent* event)
309 {
310 // we always accept the drop action
311 // to get a "drop" Event:
312
313 if (MScore::debugMode) {
314 if (!editData.dropElement)
315 qDebug("no drop element");
316 else
317 qDebug("<%s>", editData.dropElement->name());
318 }
319
320 if (!editData.dropElement || mscore->state() == STATE_PLAY) { // no editing during play
321 event->ignore();
322 return;
323 }
324
325 const QMimeData* dta = event->mimeData();
326 if (dta->hasFormat(mimeSymbolFormat)
327 || dta->hasFormat(mimeSymbolListFormat)
328 || dta->hasFormat(mimeStaffListFormat)) {
329 if (event->possibleActions() & Qt::CopyAction)
330 event->setDropAction(Qt::CopyAction);
331 }
332
333 // convert window to canvas position
334 QPointF pos(imatrix.map(QPointF(event->pos())));
335 editData.pos = pos;
336 editData.modifiers = event->keyboardModifiers();
337
338 switch (editData.dropElement->type()) {
339 case ElementType::VOLTA:
340 event->setAccepted(dragMeasureAnchorElement(pos));
341 break;
342 case ElementType::PEDAL:
343 case ElementType::LET_RING:
344 case ElementType::VIBRATO:
345 case ElementType::PALM_MUTE:
346 case ElementType::OTTAVA:
347 case ElementType::TRILL:
348 case ElementType::HAIRPIN:
349 case ElementType::TEXTLINE:
350 event->setAccepted(dragTimeAnchorElement(pos));
351 break;
352 case ElementType::IMAGE:
353 case ElementType::SYMBOL:
354 case ElementType::FSYMBOL:
355 case ElementType::DYNAMIC:
356 case ElementType::KEYSIG:
357 case ElementType::CLEF:
358 case ElementType::TIMESIG:
359 case ElementType::BAR_LINE:
360 case ElementType::ARPEGGIO:
361 case ElementType::BREATH:
362 case ElementType::GLISSANDO:
363 case ElementType::MEASURE_NUMBER:
364 case ElementType::MMREST_RANGE:
365 case ElementType::BRACKET:
366 case ElementType::ARTICULATION:
367 case ElementType::FERMATA:
368 case ElementType::CHORDLINE:
369 case ElementType::BEND:
370 case ElementType::ACCIDENTAL:
371 case ElementType::TEXT:
372 case ElementType::FINGERING:
373 case ElementType::TEMPO_TEXT:
374 case ElementType::STAFF_TEXT:
375 case ElementType::SYSTEM_TEXT:
376 case ElementType::NOTEHEAD:
377 case ElementType::TREMOLO:
378 case ElementType::LAYOUT_BREAK:
379 case ElementType::MARKER:
380 case ElementType::STAFF_STATE:
381 case ElementType::INSTRUMENT_CHANGE:
382 case ElementType::REHEARSAL_MARK:
383 case ElementType::JUMP:
384 case ElementType::REPEAT_MEASURE:
385 case ElementType::ICON:
386 case ElementType::CHORD:
387 case ElementType::SPACER:
388 case ElementType::SLUR:
389 case ElementType::HARMONY:
390 case ElementType::BAGPIPE_EMBELLISHMENT:
391 case ElementType::AMBITUS:
392 case ElementType::TREMOLOBAR:
393 case ElementType::FIGURED_BASS:
394 case ElementType::LYRICS:
395 case ElementType::FRET_DIAGRAM:
396 case ElementType::STAFFTYPE_CHANGE:
397 event->setAccepted(getDropTarget(editData));
398 break;
399 default:
400 if (MScore::debugMode)
401 qDebug("no target");
402 event->ignore();
403 break;
404 }
405 }
406
407 //---------------------------------------------------------
408 // dropEvent
409 //---------------------------------------------------------
410
dropEvent(QDropEvent * event)411 void ScoreView::dropEvent(QDropEvent* event)
412 {
413 if (state == ViewState::PLAY) {
414 event->ignore();
415 return;
416 }
417 QPointF pos(imatrix.map(QPointF(event->pos())));
418
419 editData.pos = pos;
420 editData.modifiers = event->keyboardModifiers();
421
422 if (editData.dropElement) {
423 bool firstStaffOnly = false;
424 bool applyUserOffset = false;
425 bool triggerSpannerDropApplyTour = editData.dropElement->isSpanner();
426 editData.dropElement->styleChanged();
427 _score->startCmd();
428 Q_ASSERT(editData.dropElement->score() == score());
429 _score->addRefresh(editData.dropElement->canvasBoundingRect());
430 switch (editData.dropElement->type()) {
431 case ElementType::VOLTA:
432 // voltas drop to first staff by default, or closest staff if Control is held
433 firstStaffOnly = !(editData.modifiers & Qt::ControlModifier);
434 // fall-thru
435 case ElementType::OTTAVA:
436 case ElementType::TRILL:
437 case ElementType::PEDAL:
438 case ElementType::LET_RING:
439 case ElementType::VIBRATO:
440 case ElementType::PALM_MUTE:
441 case ElementType::HAIRPIN:
442 case ElementType::TEXTLINE:
443 {
444 Spanner* spanner = static_cast<Spanner*>(editData.dropElement);
445 score()->cmdAddSpanner(spanner, pos, firstStaffOnly);
446 score()->setUpdateAll();
447 event->acceptProposedAction();
448 }
449 break;
450 case ElementType::SYMBOL:
451 case ElementType::FSYMBOL:
452 case ElementType::IMAGE:
453 applyUserOffset = true;
454 // fall-thru
455 case ElementType::DYNAMIC:
456 case ElementType::FRET_DIAGRAM:
457 case ElementType::HARMONY:
458 {
459 Element* el = elementAt(pos);
460 if (el == 0 || el->type() == ElementType::STAFF_LINES) {
461 int staffIdx;
462 Segment* seg;
463 QPointF offset;
464 el = _score->pos2measure(pos, &staffIdx, 0, &seg, &offset);
465 if (el && el->isMeasure()) {
466 editData.dropElement->setTrack(staffIdx * VOICES);
467 editData.dropElement->setParent(seg);
468 if (applyUserOffset)
469 editData.dropElement->setOffset(offset);
470 score()->undoAddElement(editData.dropElement);
471 }
472 else {
473 qDebug("cannot drop here");
474 delete editData.dropElement;
475 }
476 }
477 else {
478 _score->addRefresh(el->canvasBoundingRect());
479 _score->addRefresh(editData.dropElement->canvasBoundingRect());
480
481 if (!el->acceptDrop(editData)) {
482 qDebug("drop %s onto %s not accepted", editData.dropElement->name(), el->name());
483 break;
484 }
485 Element* dropElement = el->drop(editData);
486 _score->addRefresh(el->canvasBoundingRect());
487 if (dropElement) {
488 _score->select(dropElement, SelectType::SINGLE, 0);
489 _score->addRefresh(dropElement->canvasBoundingRect());
490 }
491 }
492 }
493 event->acceptProposedAction();
494 break;
495 case ElementType::HBOX:
496 case ElementType::VBOX:
497 case ElementType::KEYSIG:
498 case ElementType::CLEF:
499 case ElementType::TIMESIG:
500 case ElementType::BAR_LINE:
501 case ElementType::ARPEGGIO:
502 case ElementType::BREATH:
503 case ElementType::GLISSANDO:
504 case ElementType::MEASURE_NUMBER:
505 case ElementType::MMREST_RANGE:
506 case ElementType::BRACKET:
507 case ElementType::ARTICULATION:
508 case ElementType::FERMATA:
509 case ElementType::CHORDLINE:
510 case ElementType::BEND:
511 case ElementType::ACCIDENTAL:
512 case ElementType::TEXT:
513 case ElementType::FINGERING:
514 case ElementType::TEMPO_TEXT:
515 case ElementType::STAFF_TEXT:
516 case ElementType::SYSTEM_TEXT:
517 case ElementType::NOTEHEAD:
518 case ElementType::TREMOLO:
519 case ElementType::LAYOUT_BREAK:
520 case ElementType::MARKER:
521 case ElementType::STAFF_STATE:
522 case ElementType::INSTRUMENT_CHANGE:
523 case ElementType::REHEARSAL_MARK:
524 case ElementType::JUMP:
525 case ElementType::REPEAT_MEASURE:
526 case ElementType::ICON:
527 case ElementType::NOTE:
528 case ElementType::CHORD:
529 case ElementType::SPACER:
530 case ElementType::SLUR:
531 case ElementType::BAGPIPE_EMBELLISHMENT:
532 case ElementType::AMBITUS:
533 case ElementType::TREMOLOBAR:
534 case ElementType::FIGURED_BASS:
535 case ElementType::LYRICS:
536 case ElementType::STAFFTYPE_CHANGE: {
537 Element* el = getDropTarget(editData);
538 if (!el) {
539 if (!dropCanvas(editData.dropElement)) {
540 qDebug("cannot drop %s(%p) to canvas", editData.dropElement->name(), editData.dropElement);
541 delete editData.dropElement;
542 }
543 break;
544 }
545 _score->addRefresh(el->canvasBoundingRect());
546
547 // TODO: HACK ALERT!
548 if (el->isMeasure() && editData.dropElement->isLayoutBreak()) {
549 Measure* m = toMeasure(el);
550 if (m->isMMRest())
551 el = m->mmRestLast();
552 }
553
554 Element* dropElement = el->drop(editData);
555 if (dropElement && dropElement->isInstrumentChange()) {
556 mscore->currentScoreView()->selectInstrument(toInstrumentChange(dropElement));
557 }
558 _score->addRefresh(el->canvasBoundingRect());
559 if (dropElement) {
560 if (!_score->noteEntryMode())
561 _score->select(dropElement, SelectType::SINGLE, 0);
562 _score->addRefresh(dropElement->canvasBoundingRect());
563 }
564 event->acceptProposedAction();
565 }
566 break;
567 default:
568 delete editData.dropElement;
569 break;
570 }
571 editData.dropElement = 0;
572 setDropTarget(0); // this also resets dropRectangle and dropAnchor
573 score()->endCmd();
574 // update input cursor position (must be done after layout)
575 if (noteEntryMode())
576 moveCursor();
577 if (triggerSpannerDropApplyTour)
578 TourHandler::startTour("spanner-drop-apply");
579 return;
580 }
581
582 editData.dropElement = 0;
583 const QMimeData* md = event->mimeData();
584 QByteArray dta;
585 ElementType etype;
586 if (md->hasFormat(mimeSymbolListFormat)) {
587 etype = ElementType::ELEMENT_LIST;
588 dta = md->data(mimeSymbolListFormat);
589 }
590 else if (md->hasFormat(mimeStaffListFormat)) {
591 etype = ElementType::STAFF_LIST;
592 dta = md->data(mimeStaffListFormat);
593 }
594 else {
595 qDebug("cannot drop this object: unknown mime type");
596 QStringList sl = md->formats();
597 for (const QString& s : qAsConst(sl))
598 qDebug(" %s", qPrintable(s));
599 _score->update();
600 return;
601 }
602
603 qDebug("drop <%s>", dta.data());
604
605 Element* el = elementAt(pos);
606 if (el == 0 || el->type() != ElementType::MEASURE) {
607 setDropTarget(0);
608 return;
609 }
610 Measure* measure = (Measure*) el;
611
612 if (etype == ElementType::ELEMENT_LIST) {
613 qDebug("drop element list");
614 }
615 else if (etype == ElementType::MEASURE_LIST || etype == ElementType::STAFF_LIST) {
616 _score->startCmd();
617 XmlReader xml(dta);
618 System* s = measure->system();
619 int idx = s->y2staff(pos.y());
620 if (idx != -1) {
621 Segment* seg = measure->first();
622 // assume there is always a ChordRest segment
623 while (!seg->isChordRestType())
624 seg = seg->next();
625 score()->pasteStaff(xml, seg, idx);
626 }
627 event->acceptProposedAction();
628 _score->endCmd();
629 }
630 setDropTarget(0); // this also resets dropRectangle and dropAnchor
631 }
632
633 //---------------------------------------------------------
634 // dragLeaveEvent
635 //---------------------------------------------------------
636
dragLeaveEvent(QDragLeaveEvent *)637 void ScoreView::dragLeaveEvent(QDragLeaveEvent*)
638 {
639 if (editData.dropElement) {
640 _score->setUpdateAll();
641 delete editData.dropElement;
642 editData.dropElement = 0;
643 _score->update();
644 }
645 setDropTarget(0);
646 }
647
648 //---------------------------------------------------------
649 // dropCanvas
650 //---------------------------------------------------------
651
dropCanvas(Element * e)652 bool ScoreView::dropCanvas(Element* e)
653 {
654 if (e->isIcon()) {
655 switch (toIcon(e)->iconType()) {
656 case IconType::VFRAME:
657 score()->insertMeasure(ElementType::VBOX, 0);
658 break;
659 case IconType::HFRAME:
660 score()->insertMeasure(ElementType::HBOX, 0);
661 break;
662 case IconType::TFRAME:
663 score()->insertMeasure(ElementType::TBOX, 0);
664 break;
665 case IconType::FFRAME:
666 score()->insertMeasure(ElementType::FBOX, 0);
667 break;
668 case IconType::MEASURE:
669 score()->insertMeasure(ElementType::MEASURE, 0);
670 break;
671 default:
672 return false;
673 }
674 delete e;
675 return true;
676 }
677 return false;
678 }
679
680 } // namespace Ms
681