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