1 //=============================================================================
2 //  MuseScore
3 //  Linux Music Score Editor
4 //
5 //  Copyright (C) 2009-2011 Werner Schweer and others
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 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19 
20 #include "musescore.h"
21 #include "harmonyedit.h"
22 #include "harmonycanvas.h"
23 #include "palette.h"
24 #include "libmscore/accidental.h"
25 #include "libmscore/score.h"
26 #include "icons.h"
27 #include "libmscore/pitchspelling.h"
28 #include "libmscore/symbol.h"
29 #include "libmscore/chordlist.h"
30 #include "libmscore/mscore.h"
31 #include "libmscore/xml.h"
32 
33 namespace Ms {
34 
35 extern bool useFactorySettings;
36 
37 //---------------------------------------------------------
38 //   ChordStyleEditor
39 //---------------------------------------------------------
40 
ChordStyleEditor(QWidget * parent)41 ChordStyleEditor::ChordStyleEditor(QWidget* parent)
42    : QDialog(parent)
43       {
44       setObjectName("ChordStyleEditor");
45       setupUi(this);
46       setWindowTitle(tr("Chord Symbols Style Editor"));
47       setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
48 
49       chordList = 0;
50       score = 0;
51 
52       connect(fileButton, SIGNAL(clicked()), SLOT(fileButtonClicked()));
53       connect(saveButton, SIGNAL(clicked()), SLOT(saveButtonClicked()));
54       connect(harmonyList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
55          SLOT(harmonyChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
56       }
57 
58 //---------------------------------------------------------
59 //   setScore
60 //---------------------------------------------------------
61 
setScore(Score * s)62 void ChordStyleEditor::setScore(Score* s)
63       {
64       score = s;
65       setChordList(s->style().chordList());
66       }
67 
68 //---------------------------------------------------------
69 //   editChordStyle
70 //---------------------------------------------------------
71 
editChordStyle()72 void MuseScore::editChordStyle()
73       {
74       if (chordStyleEditor == 0) {
75             chordStyleEditor = new ChordStyleEditor(0);
76             chordStyleEditor->restore();
77             }
78       chordStyleEditor->setScore(cs);
79       chordStyleEditor->show();
80       chordStyleEditor->raise();
81       }
82 
83 //---------------------------------------------------------
84 //   fileButtonClicked
85 //---------------------------------------------------------
86 
fileButtonClicked()87 void ChordStyleEditor::fileButtonClicked()
88       {
89       QString fn = mscore->getChordStyleFilename(true);
90       if (fn.isEmpty())
91             return;
92       loadChordDescriptionFile(fn);
93       }
94 
95 //---------------------------------------------------------
96 //   saveButtonClicked
97 //---------------------------------------------------------
98 
saveButtonClicked()99 void ChordStyleEditor::saveButtonClicked()
100       {
101       if (!chordList)
102             return;
103       canvas->updateChordDescription();
104 
105       QString fn = mscore->getChordStyleFilename(false);
106       if (fn.isEmpty())
107             return;
108       chordList->write(fn);
109       }
110 
111 //---------------------------------------------------------
112 //   loadChordDescriptionFile
113 //---------------------------------------------------------
114 
loadChordDescriptionFile(const QString & s)115 void ChordStyleEditor::loadChordDescriptionFile(const QString& s)
116       {
117       ChordList* cl = new ChordList;
118       if (!cl->read("chords.xml")) {
119             qDebug("cannot read <chords.xml>");
120             delete cl;
121             return;
122             }
123       if (!cl->read(s)) {
124             qDebug("cannot read <%s>", qPrintable(s));
125             delete cl;
126             return;
127             }
128       setChordList(cl);
129       }
130 
131 //---------------------------------------------------------
132 //   setChordList
133 //---------------------------------------------------------
134 
setChordList(ChordList * cl)135 void ChordStyleEditor::setChordList(ChordList* cl)
136       {
137       harmonyList->clear();
138       foreach (const ChordDescription& d, *cl) {
139             QTreeWidgetItem* item = new QTreeWidgetItem;
140             item->setData(0, Qt::UserRole, QVariant::fromValue<void*>((void*)&d));
141             item->setText(0, QString("%1").arg(d.id));
142             if (!d.names.isEmpty())
143                   item->setText(1, QString("%1").arg(d.names.front()));
144             harmonyList->addTopLevelItem(item);
145             }
146       delete chordList;
147       chordList = new ChordList(*cl);
148       canvas->setChordDescription(0, 0);
149 
150       paletteTab->clear();
151       foreach(const ChordFont& f, chordList->fonts) {
152             // create symbol palette
153             Palette* p = new Palette();
154             PaletteScrollArea* accPalette = new PaletteScrollArea(p);
155             QSizePolicy policy1(QSizePolicy::Expanding, QSizePolicy::Expanding);
156             accPalette->setSizePolicy(policy1);
157             accPalette->setRestrictHeight(false);
158             p->setGrid(50, 50);
159             paletteTab->addTab(accPalette, f.family);
160             QFont qf(f.family);
161             qf.setStyleStrategy(QFont::NoFontMerging);
162             int size = lrint(20.0 * DPI / PPI);
163             qf.setPixelSize(size);
164 
165             QFontMetricsF fi(qf);
166             for (int i = 0; i < 255; ++i) {
167                   if (fi.inFont(QChar(i))) {
168                         FSymbol* s = new FSymbol(gscore);
169                         s->setFont(qf);
170                         s->setCode(i);
171                         p->append(s, "??");
172                         }
173                   }
174             }
175       raise();
176       }
177 
178 //---------------------------------------------------------
179 //   harmonyChanged
180 //---------------------------------------------------------
181 
harmonyChanged(QTreeWidgetItem * current,QTreeWidgetItem * previous)182 void ChordStyleEditor::harmonyChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
183       {
184       if (previous) {
185             // ChordDescription* d = static_cast<ChordDescription*>(previous->data(0, Qt::UserRole).value<void*>());
186             canvas->updateChordDescription();
187             }
188       if (current) {
189             ChordDescription* d = static_cast<ChordDescription*>(current->data(0, Qt::UserRole).value<void*>());
190             canvas->setChordDescription(d, chordList);
191             }
192       }
193 
194 //---------------------------------------------------------
195 //   save
196 //---------------------------------------------------------
197 
save()198 void ChordStyleEditor::save()
199       {
200       QSettings settings;
201       settings.beginGroup(objectName());
202       settings.setValue("splitter1", splitter1->saveState());
203       settings.setValue("splitter2", splitter2->saveState());
204 //      settings.setValue("list", harmonyList->saveState());
205       settings.setValue("col1", harmonyList->columnWidth(0));
206       MuseScore::saveGeometry(this);
207       }
208 
209 //---------------------------------------------------------
210 //   restore
211 //---------------------------------------------------------
212 
restore()213 void ChordStyleEditor::restore()
214       {
215       MuseScore::restoreGeometry(this);
216       if (!useFactorySettings) {
217             QSettings settings;
218             settings.beginGroup(objectName());
219             splitter1->restoreState(settings.value("splitter1").toByteArray());
220             splitter2->restoreState(settings.value("splitter2").toByteArray());
221             harmonyList->setColumnWidth(0, settings.value("col1", 30).toInt());
222             }
223       }
224 
225 //---------------------------------------------------------
226 //   HarmonyCanvas
227 //---------------------------------------------------------
228 
HarmonyCanvas(QWidget * parent)229 HarmonyCanvas::HarmonyCanvas(QWidget* parent)
230    : QFrame(parent)
231       {
232       setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
233       setAcceptDrops(true);
234       setFocusPolicy(Qt::StrongFocus);
235       extraMag = 3.0;
236       chordDescription = nullptr;
237       chordList   = nullptr;
238       moveElement = nullptr;
239       dragElement = nullptr;
240       QAction* a = getAction("delete");
241       addAction(a);
242       connect(a, SIGNAL(triggered()), SLOT(deleteAction()));
243       }
244 
245 //---------------------------------------------------------
246 //   paintEvent
247 //---------------------------------------------------------
248 
paintEvent(QPaintEvent * event)249 void HarmonyCanvas::paintEvent(QPaintEvent* event)
250       {
251       QFrame::paintEvent(event);
252       if (!chordDescription)
253             return;
254 
255       qreal spatium = gscore->spatium();
256       qreal mag = PALETTE_SPATIUM * extraMag / spatium;
257       spatium = SPATIUM20;
258 
259       QPainter p(this);
260 
261       p.setRenderHint(QPainter::Antialiasing, true);
262       qreal wh = double(height());
263       qreal ww = double(width());
264 
265       _matrix   = QTransform(mag, 0.0, 0.0, mag, ww*.1, wh*.8);
266       imatrix   = _matrix.inverted();
267 
268       p.setWorldTransform(_matrix);
269       QRectF f = imatrix.mapRect(QRectF(0.0, 0.0, ww, wh));
270 
271       p.setPen(QPen(Qt::darkGray));
272       p.drawLine(f.x(), 0.0, f.width(), 0.0);
273       p.drawLine(0.0, f.y(), 0.0, f.height());
274 
275       foreach(const TextSegment* ts, textList) {
276             p.setFont(ts->font);
277             QPen pen(ts->select ? Qt::blue : palette().color(QPalette::Text));
278             p.setPen(pen);
279             p.drawText(ts->x, ts->y, ts->text);
280             }
281 
282       if (dragElement && dragElement->type() == ElementType::FSYMBOL) {
283             FSymbol* sb = static_cast<FSymbol*>(dragElement);
284 
285 //TODO:ws             double _spatium = 2.0 * PALETTE_SPATIUM / extraMag;
286 //            const TextStyle* st = &gscore->textStyle(TextStyleType::HARMONY);
287 //            QFont ff(st->font(_spatium * MScore::pixelRatio));
288 //            ff.setFamily(sb->font().family());
289 
290             QString s;
291             int code = sb->code();
292             if (code & 0xffff0000) {
293                   s = QChar(QChar::highSurrogate(code));
294                   s += QChar(QChar::lowSurrogate(code));
295                   }
296             else
297                   s = QChar(code);
298 //TODO:ws            p.setFont(ff);
299             QPen pen(Qt::yellow);
300             p.setPen(pen);
301             p.drawText(dragElement->pos(), s);
302             }
303       }
304 
305 //---------------------------------------------------------
306 //   render
307 //---------------------------------------------------------
308 
render(const QList<RenderAction> &,double &,double &,int,NoteSpellingType,NoteCaseType)309 void HarmonyCanvas::render(const QList<RenderAction>& /*renderList*/, double& /*x*/, double& /*y*/, int /*tpc*/, NoteSpellingType /*noteSpelling*/, NoteCaseType /*noteCase*/)
310       {
311 #if 0 // TODO:ws
312       QStack<QPointF> stack;
313       int fontIdx = 0;
314       double _spatium = 2.0 * PALETTE_SPATIUM / extraMag;
315 //      qreal mag  = PALETTE_SPATIUM * extraMag / _spatium;
316 
317       QList<QFont> fontList;              // temp values used in render()
318       const TextStyle* st = &gscore->textStyle(TextStyleType::HARMONY);
319 
320       foreach(ChordFont cf, chordList->fonts) {
321             if (cf.family.isEmpty() || cf.family == "default")
322                   fontList.append(st->font(_spatium * cf.mag * MScore::pixelRatio));
323             else {
324                   QFont ff(st->font(_spatium * cf.mag * MScore::pixelRatio));
325                   ff.setFamily(cf.family);
326                   fontList.append(ff);
327                   }
328             }
329       if (fontList.isEmpty())
330             fontList.append(st->font(_spatium * MScore::pixelRatio));
331 
332       foreach(const RenderAction& a, renderList) {
333             if (a.type == RenderAction::RenderActionType::SET) {
334                   TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
335                   ChordSymbol cs = chordList->symbol(a.text);
336                   if (cs.isValid()) {
337                         ts->font = fontList[cs.fontIdx];
338                         ts->setText(cs.value);
339                         }
340                   else
341                         ts->setText(a.text);
342                   textList.append(ts);
343                   x += ts->width();
344                   }
345             else if (a.type == RenderAction::RenderActionType::MOVE) {
346                   x += a.movex;//  * mag;
347                   y += a.movey; //  * mag;
348                   }
349             else if (a.type == RenderAction::RenderActionType::PUSH)
350                   stack.push(QPointF(x,y));
351             else if (a.type == RenderAction::RenderActionType::POP) {
352                   if (!stack.isEmpty()) {
353                         QPointF pt = stack.pop();
354                         x = pt.x();
355                         y = pt.y();
356                         }
357                   else
358                         qDebug("RenderAction::RenderActionType::POP: stack empty");
359                   }
360             else if (a.type == RenderAction::RenderActionType::NOTE) {
361                   QString c;
362                   AccidentalVal acc;
363                   tpc2name(tpc, noteSpelling, noteCase, c, acc);
364                   TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
365                   QString lookup = "note" + c;
366                   ChordSymbol cs = chordList->symbol(lookup);
367                   if (!cs.isValid())
368                         cs = chordList->symbol(c);
369                   if (cs.isValid()) {
370                         ts->font = fontList[cs.fontIdx];
371                         ts->setText(cs.value);
372                         }
373                   else
374                         ts->setText(c);
375                   textList.append(ts);
376                   x += ts->width();
377                   }
378             else if (a.type == RenderAction::RenderActionType::ACCIDENTAL) {
379                   QString c;
380                   QString acc;
381                   tpc2name(tpc, noteSpelling, noteCase, c, acc);
382                   if (acc != "") {
383                         TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
384                         ChordSymbol cs = chordList->symbol(acc);
385                         if (cs.isValid()) {
386                               ts->font = fontList[cs.fontIdx];
387                               ts->setText(cs.value);
388                               }
389                         else
390                               ts->setText(acc);
391                         textList.append(ts);
392                         x += ts->width();
393                         }
394                   }
395             }
396 #endif
397       }
398 
399 //---------------------------------------------------------
400 //   mousePressEvent
401 //---------------------------------------------------------
402 
mousePressEvent(QMouseEvent * event)403 void HarmonyCanvas::mousePressEvent(QMouseEvent* event)
404       {
405       startMove = imatrix.map(QPointF(event->pos()));
406       moveElement = 0;
407       foreach(TextSegment* ts, textList) {
408             QRectF r = ts->boundingRect().translated(ts->x, ts->y);
409             ts->select = r.contains(startMove);
410             if (ts->select)
411                   moveElement = ts;
412             }
413       update();
414       }
415 
416 //---------------------------------------------------------
417 //   mouseMoveEvent
418 //---------------------------------------------------------
419 
mouseMoveEvent(QMouseEvent * event)420 void HarmonyCanvas::mouseMoveEvent(QMouseEvent* event)
421       {
422       if (moveElement == 0)
423             return;
424       QPointF p = imatrix.map(QPointF(event->pos()));
425       QPointF delta = p - startMove;
426       moveElement->x += delta.x();
427       moveElement->y += delta.y();
428       startMove = p;
429       update();
430       }
431 
432 //---------------------------------------------------------
433 //   mouseReleaseEvent
434 //---------------------------------------------------------
435 
mouseReleaseEvent(QMouseEvent *)436 void HarmonyCanvas::mouseReleaseEvent(QMouseEvent*)
437       {
438       }
439 
440 //---------------------------------------------------------
441 //   setChordDescription
442 //---------------------------------------------------------
443 
setChordDescription(ChordDescription * sd,ChordList * sl)444 void HarmonyCanvas::setChordDescription(ChordDescription* sd, ChordList* sl)
445       {
446       chordDescription = sd;
447       chordList = sl;
448 
449       foreach(TextSegment* s, textList)
450             delete s;
451       textList.clear();
452 
453       if (chordList) {
454             int tpc = 14;
455             double x = 0.0, y = 0.0;
456             NoteSpellingType rootSpelling, baseSpelling;
457             NoteCaseType rootCase = NoteCaseType::AUTO;
458             NoteCaseType baseCase = NoteCaseType::AUTO;
459             Harmony h(gscore);
460             h.determineRootBaseSpelling(rootSpelling, rootCase, baseSpelling, baseCase);
461             render(chordList->renderListRoot, x, y, tpc, rootSpelling, rootCase);
462             render(chordDescription->renderList, x, y, tpc, baseSpelling, baseCase);
463             }
464       moveElement = 0;
465       dragElement = 0;
466       update();
467       }
468 
469 //---------------------------------------------------------
470 //   dropEvent
471 //---------------------------------------------------------
472 
dropEvent(QDropEvent *)473 void HarmonyCanvas::dropEvent(QDropEvent* /*event*/)
474       {
475 #if 0       // TODO:ws
476       if (dragElement && dragElement->type() == ElementType::FSYMBOL) {
477             FSymbol* sb = static_cast<FSymbol*>(dragElement);
478 
479             double _spatium = 2.0 * PALETTE_SPATIUM / extraMag;
480             const TextStyle* st = &gscore->textStyle(TextStyleType::HARMONY);
481             QFont ff(st->font(_spatium * MScore::pixelRatio));
482             ff.setFamily(sb->font().family());
483 
484 //            qDebug("drop %s", dragElement->name());
485 
486             QString s;
487             int code = sb->code();
488             if (code & 0xffff0000) {
489                   s = QChar(QChar::highSurrogate(code));
490                   s += QChar(QChar::lowSurrogate(code));
491                   }
492             else
493                   s = QChar(code);
494 
495             QPointF pt = imatrix.map(event->pos());
496 
497             TextSegment* ts = new TextSegment(s, ff, pt.x(), pt.y());
498             textList.append(ts);
499             delete dragElement;
500             dragElement = 0;
501             update();
502             }
503 #endif
504       }
505 
506 //---------------------------------------------------------
507 //   dragEnterEvent
508 //---------------------------------------------------------
509 
dragEnterEvent(QDragEnterEvent * event)510 void HarmonyCanvas::dragEnterEvent(QDragEnterEvent* event)
511       {
512       const QMimeData* dta = event->mimeData();
513       if (dta->hasFormat(mimeSymbolFormat)) {
514             QByteArray a = dta->data(mimeSymbolFormat);
515 
516             XmlReader e(a);
517 
518             QPointF dragOffset;
519             Fraction duration;
520             ElementType type = Element::readType(e, &dragOffset, &duration);
521             if (type == ElementType::FSYMBOL) {
522                   event->acceptProposedAction();
523                   dragElement = Element::create(type, gscore);
524                   dragElement->read(e);
525                   dragElement->layout();
526                   }
527             }
528       }
529 
530 //---------------------------------------------------------
531 //   dragLeaveEvent
532 //---------------------------------------------------------
533 
dragLeaveEvent(QDragLeaveEvent *)534 void HarmonyCanvas::dragLeaveEvent(QDragLeaveEvent*)
535       {
536       delete dragElement;
537       dragElement = 0;
538       update();
539       }
540 
541 //---------------------------------------------------------
542 //   dragMoveEvent
543 //---------------------------------------------------------
544 
dragMoveEvent(QDragMoveEvent * event)545 void HarmonyCanvas::dragMoveEvent(QDragMoveEvent* event)
546       {
547       event->acceptProposedAction();
548       if (dragElement && dragElement->type() == ElementType::FSYMBOL) {
549             dragElement->setPos(imatrix.map(event->pos()));
550             update();
551             }
552       }
553 
554 //---------------------------------------------------------
555 //   deleteAction
556 //---------------------------------------------------------
557 
deleteAction()558 void HarmonyCanvas::deleteAction()
559       {
560       if (moveElement) {
561             textList.removeOne(moveElement);
562             update();
563             }
564       }
565 
566 //---------------------------------------------------------
567 //   updateHarmony
568 //---------------------------------------------------------
569 
updateHarmony(void *,Element * e)570 static void updateHarmony(void*, Element* e)
571       {
572       if (e->type() == ElementType::HARMONY)
573             static_cast<Harmony*>(e)->render();
574       }
575 
576 //---------------------------------------------------------
577 //   accept
578 //---------------------------------------------------------
579 
accept()580 void ChordStyleEditor::accept()
581       {
582       canvas->updateChordDescription();
583       score->style().setChordList(chordList);
584       chordList = 0;
585 
586       score->scanElements(0, updateHarmony);
587 
588       QDialog::accept();
589       }
590 
591 //---------------------------------------------------------
592 //   updateCurrentChordDescription
593 //---------------------------------------------------------
594 
updateChordDescription()595 void HarmonyCanvas::updateChordDescription()
596       {
597       if (!chordDescription)
598             return;
599       chordDescription->renderList.clear();
600 
601       int idx = 0;
602       double x  = 0, y = 0;
603       foreach(const TextSegment* ts, textList) {
604             ++idx;
605             if (idx == 1) {     // don’t save base
606                   x = ts->x + ts->width();
607                   y = ts->y;
608                   continue;
609                   }
610             if (ts->x != x || ts->y != y) {
611                   RenderAction ra(RenderAction::RenderActionType::MOVE);
612                   ra.movex = ts->x - x;
613                   ra.movey = ts->y - y;
614                   chordDescription->renderList.append(ra);
615                   }
616             RenderAction ra(RenderAction::RenderActionType::SET);
617             ra.text  = ts->text;
618             chordDescription->renderList.append(ra);
619             x = ts->x + ts->width();
620             y = ts->y;
621             }
622       chordDescription->exportOk = true;
623       }
624 
625 }
626 
627