diff options
Diffstat (limited to 'src/gui/editors/notation/NotePixmapFactory.cpp')
-rw-r--r-- | src/gui/editors/notation/NotePixmapFactory.cpp | 3689 |
1 files changed, 3689 insertions, 0 deletions
diff --git a/src/gui/editors/notation/NotePixmapFactory.cpp b/src/gui/editors/notation/NotePixmapFactory.cpp new file mode 100644 index 0000000..c2a99ee --- /dev/null +++ b/src/gui/editors/notation/NotePixmapFactory.cpp @@ -0,0 +1,3689 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include <cmath> +#include "NotePixmapFactory.h" +#include "misc/Debug.h" +#include "base/NotationRules.h" +#include <kapplication.h> + +#include <klocale.h> +#include <kstddirs.h> +#include <kconfig.h> +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/Exception.h" +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "gui/editors/guitar/Fingering.h" +#include "gui/editors/guitar/FingeringBox.h" +#include "gui/editors/guitar/NoteSymbols.h" +#include "gui/editors/notation/TrackHeader.h" +#include "gui/general/GUIPalette.h" +#include "gui/general/PixmapFunctions.h" +#include "gui/general/Spline.h" +#include "gui/kdeext/KStartupLogo.h" +#include "NotationStrings.h" +#include "NotationView.h" +#include "NoteCharacter.h" +#include "NoteCharacterNames.h" +#include "NoteFontFactory.h" +#include "NoteFont.h" +#include "NotePixmapParameters.h" +#include "NotePixmapPainter.h" +#include "NoteStyleFactory.h" +#include "NoteStyle.h" +#include <kglobal.h> +#include <kmessagebox.h> +#include <qbitmap.h> +#include <qcolor.h> +#include <qfile.h> +#include <qfont.h> +#include <qfontmetrics.h> +#include <qimage.h> +#include <qpainter.h> +#include <qpen.h> +#include <qpixmap.h> +#include <qpointarray.h> +#include <qpoint.h> +#include <qrect.h> +#include <qstring.h> +#include <qwmatrix.h> + + +namespace Rosegarden +{ + +using namespace Accidentals; + +static clock_t drawBeamsTime = 0; +static clock_t makeNotesTime = 0; +static int drawBeamsCount = 0; +static int drawBeamsBeamCount = 0; + +class NotePixmapCache : public std::map<CharName, QCanvasPixmap*> +{ + // nothing to add -- just so we can predeclare it in the header +}; + +const char* const NotePixmapFactory::defaultSerifFontFamily = "Bitstream Vera Serif"; +const char* const NotePixmapFactory::defaultSansSerifFontFamily = "Bitstream Vera Sans"; +const char* const NotePixmapFactory::defaultTimeSigFontFamily = "Bitstream Vera Serif"; + +NotePixmapFactory::NotePixmapFactory(std::string fontName, int size) : + m_selected(false), + m_shaded(false), + m_tupletCountFont(defaultSerifFontFamily, 8, QFont::Bold), + m_tupletCountFontMetrics(m_tupletCountFont), + m_textMarkFont(defaultSerifFontFamily, 8, QFont::Bold, true), + m_textMarkFontMetrics(m_textMarkFont), + m_fingeringFont(defaultSerifFontFamily, 8, QFont::Bold), + m_fingeringFontMetrics(m_fingeringFont), + m_timeSigFont(defaultTimeSigFontFamily, 8, QFont::Bold), + m_timeSigFontMetrics(m_timeSigFont), + m_bigTimeSigFont(defaultTimeSigFontFamily, 12, QFont::Normal), + m_bigTimeSigFontMetrics(m_bigTimeSigFont), + m_ottavaFont(defaultSerifFontFamily, 8, QFont::Normal, true), + m_ottavaFontMetrics(m_ottavaFont), + m_clefOttavaFont(defaultSerifFontFamily, 8, QFont::Normal), + m_clefOttavaFontMetrics(m_ottavaFont), + m_trackHeaderFont(defaultSansSerifFontFamily, 10, QFont::Normal), + m_trackHeaderFontMetrics(m_trackHeaderFont), + m_trackHeaderBoldFont(defaultSansSerifFontFamily, 10, QFont::Bold), + m_trackHeaderBoldFontMetrics(m_trackHeaderBoldFont), + m_generatedPixmap(0), + m_generatedMask(0), + m_generatedWidth( -1), + m_generatedHeight( -1), + m_inPrinterMethod(false), + m_p(new NotePixmapPainter()), + m_dottedRestCache(new NotePixmapCache) +{ + init(fontName, size); +} + +NotePixmapFactory::NotePixmapFactory(const NotePixmapFactory &npf) : + m_selected(false), + m_shaded(false), + m_tupletCountFont(npf.m_tupletCountFont), + m_tupletCountFontMetrics(m_tupletCountFont), + m_textMarkFont(npf.m_textMarkFont), + m_textMarkFontMetrics(m_textMarkFont), + m_fingeringFont(npf.m_fingeringFont), + m_fingeringFontMetrics(m_fingeringFont), + m_timeSigFont(npf.m_timeSigFont), + m_timeSigFontMetrics(m_timeSigFont), + m_bigTimeSigFont(npf.m_bigTimeSigFont), + m_bigTimeSigFontMetrics(m_bigTimeSigFont), + m_ottavaFont(defaultSerifFontFamily, 8, QFont::Normal, true), + m_ottavaFontMetrics(m_ottavaFont), + m_clefOttavaFont(defaultSerifFontFamily, 8, QFont::Normal), + m_clefOttavaFontMetrics(m_ottavaFont), + m_trackHeaderFont(defaultSansSerifFontFamily, 10, QFont::Normal), + m_trackHeaderFontMetrics(m_trackHeaderFont), + m_trackHeaderBoldFont(defaultSansSerifFontFamily, 10, QFont::Bold), + m_trackHeaderBoldFontMetrics(m_trackHeaderBoldFont), + m_generatedPixmap(0), + m_generatedMask(0), + m_generatedWidth( -1), + m_generatedHeight( -1), + m_inPrinterMethod(false), + m_p(new NotePixmapPainter()), + m_dottedRestCache(new NotePixmapCache) +{ + init(npf.m_font->getName(), npf.m_font->getSize()); +} + +NotePixmapFactory & +NotePixmapFactory::operator=(const NotePixmapFactory &npf) +{ + if (&npf != this) { + m_selected = npf.m_selected; + m_shaded = npf.m_shaded; + m_timeSigFont = npf.m_timeSigFont; + m_timeSigFontMetrics = QFontMetrics(m_timeSigFont); + m_bigTimeSigFont = npf.m_bigTimeSigFont; + m_bigTimeSigFontMetrics = QFontMetrics(m_bigTimeSigFont); + m_tupletCountFont = npf.m_tupletCountFont; + m_tupletCountFontMetrics = QFontMetrics(m_tupletCountFont); + m_textMarkFont = npf.m_textMarkFont; + m_textMarkFontMetrics = QFontMetrics(m_textMarkFont); + m_fingeringFont = npf.m_fingeringFont; + m_fingeringFontMetrics = QFontMetrics(m_fingeringFont); + m_ottavaFont = npf.m_ottavaFont; + m_ottavaFontMetrics = QFontMetrics(m_ottavaFont); + m_clefOttavaFont = npf.m_clefOttavaFont; + m_clefOttavaFontMetrics = QFontMetrics(m_clefOttavaFont); + m_trackHeaderFont = npf.m_trackHeaderFont; + m_trackHeaderFontMetrics = QFontMetrics(m_trackHeaderFont); + m_trackHeaderBoldFont = npf.m_trackHeaderBoldFont; + m_trackHeaderBoldFontMetrics = QFontMetrics(m_trackHeaderBoldFont); + init(npf.m_font->getName(), npf.m_font->getSize()); + m_dottedRestCache->clear(); + m_textFontCache.clear(); + } + return *this; +} + +void +NotePixmapFactory::init(std::string fontName, int size) +{ + try { + m_style = NoteStyleFactory::getStyle(NoteStyleFactory::DefaultStyle); + } catch (NoteStyleFactory::StyleUnavailable u) { + KStartupLogo::hideIfStillThere(); + KMessageBox::error(0, i18n(strtoqstr(u.getMessage()))); + throw; + } + + int origSize = size; + + if (fontName != "") { + try { + if (size < 0) + size = NoteFontFactory::getDefaultSize(fontName); + m_font = NoteFontFactory::getFont(fontName, size); + } catch (Exception f) { + fontName = ""; + // fall through + } + } + + if (fontName == "") { // either because it was passed in or because read failed + try { + fontName = NoteFontFactory::getDefaultFontName(); + size = origSize; + if (size < 0) + size = NoteFontFactory::getDefaultSize(fontName); + m_font = NoteFontFactory::getFont(fontName, size); + } catch (Exception f) { // already reported + throw; + } + } + + // Resize the fonts, because the original constructor used point + // sizes only and we want pixels + QFont timeSigFont(defaultTimeSigFontFamily), + textFont(defaultSerifFontFamily); + KConfig* config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + + m_timeSigFont = config->readFontEntry("timesigfont", &timeSigFont); + m_timeSigFont.setBold(true); + m_timeSigFont.setPixelSize(size * 5 / 2); + m_timeSigFontMetrics = QFontMetrics(m_timeSigFont); + + m_bigTimeSigFont = config->readFontEntry("timesigfont", &timeSigFont); + m_bigTimeSigFont.setPixelSize(size * 4 + 2); + m_bigTimeSigFontMetrics = QFontMetrics(m_bigTimeSigFont); + + m_tupletCountFont = config->readFontEntry("textfont", &textFont); + m_tupletCountFont.setBold(true); + m_tupletCountFont.setPixelSize(size * 2); + m_tupletCountFontMetrics = QFontMetrics(m_tupletCountFont); + + m_textMarkFont = config->readFontEntry("textfont", &textFont); + m_textMarkFont.setBold(true); + m_textMarkFont.setItalic(true); + m_textMarkFont.setPixelSize(size * 2); + m_textMarkFontMetrics = QFontMetrics(m_textMarkFont); + + m_fingeringFont = config->readFontEntry("textfont", &textFont); + m_fingeringFont.setBold(true); + m_fingeringFont.setPixelSize(size * 5 / 3); + m_fingeringFontMetrics = QFontMetrics(m_fingeringFont); + + m_ottavaFont = config->readFontEntry("textfont", &textFont); + m_ottavaFont.setPixelSize(size * 2); + m_ottavaFontMetrics = QFontMetrics(m_ottavaFont); + + m_clefOttavaFont = config->readFontEntry("textfont", &textFont); + m_clefOttavaFont.setPixelSize(getLineSpacing() * 3 / 2); + m_clefOttavaFontMetrics = QFontMetrics(m_clefOttavaFont); + + m_trackHeaderFont = config->readFontEntry("sansfont", &m_trackHeaderFont); + m_trackHeaderFont.setPixelSize(12); + m_trackHeaderFontMetrics = QFontMetrics(m_trackHeaderFont); + + m_trackHeaderBoldFont = m_trackHeaderFont; + m_trackHeaderBoldFont.setBold(true); + m_trackHeaderBoldFontMetrics = QFontMetrics(m_trackHeaderBoldFont); +} + +NotePixmapFactory::~NotePixmapFactory() +{ + delete m_p; + delete m_dottedRestCache; +} + +std::string +NotePixmapFactory::getFontName() const +{ + return m_font->getName(); +} + +int +NotePixmapFactory::getSize() const +{ + return m_font->getSize(); +} + +QPixmap +NotePixmapFactory::toQPixmap(QCanvasPixmap* cp) +{ + QPixmap p = *cp; + delete cp; + return p; +} + +void +NotePixmapFactory::dumpStats(std::ostream &s) +{ +#ifdef DUMP_STATS + s << "NotePixmapFactory: total times since last stats dump:\n" + << "makeNotePixmap: " + << (makeNotesTime * 1000 / CLOCKS_PER_SEC) << "ms\n" + << "drawBeams: " + << (drawBeamsTime * 1000 / CLOCKS_PER_SEC) << "ms" + << " (drew " << drawBeamsCount << " individual points in " << drawBeamsBeamCount << " beams)" + << endl; + makeNotesTime = 0; + drawBeamsTime = 0; + drawBeamsCount = 0; + drawBeamsBeamCount = 0; +#endif + + (void)s; // avoid warnings +} + +QCanvasPixmap* +NotePixmapFactory::makeNotePixmap(const NotePixmapParameters ¶ms) +{ + Profiler profiler("NotePixmapFactory::makeNotePixmap"); + clock_t startTime = clock(); + + drawNoteAux(params, 0, 0, 0); + + QPoint hotspot(m_left, m_above + m_noteBodyHeight / 2); + + //#define ROSE_DEBUG_NOTE_PIXMAP_FACTORY +#ifdef ROSE_DEBUG_NOTE_PIXMAP_FACTORY + + m_p->painter().setPen(Qt::red); + m_p->painter().setBrush(Qt::red); + + m_p->drawLine(0, 0, 0, m_generatedHeight - 1); + m_p->drawLine(m_generatedWidth - 1, 0, + m_generatedWidth - 1, + m_generatedHeight - 1); + + { + int hsx = hotspot.x(); + int hsy = hotspot.y(); + m_p->drawLine(hsx - 2, hsy - 2, hsx + 2, hsy + 2); + m_p->drawLine(hsx - 2, hsy + 2, hsx + 2, hsy - 2); + } +#endif + + clock_t endTime = clock(); + makeNotesTime += (endTime - startTime); + + return makeCanvasPixmap(hotspot); +} + +void +NotePixmapFactory::drawNote(const NotePixmapParameters ¶ms, + QPainter &painter, int x, int y) +{ + Profiler profiler("NotePixmapFactory::drawNote"); + m_inPrinterMethod = true; + drawNoteAux(params, &painter, x, y); + m_inPrinterMethod = false; +} + +void +NotePixmapFactory::drawNoteAux(const NotePixmapParameters ¶ms, + QPainter *painter, int x, int y) +{ + NoteFont::CharacterType charType = m_inPrinterMethod ? NoteFont::Printer : NoteFont::Screen; + + bool drawFlag = params.m_drawFlag; + + if (params.m_beamed) + drawFlag = false; + + // A note pixmap is formed of note head, stem, flags, + // accidentals, dots and beams. Assume the note head first, then + // do the rest of the calculations left to right, ie accidentals, + // stem, flags, dots, beams + + m_noteBodyWidth = getNoteBodyWidth(params.m_noteType); + m_noteBodyHeight = getNoteBodyHeight(params.m_noteType); + + // Spacing surrounding the note head. For top and bottom, we + // adjust this according to the discrepancy between the nominal + // and actual heights of the note head pixmap. For left and + // right, we use the hotspot x coordinate of the head. + int temp; + if (!m_font->getHotspot(m_style->getNoteHeadCharName(params.m_noteType).first, + m_borderX, temp)) + m_borderX = 0; + + if (params.m_noteType == Note::Minim && params.m_stemGoesUp) + m_borderX++; + int actualNoteBodyHeight = + m_font->getHeight(m_style->getNoteHeadCharName(params.m_noteType).first); + + m_left = m_right = m_borderX; + m_above = m_borderY = (actualNoteBodyHeight - m_noteBodyHeight) / 2; + m_below = (actualNoteBodyHeight - m_noteBodyHeight) - m_above; + + // NOTATION_DEBUG << "actualNoteBodyHeight: " << actualNoteBodyHeight + // << ", noteBodyHeight: " << m_noteBodyHeight << ", borderX: " + // << m_borderX << ", borderY: " + // << m_borderY << endl; + + bool isStemmed = m_style->hasStem(params.m_noteType); + int flagCount = m_style->getFlagCount(params.m_noteType); + int slashCount = params.m_slashes; + if (!slashCount) + slashCount = m_style->getSlashCount(params.m_noteType); + + if (params.m_accidental != NoAccidental) { + makeRoomForAccidental(params.m_accidental, + params.m_cautionary, + params.m_accidentalShift, + params.m_accidentalExtra); + } + + NoteCharacter dot(getCharacter(NoteCharacterNames::DOT, PlainColour, charType)); + int dotWidth = dot.getWidth(); + if (dotWidth < getNoteBodyWidth() / 2) + dotWidth = getNoteBodyWidth() / 2; + + int stemLength = getStemLength(params); + + if (params.m_marks.size() > 0) { + makeRoomForMarks(isStemmed, params, stemLength); + } + + if (params.m_legerLines != 0) { + makeRoomForLegerLines(params); + } + + if (slashCount > 0) { + m_left = std::max(m_left, m_noteBodyWidth / 2); + m_right = std::max(m_right, m_noteBodyWidth / 2); + } + + if (params.m_tupletCount > 0) { + makeRoomForTuplingLine(params); + } + + m_right = std::max(m_right, params.m_dots * dotWidth + dotWidth / 2); + if (params.m_dotShifted) { + m_right += m_noteBodyWidth; + } + if (params.m_onLine) { + m_above = std::max(m_above, dot.getHeight() / 2); + } + + if (params.m_shifted) { + if (params.m_stemGoesUp) { + m_right += m_noteBodyWidth; + } else { + m_left = std::max(m_left, m_noteBodyWidth); + } + } + + bool tieAbove = params.m_tieAbove; + if (!params.m_tiePositionExplicit) { + tieAbove = !params.m_stemGoesUp; + } + + if (params.m_tied) { + m_right = std::max(m_right, params.m_tieLength); + if (!tieAbove) { + m_below = std::max(m_below, m_noteBodyHeight * 2); + } else { + m_above = std::max(m_above, m_noteBodyHeight * 2); + } + } + + QPoint startPoint, endPoint; + if (isStemmed && params.m_drawStem) { + makeRoomForStemAndFlags(drawFlag ? flagCount : 0, stemLength, params, + startPoint, endPoint); + } + + if (isStemmed && params.m_drawStem && params.m_beamed) { + makeRoomForBeams(params); + } + + // for all other calculations we use the nominal note-body height + // (same as the gap between staff lines), but here we want to know + // if the pixmap itself is taller than that + /*!!! + int actualNoteBodyHeight = m_font->getHeight + (m_style->getNoteHeadCharName(params.m_noteType).first); + // - 2*m_origin.y(); + if (actualNoteBodyHeight > m_noteBodyHeight) { + m_below = std::max(m_below, actualNoteBodyHeight - m_noteBodyHeight); + } + */ + if (painter) { + painter->save(); + m_p->beginExternal(painter); + // NOTATION_DEBUG << "Translate: (" << x << "," << y << ")" << endl; + painter->translate(x - m_left, y - m_above - m_noteBodyHeight / 2); + } else { + createPixmapAndMask(m_noteBodyWidth + m_left + m_right, + m_noteBodyHeight + m_above + m_below); + } + + if (params.m_tupletCount > 0) { + drawTuplingLine(params); + } + + if (isStemmed && params.m_drawStem && drawFlag) { + drawFlags(flagCount, params, startPoint, endPoint); + } + + if (params.m_accidental != NoAccidental) { + drawAccidental(params.m_accidental, params.m_cautionary); + } + + NoteStyle::CharNameRec charNameRec + (m_style->getNoteHeadCharName(params.m_noteType)); + CharName charName = charNameRec.first; + bool inverted = charNameRec.second; + NoteCharacter body = getCharacter + (charName, + params.m_highlighted ? HighlightedColour : + params.m_quantized ? QuantizedColour : + params.m_trigger ? TriggerColour : + params.m_inRange ? PlainColour : OutRangeColour, + inverted); + + QPoint bodyLocation(m_left - m_borderX, + m_above - m_borderY + getStaffLineThickness() / 2); + if (params.m_shifted) { + if (params.m_stemGoesUp) { + bodyLocation.rx() += m_noteBodyWidth; + } else { + bodyLocation.rx() -= m_noteBodyWidth - 1; + } + } + + m_p->drawNoteCharacter(bodyLocation.x(), bodyLocation.y(), body); + + if (params.m_dots > 0) { + + int x = m_left + m_noteBodyWidth + dotWidth / 2; + int y = m_above + m_noteBodyHeight / 2 - dot.getHeight() / 2; + + if (params.m_onLine) + y -= m_noteBodyHeight / 2; + + if (params.m_shifted) + x += m_noteBodyWidth; + else if (params.m_dotShifted) + x += m_noteBodyWidth; + + for (int i = 0; i < params.m_dots; ++i) { + m_p->drawNoteCharacter(x, y, dot); + x += dotWidth; + } + } + + if (isStemmed && params.m_drawStem) { + + if (flagCount > 0 && !drawFlag && params.m_beamed) { + drawBeams(endPoint, params, flagCount); + } + + if (slashCount > 0) { + drawSlashes(startPoint, params, slashCount); + } + + if (m_selected) + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + else + m_p->painter().setPen(Qt::black); + + // If we draw stems after beams, instead of beams after stems, + // beam anti-aliasing won't damage stems but we have to shorten the + // stems slightly first so that the stems don't extend all the way + // through the beam into the anti-aliased region on the + // other side of the beam that faces away from the note-heads. + int shortening; + if (flagCount > 0 && !drawFlag && params.m_beamed) + shortening = 2; + else + shortening = 0; + drawStem(params, startPoint, endPoint, shortening); + } + + if (params.m_marks.size() > 0) { + drawMarks(isStemmed, params, stemLength); + } + + if (params.m_legerLines != 0) { + drawLegerLines(params); + } + + if (params.m_tied) { + drawTie(tieAbove, params.m_tieLength, dotWidth * params.m_dots); + } + + if (painter) { + painter->restore(); + } +} + + +QCanvasPixmap* +NotePixmapFactory::makeNoteHaloPixmap(const NotePixmapParameters ¶ms) +{ + int nbh0 = getNoteBodyHeight(); + int nbh = getNoteBodyHeight(params.m_noteType); + int nbw0 = getNoteBodyHeight(); + int nbw = getNoteBodyWidth(params.m_noteType); + + createPixmapAndMask(nbw + nbw0, nbh + nbh0); + drawNoteHalo(0, 0, nbw + nbw0, nbh + nbh0); + + return makeCanvasPixmap(QPoint(nbw0 / 2, nbh0)); +} + + +void +NotePixmapFactory::drawNoteHalo(int x, int y, int w, int h) { + + m_p->painter().setPen(QPen(QColor(GUIPalette::CollisionHaloHue, + GUIPalette::CollisionHaloSaturation, + 255, QColor::Hsv), 1)); + m_p->painter().setBrush(QColor(GUIPalette::CollisionHaloHue, + GUIPalette::CollisionHaloSaturation, + 255, QColor::Hsv)); + m_p->drawEllipse(x, y, w, h); +} + + + +int +NotePixmapFactory::getStemLength(const NotePixmapParameters ¶ms) const +{ + if (params.m_beamed && params.m_stemLength >= 0) { + return params.m_stemLength; + } + + int stemLength = getStemLength(); + + int flagCount = m_style->getFlagCount(params.m_noteType); + int slashCount = params.m_slashes; + bool stemUp = params.m_stemGoesUp; + int nbh = m_noteBodyHeight; + + if (flagCount > 2) { + stemLength += getLineSpacing() * (flagCount - 2); + } + + int width = 0, height = 0; + + if (flagCount > 0) { + + if (!stemUp) + stemLength += nbh / 2; + + if (m_font->getDimensions(m_style->getFlagCharName(flagCount), + width, height)) { + + stemLength = std::max(stemLength, height); + + } else if (m_font->getDimensions(m_style->getPartialFlagCharName(true), + width, height) || + m_font->getDimensions(m_style->getPartialFlagCharName(false), + width, height)) { + + unsigned int flagSpace = m_noteBodyHeight; + (void)m_font->getFlagSpacing(flagSpace); + + stemLength = std::max(stemLength, + height + (flagCount - 1) * (int)flagSpace); + } + } + + if (slashCount > 3 && flagCount < 3) { + stemLength += (slashCount - 3) * (nbh / 2); + } + + if (params.m_stemLength >= 0) { + if (flagCount == 0) + return params.m_stemLength; + stemLength = std::max(stemLength, params.m_stemLength); + } + + return stemLength; +} + +void +NotePixmapFactory::makeRoomForAccidental(Accidental a, + bool cautionary, int shift, bool extra) +{ + // General observation: where we're only using a character to + // determine its dimensions, we should (for the moment) just + // request it in screen mode, because it may be quicker and we + // don't need to render it, and the dimensions are the same. + NoteCharacter ac + (m_font->getCharacter(m_style->getAccidentalCharName(a))); + + QPoint ah(m_font->getHotspot(m_style->getAccidentalCharName(a))); + + m_left += ac.getWidth() + (m_noteBodyWidth / 4 - m_borderX); + + if (shift > 0) { + if (extra) { + // The extra flag indicates that the first shift is to get + // out of the way of a note head, thus has to move + // possibly further, or at least a different amount. So + // replace the first shift with a different one. + --shift; + m_left += m_noteBodyWidth - m_noteBodyWidth / 5; + } + if (shift > 0) { + // The amount we shift for each accidental is the greater + // of the probable shift for that accidental and the + // probable shift for a sharp, on the assumption (usually + // true in classical notation) that the sharp is the + // widest accidental and that we may have other + // accidentals possibly including sharps on other notes in + // this chord that we can't know about here. + int step = ac.getWidth() - ah.x(); + if (a != Accidentals::Sharp) { + NoteCharacter acSharp + (m_font->getCharacter(m_style->getAccidentalCharName + (Accidentals::Sharp))); + QPoint ahSharp + (m_font->getHotspot(m_style->getAccidentalCharName + (Accidentals::Sharp))); + step = std::max(step, acSharp.getWidth() - ahSharp.x()); + } + m_left += shift * step; + } + } + + if (cautionary) + m_left += m_noteBodyWidth; + + int above = ah.y() - m_noteBodyHeight / 2; + int below = (ac.getHeight() - ah.y()) - + (m_noteBodyHeight - m_noteBodyHeight / 2); // subtract in case it's odd + + if (above > 0) + m_above = std::max(m_above, above); + if (below > 0) + m_below = std::max(m_below, below); +} + +void +NotePixmapFactory::drawAccidental(Accidental a, bool cautionary) +{ + NoteCharacter ac = getCharacter + (m_style->getAccidentalCharName(a), PlainColour, false); + + QPoint ah(m_font->getHotspot(m_style->getAccidentalCharName(a))); + + int ax = 0; + + if (cautionary) { + ax += m_noteBodyWidth / 2; + int bl = ac.getHeight() * 2 / 3; + int by = m_above + m_noteBodyHeight / 2 - bl / 2; + drawBracket(bl, true, false, m_noteBodyWidth*3 / 8, by); + drawBracket(bl, false, false, ac.getWidth() + m_noteBodyWidth*5 / 8, by); + } + + m_p->drawNoteCharacter(ax, m_above + m_noteBodyHeight / 2 - ah.y(), ac); +} + +void +NotePixmapFactory::makeRoomForMarks(bool isStemmed, + const NotePixmapParameters ¶ms, + int stemLength) +{ + int height = 0, width = 0; + int gap = m_noteBodyHeight / 5 + 1; + + std::vector<Mark> normalMarks = params.getNormalMarks(); + std::vector<Mark> aboveMarks = params.getAboveMarks(); + + for (std::vector<Mark>::iterator i = normalMarks.begin(); + i != normalMarks.end(); ++i) { + + if (!Marks::isTextMark(*i)) { + + NoteCharacter character(m_font->getCharacter(m_style->getMarkCharName(*i))); + height += character.getHeight() + gap; + if (character.getWidth() > width) + width = character.getWidth(); + + } else { + // Inefficient to do this here _and_ in drawMarks, but + // text marks are not all that common + QString text = strtoqstr(Marks::getTextFromMark(*i)); + QRect bounds = m_textMarkFontMetrics.boundingRect(text); + height += bounds.height() + gap; + if (bounds.width() > width) + width = bounds.width(); + } + } + + if (height > 0) { + if (isStemmed && params.m_stemGoesUp) { + m_below += height + 1; + } else { + m_above += height + 1; + } + } + + height = 0; + + if (params.m_safeVertDistance > 0 && !aboveMarks.empty()) { + m_above = std::max(m_above, params.m_safeVertDistance); + } + + for (std::vector<Mark>::iterator i = aboveMarks.begin(); + i != aboveMarks.end(); ++i) { + + if (!Marks::isFingeringMark(*i)) { + + Mark m(*i); + + if (m == Marks::TrillLine) + m = Marks::LongTrill; + + if (m == Marks::LongTrill) { + m_right = std::max(m_right, params.m_width); + } + + NoteCharacter character(m_font->getCharacter(m_style->getMarkCharName(m))); + height += character.getHeight() + gap; + if (character.getWidth() > width) + width = character.getWidth(); + + } else { + + // Inefficient to do this here _and_ in drawMarks + QString text = strtoqstr(Marks::getFingeringFromMark(*i)); + QRect bounds = m_fingeringFontMetrics.boundingRect(text); + height += bounds.height() + gap + 3; + if (bounds.width() > width) + width = bounds.width(); + } + } + + if (height > 0) { + if (isStemmed && params.m_stemGoesUp && params.m_safeVertDistance == 0) { + m_above += stemLength + height + 1; + } else { + m_above += height + 1; + } + } + + m_left = std::max(m_left, width / 2 - m_noteBodyWidth / 2); + m_right = std::max(m_right, width / 2 - m_noteBodyWidth / 2); +} + +void +NotePixmapFactory::drawMarks(bool isStemmed, + const NotePixmapParameters ¶ms, + int stemLength) +{ + int gap = m_noteBodyHeight / 5 + 1; + int dy = gap; + + std::vector<Mark> normalMarks = params.getNormalMarks(); + std::vector<Mark> aboveMarks = params.getAboveMarks(); + + bool normalMarksAreAbove = !(isStemmed && params.m_stemGoesUp); + + for (std::vector<Mark>::iterator i = normalMarks.begin(); + i != normalMarks.end(); ++i) { + + if (!Marks::isTextMark(*i)) { + + NoteCharacter character = getCharacter + (m_style->getMarkCharName(*i), PlainColour, + !normalMarksAreAbove); + + int x = m_left + m_noteBodyWidth / 2 - character.getWidth() / 2; + int y = (normalMarksAreAbove ? + (m_above - dy - character.getHeight() - 1) : + (m_above + m_noteBodyHeight + m_borderY * 2 + dy)); + + m_p->drawNoteCharacter(x, y, character); + dy += character.getHeight() + gap; + + } else { + + QString text = strtoqstr(Marks::getTextFromMark(*i)); + QRect bounds = m_textMarkFontMetrics.boundingRect(text); + + m_p->painter().setFont(m_textMarkFont); + if (!m_inPrinterMethod) + m_p->maskPainter().setFont(m_textMarkFont); + + int x = m_left + m_noteBodyWidth / 2 - bounds.width() / 2; + int y = (normalMarksAreAbove ? + (m_above - dy - 3) : + (m_above + m_noteBodyHeight + m_borderY * 2 + dy + bounds.height() + 1)); + + m_p->drawText(x, y, text); + dy += bounds.height() + gap; + } + } + + if (!normalMarksAreAbove) + dy = gap; + if (params.m_safeVertDistance > 0) { + if (normalMarksAreAbove) { + dy = std::max(dy, params.m_safeVertDistance); + } else { + dy = params.m_safeVertDistance; + } + } else if (isStemmed && params.m_stemGoesUp) { + dy += stemLength; + } + + for (std::vector<Mark>::iterator i = aboveMarks.begin(); + i != aboveMarks.end(); ++i) { + + if (m_selected) + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + else + m_p->painter().setPen(Qt::black); + if (!Marks::isFingeringMark(*i)) { + + int x = m_left + m_noteBodyWidth / 2; + int y = m_above - dy - 1; + + if (*i != Marks::TrillLine) { + + NoteCharacter character + (getCharacter + (m_style->getMarkCharName(*i), PlainColour, + false)); + + x -= character.getWidth() / 2; + y -= character.getHeight(); + + m_p->drawNoteCharacter(x, y, character); + + y += character.getHeight() / 2; + x += character.getWidth(); + + dy += character.getHeight() + gap; + + } else { + + NoteCharacter character + (getCharacter + (m_style->getMarkCharName(Marks::Trill), PlainColour, + false)); + y -= character.getHeight() / 2; + dy += character.getHeight() + gap; + } + + if (*i == Marks::LongTrill || + *i == Marks::TrillLine) { + NoteCharacter extension; + if (getCharacter(NoteCharacterNames::TRILL_LINE, extension, + PlainColour, false)) { + x += extension.getHotspot().x(); + while (x < m_left + params.m_width - extension.getWidth()) { + x -= extension.getHotspot().x(); + m_p->drawNoteCharacter(x, y, extension); + x += extension.getWidth(); + } + } + if (*i == Marks::TrillLine) + dy += extension.getHeight() + gap; + } + + } else { + QString text = strtoqstr(Marks::getFingeringFromMark(*i)); + QRect bounds = m_fingeringFontMetrics.boundingRect(text); + + m_p->painter().setFont(m_fingeringFont); + if (!m_inPrinterMethod) + m_p->maskPainter().setFont(m_fingeringFont); + + int x = m_left + m_noteBodyWidth / 2 - bounds.width() / 2; + int y = m_above - dy - 3; + + m_p->drawText(x, y, text); + dy += bounds.height() + gap; + } + } +} + +void +NotePixmapFactory::makeRoomForLegerLines(const NotePixmapParameters ¶ms) +{ + if (params.m_legerLines < 0 || params.m_restOutsideStave) { + m_above = std::max(m_above, + (m_noteBodyHeight + 1) * + ( -params.m_legerLines / 2)); + } + if (params.m_legerLines > 0 || params.m_restOutsideStave) { + m_below = std::max(m_below, + (m_noteBodyHeight + 1) * + (params.m_legerLines / 2)); + } + if (params.m_legerLines != 0) { + m_left = std::max(m_left, m_noteBodyWidth / 5 + 1); + m_right = std::max(m_right, m_noteBodyWidth / 5 + 1); + } + if (params.m_restOutsideStave) { + m_above += 1; + m_left = std::max(m_left, m_noteBodyWidth * 3 + 1); + m_right = std::max(m_right, m_noteBodyWidth * 3 + 1); + } +} + +void +NotePixmapFactory::drawLegerLines(const NotePixmapParameters ¶ms) +{ + int x0, x1, y; + + if (params.m_legerLines == 0) + return ; + + if (params.m_restOutsideStave) { + if (m_selected) + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + else + m_p->painter().setPen(Qt::black); + } + x0 = m_left - m_noteBodyWidth / 5 - 1; + x1 = m_left + m_noteBodyWidth + m_noteBodyWidth / 5 /* + 1 */; + + if (params.m_shifted) { + if (params.m_stemGoesUp) { + x0 += m_noteBodyWidth; + x1 += m_noteBodyWidth; + } else { + x0 -= m_noteBodyWidth; + x1 -= m_noteBodyWidth; + } + } + + int offset = m_noteBodyHeight + getStaffLineThickness(); + int legerLines = params.m_legerLines; + bool below = (legerLines < 0); + + if (below) { + legerLines = -legerLines; + offset = -offset; + } + + if (params.m_restOutsideStave) + y = m_above; + else { + if (!below) { // note above staff + if (legerLines % 2) { // note is between lines + y = m_above + m_noteBodyHeight; + } else { // note is on a line + y = m_above + m_noteBodyHeight / 2 - getStaffLineThickness() / 2; + } + } else { // note below staff + if (legerLines % 2) { // note is between lines + y = m_above - getStaffLineThickness(); + } else { // note is on a line + y = m_above + m_noteBodyHeight / 2; + } + } + } + if (params.m_restOutsideStave) { + NOTATION_DEBUG << "draw leger lines: " << legerLines << " lines, below " + << below + << ", note body height " << m_noteBodyHeight + << ", thickness " << getLegerLineThickness() + << " (staff line " << getStaffLineThickness() << ")" + << ", offset " << offset << endl; + } + + // NOTATION_DEBUG << "draw leger lines: " << legerLines << " lines, below " + // << below + // << ", note body height " << m_noteBodyHeight + // << ", thickness " << getLegerLineThickness() + // << " (staff line " << getStaffLineThickness() << ")" + // << ", offset " << offset << endl; + + // bool first = true; + + if (getLegerLineThickness() > getStaffLineThickness()) { + y -= (getLegerLineThickness() - getStaffLineThickness() + 1) / 2; + } + + for (int i = legerLines - 1; i >= 0; --i) { + if (i % 2) { + // NOTATION_DEBUG << "drawing leger line at y = " << y << endl; + for (int j = 0; j < getLegerLineThickness(); ++j) { + m_p->drawLine(x0, y + j, x1, y + j); + } + y += offset; + // if (first) { + // x0 += getStemThickness(); + // x1 -= getStemThickness(); + // first = false; + // } + } + } +} + +void +NotePixmapFactory::makeRoomForStemAndFlags(int flagCount, int stemLength, + const NotePixmapParameters ¶ms, + QPoint &s0, QPoint &s1) +{ + // The coordinates we set in s0 and s1 are relative to (m_above, m_left) + + if (params.m_stemGoesUp) { + m_above = std::max + (m_above, stemLength - m_noteBodyHeight / 2); + } else { + m_below = std::max + (m_below, stemLength - m_noteBodyHeight / 2 + 1); + } + + if (flagCount > 0) { + if (params.m_stemGoesUp) { + int width = 0, height = 0; + if (!m_font->getDimensions + (m_style->getFlagCharName(flagCount), width, height)) { + width = m_font->getWidth(m_style->getPartialFlagCharName(false)); + } + m_right += width; + } + } + + unsigned int stemThickness = getStemThickness(); + + NoteStyle::HFixPoint hfix; + NoteStyle::VFixPoint vfix; + m_style->getStemFixPoints(params.m_noteType, hfix, vfix); + + switch (hfix) { + + case NoteStyle::Normal: + case NoteStyle::Reversed: + if (params.m_stemGoesUp ^ (hfix == NoteStyle::Reversed)) { + s0.setX(m_noteBodyWidth - stemThickness); + } else { + s0.setX(0); + } + break; + + case NoteStyle::Central: + if (params.m_stemGoesUp ^ (hfix == NoteStyle::Reversed)) { + s0.setX(m_noteBodyWidth / 2 + 1); + } else { + s0.setX(m_noteBodyWidth / 2); + } + break; + } + + switch (vfix) { + + case NoteStyle::Near: + case NoteStyle::Far: + if (params.m_stemGoesUp ^ (vfix == NoteStyle::Far)) { + s0.setY(0); + } else { + s0.setY(m_noteBodyHeight); + } + if (vfix == NoteStyle::Near) { + stemLength -= m_noteBodyHeight / 2; + } else { + stemLength += m_noteBodyHeight / 2; + } + break; + + case NoteStyle::Middle: + if (params.m_stemGoesUp) { + s0.setY(m_noteBodyHeight * 3 / 8); + } else { + s0.setY(m_noteBodyHeight * 5 / 8); + } + stemLength -= m_noteBodyHeight / 8; + break; + } + + if (params.m_stemGoesUp) { + s1.setY(s0.y() - stemLength + getStaffLineThickness()); + } else { + s1.setY(s0.y() + stemLength); + } + + s1.setX(s0.x()); +} + +void +NotePixmapFactory::drawFlags(int flagCount, + const NotePixmapParameters ¶ms, + const QPoint &, const QPoint &s1) +{ + if (flagCount < 1) + return ; + + NoteCharacter flagChar; + bool found = getCharacter(m_style->getFlagCharName(flagCount), + flagChar, + PlainColour, + !params.m_stemGoesUp); + + if (!found) { + + // Handle fonts that don't have all the flags in separate characters + + found = getCharacter(m_style->getPartialFlagCharName(false), + flagChar, + PlainColour, + !params.m_stemGoesUp); + + if (!found) { + std::cerr << "Warning: NotePixmapFactory::drawFlags: No way to draw note with " << flagCount << " flags in this font!?" << std::endl; + return ; + } + + QPoint hotspot = flagChar.getHotspot(); + + NoteCharacter oneFlagChar; + bool foundOne = + (flagCount > 1 ? + getCharacter(m_style->getPartialFlagCharName(true), + oneFlagChar, + PlainColour, + !params.m_stemGoesUp) : false); + + unsigned int flagSpace = m_noteBodyHeight; + (void)m_font->getFlagSpacing(flagSpace); + + for (int flag = 0; flag < flagCount; ++flag) { + + // use flag_1 in preference to flag_0 for the final flag, so + // as to end with a flourish + if (flag == flagCount - 1 && foundOne) + flagChar = oneFlagChar; + + int y = m_above + s1.y(); + if (params.m_stemGoesUp) + y += flag * flagSpace; + else + y -= (flag * flagSpace) + flagChar.getHeight(); + + if (!m_inPrinterMethod) { + + m_p->end(); + + // Super-slow + + PixmapFunctions::drawPixmapMasked(*m_generatedPixmap, + *m_generatedMask, + m_left + s1.x() - hotspot.x(), + y, + *flagChar.getPixmap()); + + m_p->begin(m_generatedPixmap, m_generatedMask); + + } else { + + // No problem with mask here + m_p->drawNoteCharacter(m_left + s1.x() - hotspot.x(), + y, + flagChar); + } + } + + } else { // the normal case + + QPoint hotspot = flagChar.getHotspot(); + + int y = m_above + s1.y(); + if (!params.m_stemGoesUp) + y -= flagChar.getHeight(); + + m_p->drawNoteCharacter(m_left + s1.x() - hotspot.x(), y, flagChar); + } +} + +void +NotePixmapFactory::drawStem(const NotePixmapParameters ¶ms, + const QPoint &s0, const QPoint &s1, + int shortening) +{ + if (params.m_stemGoesUp) + shortening = -shortening; + for (int i = 0; i < getStemThickness(); ++i) { + m_p->drawLine(m_left + s0.x() + i, m_above + s0.y(), + m_left + s1.x() + i, m_above + s1.y() - shortening); + } +} + +void +NotePixmapFactory::makeRoomForBeams(const NotePixmapParameters ¶ms) +{ + int beamSpacing = (int)(params.m_width * params.m_gradient); + + if (params.m_stemGoesUp) { + + beamSpacing = -beamSpacing; + if (beamSpacing < 0) + beamSpacing = 0; + m_above += beamSpacing + 2; + + // allow a bit extra in case the h fixpoint is non-normal + m_right = std::max(m_right, params.m_width + m_noteBodyWidth); + + } else { + + if (beamSpacing < 0) + beamSpacing = 0; + m_below += beamSpacing + 2; + + m_right = std::max(m_right, params.m_width); + } +} + +void +NotePixmapFactory::drawShallowLine(int x0, int y0, int x1, int y1, + int thickness, bool smooth) +{ + if (!smooth || m_inPrinterMethod || (y0 == y1)) { + + if (!m_inPrinterMethod) { + if (m_selected) + m_p->painter().setBrush(GUIPalette::getColour(GUIPalette::SelectedElement)); + else + m_p->painter().setBrush(Qt::black); + } + if (thickness < 4) { + for (int i = 0; i < thickness; ++i) { + m_p->drawLine(x0, y0 + i, x1, y1 + i); + } + } else { + Profiler profiler("NotePixmapFactory::drawShallowLine(polygon)"); + QPointArray qp(4); + qp.setPoint(0, x0, y0); + qp.setPoint(1, x0, y0 + thickness); + qp.setPoint(2, x1, y1 + thickness); + qp.setPoint(3, x1, y1); + m_p->drawPolygon(qp); + } + + return ; + } + + Profiler profiler("NotePixmapFactory::drawShallowLine(points)"); + + int dv = y1 - y0; + int dh = x1 - x0; + + static std::vector<QColor> colours, selectedColours; + if (colours.size() == 0) { + int h, s, v; + QColor c = GUIPalette::getColour(GUIPalette::SelectedElement); + c.hsv(&h, &s, &v); + for (int step = 0; step < 256; step += (step == 0 ? 63 : 64)) { + colours.push_back(QColor( -1, 0, step, QColor::Hsv)); + selectedColours.push_back(QColor(h, 255 - step, v, QColor::Hsv)); + } + } + + int cx = x0, cy = y0; + + int inc = 1; + + if (dv < 0) { + dv = -dv; + inc = -1; + } + + int g = 2 * dv - dh; + int dg1 = 2 * (dv - dh); + int dg2 = 2 * dv; + + int segment = (dg2 - dg1) / 4; + + while (cx < x1) { + + if (g > 0) { + g += dg1; + cy += inc; + } else { + g += dg2; + } + + int quartile = segment ? ((dg2 - g) / segment) : 0; + if (quartile < 0) + quartile = 0; + if (quartile > 3) + quartile = 3; + if (inc > 0) + quartile = 4 - quartile; + /* + NOTATION_DEBUG + << "x = " << cx << ", y = " << cy + << ", g = " << g << ", dg1 = " << dg1 << ", dg2 = " << dg2 + << ", seg = " << segment << ", q = " << quartile << endl; + */ + // I don't know enough about Qt to be sure of this, but I + // suspect this may be some of the most inefficient code ever + // written: + + int off = 0; + + if (m_selected) { + m_p->painter().setPen(selectedColours[quartile]); + } else { + m_p->painter().setPen(colours[quartile]); + } + + m_p->drawPoint(cx, cy); + drawBeamsCount ++; + + if (thickness > 1) { + if (m_selected) { + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + } else { + m_p->painter().setPen(Qt::black); + } + } + + while (++off < thickness) { + m_p->drawPoint(cx, cy + off); + drawBeamsCount ++; + } + + if (m_selected) { + m_p->painter().setPen(selectedColours[4 - quartile]); + } else { + m_p->painter().setPen(colours[4 - quartile]); + } + + m_p->drawPoint(cx, cy + off); + drawBeamsCount ++; + + ++cx; + } + + m_p->painter().setPen(Qt::black); +} + +void +NotePixmapFactory::drawBeams(const QPoint &s1, + const NotePixmapParameters ¶ms, + int beamCount) +{ + clock_t startTime = clock(); + + // draw beams: first we draw all the beams common to both ends of + // the section, then we draw beams for those that appear at the + // end only + + int startY = m_above + s1.y(), startX = m_left + s1.x(); + int commonBeamCount = std::min(beamCount, params.m_nextBeamCount); + + unsigned int thickness; + (void)m_font->getBeamThickness(thickness); + + int width = params.m_width; + double grad = params.m_gradient; + bool smooth = m_font->isSmooth(); + int spacing = getLineSpacing(); + + int sign = (params.m_stemGoesUp ? 1 : -1); + + if (!params.m_stemGoesUp) + startY -= thickness; + + if (!smooth) + startY -= sign; + else if (grad > -0.01 && grad < 0.01) + startY -= sign; + + if (m_inPrinterMethod) { + startX += getStemThickness() / 2; + } + + for (int j = 0; j < commonBeamCount; ++j) { + int y = sign * j * spacing; + drawShallowLine(startX, startY + y, startX + width, + startY + (int)(width*grad) + y, + thickness, smooth); + drawBeamsBeamCount ++; + } + + int partWidth = width / 3; + if (partWidth < 2) + partWidth = 2; + else if (partWidth > m_noteBodyWidth) + partWidth = m_noteBodyWidth; + + if (params.m_thisPartialBeams) { + for (int j = commonBeamCount; j < beamCount; ++j) { + int y = sign * j * spacing; + drawShallowLine(startX, startY + y, startX + partWidth, + startY + (int)(partWidth*grad) + y, + thickness, smooth); + drawBeamsBeamCount ++; + } + } + + if (params.m_nextPartialBeams) { + startX += width - partWidth; + startY += (int)((width - partWidth) * grad); + + for (int j = commonBeamCount; j < params.m_nextBeamCount; ++j) { + int y = sign * j * spacing; + drawShallowLine(startX, startY + y, startX + partWidth, + startY + (int)(partWidth*grad) + y, + thickness, smooth); + drawBeamsBeamCount ++; + } + } + + clock_t endTime = clock(); + drawBeamsTime += (endTime - startTime); +} + +void +NotePixmapFactory::drawSlashes(const QPoint &s0, + const NotePixmapParameters ¶ms, + int slashCount) +{ + unsigned int thickness; + (void)m_font->getBeamThickness(thickness); + thickness = thickness * 3 / 4; + if (thickness < 1) + thickness = 1; + + int gap = thickness - 1; + if (gap < 1) + gap = 1; + + bool smooth = m_font->isSmooth(); + + int width = m_noteBodyWidth * 4 / 5; + int sign = (params.m_stemGoesUp ? -1 : 1); + + int offset = + (slashCount == 1 ? m_noteBodyHeight * 2 : + slashCount == 2 ? m_noteBodyHeight * 3 / 2 : + m_noteBodyHeight); + int y = m_above + s0.y() + sign * (offset + thickness / 2); + + for (int i = 0; i < slashCount; ++i) { + int yoff = width / 2; + drawShallowLine(m_left + s0.x() - width / 2, y + yoff / 2, + m_left + s0.x() + width / 2 + getStemThickness(), y - yoff / 2, + thickness, smooth); + y += sign * (thickness + gap); + } +} + +void +NotePixmapFactory::makeRoomForTuplingLine(const NotePixmapParameters ¶ms) +{ + int lineSpacing = + (int)(params.m_tuplingLineWidth * params.m_tuplingLineGradient); + int th = m_tupletCountFontMetrics.height(); + + if (params.m_tuplingLineY < 0) { + + lineSpacing = -lineSpacing; + if (lineSpacing < 0) + lineSpacing = 0; + m_above = std::max(m_above, -params.m_tuplingLineY + th / 2); + m_above += lineSpacing + 1; + + } else { + + if (lineSpacing < 0) + lineSpacing = 0; + m_below = std::max(m_below, params.m_tuplingLineY + th / 2); + m_below += lineSpacing + 1; + } + + m_right = std::max(m_right, params.m_tuplingLineWidth); +} + +void +NotePixmapFactory::drawTuplingLine(const NotePixmapParameters ¶ms) +{ + int thickness = getStaffLineThickness() * 3 / 2; + int countSpace = thickness * 2; + + QString count; + count.setNum(params.m_tupletCount); + QRect cr = m_tupletCountFontMetrics.boundingRect(count); + + int tlw = params.m_tuplingLineWidth; + int indent = m_noteBodyWidth / 2; + + if (tlw < (cr.width() + countSpace * 2 + m_noteBodyWidth * 2)) { + tlw += m_noteBodyWidth - 1; + indent = 0; + } + + int w = (tlw - cr.width()) / 2 - countSpace; + + int startX = m_left + indent; + int endX = startX + w; + + int startY = params.m_tuplingLineY + m_above + getLineSpacing() / 2; + int endY = startY + (int)(params.m_tuplingLineGradient * w); + + if (startY == endY) + ++thickness; + + int tickOffset = getLineSpacing() / 2; + if (params.m_tuplingLineY >= 0) + tickOffset = -tickOffset; + + // NOTATION_DEBUG << "adjusted params.m_tuplingLineWidth = " + // << tlw + // << ", cr.width = " << cr.width() + // << ", tickOffset = " << tickOffset << endl; + // NOTATION_DEBUG << "line: (" << startX << "," << startY << ") -> (" + // << endX << "," << endY << ")" << endl; + + bool smooth = m_font->isSmooth(); + + if (!params.m_tuplingLineFollowsBeam) { + m_p->drawLine(startX, startY, startX, startY + tickOffset); + drawShallowLine(startX, startY, endX, endY, thickness, smooth); + } + + m_p->painter().setFont(m_tupletCountFont); + if (!m_inPrinterMethod) + m_p->maskPainter().setFont(m_tupletCountFont); + + int textX = endX + countSpace; + int textY = endY + cr.height() / 2; + // NOTATION_DEBUG << "text: (" << textX << "," << textY << ")" << endl; + + m_p->drawText(textX, textY, count); + + startX += tlw - w; + endX = startX + w; + + startY += (int)(params.m_tuplingLineGradient * (tlw - w)); + endY = startY + (int)(params.m_tuplingLineGradient * w); + + // NOTATION_DEBUG << "line: (" << startX << "," << startY << ") -> (" + // << endX << "," << endY << ")" << endl; + + if (!params.m_tuplingLineFollowsBeam) { + drawShallowLine(startX, startY, endX, endY, thickness, smooth); + m_p->drawLine(endX, endY, endX, endY + tickOffset); + } +} + +void +NotePixmapFactory::drawTie(bool above, int length, int shift) +{ +#ifdef NASTY_OLD_FLAT_TIE_CODE + + int tieThickness = getStaffLineThickness() * 2; + int tieCurve = m_font->getSize() * 2 / 3; + int height = tieCurve + tieThickness; + int x = m_left + m_noteBodyWidth; + int y = (above ? m_above - height - tieCurve / 2 : + m_above + m_noteBodyHeight + tieCurve / 2 + 1); + int i; + + length -= m_noteBodyWidth; + if (length < tieCurve * 2) + length = tieCurve * 2; + if (length < m_noteBodyWidth * 3) { + length += m_noteBodyWidth - 2; + x -= m_noteBodyWidth / 2 - 1; + } + + for (i = 0; i < tieThickness; ++i) { + + if (above) { + + m_p->drawArc + (x, y + i, tieCurve*2, tieCurve*2, 90*16, 70*16); + + m_p->drawLine + (x + tieCurve, y + i, x + length - tieCurve - 2, y + i); + + m_p->drawArc + (x + length - 2*tieCurve - 1, y + i, + tieCurve*2, tieCurve*2, 20*16, 70*16); + + } else { + + m_p->drawArc + (x, y + i - tieCurve, tieCurve*2, tieCurve*2, 200*16, 70*16); + + m_p->drawLine + (x + tieCurve, y + height - i - 1, + x + length - tieCurve - 2, y + height - i - 1); + + m_p->drawArc + (x + length - 2*tieCurve - 1, y + i - tieCurve, + tieCurve*2, tieCurve*2, 270*16, 70*16); + } + } +#else + + int origLength = length; + + int x = m_left + m_noteBodyWidth + m_noteBodyWidth / 4 + shift; + length = origLength - m_noteBodyWidth - m_noteBodyWidth / 3 - shift; + + // if the length is short, move the tie a bit closer to both notes + if (length < m_noteBodyWidth*2) { + x = m_left + m_noteBodyWidth + shift; + length = origLength - m_noteBodyWidth - shift; + } + + if (length < m_noteBodyWidth) { + length = m_noteBodyWidth; + } + + // We can't request a smooth slur here, because that always involves + // creating a new pixmap + + QPoint hotspot; + drawSlurAux(length, 0, above, false, true, false, hotspot, + &m_p->painter(), + x, + above ? m_above : m_above + m_noteBodyHeight); + // above ? m_above - m_noteBodyHeight/2 : + // m_above + m_noteBodyHeight + m_noteBodyHeight/2); + +#endif +} + +QCanvasPixmap* +NotePixmapFactory::makeRestPixmap(const NotePixmapParameters ¶ms) +{ + Profiler profiler("NotePixmapFactory::makeRestPixmap"); + + CharName charName(m_style->getRestCharName(params.m_noteType, + params.m_restOutsideStave)); + // Check whether the font has the glyph for this charName; + // if not, substitute a rest-on-stave glyph for a rest-outside-stave glyph, + // and vice-versa. + NoteCharacter character; + if (!getCharacter(charName, character, PlainColour, false)) + charName = m_style->getRestCharName(params.m_noteType, + !params.m_restOutsideStave); + + bool encache = false; + + if (params.m_tupletCount == 0 && !m_selected && !m_shaded && + !params.m_restOutsideStave) { + + if (params.m_dots == 0) { + return getCharacter(charName, PlainColour, false).getCanvasPixmap(); + } else { + NotePixmapCache::iterator ci(m_dottedRestCache->find(charName)); + if (ci != m_dottedRestCache->end()) + return new QCanvasPixmap + (*ci->second, QPoint(ci->second->offsetX(), + ci->second->offsetY())); + else + encache = true; + } + } + + QPoint hotspot(m_font->getHotspot(charName)); + drawRestAux(params, hotspot, 0, 0, 0); + + QCanvasPixmap* canvasMap = makeCanvasPixmap(hotspot); + if (encache) { + m_dottedRestCache->insert(std::pair<CharName, QCanvasPixmap*> + (charName, new QCanvasPixmap + (*canvasMap, hotspot))); + } + return canvasMap; +} + +void +NotePixmapFactory::drawRest(const NotePixmapParameters ¶ms, + QPainter &painter, int x, int y) +{ + Profiler profiler("NotePixmapFactory::drawRest"); + m_inPrinterMethod = true; + QPoint hotspot; // unused + drawRestAux(params, hotspot, &painter, x, y); + m_inPrinterMethod = false; +} + +void +NotePixmapFactory::drawRestAux(const NotePixmapParameters ¶ms, + QPoint &hotspot, QPainter *painter, int x, int y) +{ + CharName charName(m_style->getRestCharName(params.m_noteType, + params.m_restOutsideStave)); + NoteCharacter character = getCharacter(charName, + params.m_quantized ? QuantizedColour : + PlainColour, + false); + + NoteCharacter dot = getCharacter(NoteCharacterNames::DOT, PlainColour, false); + + int dotWidth = dot.getWidth(); + if (dotWidth < getNoteBodyWidth() / 2) + dotWidth = getNoteBodyWidth() / 2; + + m_above = m_left = 0; + m_below = dot.getHeight() / 2; // for dotted shallow rests like semibreve + m_right = dotWidth / 2 + dotWidth * params.m_dots; + m_noteBodyWidth = character.getWidth(); + m_noteBodyHeight = character.getHeight(); + + if (params.m_tupletCount) + makeRoomForTuplingLine(params); + + // we'll adjust this for tupling line after drawing rest character: + hotspot = m_font->getHotspot(charName); + + if (params.m_restOutsideStave && + (charName == NoteCharacterNames::MULTI_REST || + charName == NoteCharacterNames::MULTI_REST_ON_STAFF)) { + makeRoomForLegerLines(params); + } + if (painter) { + painter->save(); + m_p->beginExternal(painter); + painter->translate(x - m_left, y - m_above - hotspot.y()); + } else { + createPixmapAndMask(m_noteBodyWidth + m_left + m_right, + m_noteBodyHeight + m_above + m_below); + } + + m_p->drawNoteCharacter(m_left, m_above, character); + + if (params.m_tupletCount) + drawTuplingLine(params); + + hotspot.setX(m_left); + hotspot.setY(m_above + hotspot.y()); + + int restY = hotspot.y() - dot.getHeight() - getStaffLineThickness(); + if (params.m_noteType == Note::Semibreve || + params.m_noteType == Note::Breve) { + restY += getLineSpacing(); + } + + for (int i = 0; i < params.m_dots; ++i) { + int x = m_left + m_noteBodyWidth + i * dotWidth + dotWidth / 2; + m_p->drawNoteCharacter(x, restY, dot); + } + + if (params.m_restOutsideStave && + (charName == NoteCharacterNames::MULTI_REST || + charName == NoteCharacterNames::MULTI_REST_ON_STAFF)) { + drawLegerLines(params); + } + + if (painter) { + painter->restore(); + } +} + +QCanvasPixmap* +NotePixmapFactory::makeClefPixmap(const Clef &clef) +{ + Profiler profiler("NotePixmapFactory::makeClefPixmap"); + NoteCharacter plain = getCharacter(m_style->getClefCharName(clef), + PlainColour, false); + + int oct = clef.getOctaveOffset(); + if (oct == 0) + return plain.getCanvasPixmap(); + + // fix #1522784 and use 15 rather than 16 for double octave offset + int adjustedOctave = (8 * (oct < 0 ? -oct : oct)); + if (adjustedOctave > 8) + adjustedOctave--; + else if (adjustedOctave < 8) + adjustedOctave++; + + QString text = QString("%1").arg(adjustedOctave); + QRect rect = m_clefOttavaFontMetrics.boundingRect(text); + + createPixmapAndMask(plain.getWidth(), + plain.getHeight() + rect.height()); + + if (m_selected) { + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + } + + m_p->drawNoteCharacter(0, oct < 0 ? 0 : rect.height(), plain); + + m_p->painter().setFont(m_clefOttavaFont); + if (!m_inPrinterMethod) + m_p->maskPainter().setFont(m_clefOttavaFont); + + m_p->drawText(plain.getWidth() / 2 - rect.width() / 2, + oct < 0 ? plain.getHeight() + rect.height() - 1 : + rect.height(), text); + + m_p->painter().setPen(Qt::black); + QPoint hotspot(plain.getHotspot()); + if (oct > 0) hotspot.setY(hotspot.y() + rect.height()); + return makeCanvasPixmap(hotspot, true); +} + +QCanvasPixmap* +NotePixmapFactory::makePedalDownPixmap() +{ + return getCharacter(NoteCharacterNames::PEDAL_MARK, PlainColour, false) + .getCanvasPixmap(); +} + +QCanvasPixmap* +NotePixmapFactory::makePedalUpPixmap() +{ + return getCharacter(NoteCharacterNames::PEDAL_UP_MARK, PlainColour, false) + .getCanvasPixmap(); +} + +QCanvasPixmap* +NotePixmapFactory::makeUnknownPixmap() +{ + Profiler profiler("NotePixmapFactory::makeUnknownPixmap"); + return getCharacter(NoteCharacterNames::UNKNOWN, PlainColour, false) + .getCanvasPixmap(); +} + +QCanvasPixmap* +NotePixmapFactory::makeToolbarPixmap(const char *name, bool menuSize) +{ + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QString fileBase = pixmapDir + "/toolbar/"; + if (menuSize) fileBase += "menu-"; + fileBase += name; + if (QFile(fileBase + ".png").exists()) { + return new QCanvasPixmap(fileBase + ".png"); + } else if (QFile(fileBase + ".xpm").exists()) { + return new QCanvasPixmap(fileBase + ".xpm"); + } else if (menuSize) { + return makeToolbarPixmap(name, false); + } else { + // this will fail, but we don't want to return a null pointer + return new QCanvasPixmap(fileBase + ".png"); + } +} + +QCanvasPixmap* +NotePixmapFactory::makeNoteMenuPixmap(timeT duration, + timeT &errorReturn) +{ + Note nearestNote = Note::getNearestNote(duration); + bool triplet = false; + errorReturn = 0; + + if (nearestNote.getDuration() != duration) { + Note tripletNote = Note::getNearestNote(duration * 3 / 2); + if (tripletNote.getDuration() == duration * 3 / 2) { + nearestNote = tripletNote; + triplet = true; + } else { + errorReturn = duration - nearestNote.getDuration(); + } + } + + QString noteName = NotationStrings::getReferenceName(nearestNote); + if (triplet) + noteName = "3-" + noteName; + noteName = "menu-" + noteName; + return makeToolbarPixmap(noteName); +} + +QCanvasPixmap * +NotePixmapFactory::makeMarkMenuPixmap(Mark mark) +{ + if (mark == Marks::Sforzando || + mark == Marks::Rinforzando) { + return makeToolbarPixmap(mark.c_str()); + } else { + NoteFont *font = 0; + try { + font = NoteFontFactory::getFont + (NoteFontFactory::getDefaultFontName(), 6); + } catch (Exception) { + font = NoteFontFactory::getFont + (NoteFontFactory::getDefaultFontName(), + NoteFontFactory::getDefaultSize(NoteFontFactory::getDefaultFontName())); + } + NoteCharacter character = font->getCharacter + (NoteStyleFactory::getStyle(NoteStyleFactory::DefaultStyle)-> + getMarkCharName(mark)); + return character.getCanvasPixmap(); + } +} + +QCanvasPixmap* +NotePixmapFactory::makeKeyPixmap(const Key &key, + const Clef &clef, + Key previousKey) +{ + Profiler profiler("NotePixmapFactory::makeKeyPixmap"); + + std::vector<int> ah0 = previousKey.getAccidentalHeights(clef); + std::vector<int> ah1 = key.getAccidentalHeights(clef); + + int cancelCount = 0; + if (key.isSharp() != previousKey.isSharp()) + cancelCount = ah0.size(); + else if (ah1.size() < ah0.size()) + cancelCount = ah0.size() - ah1.size(); + + CharName keyCharName; + if (key.isSharp()) + keyCharName = NoteCharacterNames::SHARP; + else + keyCharName = NoteCharacterNames::FLAT; + + NoteCharacter keyCharacter; + NoteCharacter cancelCharacter; + + keyCharacter = getCharacter(keyCharName, PlainColour, false); + if (cancelCount > 0) { + cancelCharacter = getCharacter(NoteCharacterNames::NATURAL, PlainColour, false); + } + + int x = 0; + int lw = getLineSpacing(); + int keyDelta = keyCharacter.getWidth() - keyCharacter.getHotspot().x(); + + int cancelDelta = 0; + int between = 0; + if (cancelCount > 0) { + cancelDelta = cancelCharacter.getWidth() + cancelCharacter.getWidth() / 3; + between = cancelCharacter.getWidth(); + } + + createPixmapAndMask(keyDelta * ah1.size() + cancelDelta * cancelCount + between + + keyCharacter.getWidth() / 4, lw * 8 + 1); + + if (key.isSharp() != previousKey.isSharp()) { + + // cancellation first + + for (int i = 0; i < cancelCount; ++i) { + + int h = ah0[ah0.size() - cancelCount + i]; + int y = (lw * 2) + ((8 - h) * lw) / 2 - cancelCharacter.getHotspot().y(); + + m_p->drawNoteCharacter(x, y, cancelCharacter); + + x += cancelDelta; + } + + if (cancelCount > 0) { + x += between; + } + } + + for (unsigned int i = 0; i < ah1.size(); ++i) { + + int h = ah1[i]; + int y = (lw * 2) + ((8 - h) * lw) / 2 - keyCharacter.getHotspot().y(); + + m_p->drawNoteCharacter(x, y, keyCharacter); + + x += keyDelta; + } + + if (key.isSharp() == previousKey.isSharp()) { + + // cancellation afterwards + + if (cancelCount > 0) { + x += between; + } + + for (int i = 0; i < cancelCount; ++i) { + + int h = ah0[ah0.size() - cancelCount + i]; + int y = (lw * 2) + ((8 - h) * lw) / 2 - cancelCharacter.getHotspot().y(); + + m_p->drawNoteCharacter(x, y, cancelCharacter); + + x += cancelDelta; + } + } + + return makeCanvasPixmap(m_pointZero); +} + +QCanvasPixmap* +NotePixmapFactory::makeClefDisplayPixmap(const Clef &clef) +{ + QCanvasPixmap* clefPixmap = makeClefPixmap(clef); + + int lw = getLineSpacing(); + int width = clefPixmap->width() + 6 * getNoteBodyWidth(); + + createPixmapAndMask(width, lw * 10 + 1); + + int h = clef.getAxisHeight(); + int y = (lw * 3) + ((8 - h) * lw) / 2; + int x = 3 * getNoteBodyWidth(); + m_p->drawPixmap(x, y - clefPixmap->offsetY(), *clefPixmap); + + for (h = 0; h <= 8; h += 2) { + y = (lw * 3) + ((8 - h) * lw) / 2; + m_p->drawLine(x / 2, y, m_generatedWidth - x / 2 - 1, y); + } + + delete clefPixmap; + + return makeCanvasPixmap(m_pointZero); +} + +QCanvasPixmap* +NotePixmapFactory::makeKeyDisplayPixmap(const Key &key, const Clef &clef) +{ + std::vector<int> ah = key.getAccidentalHeights(clef); + + CharName charName = (key.isSharp() ? + NoteCharacterNames::SHARP : + NoteCharacterNames::FLAT); + + QCanvasPixmap* clefPixmap = makeClefPixmap(clef); + QPixmap accidentalPixmap(*m_font->getCharacter(charName).getPixmap()); + QPoint hotspot(m_font->getHotspot(charName)); + + int lw = getLineSpacing(); + int delta = accidentalPixmap.width() - hotspot.x(); + int maxDelta = getAccidentalWidth(Sharp); + int width = clefPixmap->width() + 5 * maxDelta + 7 * maxDelta; + int x = clefPixmap->width() + 5 * maxDelta / 2; + + createPixmapAndMask(width, lw * 10 + 1); + + int h = clef.getAxisHeight(); + int y = (lw * 3) + ((8 - h) * lw) / 2; + m_p->drawPixmap(2 * maxDelta, y - clefPixmap->offsetY(), *clefPixmap); + + for (unsigned int i = 0; i < ah.size(); ++i) { + + h = ah[i]; + y = (lw * 3) + ((8 - h) * lw) / 2 - hotspot.y(); + + m_p->drawPixmap(x, y, accidentalPixmap); + + x += delta; + } + + for (h = 0; h <= 8; h += 2) { + y = (lw * 3) + ((8 - h) * lw) / 2; + m_p->drawLine(maxDelta, y, m_generatedWidth - 2*maxDelta - 1, y); + } + + delete clefPixmap; + return makeCanvasPixmap(m_pointZero); +} + +int +NotePixmapFactory::getClefAndKeyWidth(const Key &key, const Clef &clef) +{ + std::vector<int> ah = key.getAccidentalHeights(clef); + Accidental accidental = key.isSharp() ? Sharp : Flat; + NoteCharacter plain = getCharacter(m_style->getClefCharName(clef), + PlainColour, false); + + int clefWidth = plain.getWidth(); + int accWidth = getAccidentalWidth(accidental); + int maxDelta = getAccidentalWidth(Sharp); + + int width = clefWidth + 2 * maxDelta + ah.size() * accWidth; + + return width; +} + +QCanvasPixmap* +NotePixmapFactory::makeTrackHeaderPixmap( + int width, int height, TrackHeader *header) +{ + + height -= 4; // Make room to the label frame : + // 4 = 2 * (margin + lineWidth) + + createPixmapAndMask(width, height); + + int lw = getLineSpacing(); + int h; + QColor colour; + int maxDelta = getAccidentalWidth(Sharp); + + // Staff Y position inside the whole header + int offset = (height - 10 * lw -1) / 2; + + // Draw staff lines + m_p->painter().setPen(QPen(Qt::black, getStaffLineThickness())); + for (h = 0; h <= 8; h += 2) { + int y = (lw * 3) + ((8 - h) * lw) / 2; + m_p->drawLine(maxDelta/2, y + offset, m_generatedWidth - maxDelta/2, y + offset); + } + + if (header->isAClefToDraw()) { + const Clef &clef = header->getClef(); + // TODO : use colours from GUIPalette + colour = header->isClefInconsistent() ? Qt::red : Qt::black; + + int hue, sat, val; + colour.getHsv(&hue, &sat, &val); + NoteCharacter clefChar = m_font->getCharacterColoured + (m_style->getClefCharName(clef), + hue, val, NoteFont::Screen, false); + + // Draw clef + h = clef.getAxisHeight(); + int y = (lw * 3) + ((8 - h) * lw) / 2; + m_p->drawNoteCharacter(maxDelta, + y - clefChar.getHotspot().y() + offset, clefChar); + + // If necessary, write 8 or 15 above or under the clef + int oct = clef.getOctaveOffset(); + if (oct != 0) { + + int adjustedOctave = (8 * (oct < 0 ? -oct : oct)); + if (adjustedOctave > 8) + adjustedOctave--; + else if (adjustedOctave < 8) + adjustedOctave++; + + QString text = QString("%1").arg(adjustedOctave); + QRect rect = m_clefOttavaFontMetrics.boundingRect(text); + + m_p->painter().setPen(colour); + + m_p->painter().setFont(m_clefOttavaFont); + // m_p->maskPainter().setFont(m_clefOttavaFont); + int xpos = maxDelta + clefChar.getWidth() / 2 - rect.width() / 2; + int ypos = y - clefChar.getHotspot().y() + offset + + (oct < 0 ? clefChar.getHeight() + rect.height() - 1 : - rect.height() / 3); + m_p->drawText(xpos, ypos, text); + } + + // TODO : use colours from GUIPalette + colour = header->isKeyInconsistent() ? Qt::red : Qt::black; + + + // Draw the key signature if any + + const Key &key = header->getKey(); + std::vector<int> ah = key.getAccidentalHeights(clef); + + CharName charName = key.isSharp() ? + NoteCharacterNames::SHARP : + NoteCharacterNames::FLAT; + + colour.getHsv(&hue, &sat, &val); + NoteCharacter accident = m_font->getCharacterColoured(charName, + hue, val, NoteFont::Screen, false); + + QPoint hotspot(m_font->getHotspot(charName)); + int delta = accident.getWidth() - hotspot.x(); + + int x = clefChar.getWidth() + maxDelta; + for (unsigned int i = 0; i < ah.size(); ++i) { + h = ah[i]; + y = (lw * 3) + ((8 - h) * lw) / 2 - hotspot.y() + offset; + m_p->drawNoteCharacter(x, y, accident); + + x += delta; + } + + } + + m_p->painter().setFont(m_trackHeaderFont); + // m_p->maskPainter().setFont(m_trackHeaderFont); + + QString text; + QString textLine; + + int charHeight = m_trackHeaderFontMetrics.height(); + int charWidth = m_trackHeaderFontMetrics.maxWidth(); + + const QString transposeText = header->getTransposeText(); + QRect bounds = m_trackHeaderBoldFontMetrics.boundingRect(transposeText); + int transposeWidth = bounds.width(); + + + // Write upper text (track name and track label) + + m_p->painter().setPen(Qt::black); + text = header->getUpperText(); + int numberOfTextLines = header->getNumberOfTextLines(); + + for (int l=1; l<=numberOfTextLines; l++) { + int upperTextY = charHeight + (l - 1) * getTrackHeaderTextLineSpacing(); + if (l == numberOfTextLines) { + int transposeSpace = transposeWidth ? transposeWidth + charWidth / 4 : 0; + textLine = getOneLine(text, width - transposeSpace - charWidth / 2); + if (!text.isEmpty()) { + // String too long : cut it and replace last character by dots + int len = textLine.length(); + if (len > 1) textLine.replace(len - 1, 1, i18n("...")); + } + } else { + textLine = getOneLine(text, width - charWidth / 2); + } + if (textLine.isEmpty()) break; + m_p->drawText(charWidth / 4, upperTextY, textLine); + } + + + // Write transposition text + + // TODO : use colours from GUIPalette + colour = header->isTransposeInconsistent() ? Qt::red : Qt::black; + m_p->painter().setFont(m_trackHeaderBoldFont); + // m_p->maskPainter().setFont(m_trackHeaderBoldFont); + m_p->painter().setPen(colour); + + m_p->drawText(width - transposeWidth - charWidth / 4, + charHeight + + (numberOfTextLines - 1) * getTrackHeaderTextLineSpacing(), + transposeText); + + + // Write lower text (segment label) + + // TODO : use colours from GUIPalette + colour = header->isLabelInconsistent() ? Qt::red : Qt::black; + m_p->painter().setFont(m_trackHeaderFont); + // m_p->maskPainter().setFont(m_trackHeaderFont); + + m_p->painter().setPen(colour); + text = header->getLowerText(); + + for (int l=1; l<=numberOfTextLines; l++) { + int lowerTextY = m_generatedHeight - 4 // -4 : adjust + - (numberOfTextLines - l) * getTrackHeaderTextLineSpacing(); + + QString textLine = getOneLine(text, width - charWidth / 2); + if (textLine.isEmpty()) break; + + if ((l == numberOfTextLines) && !text.isEmpty()) { + // String too long : cut it and replace last character by dots + int len = textLine.length(); + if (len > 1) textLine.replace(len - 1, 1, i18n("...")); + } + + m_p->drawText(charWidth / 4, lowerTextY, textLine); + } + + return makeCanvasPixmap(m_pointZero, true); +} + +int +NotePixmapFactory::getTrackHeaderNTL(int height) +{ + int clefMaxHeight = 12 * getLineSpacing(); + int textLineHeight = getTrackHeaderTextLineSpacing(); + int numberOfLines = ((height - clefMaxHeight) / 2) / textLineHeight; + return (numberOfLines > 0) ? numberOfLines : 1; +} + +int +NotePixmapFactory::getTrackHeaderTextWidth(QString str) +{ + QRect bounds = m_trackHeaderFontMetrics.boundingRect(str); + return bounds.width(); +} + +int +NotePixmapFactory::getTrackHeaderTextLineSpacing() +{ + // 3/2 is some arbitrary line spacing + return m_trackHeaderFont.pixelSize() * 3 / 2; +} + +QString +NotePixmapFactory::getOneLine(QString &text, int width) +{ + QString str; + int n; + + // Immediately stop if string is empty or only contains white spaces ... + if (text.stripWhiteSpace().isEmpty()) return QString(""); + + // ... or if width is too small. + if (width < m_trackHeaderFontMetrics.boundingRect(text.left(1)).width()) + return QString(""); + + // Get a first approx. string length + int totalLength = text.length(); + n = totalLength * width / getTrackHeaderTextWidth(text) + 1; + if (n > totalLength) n = totalLength; + + // Verify string size is less than width then correct it if necessary + while (((getTrackHeaderTextWidth(text.left(n))) > width) && n) n--; + + if (n == 0) { + str = text; + text = QString(""); + } else { + str = text.left(n); + text.remove(0, n); + } + + return str; +} + +QCanvasPixmap* +NotePixmapFactory::makePitchDisplayPixmap(int p, const Clef &clef, + bool useSharps) +{ + NotationRules rules; + + Pitch pitch(p); + Accidental accidental(pitch.getAccidental(useSharps)); + NotePixmapParameters params(Note::Crotchet, 0, accidental); + + QCanvasPixmap* clefPixmap = makeClefPixmap(clef); + + int lw = getLineSpacing(); + int width = getClefWidth(Clef::Bass) + 10 * getNoteBodyWidth(); + + int h = pitch.getHeightOnStaff(clef, useSharps); + params.setStemGoesUp(rules.isStemUp(h)); + + if (h < -1) + params.setStemLength(lw * (4 - h) / 2); + else if (h > 9) + params.setStemLength(lw * (h - 4) / 2); + if (h > 8) + params.setLegerLines(h - 8); + else if (h < 0) + params.setLegerLines(h); + + params.setIsOnLine(h % 2 == 0); + params.setSelected(m_selected); + + QCanvasPixmap *notePixmap = makeNotePixmap(params); + + int pixmapHeight = lw * 12 + 1; + int yoffset = lw * 3; + if (h > 12) { + pixmapHeight += 6 * lw; + yoffset += 6 * lw; + } else if (h < -4) { + pixmapHeight += 6 * lw; + } + + createPixmapAndMask(width, pixmapHeight); + + int x = + getClefWidth(Clef::Bass) + 5 * getNoteBodyWidth() - + getAccidentalWidth(accidental); + int y = yoffset + ((8 - h) * lw) / 2 - notePixmap->offsetY(); + m_p->drawPixmap(x, y, *notePixmap); + + h = clef.getAxisHeight(); + x = 3 * getNoteBodyWidth(); + y = yoffset + ((8 - h) * lw) / 2; + m_p->drawPixmap(x, y - clefPixmap->offsetY(), *clefPixmap); + + for (h = 0; h <= 8; h += 2) { + y = yoffset + ((8 - h) * lw) / 2; + m_p->drawLine(x / 2, y, m_generatedWidth - x / 2, y); + } + + delete clefPixmap; + delete notePixmap; + + return makeCanvasPixmap(m_pointZero); +} + +QCanvasPixmap* +NotePixmapFactory::makePitchDisplayPixmap(int p, const Clef &clef, + int octave, int step) +{ + NotationRules rules; + + Pitch pitch(step, octave, p, 0); + Accidental accidental = pitch.getDisplayAccidental(Key("C major")); + NotePixmapParameters params(Note::Crotchet, 0, accidental); + + QCanvasPixmap* clefPixmap = makeClefPixmap(clef); + + int lw = getLineSpacing(); + int width = getClefWidth(Clef::Bass) + 10 * getNoteBodyWidth(); + + int h = pitch.getHeightOnStaff + (clef, + Key("C major")); + params.setStemGoesUp(rules.isStemUp(h)); + + if (h < -1) + params.setStemLength(lw * (4 - h) / 2); + else if (h > 9) + params.setStemLength(lw * (h - 4) / 2); + if (h > 8) + params.setLegerLines(h - 8); + else if (h < 0) + params.setLegerLines(h); + + params.setIsOnLine(h % 2 == 0); + params.setSelected(m_selected); + + QCanvasPixmap *notePixmap = makeNotePixmap(params); + + int pixmapHeight = lw * 12 + 1; + int yoffset = lw * 3; + if (h > 12) { + pixmapHeight += 6 * lw; + yoffset += 6 * lw; + } else if (h < -4) { + pixmapHeight += 6 * lw; + } + + createPixmapAndMask(width, pixmapHeight); + + int x = + getClefWidth(Clef::Bass) + 5 * getNoteBodyWidth() - + getAccidentalWidth(accidental); + int y = yoffset + ((8 - h) * lw) / 2 - notePixmap->offsetY(); + m_p->drawPixmap(x, y, *notePixmap); + + h = clef.getAxisHeight(); + x = 3 * getNoteBodyWidth(); + y = yoffset + ((8 - h) * lw) / 2; + m_p->drawPixmap(x, y - clefPixmap->offsetY(), *clefPixmap); + + for (h = 0; h <= 8; h += 2) { + y = yoffset + ((8 - h) * lw) / 2; + m_p->drawLine(x / 2, y, m_generatedWidth - x / 2, y); + } + + delete clefPixmap; + delete notePixmap; + + return makeCanvasPixmap(m_pointZero); +} + +QCanvasPixmap* +NotePixmapFactory::makeHairpinPixmap(int length, bool isCrescendo) +{ + Profiler profiler("NotePixmapFactory::makeHairpinPixmap"); + drawHairpinAux(length, isCrescendo, 0, 0, 0); + return makeCanvasPixmap(QPoint(0, m_generatedHeight / 2)); +} + +void +NotePixmapFactory::drawHairpin(int length, bool isCrescendo, + QPainter &painter, int x, int y) +{ + Profiler profiler("NotePixmapFactory::drawHairpin"); + m_inPrinterMethod = true; + drawHairpinAux(length, isCrescendo, &painter, x, y); + m_inPrinterMethod = false; +} + +void +NotePixmapFactory::drawHairpinAux(int length, bool isCrescendo, + QPainter *painter, int x, int y) +{ + int nbh = getNoteBodyHeight(); + int nbw = getNoteBodyWidth(); + + int height = (int)(((double)nbh / (double)(nbw * 40)) * length) + nbh; + int thickness = getStaffLineThickness() * 3 / 2; + + // NOTATION_DEBUG << "NotePixmapFactory::makeHairpinPixmap: mapped length " << length << " to height " << height << " (nbh = " << nbh << ", nbw = " << nbw << ")" << endl; + + if (height < nbh) + height = nbh; + if (height > nbh*2) + height = nbh * 2; + + height += thickness - 1; + + if (painter) { + painter->save(); + m_p->beginExternal(painter); + painter->translate(x, y - height / 2); + } else { + createPixmapAndMask(length, height); + } + + if (m_selected) { + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + } + + int left = 1, right = length - 2 * nbw / 3 + 1; + + bool smooth = m_font->isSmooth(); + + if (isCrescendo) { + drawShallowLine(left, height / 2 - 1, + right, height - thickness - 1, thickness, smooth); + drawShallowLine(left, height / 2 - 1, right, 0, thickness, smooth); + } else { + drawShallowLine(left, 0, right, height / 2 - 1, thickness, smooth); + drawShallowLine(left, height - thickness - 1, + right, height / 2 - 1, thickness, smooth); + } + + m_p->painter().setPen(Qt::black); + + if (painter) { + painter->restore(); + } +} + +QCanvasPixmap* +NotePixmapFactory::makeSlurPixmap(int length, int dy, bool above, bool phrasing) +{ + Profiler profiler("NotePixmapFactory::makeSlurPixmap"); + + //!!! could remove "height > 5" requirement if we did a better job of + // sizing so that any horizontal part was rescaled down to exactly + // 1 pixel wide instead of blurring + bool smooth = m_font->isSmooth() && getNoteBodyHeight() > 5; + QPoint hotspot; + if (length < getNoteBodyWidth()*2) + length = getNoteBodyWidth() * 2; + drawSlurAux(length, dy, above, smooth, false, phrasing, hotspot, 0, 0, 0); + + m_p->end(); + + if (smooth) { + + QImage i = m_generatedPixmap->convertToImage(); + if (i.depth() == 1) + i = i.convertDepth(32); + i = i.smoothScale(i.width() / 2, i.height() / 2); + + delete m_generatedPixmap; + delete m_generatedMask; + QPixmap newPixmap(i); + QCanvasPixmap *p = new QCanvasPixmap(newPixmap, hotspot); + p->setMask(PixmapFunctions::generateMask(newPixmap, + Qt::white.rgb())); + return p; + + } else { + + QCanvasPixmap *p = new QCanvasPixmap(*m_generatedPixmap, hotspot); + p->setMask(PixmapFunctions::generateMask(*m_generatedPixmap, + Qt::white.rgb())); + delete m_generatedPixmap; + delete m_generatedMask; + return p; + } +} + +void +NotePixmapFactory::drawSlur(int length, int dy, bool above, bool phrasing, + QPainter &painter, int x, int y) +{ + Profiler profiler("NotePixmapFactory::drawSlur"); + QPoint hotspot; + m_inPrinterMethod = true; + if (length < getNoteBodyWidth()*2) + length = getNoteBodyWidth() * 2; + drawSlurAux(length, dy, above, false, false, phrasing, hotspot, &painter, x, y); + m_inPrinterMethod = false; +} + +void +NotePixmapFactory::drawSlurAux(int length, int dy, bool above, + bool smooth, bool flat, bool phrasing, + QPoint &hotspot, QPainter *painter, int x, int y) +{ + QWMatrix::TransformationMode mode = QWMatrix::transformationMode(); + QWMatrix::setTransformationMode(QWMatrix::Points); + + int thickness = getStaffLineThickness() * 2; + if (phrasing) + thickness = thickness * 3 / 4; + int nbh = getNoteBodyHeight(), nbw = getNoteBodyWidth(); + + // Experiment with rotating the painter rather than the control points. + double theta = 0; + bool rotate = false; + if (dy != 0) { + // We have opposite (dy) and adjacent (length). + theta = atan(double(dy) / double(length)) * 180.0 / M_PI; + // NOTATION_DEBUG << "slur: dy is " << dy << ", length " << length << ", rotating through " << theta << endl; + rotate = true; + } + + // draw normal slur for very slopey phrasing slur: + if (theta < -5 || theta > 5) + phrasing = false; + + int y0 = 0, my = 0; + + float noteLengths = float(length) / nbw; + if (noteLengths < 1) + noteLengths = 1; + + my = int(0 - nbh * sqrt(noteLengths) / 2); + if (flat) + my = my * 2 / 3; + else if (phrasing) + my = my * 3 / 4; + if (!above) + my = -my; + + bool havePixmap = false; + QPoint topLeft, bottomRight; + + if (smooth) + thickness += 2; + + for (int i = 0; i < thickness; ++i) { + + Spline::PointList pl; + + if (!phrasing) { + pl.push_back(QPoint(length / 6, my)); + pl.push_back(QPoint(length - length / 6, my)); + } else { + pl.push_back(QPoint(abs(my) / 4, my / 3)); + pl.push_back(QPoint(length / 6, my)); + + if (theta > 1) { + pl.push_back(QPoint(length * 3 / 8, my * 3 / 2)); + } else if (theta < -1) { + pl.push_back(QPoint(length * 5 / 8, my * 3 / 2)); + } else { + pl.push_back(QPoint(length / 2, my * 4 / 3)); + } + + pl.push_back(QPoint(length - length / 6, my)); + pl.push_back(QPoint(length - abs(my) / 4, my / 3)); + } + + Spline::PointList *polyPoints = Spline::calculate + (QPoint(0, y0), QPoint(length - 1, y0), pl, topLeft, bottomRight); + + if (!havePixmap) { + int width = bottomRight.x() - topLeft.x(); + int height = bottomRight.y() - topLeft.y() + thickness - 1 + abs(dy); + hotspot = QPoint(0, -topLeft.y() + (dy < 0 ? -dy : 0)); + + // NOTATION_DEBUG << "slur: bottomRight (" << bottomRight.x() << "," << bottomRight.y() << "), topLeft (" << topLeft.x() << "," << topLeft.y() << "), width " << width << ", height " << height << ", hotspot (" << hotspot.x() << "," << hotspot.y() << "), dy " << dy << ", thickness " << thickness << endl; + + if (painter) { + + // This conditional is because we're also called with + // a painter arg from non-printer drawTie. It's a big + // hack. + + if (m_inPrinterMethod) { + painter->save(); + m_p->beginExternal(painter); + painter->translate(x, y); + if (rotate) + painter->rotate(theta); + } else { + m_p->painter().save(); + m_p->maskPainter().save(); + m_p->painter().translate(x, y); + m_p->maskPainter().translate(x, y); + if (rotate) { + m_p->painter().rotate(theta); + m_p->maskPainter().rotate(theta); + } + } + + } else { + createPixmapAndMask(smooth ? width*2 + 1 : width, + smooth ? height*2 + thickness*2 : height + thickness, + width, height); + + QWMatrix m; + if (smooth) + m.translate(2 * hotspot.x(), 2 * hotspot.y()); + else + m.translate(hotspot.x(), hotspot.y()); + m.rotate(theta); + m_p->painter().setWorldMatrix(m); + m_p->maskPainter().setWorldMatrix(m); + } + + if (m_selected) + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + else if (m_shaded) { + m_p->painter().setPen(Qt::gray); + } + havePixmap = true; + } + /* + for (int j = 0; j < pl.size(); ++j) { + if (smooth) { + m_p->drawPoint(pl[j].x()*2, pl[j].y()*2); + } else { + m_p->drawPoint(pl[j].x(), pl[j].y()); + } + } + */ + int ppc = polyPoints->size(); + QPointArray qp(ppc); + + for (int j = 0; j < ppc; ++j) { + qp.setPoint(j, (*polyPoints)[j].x(), (*polyPoints)[j].y()); + } + + delete polyPoints; + + if (!smooth || (i > 0 && i < thickness - 1)) { + if (smooth) { + for (int j = 0; j < ppc; ++j) { + qp.setPoint(j, qp.point(j).x()*2, qp.point(j).y()*2); + } + m_p->drawPolyline(qp); + for (int j = 0; j < ppc; ++j) { + qp.setPoint(j, qp.point(j).x(), qp.point(j).y() + 1); + } + m_p->drawPolyline(qp); + } else { + m_p->drawPolyline(qp); + } + } + + if (above) { + ++my; + if (i % 2) + ++y0; + } else { + --my; + if (i % 2) + --y0; + } + } + + if (m_selected) { + m_p->painter().setPen(Qt::black); + } + + QWMatrix::setTransformationMode(mode); + + if (painter) { + painter->restore(); + if (!m_inPrinterMethod) + m_p->maskPainter().restore(); + } +} + +QCanvasPixmap* +NotePixmapFactory::makeOttavaPixmap(int length, int octavesUp) +{ + Profiler profiler("NotePixmapFactory::makeOttavaPixmap"); + m_inPrinterMethod = false; + drawOttavaAux(length, octavesUp, 0, 0, 0); + return makeCanvasPixmap(QPoint(0, m_generatedHeight - 1)); +} + +void +NotePixmapFactory::drawOttava(int length, int octavesUp, + QPainter &painter, int x, int y) +{ + Profiler profiler("NotePixmapFactory::drawOttava"); + m_inPrinterMethod = true; + drawOttavaAux(length, octavesUp, &painter, x, y); + m_inPrinterMethod = false; +} + +void +NotePixmapFactory::drawOttavaAux(int length, int octavesUp, + QPainter *painter, int x, int y) +{ + int height = m_ottavaFontMetrics.height(); + int backpedal = 0; + QString label; + QRect r; + + if (octavesUp == 2 || octavesUp == -2) { + label = "15ma "; + backpedal = m_ottavaFontMetrics.width("15") / 2; + } else { + label = "8va "; + backpedal = m_ottavaFontMetrics.width("8") / 2; + } + + int width = length + backpedal; + + if (painter) { + painter->save(); + m_p->beginExternal(painter); + painter->translate(x - backpedal, y - height); + } else { + NOTATION_DEBUG << "NotePixmapFactory::drawOttavaAux: making pixmap and mask " << width << "x" << height << endl; + createPixmapAndMask(width, height); + } + + int thickness = getStemThickness(); + QPen pen(Qt::black, thickness, Qt::DotLine); + + if (m_selected) { + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + pen.setColor(GUIPalette::getColour(GUIPalette::SelectedElement)); + } else if (m_shaded) { + m_p->painter().setPen(Qt::gray); + pen.setColor(Qt::gray); + } + + m_p->painter().setFont(m_ottavaFont); + if (!m_inPrinterMethod) + m_p->maskPainter().setFont(m_ottavaFont); + + m_p->drawText(0, m_ottavaFontMetrics.ascent(), label); + + m_p->painter().setPen(pen); + // if (!m_inPrinterMethod) m_p->maskPainter().setPen(pen); + + int x0 = m_ottavaFontMetrics.width(label) + thickness; + int x1 = width - thickness; + int y0 = m_ottavaFontMetrics.ascent() * 2 / 3 - thickness / 2; + int y1 = (octavesUp < 0 ? 0 : m_ottavaFontMetrics.ascent()); + + NOTATION_DEBUG << "NotePixmapFactory::drawOttavaAux: drawing " << x0 << "," << y0 << " to " << x1 << "," << y0 << ", thickness " << thickness << endl; + + m_p->drawLine(x0, y0, x1, y0); + + pen.setStyle(Qt::SolidLine); + m_p->painter().setPen(pen); + // if (!m_inPrinterMethod) m_p->maskPainter().setPen(pen); + + NOTATION_DEBUG << "NotePixmapFactory::drawOttavaAux: drawing " << x1 << "," << y0 << " to " << x1 << "," << y1 << ", thickness " << thickness << endl; + + m_p->drawLine(x1, y0, x1, y1); + + m_p->painter().setPen(QPen()); + if (!m_inPrinterMethod) + m_p->maskPainter().setPen(QPen()); + + if (painter) { + painter->restore(); + } +} + +void +NotePixmapFactory::drawBracket(int length, bool left, bool curly, int x, int y) +{ + // curly mode not yet implemented + + int thickness = getStemThickness() * 2; + + int m1 = length / 6; + int m2 = length - length / 6 - 1; + + int off0 = 0, moff = 0; + + int nbh = getNoteBodyHeight(), nbw = getNoteBodyWidth(); + float noteLengths = float(length) / nbw; + if (noteLengths < 1) + noteLengths = 1; + moff = int(nbh * sqrt(noteLengths) / 2); + moff = moff * 2 / 3; + + if (left) + moff = -moff; + + QPoint topLeft, bottomRight; + + for (int i = 0; i < thickness; ++i) { + + Spline::PointList pl; + pl.push_back(QPoint((int)moff, m1)); + pl.push_back(QPoint((int)moff, m2)); + /* + NOTATION_DEBUG << "bracket spline controls: " << moff << "," << m1 + << ", " << moff << "," << m2 << "; end points " + << off0 << ",0, " << off0 << "," << length-1 + << endl; + */ + Spline::PointList *polyPoints = Spline::calculate + (QPoint(off0, 0), QPoint(off0, length - 1), pl, topLeft, bottomRight); + + int ppc = polyPoints->size(); + QPointArray qp(ppc); + /* + NOTATION_DEBUG << "bracket spline polypoints: " << endl; + for (int j = 0; j < ppc; ++j) { + NOTATION_DEBUG << (*polyPoints)[j].x() << "," << (*polyPoints)[j].y() << endl; + } + */ + + for (int j = 0; j < ppc; ++j) { + qp.setPoint(j, x + (*polyPoints)[j].x(), y + (*polyPoints)[j].y()); + } + + delete polyPoints; + + m_p->drawPolyline(qp); + + if (!left) { + ++moff; + if (i % 2) + ++off0; + } else { + --moff; + if (i % 2) + --off0; + } + } +} + +QCanvasPixmap* +NotePixmapFactory::makeTimeSigPixmap(const TimeSignature& sig) +{ + Profiler profiler("NotePixmapFactory::makeTimeSigPixmap"); + + if (sig.isCommon()) { + + NoteCharacter character; + + CharName charName; + if (sig.getNumerator() == 2) { + charName = NoteCharacterNames::CUT_TIME; + } else { + charName = NoteCharacterNames::COMMON_TIME; + } + + if (getCharacter(charName, character, PlainColour, false)) { + createPixmapAndMask(character.getWidth(), character.getHeight()); + m_p->drawNoteCharacter(0, 0, character); + return makeCanvasPixmap(QPoint(0, character.getHeight() / 2)); + } + + QString c("c"); + QRect r = m_bigTimeSigFontMetrics.boundingRect(c); + + int dy = getLineSpacing() / 4; + createPixmapAndMask(r.width(), r.height() + dy*2); + + if (m_selected) { + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + } else if (m_shaded) { + m_p->painter().setPen(Qt::gray); + } + + m_p->painter().setFont(m_bigTimeSigFont); + if (!m_inPrinterMethod) + m_p->maskPainter().setFont(m_bigTimeSigFont); + + m_p->drawText(0, r.height() + dy, c); + + if (sig.getNumerator() == 2) { // cut common + + int x = r.width() * 3 / 5 - getStemThickness(); + + for (int i = 0; i < getStemThickness() * 2; ++i, ++x) { + m_p->drawLine(x, 0, x, r.height() + dy*2 - 1); + } + } + + m_p->painter().setPen(Qt::black); + return makeCanvasPixmap(QPoint(0, r.height() / 2 + dy)); + + } else { + + int numerator = sig.getNumerator(), + denominator = sig.getDenominator(); + + QString numS, denomS; + + numS.setNum(numerator); + denomS.setNum(denominator); + + NoteCharacter character; + if (getCharacter(m_style->getTimeSignatureDigitName(0), character, + PlainColour, false)) { + + // if the 0 digit exists, we assume 1-9 also all exist + // and all have the same width + + int numW = character.getWidth() * numS.length(); + int denomW = character.getWidth() * denomS.length(); + + int width = std::max(numW, denomW); + int height = getLineSpacing() * 4 - getStaffLineThickness(); + + createPixmapAndMask(width, height); + + for (unsigned int i = 0; i < numS.length(); ++i) { + int x = width - (width - numW) / 2 - (i + 1) * character.getWidth(); + int y = height / 4 - (character.getHeight() / 2); + NoteCharacter charCharacter = getCharacter + (m_style->getTimeSignatureDigitName(numerator % 10), + PlainColour, false); + m_p->drawNoteCharacter(x, y, charCharacter); + numerator /= 10; + } + + for (unsigned int i = 0; i < denomS.length(); ++i) { + int x = width - (width - denomW) / 2 - (i + 1) * character.getWidth(); + int y = height - height / 4 - (character.getHeight() / 2); + NoteCharacter charCharacter = getCharacter + (m_style->getTimeSignatureDigitName(denominator % 10), + PlainColour, false); + m_p->drawNoteCharacter(x, y, charCharacter); + denominator /= 10; + } + + return makeCanvasPixmap(QPoint(0, height / 2)); + } + + QRect numR = m_timeSigFontMetrics.boundingRect(numS); + QRect denomR = m_timeSigFontMetrics.boundingRect(denomS); + int width = std::max(numR.width(), denomR.width()) + 2; + int x; + + createPixmapAndMask(width, denomR.height() * 2 + getNoteBodyHeight()); + + if (m_selected) { + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + } else if (m_shaded) { + m_p->painter().setPen(Qt::gray); + } + + m_p->painter().setFont(m_timeSigFont); + if (!m_inPrinterMethod) + m_p->maskPainter().setFont(m_timeSigFont); + + x = (width - numR.width()) / 2 - 1; + m_p->drawText(x, denomR.height(), numS); + + x = (width - denomR.width()) / 2 - 1; + m_p->drawText(x, denomR.height() * 2 + (getNoteBodyHeight() / 2) - 1, denomS); + + m_p->painter().setPen(Qt::black); + + return makeCanvasPixmap(QPoint(0, denomR.height() + + (getNoteBodyHeight() / 4) - 1), + true); + } +} + +int NotePixmapFactory::getTimeSigWidth(const TimeSignature &sig) const +{ + if (sig.isCommon()) { + + QRect r(m_bigTimeSigFontMetrics.boundingRect("c")); + return r.width() + 2; + + } else { + + int numerator = sig.getNumerator(), + denominator = sig.getDenominator(); + + QString numS, denomS; + + numS.setNum(numerator); + denomS.setNum(denominator); + + QRect numR = m_timeSigFontMetrics.boundingRect(numS); + QRect denomR = m_timeSigFontMetrics.boundingRect(denomS); + int width = std::max(numR.width(), denomR.width()) + 2; + + return width; + } +} + +QFont +NotePixmapFactory::getTextFont(const Text &text) const +{ + std::string type(text.getTextType()); + TextFontCache::iterator i = m_textFontCache.find(type.c_str()); + if (i != m_textFontCache.end()) + return i->second; + + /* + * Text types: + * + * UnspecifiedType: Nothing known, use small roman + * StaffName: Large roman, to left of start of staff + * ChordName: Not normally shown in score, use small roman + * KeyName: Not normally shown in score, use small roman + * Lyric: Small roman, below staff and dynamic texts + * Chord: Small bold roman, above staff + * Dynamic: Small italic, below staff + * Direction: Large roman, above staff (by barline?) + * LocalDirection: Small bold italic, below staff (by barline?) + * Tempo: Large bold roman, above staff + * LocalTempo: Small bold roman, above staff + * Annotation: Very small sans-serif, in a yellow box + * LilyPondDirective: Very small sans-serif, in a green box + */ + + int weight = QFont::Normal; + bool italic = false; + bool large = false; + bool tiny = false; + bool serif = true; + + if (type == Text::Tempo || + type == Text::LocalTempo || + type == Text::LocalDirection || + type == Text::Chord) { + weight = QFont::Bold; + } + + if (type == Text::Dynamic || + type == Text::LocalDirection) { + italic = true; + } + + if (type == Text::StaffName || + type == Text::Direction || + type == Text::Tempo) { + large = true; + } + + if (type == Text::Annotation || + type == Text::LilyPondDirective) { + serif = false; + tiny = true; + } + + KConfig* config = kapp->config(); + + QFont textFont; + + if (serif) { + textFont = QFont(defaultSerifFontFamily); + textFont = config->readFontEntry("textfont", &textFont); + } else { + textFont = QFont(defaultSansSerifFontFamily); + textFont = config->readFontEntry("sansfont", &textFont); + } + + textFont.setStyleStrategy(QFont::StyleStrategy(QFont::PreferDefault | + QFont::PreferMatch)); + + int size; + if (large) + size = (getLineSpacing() * 7) / 2; + else if (tiny) + size = (getLineSpacing() * 4) / 3; + else if (serif) + size = (getLineSpacing() * 2); + else + size = (getLineSpacing() * 3) / 2; + + textFont.setPixelSize(size); + textFont.setStyleHint(serif ? QFont::Serif : QFont::SansSerif); + textFont.setWeight(weight); + textFont.setItalic(italic); + + NOTATION_DEBUG << "NotePixmapFactory::getTextFont: requested size " << size + << " for type " << type << endl; + + NOTATION_DEBUG << "NotePixmapFactory::getTextFont: returning font '" + << textFont.toString() << "' for type " << type.c_str() + << " text : " << text.getText().c_str() << endl; + + m_textFontCache[type.c_str()] = textFont; + return textFont; +} + +QCanvasPixmap* +NotePixmapFactory::makeTextPixmap(const Text &text) +{ + Profiler profiler("NotePixmapFactory::makeTextPixmap"); + + std::string type(text.getTextType()); + + if (type == Text::Annotation || + type == Text::LilyPondDirective) { + return makeAnnotationPixmap(text, (type == Text::LilyPondDirective)); + } + + drawTextAux(text, 0, 0, 0); + return makeCanvasPixmap(QPoint(2, 2), true); +} + +QCanvasPixmap* +NotePixmapFactory::makeGuitarChordPixmap(const Guitar::Fingering &fingering, + int x, + int y) +{ + using namespace Guitar; + Profiler profiler("NotePixmapFactory::makeGuitarChordPixmap"); + + int guitarChordWidth = getLineSpacing() * 6; + int guitarChordHeight = getLineSpacing() * 6; + + createPixmapAndMask(guitarChordWidth, guitarChordHeight); + + if (m_selected) { + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + m_p->painter().setBrush(GUIPalette::getColour(GUIPalette::SelectedElement)); + } else { + m_p->painter().setPen(Qt::black); + m_p->painter().setBrush(Qt::black); + } + + Guitar::NoteSymbols ns(Guitar::Fingering::DEFAULT_NB_STRINGS, FingeringBox::DEFAULT_NB_DISPLAYED_FRETS); + Guitar::NoteSymbols::drawFingeringPixmap(fingering, ns, &(m_p->painter())); + + return makeCanvasPixmap(QPoint (x, y), true); +} + +void +NotePixmapFactory::drawText(const Text &text, + QPainter &painter, int x, int y) +{ + Profiler profiler("NotePixmapFactory::drawText"); + + // NOTATION_DEBUG << "NotePixmapFactory::drawText() " << text.getText().c_str() + // << " - type : " << text.getTextType().c_str() << endl; + + std::string type(text.getTextType()); + + if (type == Text::Annotation || + type == Text::LilyPondDirective) { + QCanvasPixmap *map = makeAnnotationPixmap(text, (type == Text::LilyPondDirective)); + painter.drawPixmap(x, y, *map); + return ; + } + + m_inPrinterMethod = true; + drawTextAux(text, &painter, x, y); + m_inPrinterMethod = false; +} + +void +NotePixmapFactory::drawTextAux(const Text &text, + QPainter *painter, int x, int y) +{ + QString s(strtoqstr(text.getText())); + QFont textFont(getTextFont(text)); + QFontMetrics textMetrics(textFont); + + int offset = 2; + int width = textMetrics.width(s) + 2 * offset; + int height = textMetrics.height() + 2 * offset; + + if (painter) { + painter->save(); + m_p->beginExternal(painter); + painter->translate(x - offset, y - offset); + } else { + createPixmapAndMask(width, height); + } + + if (m_selected) + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + else if (m_shaded) + m_p->painter().setPen(Qt::gray); + + m_p->painter().setFont(textFont); + if (!m_inPrinterMethod) + m_p->maskPainter().setFont(textFont); + + m_p->drawText(offset, textMetrics.ascent() + offset, s); + + m_p->painter().setPen(Qt::black); + + if (painter) { + painter->restore(); + } +} + +QCanvasPixmap* +NotePixmapFactory::makeAnnotationPixmap(const Text &text) +{ + return makeAnnotationPixmap(text, false); +} + +QCanvasPixmap* +NotePixmapFactory::makeAnnotationPixmap(const Text &text, const bool isLilyPondDirective) +{ + QString s(strtoqstr(text.getText())); + + QFont textFont(getTextFont(text)); + QFontMetrics textMetrics(textFont); + + int annotationWidth = getLineSpacing() * 16; + int annotationHeight = getLineSpacing() * 6; + + int topGap = getLineSpacing() / 4 + 1; + int bottomGap = getLineSpacing() / 3 + 1; + int sideGap = getLineSpacing() / 4 + 1; + + QRect r = textMetrics.boundingRect + (0, 0, annotationWidth, annotationHeight, Qt::WordBreak, s); + + int pixmapWidth = r.width() + sideGap * 2; + int pixmapHeight = r.height() + topGap + bottomGap; + + createPixmapAndMask(pixmapWidth, pixmapHeight); + + if (m_selected) + m_p->painter().setPen(GUIPalette::getColour(GUIPalette::SelectedElement)); + else if (m_shaded) + m_p->painter().setPen(Qt::gray); + + m_p->painter().setFont(textFont); + if (!m_inPrinterMethod) + m_p->maskPainter().setFont(textFont); + + if (isLilyPondDirective) { + m_p->painter().setBrush(GUIPalette::getColour(GUIPalette::TextLilyPondDirectiveBackground)); + } else { + m_p->painter().setBrush(GUIPalette::getColour(GUIPalette::TextAnnotationBackground)); + } + + m_p->drawRect(0, 0, pixmapWidth, pixmapHeight); + + m_p->painter().setBrush(Qt::black); + m_p->painter().drawText(QRect(sideGap, topGap, + annotationWidth + sideGap, + pixmapHeight - bottomGap), + Qt::WordBreak, s); + + /* unnecessary following the rectangle draw + m_pm.drawText(QRect(sideGap, topGap, + annotationWidth + sideGap, annotationHeight + topGap), + Qt::WordBreak, s); + */ + + return makeCanvasPixmap(QPoint(0, 0)); +} + +void +NotePixmapFactory::createPixmapAndMask(int width, int height, + int maskWidth, int maskHeight) +{ + if (maskWidth < 0) + maskWidth = width; + if (maskHeight < 0) + maskHeight = height; + + m_generatedWidth = width; + m_generatedHeight = height; + m_generatedPixmap = new QPixmap(width, height); + m_generatedMask = new QBitmap(maskWidth, maskHeight); + + static unsigned long total = 0; + total += width * height; +// NOTATION_DEBUG << "createPixmapAndMask: " << width << "x" << height << " (" << (width*height) << " px, " << total << " total)" << endl; + + // clear up pixmap and mask + m_generatedPixmap->fill(); + m_generatedMask->fill(Qt::color0); + + // initiate painting + m_p->begin(m_generatedPixmap, m_generatedMask); + + m_p->painter().setPen(Qt::black); + m_p->painter().setBrush(Qt::black); + m_p->maskPainter().setPen(Qt::white); + m_p->maskPainter().setBrush(Qt::white); +} + +QCanvasPixmap* +NotePixmapFactory::makeCanvasPixmap(QPoint hotspot, bool generateMask) +{ + m_p->end(); + + QCanvasPixmap* p = new QCanvasPixmap(*m_generatedPixmap, hotspot); + + if (generateMask) { + p->setMask(PixmapFunctions::generateMask(*p)); + } else { + p->setMask(*m_generatedMask); + } + + delete m_generatedPixmap; + delete m_generatedMask; + return p; +} + +NoteCharacter +NotePixmapFactory::getCharacter(CharName name, ColourType type, bool inverted) +{ + NoteCharacter ch; + getCharacter(name, ch, type, inverted); + return ch; +} + +bool +NotePixmapFactory::getCharacter(CharName name, NoteCharacter &ch, + ColourType type, bool inverted) +{ + NoteFont::CharacterType charType = + m_inPrinterMethod ? NoteFont::Printer : NoteFont::Screen; + + if (m_selected) { + return m_font->getCharacterColoured + (name, + GUIPalette::SelectedElementHue, + GUIPalette::SelectedElementMinValue, + ch, charType, inverted); + } + + if (m_shaded) { + return m_font->getCharacterShaded(name, ch, charType, inverted); + } + + switch (type) { + + case PlainColour: + return m_font->getCharacter(name, ch, charType, inverted); + + case QuantizedColour: + return m_font->getCharacterColoured + (name, + GUIPalette::QuantizedNoteHue, + GUIPalette::QuantizedNoteMinValue, + ch, charType, inverted); + + case HighlightedColour: + return m_font->getCharacterColoured + (name, + GUIPalette::HighlightedElementHue, + GUIPalette::HighlightedElementMinValue, + ch, charType, inverted); + + case TriggerColour: + return m_font->getCharacterColoured + (name, + GUIPalette::TriggerNoteHue, + GUIPalette::TriggerNoteMinValue, + ch, charType, inverted); + + case OutRangeColour: + return m_font->getCharacterColoured + (name, + GUIPalette::OutRangeNoteHue, + GUIPalette::OutRangeNoteMinValue, + ch, charType, inverted); + } + + return m_font->getCharacter(name, ch, charType, inverted); +} + +QPoint +NotePixmapFactory::m_pointZero; + + +int NotePixmapFactory::getNoteBodyWidth(Note::Type type) +const +{ + CharName charName(m_style->getNoteHeadCharName(type).first); + int hx, hy; + if (!m_font->getHotspot(charName, hx, hy)) + hx = 0; + return m_font->getWidth(charName) - hx * 2; +} + +int NotePixmapFactory::getNoteBodyHeight(Note::Type ) +const +{ + // this is by definition + return m_font->getSize(); +} + +int NotePixmapFactory::getLineSpacing() const +{ + return m_font->getSize() + getStaffLineThickness(); +} + +int NotePixmapFactory::getAccidentalWidth(const Accidental &a, + int shift, bool extraShift) const +{ + if (a == Accidentals::NoAccidental) + return 0; + int w = m_font->getWidth(m_style->getAccidentalCharName(a)); + if (!shift) + return w; + else { + int sw = w; + if (extraShift) { + --shift; + w += getNoteBodyWidth() + getStemThickness(); + } + w += shift * + (sw - m_font->getHotspot(m_style->getAccidentalCharName(a)).x()); + } + return w; +} + +int NotePixmapFactory::getAccidentalHeight(const Accidental &a) const +{ + return m_font->getHeight(m_style->getAccidentalCharName(a)); +} + +int NotePixmapFactory::getStemLength() const +{ + unsigned int l = 1; + (void)m_font->getStemLength(l); + return l; +} + +int NotePixmapFactory::getStemThickness() const +{ + unsigned int i = 1; + (void)m_font->getStemThickness(i); + return i; +} + +int NotePixmapFactory::getStaffLineThickness() const +{ + unsigned int i; + (void)m_font->getStaffLineThickness(i); + return i; +} + +int NotePixmapFactory::getLegerLineThickness() const +{ + unsigned int i; + (void)m_font->getLegerLineThickness(i); + return i; +} + +int NotePixmapFactory::getDotWidth() const +{ + return m_font->getWidth(NoteCharacterNames::DOT); +} + +int NotePixmapFactory::getClefWidth(const Clef &clef) const +{ + return m_font->getWidth(m_style->getClefCharName(clef.getClefType())); +} + +int NotePixmapFactory::getBarMargin() const +{ + return getNoteBodyWidth() * 2; +} + +int NotePixmapFactory::getRestWidth(const Note &restType) const +{ + return m_font->getWidth(m_style->getRestCharName(restType.getNoteType(), + false)) // small inaccuracy! + + (restType.getDots() * getDotWidth()); +} + +int NotePixmapFactory::getKeyWidth(const Key &key, + Key previousKey) const +{ + std::vector<int> ah0 = previousKey.getAccidentalHeights(Clef()); + std::vector<int> ah1 = key.getAccidentalHeights(Clef()); + + int cancelCount = 0; + if (key.isSharp() != previousKey.isSharp()) + cancelCount = ah0.size(); + else if (ah1.size() < ah0.size()) + cancelCount = ah0.size() - ah1.size(); + + CharName keyCharName; + if (key.isSharp()) + keyCharName = NoteCharacterNames::SHARP; + else + keyCharName = NoteCharacterNames::FLAT; + + NoteCharacter keyCharacter; + NoteCharacter cancelCharacter; + + keyCharacter = m_font->getCharacter(keyCharName); + if (cancelCount > 0) { + cancelCharacter = m_font->getCharacter(NoteCharacterNames::NATURAL); + } + + //int x = 0; + //int lw = getLineSpacing(); + int keyDelta = keyCharacter.getWidth() - keyCharacter.getHotspot().x(); + + int cancelDelta = 0; + int between = 0; + if (cancelCount > 0) { + cancelDelta = cancelCharacter.getWidth() + cancelCharacter.getWidth() / 3; + between = cancelCharacter.getWidth(); + } + + return (keyDelta * ah1.size() + cancelDelta * cancelCount + between + + keyCharacter.getWidth() / 4); +} + +int NotePixmapFactory::getTextWidth(const Text &text) const +{ + QFontMetrics metrics(getTextFont(text)); + return metrics.boundingRect(strtoqstr(text.getText())).width() + 4; +} + +} |