1 //=============================================================================
2 //  MuseScore
3 //  Music Composition & Notation
4 //
5 //  Copyright (C) 2002-2017 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 "scoreview.h"
14 #include "musescore.h"
15 #include "libmscore/score.h"
16 #include "libmscore/lasso.h"
17 #include "icons.h"
18 #include "libmscore/page.h"
19 #include "preferences.h"
20 #include "libmscore/image.h"
21 #include "libmscore/mscore.h"
22 #include "svggenerator.h"
23 #include "inspector/inspector.h"
24 #include "fotomode.h"
25 
26 namespace Ms {
27 
28 //---------------------------------------------------------
29 //   startEdit
30 //---------------------------------------------------------
31 
startEdit(EditData & ed)32 void FotoLasso::startEdit(EditData& ed)
33       {
34       Lasso::startEdit(ed);
35       QRectF view = ((ScoreView*)ed.view)->toLogical(QRect(0.0, 0.0, ed.view->geometry().width(), ed.view->geometry().height()));
36       if (bbox().isEmpty() || !view.intersects(bbox())) {
37             // rect not found - construct new rect with default size & relative position
38             qreal w = view.width();
39             qreal h = view.height();
40             QRectF rect(w * .3, h * .3, w * .4, h * .4);
41             // convert to absolute position
42             setbbox(rect.translated(view.topLeft()));
43             }
44       setVisible(false);
45       }
46 
47 //---------------------------------------------------------
48 //   endEdit
49 //---------------------------------------------------------
50 
endEdit(EditData &)51 void FotoLasso::endEdit(EditData&)
52       {
53       setVisible(false);
54       }
55 
56 //---------------------------------------------------------
57 //   drawEditMode
58 //---------------------------------------------------------
59 
drawEditMode(QPainter * p,EditData & ed)60 void FotoLasso::drawEditMode(QPainter* p, EditData& ed)
61       {
62       QPointF pos(canvasPos());
63       p->translate(pos);
64       draw(p);
65       p->translate(-pos);
66       Lasso::drawEditMode(p, ed);
67       }
68 
69 //---------------------------------------------------------
70 //   startFotoMode
71 //---------------------------------------------------------
72 
startFotomode()73 void ScoreView::startFotomode()
74       {
75       if (!_foto)
76             _foto = new FotoLasso(_score);
77       else
78             _foto->setScore(_score);
79       QRectF view = toLogical(QRect(0.0, 0.0, width(), height()));
80       if (_foto->bbox().isEmpty() || !view.intersects(_foto->bbox())) {
81             // rect not found - construct new rect with default size & relative position
82             qreal w = view.width();
83             qreal h = view.height();
84             QRectF r(w * .3, h * .3, w * .4, h * .4);
85             // convert to absolute position
86             _foto->setbbox(toPhysical(r));
87             }
88       _foto->setFlag(ElementFlag::MOVABLE, true);
89       _foto->setVisible(true);
90       _score->select(_foto);
91       setEditElement(_foto);
92       QAction* a = getAction("fotomode");
93       a->setChecked(true);
94       startEdit();
95       }
96 
97 //---------------------------------------------------------
98 //   stopFotomode
99 //---------------------------------------------------------
100 
stopFotomode()101 void ScoreView::stopFotomode()
102       {
103       QAction* a = getAction("fotomode");
104       a->setChecked(false);
105       _foto->setVisible(false);
106       endEdit();
107       update();
108       }
109 
110 //---------------------------------------------------------
111 //   startFotoDrag
112 //---------------------------------------------------------
113 
startFotoDrag()114 void ScoreView::startFotoDrag()
115       {
116       _score->addRefresh(_foto->abbox());
117       _score->update();
118       editData.grips = 0;
119       }
120 
121 //---------------------------------------------------------
122 //   doDragFoto
123 //    drag canvas in foto mode
124 //---------------------------------------------------------
125 
doDragFoto(QMouseEvent * ev)126 void ScoreView::doDragFoto(QMouseEvent* ev)
127       {
128       _foto->setOffset(QPointF(0.0, 0.0));
129       QPointF p = toLogical(ev->pos());
130       QPointF sm = editData.startMove;
131 
132       QRectF r;
133       r.setCoords(sm.x(), sm.y(), p.x(), p.y());
134 
135       _foto->setbbox(r.normalized());
136 
137       QRectF rr(_foto->bbox());
138       r = _matrix.mapRect(rr);
139       //QSize sz(r.size().toSize());
140       //mscore->statusBar()->showMessage(QString("%1 x %2").arg(sz.width()).arg(sz.height()), 3000);
141 
142       update();
143       //mscore->showMessage("drag", 2000);
144       }
145 
146 //---------------------------------------------------------
147 //   endFotoDrag
148 //---------------------------------------------------------
149 
endFotoDrag()150 void ScoreView::endFotoDrag()
151       {
152       qreal w = 8.0 / _matrix.m11();
153       qreal h = 8.0 / _matrix.m22();
154       QRectF r(-w*.5, -h*.5, w, h);
155       editData.grip.resize(8);
156       for (int i = 0; i < 8; ++i)
157             editData.grip[i] = r;
158       setEditElement(_foto);
159       updateGrips();
160       _score->setUpdateAll();
161       _score->update();
162       }
163 
164 //---------------------------------------------------------
165 //   doFotoDragEdit
166 //---------------------------------------------------------
167 
doFotoDragEdit(QMouseEvent * ev)168 void ScoreView::doFotoDragEdit(QMouseEvent* ev)
169       {
170       QPointF p     = toLogical(ev->pos());
171       QPointF delta = p - editData.startMove;
172       score()->addRefresh(_foto->abbox());
173 
174       editData.delta   = delta;
175       _foto->editDrag(editData);
176       updateGrips();
177       editData.startMove = p;
178       _score->update();
179       if (mscore->inspector())
180             mscore->inspector()->update(_foto->score());
181       }
182 
183 //---------------------------------------------------------
184 //   endFotoDragEdit
185 //---------------------------------------------------------
186 
endFotoDragEdit()187 void ScoreView::endFotoDragEdit()
188       {
189       }
190 
191 //---------------------------------------------------------
192 //   fotoEditElementDragTransition
193 //---------------------------------------------------------
194 
fotoEditElementDragTransition(QMouseEvent * ev)195 bool ScoreView::fotoEditElementDragTransition(QMouseEvent* ev)
196       {
197       editData.startMove = imatrix.map(QPointF(ev->pos()));
198       int i;
199       for (i = 0; i < editData.grips; ++i) {
200             if (editData.grip[i].contains(editData.startMove)) {
201                   editData.curGrip = Grip(i);
202                   switch (int(editData.curGrip)) {
203                         case 0:
204                         case 2:
205                               setCursor(Qt::SizeFDiagCursor);
206                               break;
207                         case 1:
208                         case 3:
209                               setCursor(Qt::SizeBDiagCursor);
210                               break;
211                         case 4:
212                         case 6:
213                               setCursor(Qt::SizeVerCursor);
214                               break;
215                         case 5:
216                         case 7:
217                               setCursor(Qt::SizeHorCursor);
218                               break;
219                         }
220                   updateGrips();
221                   score()->update();
222                   break;
223                   }
224             }
225       return i != editData.grips;
226       }
227 
228 //---------------------------------------------------------
229 //   fotoScoreViewDragTest
230 //---------------------------------------------------------
231 
fotoScoreViewDragTest(QMouseEvent * me)232 bool ScoreView::fotoScoreViewDragTest(QMouseEvent* me)
233       {
234       QPointF p(imatrix.map(QPointF(me->pos())));
235       if (_foto->bbox().contains(p))
236             return false;
237       for (int i = 0; i < editData.grips; ++i) {
238             if (editData.grip[i].contains(p))
239                   return false;
240             }
241       editData.startMove = p;
242       return true;
243       }
244 
245 //---------------------------------------------------------
246 //   fotoScoreViewDragRectTest
247 //---------------------------------------------------------
248 
fotoScoreViewDragRectTest(QMouseEvent * me)249 bool ScoreView::fotoScoreViewDragRectTest(QMouseEvent* me)
250       {
251       QPointF p(toLogical(me->pos()));
252       if (!_foto->bbox().contains(p))
253             return false;
254       for (int i = 0; i < editData.grips; ++i) {
255             if (editData.grip[i].contains(p))
256                   return false;
257             }
258       editData.startMove = p;
259       return true;
260       }
261 
262 //---------------------------------------------------------
263 //   doDragFotoRect
264 //---------------------------------------------------------
265 
doDragFotoRect(QMouseEvent * ev)266 void ScoreView::doDragFotoRect(QMouseEvent* ev)
267       {
268       QPointF p(toLogical(ev->pos()));
269       QPointF delta = p - editData.startMove;
270       score()->addRefresh(_foto->abbox());
271       _foto->setbbox(_foto->bbox().translated(delta));
272       score()->addRefresh(_foto->abbox());
273       editData.startMove = p;
274       updateGrips();
275       _score->update();
276       if (mscore->inspector())
277             mscore->inspector()->update(_foto->score());
278       }
279 
280 //---------------------------------------------------------
281 //   MenuEntry
282 //---------------------------------------------------------
283 
284 struct MenuEntry {
285       const char* text;
286       const char* label;
287       };
288 
289 static const MenuEntry resizeEntry[4] {
290       { QT_TRANSLATE_NOOP("fotomode", "Resize to A"), "resizeA" },
291       { QT_TRANSLATE_NOOP("fotomode", "Resize to B"), "resizeB" },
292       { QT_TRANSLATE_NOOP("fotomode", "Resize to C"), "resizeC" },
293       { QT_TRANSLATE_NOOP("fotomode", "Resize to D"), "resizeD" }
294       };
295 
296 static const MenuEntry setSizeEntry[4] {
297       { QT_TRANSLATE_NOOP("fotomode", "Set size A"), "setA" },
298       { QT_TRANSLATE_NOOP("fotomode", "Set size B"), "setB" },
299       { QT_TRANSLATE_NOOP("fotomode", "Set size C"), "setC" },
300       { QT_TRANSLATE_NOOP("fotomode", "Set size D"), "setD" }
301       };
302 
303 //---------------------------------------------------------
304 //   fotoContextPopup
305 //---------------------------------------------------------
306 
fotoContextPopup(QContextMenuEvent * ev)307 void ScoreView::fotoContextPopup(QContextMenuEvent* ev)
308       {
309       QPoint pos(ev->globalPos());
310       QMenu* popup = new QMenu(this);
311       popup->setSeparatorsCollapsible(false);
312       QAction* a = popup->addSeparator();
313       a->setText(tr("Image Capture"));
314 
315       a = getAction("copy");
316       popup->addAction(a);
317       a = new QAction(tr("Copy with Link to Score"), this);
318       a->setData("copy-link");
319       popup->addAction(a);
320 
321       popup->addSeparator();
322       a = popup->addAction(tr("Resolution (%1 DPI)…").arg(preferences.getDouble(PREF_EXPORT_PNG_RESOLUTION)));
323       a->setData("set-res");
324       QAction* bgAction = popup->addAction(tr("Transparent background"));
325       bgAction->setCheckable(true);
326       bgAction->setChecked(preferences.getBool(PREF_EXPORT_PNG_USETRANSPARENCY));
327       bgAction->setData("set-bg");
328 
329       popup->addSeparator();
330       a = new QAction(tr("Auto-resize to page"), this);
331       a->setData("resizePage");
332       popup->addAction(a);
333       for (int i = 0; i < 4; ++i) {
334             a = new QAction(qApp->translate("fotomode", resizeEntry[i].text), this);
335             a->setData(resizeEntry[i].label);
336             popup->addAction(a);
337             }
338       QMenu* setSize = new QMenu(tr("Set Standard Size…"));
339       for (int i = 0; i < 4; ++i) {
340             a = new QAction(qApp->translate("fotomode", setSizeEntry[i].text), this);
341             a->setData(setSizeEntry[i].label);
342             setSize->addAction(a);
343             }
344       popup->addMenu(setSize);
345 
346       popup->addSeparator();
347       a = new QAction(tr("Save As (Print Mode)…"), this);
348       a->setData("print");
349       popup->addAction(a);
350       a = new QAction(tr("Save As (Screenshot Mode)…"), this);
351       a->setData("screenshot");
352       popup->addAction(a);
353 
354       a = popup->exec(pos);
355       if (a == 0)
356             return;
357       QString cmd(a->data().toString());
358       if (cmd == "print")
359             saveFotoAs(true, _foto->canvasBoundingRect());
360       else if (cmd == "screenshot")
361             saveFotoAs(false, _foto->canvasBoundingRect());
362       else if (cmd == "copy")
363             ;
364       else if (cmd == "copy-link")
365             fotoModeCopy(true);
366       else if (cmd == "set-res") {
367             bool ok;
368             double resolution = QInputDialog::getDouble(this,
369                tr("Set Output Resolution"),
370                tr("Set output resolution for PNG"),
371                preferences.getDouble(PREF_EXPORT_PNG_RESOLUTION),
372                16.0, 2400.0, 1,
373                &ok
374                );
375             if (ok) {
376                   preferences.setPreference(PREF_EXPORT_PNG_RESOLUTION, resolution);
377                   }
378             }
379       else if (cmd == "resizePage") {
380             QRectF r = _foto->bbox();
381             Page* page = point2page(r.center());
382             if (page) {
383                   r = page->tbbox().translated(page->canvasPos());
384                   _foto->setbbox(r);
385                   updateGrips();
386                   }
387             }
388       else if (cmd.startsWith("resize")) {
389             QString size = QSettings().value(QString("fotoSize%1").arg(cmd[6]), "50x40").toString();
390             qreal w = size.split("x")[0].toDouble();
391             qreal h = size.split("x")[1].toDouble();
392             _foto->bbox().setSize(QSizeF(w * DPMM, h * DPMM));
393             updateGrips();
394             }
395       else if (cmd.startsWith("set")) {
396             qreal w   = _foto->bbox().width() / DPMM;
397             qreal h   = _foto->bbox().height() / DPMM;
398             QString val(QString("%1x%2").arg(w).arg(h));
399             QSettings().setValue(QString("fotoSize%1").arg(cmd[3]), val);
400             }
401       if (bgAction->isChecked() != preferences.getBool(PREF_EXPORT_PNG_USETRANSPARENCY)) {
402             preferences.setPreference(PREF_EXPORT_PNG_USETRANSPARENCY, bgAction->isChecked());
403             }
404       }
405 
406 //---------------------------------------------------------
407 //   getRectImage
408 //---------------------------------------------------------
409 
getRectImage(const QRectF & rect,double dpi,bool transparent,bool printMode)410 QImage ScoreView::getRectImage(const QRectF& rect, double dpi, bool transparent, bool printMode)
411       {
412       const double mag = dpi / DPI;
413       const int w = lrint(rect.width()  * mag);
414       const int h = lrint(rect.height() * mag);
415 
416       QImage::Format f = QImage::Format_ARGB32_Premultiplied;
417       QImage img(w, h, f);
418       img.setDotsPerMeterX(lrint((dpi * 1000) / INCH));
419       img.setDotsPerMeterY(lrint((dpi * 1000) / INCH));
420       img.fill(transparent ? 0 : 0xffffffff);
421 
422       const auto pr = MScore::pixelRatio;
423       MScore::pixelRatio = 1.0 / mag;
424       QPainter p(&img);
425       paintRect(printMode, p, rect, mag);
426       MScore::pixelRatio = pr;
427 
428       return img;
429       }
430 
431 //---------------------------------------------------------
432 //   fotoModeCopy
433 //---------------------------------------------------------
434 
fotoModeCopy(bool includeLink)435 void ScoreView::fotoModeCopy(bool includeLink)
436       {
437 #if defined(Q_OS_WIN)
438       // See https://bugreports.qt.io/browse/QTBUG-11463
439       // while transparent copy/paste works fine inside musescore,
440       // it does not paste into other programs in Windows though
441       bool transparent = false; // preferences.getBool(PREF_EXPORT_PNG_USETRANSPARENCY);
442 #else
443       bool transparent = preferences.getBool(PREF_EXPORT_PNG_USETRANSPARENCY);
444 #endif
445       double convDpi   = preferences.getDouble(PREF_EXPORT_PNG_RESOLUTION);
446       QRectF r(_foto->canvasBoundingRect());
447 
448       QImage printer(getRectImage(r, convDpi, transparent, /* printMode */ true));
449       QApplication::clipboard()->clear();
450 
451       if (includeLink) {
452             QUrl url = QUrl::fromLocalFile(score()->masterScore()->fileInfo()->canonicalFilePath());
453             QByteArray imageData;
454             QBuffer buffer(&imageData);
455             buffer.open(QIODevice::WriteOnly);
456             printer.save(&buffer, "PNG");
457             buffer.close();
458             QString html = "<a href=\"" + url.toString() + "\"><img src=\"data:image/png," + imageData.toPercentEncoding() + "\" /></a>";
459             QMimeData *mdata = new QMimeData;
460             mdata->setHtml(html);
461             QApplication::clipboard()->setMimeData(mdata);
462             // TODO: add both, with priority to html
463             //QApplication::clipboard()->setImage(printer);
464             }
465       else {
466             QApplication::clipboard()->setImage(printer);
467             }
468       }
469 
470 //---------------------------------------------------------
471 //   fotoRectHit
472 //---------------------------------------------------------
473 
fotoRectHit(const QPoint & pos)474 bool ScoreView::fotoRectHit(const QPoint& pos)
475       {
476       QPointF p = toLogical(pos);
477       for (int i = 0; i < editData.grips; ++i) {
478             if (editData.grip[i].contains(p))
479                   return false;
480             }
481       editData.startMove = p;
482       return _foto->bbox().contains(p);
483       }
484 
485 //---------------------------------------------------------
486 //   saveFotoAs
487 //    return true on success
488 //---------------------------------------------------------
489 
saveFotoAs(bool printMode,const QRectF & r)490 bool ScoreView::saveFotoAs(bool printMode, const QRectF& r)
491       {
492       QStringList fl;
493       fl.append(tr("PNG Bitmap Graphic") + " (*.png)");
494       fl.append(tr("PDF File") + " (*.pdf)");
495       fl.append(tr("Scalable Vector Graphics") + " (*.svg)");
496 
497       QString selectedFilter;
498       QString filter = fl.join(";;");
499       QString fn = mscore->getFotoFilename(filter, &selectedFilter);
500 
501       if (fn.isEmpty())
502             return false;
503 
504       QFileInfo fi(fn);
505       mscore->lastSaveDirectory = fi.absolutePath();
506 
507       QString ext;
508       if (selectedFilter.isEmpty()) {
509             ext = fi.suffix();
510             }
511       else {
512             int idx = fl.indexOf(selectedFilter);
513             if (idx != -1) {
514                   static const char* extensions[] = {
515                         "png",
516                         "pdf",
517                         "svg"
518                         };
519                   ext = extensions[idx];
520                   }
521             }
522 
523       if (ext.isEmpty()) {
524             QMessageBox::critical(mscore, tr("Save As"), tr("Cannot determine file type"));
525             return false;
526             }
527 
528       ext = ext.toLower();
529       if (fi.suffix().toLower() != ext)
530             fn += "." + ext;
531 
532       bool transparent = preferences.getBool(PREF_EXPORT_PNG_USETRANSPARENCY);
533       double convDpi   = preferences.getDouble(PREF_EXPORT_PNG_RESOLUTION);
534       double mag       = convDpi / DPI;
535 
536       if (ext == "svg")
537             mag = 1; // SVG is not scaled, it's scalable.
538 
539       int w = lrint(r.width()  * mag);
540       int h = lrint(r.height() * mag);
541 
542       double pr = MScore::pixelRatio;
543       if (ext == "pdf") {
544             QPdfWriter pdfWriter(fn);
545             pdfWriter.setResolution(preferences.getInt(PREF_EXPORT_PDF_DPI));
546             mag = pdfWriter.logicalDpiX() / DPI;
547             QSizeF size(r.width() / DPI, r.height() / DPI);
548             QPageSize ps(size, QPageSize::Inch, "", QPageSize::ExactMatch);
549             pdfWriter.setPageSize(ps);
550             pdfWriter.setPageMargins(QMarginsF(0.0, 0.0, 0.0, 0.0));
551             pdfWriter.setCreator("MuseScore Version: " VERSION);
552             pdfWriter.setTitle(fn);
553             MScore::pixelRatio = DPI / pdfWriter.logicalDpiX();
554             QPainter p(&pdfWriter);
555             MScore::pdfPrinting = true;
556             paintRect(printMode, p, r, mag);
557             MScore::pdfPrinting = false;
558             }
559       else if (ext == "svg") {
560             // note that clipping is not implemented
561             // (as of 4.8)
562             SvgGenerator printer;
563             printer.setFileName(fn);
564             printer.setTitle(_score->title());
565             printer.setSize(QSize(w, h));
566             printer.setViewBox(QRect(0, 0, w, h));
567             MScore::pixelRatio = DPI / printer.logicalDpiX();
568             QPainter p(&printer);
569             MScore::pdfPrinting = true;
570             paintRect(printMode, p, r, mag);
571             MScore::pdfPrinting = false;
572             }
573       else if (ext == "png") {
574             QImage printer(getRectImage(r, convDpi, transparent, printMode));
575             printer.save(fn, "png");
576             }
577       else
578             qDebug("unknown extension <%s>", qPrintable(ext));
579       MScore::pixelRatio = pr;
580       return true;
581       }
582 
583 //---------------------------------------------------------
584 //   paintRect
585 //---------------------------------------------------------
586 
paintRect(bool printMode,QPainter & p,const QRectF & r,double mag)587 void ScoreView::paintRect(bool printMode, QPainter& p, const QRectF& r, double mag)
588       {
589       p.scale(mag, mag);
590       p.translate(-r.topLeft());
591       p.setRenderHint(QPainter::Antialiasing, true);
592       p.setRenderHint(QPainter::TextAntialiasing, true);
593 
594       score()->setPrinting(printMode);
595 
596       foreach (Page* page, _score->pages()) {
597             // QRectF pr(page->abbox());
598             QRectF pr(page->canvasBoundingRect());
599             if (pr.right() < r.left())
600                   continue;
601             if (pr.left() > r.right())
602                   break;
603             p.translate(page->pos());
604             QList<Element*> ell = page->items(r.translated(-page->pos()));
605             drawElements(p, ell, nullptr);
606             p.translate(-page->pos());
607             }
608 
609       score()->setPrinting(false);
610       p.end();
611       }
612 
613 //---------------------------------------------------------
614 //   fotoDragDrop
615 //---------------------------------------------------------
616 
fotoDragDrop(QMouseEvent *)617 void ScoreView::fotoDragDrop(QMouseEvent*)
618       {
619       bool printMode   = true;
620       QRectF r(_foto->bbox());
621 
622       QTemporaryFile tf(QDir::tempPath() + QString("/imgXXXXXX.svg"));
623       tf.setAutoRemove(false);  // TODO: find out whether, where, when and how to delete it
624       tf.open();
625       tf.close();
626       qDebug("Temp File <%s>", qPrintable(tf.fileName()));
627 
628 //      QString fn = "/home/ws/mops.eps";
629       QString fn = tf.fileName();
630 
631       int w = lrint(r.width());
632       int h = lrint(r.height());
633       SvgGenerator printer;
634       printer.setFileName(fn);
635       printer.setTitle(_score->title());
636       printer.setSize(QSize(w, h));
637       printer.setViewBox(QRect(0, 0, w, h));
638       QPainter p(&printer);
639       MScore::pdfPrinting = true;
640       paintRect(printMode, p, r, 1);
641       MScore::pdfPrinting = false;
642 
643       QDrag* drag = new QDrag(this);
644       QMimeData* mimeData = new QMimeData;
645 
646       QUrl url = QUrl::fromLocalFile(fn);
647       QList<QUrl> ul;
648       ul.append(url);
649       mimeData->setUrls(ul);
650 
651       drag->setMimeData(mimeData);
652       drag->exec(Qt::CopyAction);
653       }
654 }
655 
656