//=============================================================================
//  MuseScore
//  Linux Music Score Editor
//  $Id:$
//
//  Copyright (C) 2009-2011 Werner Schweer and others
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
 
#include "musescore.h"
#include "harmonyedit.h"
#include "harmonycanvas.h"
#include "palette.h"
#include "libmscore/accidental.h"
#include "libmscore/score.h"
#include "icons.h"
#include "libmscore/pitchspelling.h"
#include "libmscore/symbol.h"
#include "libmscore/chordlist.h"
#include "libmscore/mscore.h"
#include "libmscore/xml.h"
 
namespace Ms {
 
extern bool useFactorySettings;
 
//---------------------------------------------------------
//   ChordStyleEditor
//---------------------------------------------------------
 
ChordStyleEditor::ChordStyleEditor(QWidget* parent)
   : QDialog(parent)
      {
      setObjectName("ChordStyleEditor");
      setupUi(this);
      setWindowTitle(tr("Chord Symbols Style Editor"));
      setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
 
      chordList = 0;
      score = 0;
 
      connect(fileButton, SIGNAL(clicked()), SLOT(fileButtonClicked()));
      connect(saveButton, SIGNAL(clicked()), SLOT(saveButtonClicked()));
      connect(harmonyList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
         SLOT(harmonyChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
      }
 
//---------------------------------------------------------
//   setScore
//---------------------------------------------------------
 
void ChordStyleEditor::setScore(Score* s)
      {
      score = s;
      setChordList(s->style().chordList());
      }
 
//---------------------------------------------------------
//   editChordStyle
//---------------------------------------------------------
 
void MuseScore::editChordStyle()
      {
      if (chordStyleEditor == 0) {
            chordStyleEditor = new ChordStyleEditor(0);
            chordStyleEditor->restore();
            }
      chordStyleEditor->setScore(cs);
      chordStyleEditor->show();
      chordStyleEditor->raise();
      }
 
//---------------------------------------------------------
//   fileButtonClicked
//---------------------------------------------------------
 
void ChordStyleEditor::fileButtonClicked()
      {
      QString fn = mscore->getChordStyleFilename(true);
      if (fn.isEmpty())
            return;
      loadChordDescriptionFile(fn);
      }
 
//---------------------------------------------------------
//   saveButtonClicked
//---------------------------------------------------------
 
void ChordStyleEditor::saveButtonClicked()
      {
      if (!chordList)
            return;
      canvas->updateChordDescription();
 
      QString fn = mscore->getChordStyleFilename(false);
      if (fn.isEmpty())
            return;
      chordList->write(fn);
      }
 
//---------------------------------------------------------
//   loadChordDescriptionFile
//---------------------------------------------------------
 
void ChordStyleEditor::loadChordDescriptionFile(const QString& s)
      {
      ChordList* cl = new ChordList;
      if (!cl->read("chords.xml")) {
            qDebug("cannot read <chords.xml>");
            return;
            }
      if (!cl->read(s)) {
            qDebug("cannot read <%s>", qPrintable(s));
            return;
            }
      setChordList(cl);
      }
 
//---------------------------------------------------------
//   setChordList
//---------------------------------------------------------
 
void ChordStyleEditor::setChordList(ChordList* cl)
      {
      harmonyList->clear();
      foreach (const ChordDescription& d, *cl) {
            QTreeWidgetItem* item = new QTreeWidgetItem;
            item->setData(0, Qt::UserRole, QVariant::fromValue<void*>((void*)&d));
            item->setText(0, QString("%1").arg(d.id));
            if (!d.names.isEmpty())
                  item->setText(1, QString("%1").arg(d.names.front()));
            harmonyList->addTopLevelItem(item);
            }
      delete chordList;
      chordList = new ChordList(*cl);
      canvas->setChordDescription(0, 0);
 
      paletteTab->clear();
      foreach(const ChordFont& f, chordList->fonts) {
            // create symbol palette
            Palette* p = new Palette();
            PaletteScrollArea* accPalette = new PaletteScrollArea(p);
            QSizePolicy policy1(QSizePolicy::Expanding, QSizePolicy::Expanding);
            accPalette->setSizePolicy(policy1);
            accPalette->setRestrictHeight(false);
            p->setGrid(50, 50);
            paletteTab->addTab(accPalette, f.family);
            QFont qf(f.family);
            qf.setStyleStrategy(QFont::NoFontMerging);
            int size = lrint(20.0 * DPI / PPI);
            qf.setPixelSize(size);
 
            QFontMetricsF fi(qf);
            for (int i = 0; i < 255; ++i) {
                  if (fi.inFont(QChar(i))) {
                        FSymbol* s = new FSymbol(gscore);
                        s->setFont(qf);
                        s->setCode(i);
                        p->append(s, "??");
                        }
                  }
            }
      raise();
      }
 
//---------------------------------------------------------
//   harmonyChanged
//---------------------------------------------------------
 
void ChordStyleEditor::harmonyChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
      {
      if (previous) {
            // ChordDescription* d = static_cast<ChordDescription*>(previous->data(0, Qt::UserRole).value<void*>());
            canvas->updateChordDescription();
            }
      if (current) {
            ChordDescription* d = static_cast<ChordDescription*>(current->data(0, Qt::UserRole).value<void*>());
            canvas->setChordDescription(d, chordList);
            }
      }
 
//---------------------------------------------------------
//   save
//---------------------------------------------------------
 
void ChordStyleEditor::save()
      {
      QSettings settings;
      settings.beginGroup(objectName());
      settings.setValue("splitter1", splitter1->saveState());
      settings.setValue("splitter2", splitter2->saveState());
//      settings.setValue("list", harmonyList->saveState());
      settings.setValue("col1", harmonyList->columnWidth(0));
      MuseScore::saveGeometry(this);
      }
 
//---------------------------------------------------------
//   restore
//---------------------------------------------------------
 
void ChordStyleEditor::restore()
      {
      MuseScore::restoreGeometry(this);
      if (!useFactorySettings) {
            QSettings settings;
            settings.beginGroup(objectName());
            splitter1->restoreState(settings.value("splitter1").toByteArray());
            splitter2->restoreState(settings.value("splitter2").toByteArray());
            harmonyList->setColumnWidth(0, settings.value("col1", 30).toInt());
            }
      }
 
//---------------------------------------------------------
//   HarmonyCanvas
//---------------------------------------------------------
 
HarmonyCanvas::HarmonyCanvas(QWidget* parent)
   : QFrame(parent)
      {
      setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
      setAcceptDrops(true);
      setFocusPolicy(Qt::StrongFocus);
      extraMag = 3.0;
      chordDescription = 0;
      chordList = 0;
      moveElement = 0;
      QAction* a = getAction("delete");
      addAction(a);
      connect(a, SIGNAL(triggered()), SLOT(deleteAction()));
      }
 
//---------------------------------------------------------
//   paintEvent
//---------------------------------------------------------
 
void HarmonyCanvas::paintEvent(QPaintEvent* event)
      {
      QFrame::paintEvent(event);
      if (!chordDescription)
            return;
 
      qreal spatium = gscore->spatium();
      qreal mag = PALETTE_SPATIUM * extraMag / spatium;
      spatium = SPATIUM20;
 
      QPainter p(this);
 
      p.setRenderHint(QPainter::Antialiasing, true);
      qreal wh = double(height());
      qreal ww = double(width());
 
      _matrix   = QTransform(mag, 0.0, 0.0, mag, ww*.1, wh*.8);
      imatrix   = _matrix.inverted();
 
      p.setWorldTransform(_matrix);
      QRectF f = imatrix.mapRect(QRectF(0.0, 0.0, ww, wh));
 
      p.setPen(QPen(Qt::darkGray));
      p.drawLine(f.x(), 0.0, f.width(), 0.0);
      p.drawLine(0.0, f.y(), 0.0, f.height());
 
      foreach(const TextSegment* ts, textList) {
            p.setFont(ts->font);
            QPen pen(ts->select ? Qt::blue : palette().color(QPalette::Text));
            p.setPen(pen);
            p.drawText(ts->x, ts->y, ts->text);
            }
 
      if (dragElement && dragElement->type() == ElementType::FSYMBOL) {
            FSymbol* sb = static_cast<FSymbol*>(dragElement);
 
//TODO:ws             double _spatium = 2.0 * PALETTE_SPATIUM / extraMag;
//            const TextStyle* st = &gscore->textStyle(TextStyleType::HARMONY);
//            QFont ff(st->font(_spatium * MScore::pixelRatio));
//            ff.setFamily(sb->font().family());
 
            QString s;
            int code = sb->code();
            if (code & 0xffff0000) {
                  s = QChar(QChar::highSurrogate(code));
                  s += QChar(QChar::lowSurrogate(code));
                  }
            else
                  s = QChar(code);
//TODO:ws            p.setFont(ff);
            QPen pen(Qt::yellow);
            p.setPen(pen);
            p.drawText(dragElement->pos(), s);
            }
      }
 
//---------------------------------------------------------
//   render
//---------------------------------------------------------
 
void HarmonyCanvas::render(const QList<RenderAction>& /*renderList*/, double& /*x*/, double& /*y*/, int /*tpc*/, NoteSpellingType /*noteSpelling*/, NoteCaseType /*noteCase*/)
      {
#if 0 // TODO:ws
      QStack<QPointF> stack;
      int fontIdx = 0;
      double _spatium = 2.0 * PALETTE_SPATIUM / extraMag;
//      qreal mag  = PALETTE_SPATIUM * extraMag / _spatium;
 
      QList<QFont> fontList;              // temp values used in render()
      const TextStyle* st = &gscore->textStyle(TextStyleType::HARMONY);
 
      foreach(ChordFont cf, chordList->fonts) {
            if (cf.family.isEmpty() || cf.family == "default")
                  fontList.append(st->font(_spatium * cf.mag * MScore::pixelRatio));
            else {
                  QFont ff(st->font(_spatium * cf.mag * MScore::pixelRatio));
                  ff.setFamily(cf.family);
                  fontList.append(ff);
                  }
            }
      if (fontList.isEmpty())
            fontList.append(st->font(_spatium * MScore::pixelRatio));
 
      foreach(const RenderAction& a, renderList) {
            if (a.type == RenderAction::RenderActionType::SET) {
                  TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
                  ChordSymbol cs = chordList->symbol(a.text);
                  if (cs.isValid()) {
                        ts->font = fontList[cs.fontIdx];
                        ts->setText(cs.value);
                        }
                  else
                        ts->setText(a.text);
                  textList.append(ts);
                  x += ts->width();
                  }
            else if (a.type == RenderAction::RenderActionType::MOVE) {
                  x += a.movex;//  * mag;
                  y += a.movey; //  * mag;
                  }
            else if (a.type == RenderAction::RenderActionType::PUSH)
                  stack.push(QPointF(x,y));
            else if (a.type == RenderAction::RenderActionType::POP) {
                  if (!stack.isEmpty()) {
                        QPointF pt = stack.pop();
                        x = pt.x();
                        y = pt.y();
                        }
                  else
                        qDebug("RenderAction::RenderActionType::POP: stack empty");
                  }
            else if (a.type == RenderAction::RenderActionType::NOTE) {
                  QString c;
                  int acc;
                  tpc2name(tpc, noteSpelling, noteCase, c, acc);
                  TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
                  QString lookup = "note" + c;
                  ChordSymbol cs = chordList->symbol(lookup);
                  if (!cs.isValid())
                        cs = chordList->symbol(c);
                  if (cs.isValid()) {
                        ts->font = fontList[cs.fontIdx];
                        ts->setText(cs.value);
                        }
                  else
                        ts->setText(c);
                  textList.append(ts);
                  x += ts->width();
                  }
            else if (a.type == RenderAction::RenderActionType::ACCIDENTAL) {
                  QString c;
                  QString acc;
                  tpc2name(tpc, noteSpelling, noteCase, c, acc);
                  if (acc != "") {
                        TextSegment* ts = new TextSegment(fontList[fontIdx], x, y);
                        ChordSymbol cs = chordList->symbol(acc);
                        if (cs.isValid()) {
                              ts->font = fontList[cs.fontIdx];
                              ts->setText(cs.value);
                              }
                        else
                              ts->setText(acc);
                        textList.append(ts);
                        x += ts->width();
                        }
                  }
            }
#endif
      }
 
//---------------------------------------------------------
//   mousePressEvent
//---------------------------------------------------------
 
void HarmonyCanvas::mousePressEvent(QMouseEvent* event)
      {
      startMove = imatrix.map(QPointF(event->pos()));
      moveElement = 0;
      foreach(TextSegment* ts, textList) {
            QRectF r = ts->boundingRect().translated(ts->x, ts->y);
            ts->select = r.contains(startMove);
            if (ts->select)
                  moveElement = ts;
            }
      update();
      }
 
//---------------------------------------------------------
//   mouseMoveEvent
//---------------------------------------------------------
 
void HarmonyCanvas::mouseMoveEvent(QMouseEvent* event)
      {
      if (moveElement == 0)
            return;
      QPointF p = imatrix.map(QPointF(event->pos()));
      QPointF delta = p - startMove;
      moveElement->x += delta.x();
      moveElement->y += delta.y();
      startMove = p;
      update();
      }
 
//---------------------------------------------------------
//   mouseReleaseEvent
//---------------------------------------------------------
 
void HarmonyCanvas::mouseReleaseEvent(QMouseEvent*)
      {
      }
 
//---------------------------------------------------------
//   setChordDescription
//---------------------------------------------------------
 
void HarmonyCanvas::setChordDescription(ChordDescription* sd, ChordList* sl)
      {
      chordDescription = sd;
      chordList = sl;
 
      foreach(TextSegment* s, textList)
            delete s;
      textList.clear();
 
      if (chordList) {
            int tpc = 14;
            double x = 0.0, y = 0.0;
            NoteSpellingType rootSpelling, baseSpelling;
            NoteCaseType rootCase = NoteCaseType::AUTO;
            NoteCaseType baseCase = NoteCaseType::AUTO;
            Harmony h(gscore);
            h.determineRootBaseSpelling(rootSpelling, rootCase, baseSpelling, baseCase);
            render(chordList->renderListRoot, x, y, tpc, rootSpelling, rootCase);
            render(chordDescription->renderList, x, y, tpc, baseSpelling, baseCase);
            }
      moveElement = 0;
      dragElement = 0;
      update();
      }
 
//---------------------------------------------------------
//   dropEvent
//---------------------------------------------------------
 
void HarmonyCanvas::dropEvent(QDropEvent* /*event*/)
      {
#if 0       // TODO:ws
      if (dragElement && dragElement->type() == ElementType::FSYMBOL) {
            FSymbol* sb = static_cast<FSymbol*>(dragElement);
 
            double _spatium = 2.0 * PALETTE_SPATIUM / extraMag;
            const TextStyle* st = &gscore->textStyle(TextStyleType::HARMONY);
            QFont ff(st->font(_spatium * MScore::pixelRatio));
            ff.setFamily(sb->font().family());
 
//            qDebug("drop %s", dragElement->name());
 
            QString s;
            int code = sb->code();
            if (code & 0xffff0000) {
                  s = QChar(QChar::highSurrogate(code));
                  s += QChar(QChar::lowSurrogate(code));
                  }
            else
                  s = QChar(code);
 
            QPointF pt = imatrix.map(event->pos());
 
            TextSegment* ts = new TextSegment(s, ff, pt.x(), pt.y());
            textList.append(ts);
            delete dragElement;
            dragElement = 0;
            update();
            }
#endif
      }
 
//---------------------------------------------------------
//   dragEnterEvent
//---------------------------------------------------------
 
void HarmonyCanvas::dragEnterEvent(QDragEnterEvent* event)
      {
      const QMimeData* data = event->mimeData();
      if (data->hasFormat(mimeSymbolFormat)) {
            QByteArray a = data->data(mimeSymbolFormat);
 
            XmlReader e(gscore, a);
 
            QPointF dragOffset;
            Fraction duration;
            ElementType type = Element::readType(e, &dragOffset, &duration);
            if (type == ElementType::FSYMBOL) {
                  event->acceptProposedAction();
                  dragElement = Element::create(type, gscore);
                  dragElement->read(e);
                  dragElement->layout();
                  }
            }
      }
 
//---------------------------------------------------------
//   dragLeaveEvent
//---------------------------------------------------------
 
void HarmonyCanvas::dragLeaveEvent(QDragLeaveEvent*)
      {
      delete dragElement;
      dragElement = 0;
      update();
      }
 
//---------------------------------------------------------
//   dragMoveEvent
//---------------------------------------------------------
 
void HarmonyCanvas::dragMoveEvent(QDragMoveEvent* event)
      {
      event->acceptProposedAction();
      if (dragElement && dragElement->type() == ElementType::FSYMBOL) {
            dragElement->setPos(imatrix.map(event->pos()));
            update();
            }
      }
 
//---------------------------------------------------------
//   deleteAction
//---------------------------------------------------------
 
void HarmonyCanvas::deleteAction()
      {
      if (moveElement) {
            textList.removeOne(moveElement);
            update();
            }
      }
 
//---------------------------------------------------------
//   updateHarmony
//---------------------------------------------------------
 
static void updateHarmony(void*, Element* e)
      {
      if (e->type() == ElementType::HARMONY)
            static_cast<Harmony*>(e)->render();
      }
 
//---------------------------------------------------------
//   accept
//---------------------------------------------------------
 
void ChordStyleEditor::accept()
      {
      canvas->updateChordDescription();
      score->style().setChordList(chordList);
      chordList = 0;
 
      score->scanElements(0, updateHarmony);
 
      QDialog::accept();
      }
 
//---------------------------------------------------------
//   updateCurrentChordDescription
//---------------------------------------------------------
 
void HarmonyCanvas::updateChordDescription()
      {
      if (!chordDescription)
            return;
      chordDescription->renderList.clear();
 
      int idx = 0;
      double x  = 0, y = 0;
      foreach(const TextSegment* ts, textList) {
            ++idx;
            if (idx == 1) {     // dont save base
                  x = ts->x + ts->width();
                  y = ts->y;
                  continue;
                  }
            if (ts->x != x || ts->y != y) {
                  RenderAction ra(RenderAction::RenderActionType::MOVE);
                  ra.movex = ts->x - x;
                  ra.movey = ts->y - y;
                  chordDescription->renderList.append(ra);
                  }
            RenderAction ra(RenderAction::RenderActionType::SET);
            ra.text  = ts->text;
            chordDescription->renderList.append(ra);
            x = ts->x + ts->width();
            y = ts->y;
            }
      chordDescription->exportOk = true;
      }
 
}
 

V773 The function was exited without releasing the 'cl' pointer. A memory leak is possible.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: dragElement.