You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2111 lines
70 KiB
2111 lines
70 KiB
/* -*- 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 <glaurent@telegraph-road.org>,
|
|
Chris Cannam <cannam@all-day-breakfast.com>,
|
|
Richard Bown <richard.bown@ferventsoftware.com>
|
|
|
|
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 "NotationHLayout.h"
|
|
#include "misc/Debug.h"
|
|
#include <tdeapplication.h>
|
|
|
|
#include "base/Composition.h"
|
|
#include "base/LayoutEngine.h"
|
|
#include "base/NotationTypes.h"
|
|
#include "base/Profiler.h"
|
|
#include "base/NotationQuantizer.h"
|
|
#include "base/RulerScale.h"
|
|
#include "base/Segment.h"
|
|
#include "base/SegmentNotationHelper.h"
|
|
#include "base/Staff.h"
|
|
#include "base/ViewElement.h"
|
|
#include "gui/editors/guitar/Chord.h"
|
|
#include "gui/general/ProgressReporter.h"
|
|
#include "gui/widgets/ProgressDialog.h"
|
|
#include "NotationChord.h"
|
|
#include "NotationElement.h"
|
|
#include "NotationGroup.h"
|
|
#include "NotationProperties.h"
|
|
#include "NotationStaff.h"
|
|
#include "NotePixmapFactory.h"
|
|
#include <tdeconfig.h>
|
|
#include <tqobject.h>
|
|
#include <cmath>
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
using namespace BaseProperties;
|
|
|
|
|
|
NotationHLayout::NotationHLayout(Composition *c, NotePixmapFactory *npf,
|
|
const NotationProperties &properties,
|
|
TQObject* parent, const char* name) :
|
|
ProgressReporter(parent, name),
|
|
HorizontalLayoutEngine(c),
|
|
m_totalWidth(0.),
|
|
m_pageMode(false),
|
|
m_pageWidth(0.),
|
|
m_spacing(100),
|
|
m_proportion(60),
|
|
m_keySigCancelMode(1),
|
|
m_npf(npf),
|
|
m_notationQuantizer(c->getNotationQuantizer()),
|
|
m_properties(properties),
|
|
m_timePerProgressIncrement(0),
|
|
m_staffCount(0)
|
|
{
|
|
// NOTATION_DEBUG << "NotationHLayout::NotationHLayout()" << endl;
|
|
|
|
TDEConfig *config = kapp->config();
|
|
config->setGroup("Notation Options");
|
|
m_keySigCancelMode = config->readNumEntry("keysigcancelmode", 1);
|
|
}
|
|
|
|
NotationHLayout::~NotationHLayout()
|
|
{
|
|
// empty
|
|
}
|
|
|
|
std::vector<int>
|
|
NotationHLayout::getAvailableSpacings()
|
|
{
|
|
if (m_availableSpacings.size() == 0) {
|
|
m_availableSpacings.push_back(30);
|
|
m_availableSpacings.push_back(60);
|
|
m_availableSpacings.push_back(85);
|
|
m_availableSpacings.push_back(100);
|
|
m_availableSpacings.push_back(130);
|
|
m_availableSpacings.push_back(170);
|
|
m_availableSpacings.push_back(220);
|
|
}
|
|
return m_availableSpacings;
|
|
}
|
|
|
|
std::vector<int>
|
|
NotationHLayout::getAvailableProportions()
|
|
{
|
|
if (m_availableProportions.size() == 0) {
|
|
m_availableProportions.push_back(0);
|
|
m_availableProportions.push_back(20);
|
|
m_availableProportions.push_back(40);
|
|
m_availableProportions.push_back(60);
|
|
m_availableProportions.push_back(80);
|
|
m_availableProportions.push_back(100);
|
|
}
|
|
return m_availableProportions;
|
|
}
|
|
|
|
NotationHLayout::BarDataList &
|
|
|
|
NotationHLayout::getBarData(Staff &staff)
|
|
{
|
|
BarDataMap::iterator i = m_barData.find(&staff);
|
|
if (i == m_barData.end()) {
|
|
m_barData[&staff] = BarDataList();
|
|
}
|
|
|
|
return m_barData[&staff];
|
|
}
|
|
|
|
const NotationHLayout::BarDataList &
|
|
|
|
NotationHLayout::getBarData(Staff &staff) const
|
|
{
|
|
return ((NotationHLayout *)this)->getBarData(staff);
|
|
}
|
|
|
|
NotationElementList::iterator
|
|
NotationHLayout::getStartOfQuantizedSlice(NotationElementList *notes,
|
|
timeT t)
|
|
const
|
|
{
|
|
NotationElementList::iterator i = notes->findTime(t);
|
|
NotationElementList::iterator j(i);
|
|
|
|
while (true) {
|
|
if (i == notes->begin())
|
|
return i;
|
|
--j;
|
|
if ((*j)->getViewAbsoluteTime() < t)
|
|
return i;
|
|
i = j;
|
|
}
|
|
}
|
|
|
|
NotePixmapFactory *
|
|
NotationHLayout::getNotePixmapFactory(Staff &staff)
|
|
{
|
|
NotationStaff *ns = dynamic_cast<NotationStaff *>(&staff);
|
|
if (ns) return &ns->getNotePixmapFactory(false);
|
|
else return 0;
|
|
}
|
|
|
|
NotePixmapFactory *
|
|
NotationHLayout::getGraceNotePixmapFactory(Staff &staff)
|
|
{
|
|
NotationStaff *ns = dynamic_cast<NotationStaff *>(&staff);
|
|
if (ns) return &ns->getNotePixmapFactory(true);
|
|
else return 0;
|
|
}
|
|
|
|
void
|
|
NotationHLayout::scanStaff(Staff &staff, timeT startTime, timeT endTime)
|
|
{
|
|
throwIfCancelled();
|
|
Profiler profiler("NotationHLayout::scanStaff");
|
|
|
|
Segment &segment(staff.getSegment());
|
|
bool isFullScan = (startTime == endTime);
|
|
int startBarOfStaff = getComposition()->getBarNumber(segment.getStartTime());
|
|
|
|
if (isFullScan) {
|
|
clearBarList(staff);
|
|
startTime = segment.getStartTime();
|
|
endTime = segment.getEndMarkerTime();
|
|
} else {
|
|
startTime = getComposition()->getBarStartForTime(startTime);
|
|
endTime = getComposition()->getBarEndForTime(endTime);
|
|
}
|
|
|
|
NotationElementList *notes = staff.getViewElementList();
|
|
BarDataList &barList(getBarData(staff));
|
|
|
|
NotePixmapFactory *npf = getNotePixmapFactory(staff);
|
|
|
|
int startBarNo = getComposition()->getBarNumber(startTime);
|
|
int endBarNo = getComposition()->getBarNumber(endTime);
|
|
/*
|
|
if (endBarNo > startBarNo &&
|
|
getComposition()->getBarStart(endBarNo) == segment.getEndMarkerTime()) {
|
|
--endBarNo;
|
|
}
|
|
*/
|
|
std::string name =
|
|
segment.getComposition()->
|
|
getTrackById(segment.getTrack())->getLabel();
|
|
m_staffNameWidths[&staff] =
|
|
npf->getNoteBodyWidth() * 2 +
|
|
npf->getTextWidth(Text(name, Text::StaffName));
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::scanStaff: full scan " << isFullScan << ", times " << startTime << "->" << endTime << ", bars " << startBarNo << "->" << endBarNo << ", staff name \"" << segment.getLabel() << "\", width " << m_staffNameWidths[&staff] << endl;
|
|
|
|
SegmentNotationHelper helper(segment);
|
|
if (isFullScan) {
|
|
helper.setNotationProperties();
|
|
} else {
|
|
helper.setNotationProperties(startTime, endTime);
|
|
}
|
|
|
|
::Rosegarden::Key key = segment.getKeyAtTime(startTime);
|
|
Clef clef = segment.getClefAtTime(startTime);
|
|
TimeSignature timeSignature =
|
|
segment.getComposition()->getTimeSignatureAt(startTime);
|
|
bool barCorrect = true;
|
|
|
|
int ottavaShift = 0;
|
|
timeT ottavaEnd = segment.getEndMarkerTime();
|
|
|
|
if (isFullScan) {
|
|
|
|
NOTATION_DEBUG << "full scan: setting haveOttava false" << endl;
|
|
|
|
m_haveOttavaSomewhere[&staff] = false;
|
|
|
|
} else if (m_haveOttavaSomewhere[&staff]) {
|
|
|
|
NOTATION_DEBUG << "not full scan but ottava is listed" << endl;
|
|
|
|
Segment::iterator i = segment.findTime(startTime);
|
|
while (1) {
|
|
if ((*i)->isa(Indication::EventType)) {
|
|
try {
|
|
Indication indication(**i);
|
|
if (indication.isOttavaType()) {
|
|
ottavaShift = indication.getOttavaShift();
|
|
ottavaEnd = (*i)->getAbsoluteTime() +
|
|
indication.getIndicationDuration();
|
|
break;
|
|
}
|
|
} catch (...) { }
|
|
}
|
|
if (i == segment.begin())
|
|
break;
|
|
--i;
|
|
}
|
|
}
|
|
|
|
NOTATION_DEBUG << "ottava shift at start:" << ottavaShift << ", ottavaEnd " << ottavaEnd << endl;
|
|
|
|
TDEConfig *config = kapp->config();
|
|
config->setGroup("Notation Options");
|
|
|
|
int accOctaveMode = config->readNumEntry("accidentaloctavemode", 1);
|
|
AccidentalTable::OctaveType octaveType =
|
|
(accOctaveMode == 0 ? AccidentalTable::OctavesIndependent :
|
|
accOctaveMode == 1 ? AccidentalTable::OctavesCautionary :
|
|
AccidentalTable::OctavesEquivalent);
|
|
|
|
int accBarMode = config->readNumEntry("accidentalbarmode", 0);
|
|
AccidentalTable::BarResetType barResetType =
|
|
(accBarMode == 0 ? AccidentalTable::BarResetNone :
|
|
accBarMode == 1 ? AccidentalTable::BarResetCautionary :
|
|
AccidentalTable::BarResetExplicit);
|
|
|
|
bool showInvisibles = config->readBoolEntry("showinvisibles", true);
|
|
|
|
if (barResetType != AccidentalTable::BarResetNone) {
|
|
//!!! very crude and expensive way of making sure we see the
|
|
// accidentals from previous bar:
|
|
if (startBarNo > segment.getComposition()->getBarNumber(segment.getStartTime())) {
|
|
--startBarNo;
|
|
}
|
|
}
|
|
|
|
AccidentalTable accTable(key, clef, octaveType, barResetType);
|
|
|
|
for (int barNo = startBarNo; barNo <= endBarNo; ++barNo) {
|
|
|
|
std::pair<timeT, timeT> barTimes =
|
|
getComposition()->getBarRange(barNo);
|
|
|
|
if (barTimes.first >= segment.getEndMarkerTime()) {
|
|
// clear data if we have any old stuff
|
|
BarDataList::iterator i(barList.find(barNo));
|
|
if (i != barList.end()) {
|
|
barList.erase(i);
|
|
}
|
|
continue; // so as to erase any further bars next time around
|
|
}
|
|
|
|
NotationElementList::iterator from =
|
|
getStartOfQuantizedSlice(notes, barTimes.first);
|
|
|
|
NOTATION_DEBUG << "getStartOfQuantizedSlice returned " <<
|
|
(from != notes->end() ? (*from)->getViewAbsoluteTime() : -1)
|
|
<< " from " << barTimes.first << endl;
|
|
|
|
NotationElementList::iterator to =
|
|
getStartOfQuantizedSlice(notes, barTimes.second);
|
|
|
|
if (barTimes.second >= segment.getEndMarkerTime()) {
|
|
to = notes->end();
|
|
}
|
|
|
|
bool newTimeSig = false;
|
|
timeSignature = getComposition()->getTimeSignatureInBar
|
|
(barNo, newTimeSig);
|
|
NOTATION_DEBUG << "bar " << barNo << ", startBarOfStaff " << startBarOfStaff
|
|
<< ", newTimeSig " << newTimeSig << endl;
|
|
|
|
float fixedWidth = 0.0;
|
|
if (newTimeSig && !timeSignature.isHidden()) {
|
|
fixedWidth += npf->getNoteBodyWidth() +
|
|
npf->getTimeSigWidth(timeSignature);
|
|
}
|
|
|
|
setBarBasicData(staff, barNo, from, barCorrect, timeSignature, newTimeSig);
|
|
BarDataList::iterator bdli(barList.find(barNo));
|
|
bdli->second.layoutData.needsLayout = true;
|
|
|
|
ChunkList &chunks = bdli->second.chunks;
|
|
chunks.clear();
|
|
|
|
float lyricWidth = 0;
|
|
int graceCount = 0;
|
|
|
|
typedef std::set
|
|
<long> GroupIdSet;
|
|
GroupIdSet groupIds;
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::scanStaff: bar " << barNo << ", from " << barTimes.first << ", to " << barTimes.second << " (end " << segment.getEndMarkerTime() << "); from is at " << (from == notes->end() ? -1 : (*from)->getViewAbsoluteTime()) << ", to is at " << (to == notes->end() ? -1 : (*to)->getViewAbsoluteTime()) << endl;
|
|
|
|
timeT actualBarEnd = barTimes.first;
|
|
|
|
accTable.newBar();
|
|
|
|
for (NotationElementList::iterator itr = from; itr != to; ++itr) {
|
|
|
|
NotationElement *el = static_cast<NotationElement*>((*itr));
|
|
NOTATION_DEBUG << "element is a " << el->event()->getType() << endl;
|
|
|
|
if (ottavaShift != 0) {
|
|
if (el->event()->getAbsoluteTime() >= ottavaEnd) {
|
|
NOTATION_DEBUG << "reached end of ottava" << endl;
|
|
ottavaShift = 0;
|
|
}
|
|
}
|
|
|
|
bool invisible = false;
|
|
if (el->event()->get<Bool>(INVISIBLE, invisible) && invisible) {
|
|
if (!showInvisibles)
|
|
continue;
|
|
}
|
|
|
|
if (el->event()->has(BEAMED_GROUP_ID)) {
|
|
NOTATION_DEBUG << "element is beamed" << endl;
|
|
long groupId = el->event()->get<Int>(BEAMED_GROUP_ID);
|
|
if (groupIds.find(groupId) == groupIds.end()) {
|
|
NOTATION_DEBUG << "it's a new beamed group, applying stem properties" << endl;
|
|
NotationGroup group(*staff.getViewElementList(),
|
|
itr,
|
|
m_notationQuantizer,
|
|
barTimes,
|
|
m_properties,
|
|
clef, key);
|
|
group.applyStemProperties();
|
|
groupIds.insert(groupId);
|
|
}
|
|
}
|
|
|
|
if (el->event()->isa(Clef::EventType)) {
|
|
|
|
// NOTATION_DEBUG << "Found clef" << endl;
|
|
chunks.push_back(Chunk(el->event()->getSubOrdering(),
|
|
getLayoutWidth(*el, npf, key)));
|
|
|
|
clef = Clef(*el->event());
|
|
accTable.newClef(clef);
|
|
|
|
} else if (el->event()->isa(::Rosegarden::Key::EventType)) {
|
|
|
|
// NOTATION_DEBUG << "Found key" << endl;
|
|
chunks.push_back(Chunk(el->event()->getSubOrdering(),
|
|
getLayoutWidth(*el, npf, key)));
|
|
|
|
key = ::Rosegarden::Key(*el->event());
|
|
|
|
accTable = AccidentalTable
|
|
(key, clef, octaveType, barResetType);
|
|
|
|
} else if (el->event()->isa(Text::EventType)) {
|
|
|
|
// the only text events of interest are lyrics, which
|
|
// contribute to a fixed area following the next chord
|
|
|
|
if (el->event()->has(Text::TextTypePropertyName) &&
|
|
el->event()->get<String>(Text::TextTypePropertyName) ==
|
|
Text::Lyric) {
|
|
lyricWidth = std::max
|
|
(lyricWidth, float(npf->getTextWidth(Text(*el->event()))));
|
|
NOTATION_DEBUG << "Setting lyric width to " << lyricWidth
|
|
<< " for text " << el->event()->get<String>(Text::TextPropertyName) << endl;
|
|
}
|
|
chunks.push_back(Chunk(el->event()->getSubOrdering(), 0));
|
|
|
|
} else if (el->isNote()) {
|
|
|
|
NotePixmapFactory *cnpf = npf;
|
|
if (el->isGrace()) cnpf = getGraceNotePixmapFactory(staff);
|
|
|
|
scanChord(notes, itr, clef, key, accTable,
|
|
lyricWidth, chunks, cnpf, ottavaShift, to);
|
|
|
|
} else if (el->isRest()) {
|
|
|
|
chunks.push_back(Chunk(el->getViewDuration(),
|
|
el->event()->getSubOrdering(),
|
|
0,
|
|
getLayoutWidth(*el, npf, key)));
|
|
|
|
} else if (el->event()->isa(Indication::EventType)) {
|
|
|
|
// NOTATION_DEBUG << "Found indication" << endl;
|
|
|
|
chunks.push_back(Chunk(el->event()->getSubOrdering(), 0));
|
|
|
|
try {
|
|
Indication indication(*el->event());
|
|
if (indication.isOttavaType()) {
|
|
ottavaShift = indication.getOttavaShift();
|
|
ottavaEnd = el->event()->getAbsoluteTime() +
|
|
indication.getIndicationDuration();
|
|
m_haveOttavaSomewhere[&staff] = true;
|
|
}
|
|
} catch (...) {
|
|
NOTATION_DEBUG << "Bad indication!" << endl;
|
|
}
|
|
|
|
} else {
|
|
|
|
// NOTATION_DEBUG << "Found something I don't know about (type is " << el->event()->getType() << ")" << endl;
|
|
chunks.push_back(Chunk(el->event()->getSubOrdering(),
|
|
getLayoutWidth(*el, npf, key)));
|
|
}
|
|
|
|
actualBarEnd = el->getViewAbsoluteTime() + el->getViewDuration();
|
|
}
|
|
|
|
if (actualBarEnd == barTimes.first)
|
|
actualBarEnd = barTimes.second;
|
|
barCorrect = (actualBarEnd == barTimes.second);
|
|
|
|
setBarSizeData(staff, barNo, fixedWidth,
|
|
actualBarEnd - barTimes.first);
|
|
|
|
if ((endTime > startTime) && (barNo % 20 == 0)) {
|
|
emit setProgress((barTimes.second - startTime) * 95 /
|
|
(endTime - startTime));
|
|
ProgressDialog::processEvents();
|
|
}
|
|
|
|
throwIfCancelled();
|
|
}
|
|
/*
|
|
BarDataList::iterator ei(barList.end());
|
|
while (ei != barList.begin() && (--ei)->first > endBarNo) {
|
|
barList.erase(ei);
|
|
ei = barList.end();
|
|
}
|
|
*/
|
|
}
|
|
|
|
void
|
|
NotationHLayout::clearBarList(Staff &staff)
|
|
{
|
|
BarDataList &bdl = m_barData[&staff];
|
|
bdl.clear();
|
|
}
|
|
|
|
void
|
|
NotationHLayout::setBarBasicData(Staff &staff,
|
|
int barNo,
|
|
NotationElementList::iterator start,
|
|
bool correct,
|
|
TimeSignature timeSig,
|
|
bool newTimeSig)
|
|
{
|
|
// NOTATION_DEBUG << "setBarBasicData for " << barNo << endl;
|
|
|
|
BarDataList &bdl(m_barData[&staff]);
|
|
|
|
BarDataList::iterator i(bdl.find(barNo));
|
|
if (i == bdl.end()) {
|
|
NotationElementList::iterator endi = staff.getViewElementList()->end();
|
|
bdl.insert(BarDataPair(barNo, BarData(endi, true,
|
|
TimeSignature(), false)));
|
|
i = bdl.find(barNo);
|
|
}
|
|
|
|
i->second.basicData.start = start;
|
|
i->second.basicData.correct = correct;
|
|
i->second.basicData.timeSignature = timeSig;
|
|
i->second.basicData.newTimeSig = newTimeSig;
|
|
}
|
|
|
|
void
|
|
NotationHLayout::setBarSizeData(Staff &staff,
|
|
int barNo,
|
|
float fixedWidth,
|
|
timeT actualDuration)
|
|
{
|
|
// NOTATION_DEBUG << "setBarSizeData for " << barNo << endl;
|
|
|
|
BarDataList &bdl(m_barData[&staff]);
|
|
|
|
BarDataList::iterator i(bdl.find(barNo));
|
|
if (i == bdl.end()) {
|
|
NotationElementList::iterator endi = staff.getViewElementList()->end();
|
|
bdl.insert(BarDataPair(barNo, BarData(endi, true,
|
|
TimeSignature(), false)));
|
|
i = bdl.find(barNo);
|
|
}
|
|
|
|
i->second.sizeData.actualDuration = actualDuration;
|
|
i->second.sizeData.idealWidth = 0.0;
|
|
i->second.sizeData.reconciledWidth = 0.0;
|
|
i->second.sizeData.clefKeyWidth = 0;
|
|
i->second.sizeData.fixedWidth = fixedWidth;
|
|
}
|
|
|
|
void
|
|
NotationHLayout::scanChord(NotationElementList *notes,
|
|
NotationElementList::iterator &itr,
|
|
const Clef &clef,
|
|
const ::Rosegarden::Key &key,
|
|
AccidentalTable &accTable,
|
|
float &lyricWidth,
|
|
ChunkList &chunks,
|
|
NotePixmapFactory *npf,
|
|
int ottavaShift,
|
|
NotationElementList::iterator &to)
|
|
{
|
|
NotationChord chord(*notes, itr, m_notationQuantizer, m_properties);
|
|
Accidental someAccidental = Accidentals::NoAccidental;
|
|
bool someCautionary = false;
|
|
bool barEndsInChord = false;
|
|
bool grace = false;
|
|
|
|
// std::cerr << "NotationHLayout::scanChord: "
|
|
// << chord.size() << "-voice chord at "
|
|
// << (*itr)->event()->getAbsoluteTime()
|
|
// << " unquantized, "
|
|
// << (*itr)->getViewAbsoluteTime()
|
|
// << " quantized" << std::endl;
|
|
|
|
// NOTATION_DEBUG << "Contents:" << endl;
|
|
|
|
/*
|
|
for (NotationElementList::iterator i = chord.getInitialElement();
|
|
i != notes->end(); ++i) {
|
|
(*i)->event()->dump(std::cerr);
|
|
if (i == chord.getFinalElement()) break;
|
|
}
|
|
*/
|
|
// We don't need to get the chord's notes in pitch order here,
|
|
// but we do need to ensure we see any random non-note events
|
|
// that may crop up in the middle of it.
|
|
|
|
for (NotationElementList::iterator i = chord.getInitialElement();
|
|
i != notes->end(); ++i) {
|
|
|
|
NotationElement *el = static_cast<NotationElement*>(*i);
|
|
if (el->isRest()) {
|
|
el->event()->setMaybe<Bool>(m_properties.REST_TOO_SHORT, true);
|
|
if (i == chord.getFinalElement())
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
if (el->isGrace()) {
|
|
grace = true;
|
|
}
|
|
|
|
long pitch = 64;
|
|
if (!el->event()->get<Int>(PITCH, pitch)) {
|
|
NOTATION_DEBUG <<
|
|
"WARNING: NotationHLayout::scanChord: couldn't get pitch for element, using default pitch of " << pitch << endl;
|
|
}
|
|
|
|
Accidental explicitAccidental = Accidentals::NoAccidental;
|
|
(void)el->event()->get<String>(ACCIDENTAL, explicitAccidental);
|
|
|
|
Pitch p(pitch, explicitAccidental);
|
|
int h = p.getHeightOnStaff(clef, key);
|
|
Accidental acc = p.getDisplayAccidental(key);
|
|
|
|
h -= 7 * ottavaShift;
|
|
|
|
el->event()->setMaybe<Int>(NotationProperties::OTTAVA_SHIFT, ottavaShift);
|
|
el->event()->setMaybe<Int>(NotationProperties::HEIGHT_ON_STAFF, h);
|
|
el->event()->setMaybe<String>(m_properties.CALCULATED_ACCIDENTAL, acc);
|
|
|
|
// update display acc for note according to the accTable
|
|
// (accidentals in force when the last chord ended) and tell
|
|
// accTable about accidentals from this note.
|
|
|
|
bool cautionary = false;
|
|
if (el->event()->has(m_properties.USE_CAUTIONARY_ACCIDENTAL)) {
|
|
cautionary = el->event()->get<Bool>(m_properties.USE_CAUTIONARY_ACCIDENTAL);
|
|
}
|
|
Accidental dacc = accTable.processDisplayAccidental(acc, h, cautionary);
|
|
el->event()->setMaybe<String>(m_properties.DISPLAY_ACCIDENTAL, dacc);
|
|
el->event()->setMaybe<Bool>(m_properties.DISPLAY_ACCIDENTAL_IS_CAUTIONARY,
|
|
cautionary);
|
|
if (cautionary) {
|
|
someCautionary = true;
|
|
}
|
|
|
|
if (someAccidental == Accidentals::NoAccidental)
|
|
someAccidental = dacc;
|
|
|
|
if (i == to)
|
|
barEndsInChord = true;
|
|
|
|
if (i == chord.getFinalElement())
|
|
break;
|
|
}
|
|
|
|
// tell accTable the chord has ended, so to bring its accidentals
|
|
// into force for future chords
|
|
accTable.update();
|
|
|
|
chord.applyAccidentalShiftProperties();
|
|
|
|
float extraWidth = 0;
|
|
|
|
if (someAccidental != Accidentals::NoAccidental) {
|
|
bool extraShift = false;
|
|
int shift = chord.getMaxAccidentalShift(extraShift);
|
|
int e = npf->getAccidentalWidth(someAccidental, shift, extraShift);
|
|
if (someAccidental != Accidentals::Sharp) {
|
|
e = std::max(e, npf->getAccidentalWidth(Accidentals::Sharp, shift, extraShift));
|
|
}
|
|
if (someCautionary) {
|
|
e += npf->getNoteBodyWidth();
|
|
}
|
|
extraWidth += e;
|
|
}
|
|
|
|
float layoutExtra = 0;
|
|
if (chord.hasNoteHeadShifted()) {
|
|
if (chord.hasStemUp()) {
|
|
layoutExtra += npf->getNoteBodyWidth();
|
|
} else {
|
|
extraWidth = std::max(extraWidth, float(npf->getNoteBodyWidth()));
|
|
}
|
|
}
|
|
/*!!!
|
|
if (grace) {
|
|
std::cerr << "Grace note: subordering " << chord.getSubOrdering() << std::endl;
|
|
chunks.push_back(Chunk(-10 + graceCount,
|
|
extraWidth + npf->getNoteBodyWidth()));
|
|
if (graceCount < 9) ++graceCount;
|
|
return;
|
|
} else {
|
|
std::cerr << "Non-grace note (grace count was " << graceCount << ")" << std::endl;
|
|
graceCount = 0;
|
|
}
|
|
*/
|
|
NotationElementList::iterator myLongest = chord.getLongestElement();
|
|
if (myLongest == notes->end()) {
|
|
NOTATION_DEBUG << "WARNING: NotationHLayout::scanChord: No longest element in chord!" << endl;
|
|
}
|
|
|
|
timeT d = (*myLongest)->getViewDuration();
|
|
|
|
NOTATION_DEBUG << "Lyric width is " << lyricWidth << endl;
|
|
|
|
if (grace) {
|
|
chunks.push_back(Chunk(d, chord.getSubOrdering(),
|
|
extraWidth + layoutExtra
|
|
+ getLayoutWidth(**myLongest, npf, key)
|
|
- npf->getNoteBodyWidth(), // tighten up
|
|
0));
|
|
} else {
|
|
chunks.push_back(Chunk(d, 0, extraWidth,
|
|
std::max(layoutExtra +
|
|
getLayoutWidth(**myLongest, npf, key),
|
|
lyricWidth)));
|
|
lyricWidth = 0;
|
|
}
|
|
|
|
itr = chord.getFinalElement();
|
|
if (barEndsInChord) {
|
|
to = itr;
|
|
++to;
|
|
}
|
|
}
|
|
|
|
struct ChunkLocation {
|
|
timeT time;
|
|
short subordering;
|
|
ChunkLocation(timeT t, short s) : time(t), subordering(s) { }
|
|
};
|
|
|
|
bool operator<(const ChunkLocation &l0, const ChunkLocation &l1) {
|
|
return ((l0.time < l1.time) ||
|
|
((l0.time == l1.time) && (l0.subordering < l1.subordering)));
|
|
}
|
|
|
|
|
|
|
|
void
|
|
NotationHLayout::preSquishBar(int barNo)
|
|
{
|
|
typedef std::vector<Chunk *> ChunkRefList;
|
|
typedef std::map<ChunkLocation, ChunkRefList> ColumnMap;
|
|
static ColumnMap columns;
|
|
bool haveSomething = false;
|
|
|
|
columns.clear();
|
|
|
|
for (BarDataMap::iterator mi = m_barData.begin();
|
|
mi != m_barData.end(); ++mi) {
|
|
|
|
BarDataList &bdl = mi->second;
|
|
BarDataList::iterator bdli = bdl.find(barNo);
|
|
|
|
if (bdli != bdl.end()) {
|
|
|
|
haveSomething = true;
|
|
ChunkList &cl(bdli->second.chunks);
|
|
timeT aggregateTime = 0;
|
|
|
|
for (ChunkList::iterator cli = cl.begin(); cli != cl.end(); ++cli) {
|
|
|
|
// Subordering is typically zero for notes, positive
|
|
// for rests and negative for other stuff. We want to
|
|
// handle notes and rests together, but not the others.
|
|
|
|
int subordering = cli->subordering;
|
|
if (subordering > 0)
|
|
subordering = 0;
|
|
|
|
columns[ChunkLocation(aggregateTime, subordering)].push_back(&(*cli));
|
|
|
|
aggregateTime += cli->duration;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!haveSomething)
|
|
return ;
|
|
|
|
// now modify chunks in-place
|
|
|
|
// What we want to do here is idle along the whole set of chunk
|
|
// lists, inspecting all the chunks that occur at each moment in
|
|
// turn and choosing a "rate" from the "slowest" of these
|
|
// (i.e. most space per time)
|
|
|
|
float x = 0.0;
|
|
timeT prevTime = 0;
|
|
double prevRate = 0.0;
|
|
float maxStretchy = 0.0;
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::preSquishBar(" << barNo << "): have "
|
|
<< columns.size() << " columns" << endl;
|
|
|
|
for (ColumnMap::iterator i = columns.begin(); i != columns.end(); ++i) {
|
|
|
|
timeT time = i->first.time;
|
|
ChunkRefList &list = i->second;
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::preSquishBar: "
|
|
<< "column at " << time << " : " << i->first.subordering << endl;
|
|
|
|
|
|
double minRate = -1.0;
|
|
float totalFixed = 0.0;
|
|
maxStretchy = 0.0;
|
|
|
|
for (ChunkRefList::iterator j = list.begin(); j != list.end(); ++j) {
|
|
if ((*j)->stretchy > 0.0) {
|
|
double rate = (*j)->duration / (*j)->stretchy; // time per px
|
|
NOTATION_DEBUG << "NotationHLayout::preSquishBar: rate " << rate << endl;
|
|
if (minRate < 0.0 || rate < minRate)
|
|
minRate = rate;
|
|
} else {
|
|
NOTATION_DEBUG << "NotationHLayout::preSquishBar: not stretchy" << endl;
|
|
}
|
|
|
|
maxStretchy = std::max(maxStretchy, (*j)->stretchy);
|
|
totalFixed = std::max(totalFixed, (*j)->fixed);
|
|
}
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::preSquishBar: minRate " << minRate << ", maxStretchy " << maxStretchy << ", totalFixed " << totalFixed << endl;
|
|
|
|
// we have the rate from this point, but we want to assign
|
|
// these elements an x coord based on the rate and distance
|
|
// from the previous point, plus fixed space for this point
|
|
// if it's a note (otherwise fixed space goes afterwards)
|
|
|
|
if (i->first.subordering == 0)
|
|
x += totalFixed;
|
|
if (prevRate > 0.0)
|
|
x += (time - prevTime) / prevRate;
|
|
|
|
for (ChunkRefList::iterator j = list.begin(); j != list.end(); ++j) {
|
|
NOTATION_DEBUG << "Setting x for time " << time << " to " << x << " in chunk at " << *j << endl;
|
|
(*j)->x = x;
|
|
}
|
|
|
|
if (i->first.subordering != 0)
|
|
x += totalFixed;
|
|
|
|
prevTime = time;
|
|
prevRate = minRate;
|
|
}
|
|
|
|
x += maxStretchy;
|
|
|
|
for (BarDataMap::iterator mi = m_barData.begin();
|
|
mi != m_barData.end(); ++mi) {
|
|
|
|
BarDataList &bdl = mi->second;
|
|
BarDataList::iterator bdli = bdl.find(barNo);
|
|
if (bdli != bdl.end()) {
|
|
|
|
bdli->second.sizeData.idealWidth =
|
|
bdli->second.sizeData.fixedWidth + x;
|
|
|
|
if (!bdli->second.basicData.timeSignature.hasHiddenBars()) {
|
|
bdli->second.sizeData.idealWidth += getBarMargin();
|
|
} else if (bdli->second.basicData.newTimeSig) {
|
|
bdli->second.sizeData.idealWidth += getPostBarMargin();
|
|
}
|
|
|
|
bdli->second.sizeData.reconciledWidth =
|
|
bdli->second.sizeData.idealWidth;
|
|
|
|
bdli->second.layoutData.needsLayout = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
Staff *
|
|
NotationHLayout::getStaffWithWidestBar(int barNo)
|
|
{
|
|
float maxWidth = -1;
|
|
Staff *widest = 0;
|
|
|
|
for (BarDataMap::iterator mi = m_barData.begin();
|
|
mi != m_barData.end(); ++mi) {
|
|
|
|
BarDataList &list = mi->second;
|
|
BarDataList::iterator li = list.find(barNo);
|
|
if (li != list.end()) {
|
|
|
|
NOTATION_DEBUG << "getStaffWithWidestBar: idealWidth is " << li->second.sizeData.idealWidth << endl;
|
|
|
|
if (li->second.sizeData.idealWidth == 0.0) {
|
|
NOTATION_DEBUG << "getStaffWithWidestBar(" << barNo << "): found idealWidth of zero, presquishing" << endl;
|
|
preSquishBar(barNo);
|
|
}
|
|
|
|
if (li->second.sizeData.idealWidth > maxWidth) {
|
|
maxWidth = li->second.sizeData.idealWidth;
|
|
widest = mi->first;
|
|
}
|
|
}
|
|
}
|
|
|
|
return widest;
|
|
}
|
|
|
|
int
|
|
NotationHLayout::getMaxRepeatedClefAndKeyWidth(int barNo)
|
|
{
|
|
int max = 0;
|
|
|
|
timeT barStart = 0;
|
|
|
|
for (BarDataMap::iterator mi = m_barData.begin();
|
|
mi != m_barData.end(); ++mi) {
|
|
|
|
Staff *staff = mi->first;
|
|
if (mi == m_barData.begin()) {
|
|
barStart = staff->getSegment().getComposition()->getBarStart(barNo);
|
|
}
|
|
|
|
timeT t;
|
|
int w = 0;
|
|
|
|
Clef clef = staff->getSegment().getClefAtTime(barStart, t);
|
|
if (t < barStart)
|
|
w += m_npf->getClefWidth(clef);
|
|
|
|
::Rosegarden::Key key = staff->getSegment().getKeyAtTime(barStart, t);
|
|
if (t < barStart)
|
|
w += m_npf->getKeyWidth(key);
|
|
|
|
if (w > max)
|
|
max = w;
|
|
}
|
|
|
|
NOTATION_DEBUG << "getMaxRepeatedClefAndKeyWidth(" << barNo << "): " << max
|
|
<< endl;
|
|
|
|
if (max > 0)
|
|
return max + getFixedItemSpacing() * 2;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
NotationHLayout::reconcileBarsLinear()
|
|
{
|
|
Profiler profiler("NotationHLayout::reconcileBarsLinear");
|
|
|
|
// Ensure that concurrent bars on all staffs have the same width,
|
|
// which for now we make the maximum width required for this bar
|
|
// on any staff. These days getStaffWithWidestBar actually does
|
|
// most of the work in its call to preSquishBar, but this function
|
|
// still sets the bar line positions etc.
|
|
|
|
int barNo = getFirstVisibleBar();
|
|
|
|
m_totalWidth = 0.0;
|
|
for (StaffIntMap::iterator i = m_staffNameWidths.begin();
|
|
i != m_staffNameWidths.end(); ++i) {
|
|
if (i->second > m_totalWidth)
|
|
m_totalWidth = double(i->second);
|
|
}
|
|
|
|
for (;;) {
|
|
|
|
Staff *widest = getStaffWithWidestBar(barNo);
|
|
|
|
if (!widest) {
|
|
// have we reached the end of the piece?
|
|
if (barNo >= getLastVisibleBar()) { // yes
|
|
break;
|
|
} else {
|
|
m_totalWidth += m_spacing / 3;
|
|
NOTATION_DEBUG << "Setting bar position for degenerate bar "
|
|
<< barNo << " to " << m_totalWidth << endl;
|
|
|
|
m_barPositions[barNo] = m_totalWidth;
|
|
++barNo;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
float maxWidth = m_barData[widest].find(barNo)->second.sizeData.idealWidth;
|
|
if (m_pageWidth > 0.1 && maxWidth > m_pageWidth) {
|
|
maxWidth = m_pageWidth;
|
|
}
|
|
|
|
NOTATION_DEBUG << "Setting bar position for bar " << barNo
|
|
<< " to " << m_totalWidth << endl;
|
|
|
|
m_barPositions[barNo] = m_totalWidth;
|
|
m_totalWidth += maxWidth;
|
|
|
|
// Now apply width to this bar on all staffs
|
|
|
|
for (BarDataMap::iterator i = m_barData.begin();
|
|
i != m_barData.end(); ++i) {
|
|
|
|
BarDataList &list = i->second;
|
|
BarDataList::iterator bdli = list.find(barNo);
|
|
if (bdli != list.end()) {
|
|
|
|
BarData::SizeData &bd(bdli->second.sizeData);
|
|
|
|
NOTATION_DEBUG << "Changing width from " << bd.reconciledWidth << " to " << maxWidth << endl;
|
|
|
|
double diff = maxWidth - bd.reconciledWidth;
|
|
if (diff < -0.1 || diff > 0.1) {
|
|
NOTATION_DEBUG << "(So needsLayout becomes true)" << endl;
|
|
bdli->second.layoutData.needsLayout = true;
|
|
}
|
|
bd.reconciledWidth = maxWidth;
|
|
}
|
|
}
|
|
|
|
++barNo;
|
|
}
|
|
|
|
NOTATION_DEBUG << "Setting bar position for bar " << barNo
|
|
<< " to " << m_totalWidth << endl;
|
|
|
|
m_barPositions[barNo] = m_totalWidth;
|
|
}
|
|
|
|
void
|
|
NotationHLayout::reconcileBarsPage()
|
|
{
|
|
Profiler profiler("NotationHLayout::reconcileBarsPage");
|
|
|
|
int barNo = getFirstVisibleBar();
|
|
int barNoThisRow = 0;
|
|
|
|
// pair of the recommended number of bars with those bars'
|
|
// original total width, for each row
|
|
std::vector<std::pair<int, double> > rowData;
|
|
|
|
double stretchFactor = 10.0;
|
|
double maxStaffNameWidth = 0.0;
|
|
|
|
for (StaffIntMap::iterator i = m_staffNameWidths.begin();
|
|
i != m_staffNameWidths.end(); ++i) {
|
|
if (i->second > maxStaffNameWidth) {
|
|
maxStaffNameWidth = double(i->second);
|
|
}
|
|
}
|
|
|
|
double pageWidthSoFar = maxStaffNameWidth;
|
|
m_totalWidth = maxStaffNameWidth + getPreBarMargin();
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::reconcileBarsPage: pageWidthSoFar is " << pageWidthSoFar << endl;
|
|
|
|
for (;;) {
|
|
|
|
Staff *widest = getStaffWithWidestBar(barNo);
|
|
double maxWidth = m_spacing / 3;
|
|
|
|
if (!widest) {
|
|
// have we reached the end of the piece?
|
|
if (barNo >= getLastVisibleBar())
|
|
break; // yes
|
|
} else {
|
|
maxWidth =
|
|
m_barData[widest].find(barNo)->second.sizeData.idealWidth;
|
|
}
|
|
|
|
// Work on the assumption that this bar is the last in the
|
|
// row. How would that make things look?
|
|
|
|
double nextPageWidth = pageWidthSoFar + maxWidth;
|
|
double nextStretchFactor = m_pageWidth / nextPageWidth;
|
|
|
|
NOTATION_DEBUG << "barNo is " << barNo << ", maxWidth " << maxWidth << ", nextPageWidth " << nextPageWidth << ", nextStretchFactor " << nextStretchFactor << ", m_pageWidth " << m_pageWidth << endl;
|
|
|
|
// We have to have at least one bar per row
|
|
|
|
bool tooFar = false;
|
|
|
|
if (barNoThisRow >= 1) {
|
|
|
|
// If this stretch factor is "worse" than the previous
|
|
// one, we've come too far and have too many bars
|
|
|
|
if (fabs(1.0 - nextStretchFactor) > fabs(1.0 - stretchFactor)) {
|
|
tooFar = true;
|
|
}
|
|
|
|
// If the next stretch factor is less than 1 and would
|
|
// make this bar on any of the staffs narrower than it can
|
|
// afford to be, then we've got too many bars
|
|
//!!! rework this -- we have no concept of "too narrow"
|
|
// any more but we can declare we don't want it any
|
|
// narrower than e.g. 90% or something based on the spacing
|
|
/*!!!
|
|
if (!tooFar && (nextStretchFactor < 1.0)) {
|
|
|
|
for (BarDataMap::iterator i = m_barData.begin();
|
|
i != m_barData.end(); ++i) {
|
|
|
|
BarDataList &list = i->second;
|
|
BarDataList::iterator bdli = list.find(barNo);
|
|
if (bdli != list.end()) {
|
|
BarData::SizeData &bd(bdli->second.sizeData);
|
|
if ((nextStretchFactor * bd.idealWidth) <
|
|
(double)(bd.fixedWidth + bd.baseWidth)) {
|
|
tooFar = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
if (tooFar) {
|
|
rowData.push_back(std::pair<int, double>(barNoThisRow,
|
|
pageWidthSoFar));
|
|
barNoThisRow = 1;
|
|
|
|
// When we start a new row, we always need to allow for the
|
|
// repeated clef and key at the start of it.
|
|
int maxClefKeyWidth = getMaxRepeatedClefAndKeyWidth(barNo);
|
|
|
|
for (BarDataMap::iterator i = m_barData.begin();
|
|
i != m_barData.end(); ++i) {
|
|
|
|
BarDataList &list = i->second;
|
|
BarDataList::iterator bdli = list.find(barNo);
|
|
|
|
if (bdli != list.end()) {
|
|
bdli->second.sizeData.clefKeyWidth = maxClefKeyWidth;
|
|
}
|
|
}
|
|
|
|
pageWidthSoFar = maxWidth + maxClefKeyWidth;
|
|
stretchFactor = m_pageWidth / pageWidthSoFar;
|
|
} else {
|
|
++barNoThisRow;
|
|
pageWidthSoFar = nextPageWidth;
|
|
stretchFactor = nextStretchFactor;
|
|
}
|
|
|
|
++barNo;
|
|
}
|
|
|
|
if (barNoThisRow > 0) {
|
|
rowData.push_back(std::pair<int, double>(barNoThisRow,
|
|
pageWidthSoFar));
|
|
}
|
|
|
|
// Now we need to actually apply the widths
|
|
|
|
barNo = getFirstVisibleBar();
|
|
|
|
for (unsigned int row = 0; row < rowData.size(); ++row) {
|
|
|
|
barNoThisRow = barNo;
|
|
int finalBarThisRow = barNo + rowData[row].first - 1;
|
|
|
|
pageWidthSoFar = (row > 0 ? 0 : maxStaffNameWidth + getPreBarMargin());
|
|
stretchFactor = m_pageWidth / rowData[row].second;
|
|
|
|
for (; barNoThisRow <= finalBarThisRow; ++barNoThisRow, ++barNo) {
|
|
|
|
bool finalRow = (row == rowData.size() - 1);
|
|
|
|
Staff *widest = getStaffWithWidestBar(barNo);
|
|
if (finalRow && (stretchFactor > 1.0))
|
|
stretchFactor = 1.0;
|
|
double maxWidth = 0.0;
|
|
|
|
if (!widest) {
|
|
// have we reached the end of the piece? (shouldn't happen)
|
|
if (barNo >= getLastVisibleBar())
|
|
break; // yes
|
|
else
|
|
maxWidth = stretchFactor * (m_spacing / 3);
|
|
} else {
|
|
BarData &bd = m_barData[widest].find(barNo)->second;
|
|
maxWidth = (stretchFactor * bd.sizeData.idealWidth) +
|
|
bd.sizeData.clefKeyWidth;
|
|
NOTATION_DEBUG << "setting maxWidth to " << (stretchFactor * bd.sizeData.idealWidth) << " + " << bd.sizeData.clefKeyWidth << " = " << maxWidth << endl;
|
|
}
|
|
|
|
if (barNoThisRow == finalBarThisRow) {
|
|
if (!finalRow ||
|
|
(maxWidth > (m_pageWidth - pageWidthSoFar))) {
|
|
maxWidth = m_pageWidth - pageWidthSoFar;
|
|
NOTATION_DEBUG << "reset maxWidth to " << m_pageWidth << " - " << pageWidthSoFar << " = " << maxWidth << endl;
|
|
}
|
|
}
|
|
|
|
m_barPositions[barNo] = m_totalWidth;
|
|
m_totalWidth += maxWidth;
|
|
|
|
for (BarDataMap::iterator i = m_barData.begin();
|
|
i != m_barData.end(); ++i) {
|
|
|
|
BarDataList &list = i->second;
|
|
BarDataList::iterator bdli = list.find(barNo);
|
|
if (bdli != list.end()) {
|
|
BarData::SizeData &bd(bdli->second.sizeData);
|
|
double diff = maxWidth - bd.reconciledWidth;
|
|
if (diff < -0.1 || diff > 0.1) {
|
|
bdli->second.layoutData.needsLayout = true;
|
|
}
|
|
bd.reconciledWidth = maxWidth;
|
|
}
|
|
}
|
|
|
|
pageWidthSoFar += maxWidth;
|
|
}
|
|
}
|
|
|
|
m_barPositions[barNo] = m_totalWidth;
|
|
}
|
|
|
|
void
|
|
NotationHLayout::finishLayout(timeT startTime, timeT endTime)
|
|
{
|
|
Profiler profiler("NotationHLayout::finishLayout");
|
|
m_barPositions.clear();
|
|
|
|
bool isFullLayout = (startTime == endTime);
|
|
if (m_pageMode && (m_pageWidth > 0.1))
|
|
reconcileBarsPage();
|
|
else
|
|
reconcileBarsLinear();
|
|
|
|
int staffNo = 0;
|
|
|
|
for (BarDataMap::iterator i(m_barData.begin());
|
|
i != m_barData.end(); ++i) {
|
|
|
|
emit setProgress(100 * staffNo / m_barData.size());
|
|
ProgressDialog::processEvents();
|
|
|
|
throwIfCancelled();
|
|
|
|
timeT timeCovered = endTime - startTime;
|
|
|
|
if (isFullLayout) {
|
|
NotationElementList *notes = i->first->getViewElementList();
|
|
if (notes->begin() != notes->end()) {
|
|
NotationElementList::iterator j(notes->end());
|
|
timeCovered =
|
|
(*--j)->getViewAbsoluteTime() -
|
|
(*notes->begin())->getViewAbsoluteTime();
|
|
}
|
|
}
|
|
|
|
m_timePerProgressIncrement = timeCovered / (100 / m_barData.size());
|
|
|
|
layout(i, startTime, endTime);
|
|
++staffNo;
|
|
}
|
|
}
|
|
|
|
void
|
|
NotationHLayout::layout(BarDataMap::iterator i, timeT startTime, timeT endTime)
|
|
{
|
|
Profiler profiler("NotationHLayout::layout");
|
|
|
|
Staff &staff = *(i->first);
|
|
NotationElementList *notes = staff.getViewElementList();
|
|
BarDataList &barList(getBarData(staff));
|
|
NotationStaff ¬ationStaff = dynamic_cast<NotationStaff &>(staff);
|
|
|
|
bool isFullLayout = (startTime == endTime);
|
|
|
|
// these two are for partial layouts:
|
|
// bool haveSimpleOffset = false;
|
|
// double simpleOffset = 0;
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::layout: full layout " << isFullLayout << ", times " << startTime << "->" << endTime << endl;
|
|
|
|
double x = 0, barX = 0;
|
|
TieMap tieMap;
|
|
|
|
timeT lastIncrement =
|
|
(isFullLayout && (notes->begin() != notes->end())) ?
|
|
(*notes->begin())->getViewAbsoluteTime() : startTime;
|
|
|
|
::Rosegarden::Key key = notationStaff.getSegment().getKeyAtTime(lastIncrement);
|
|
Clef clef = notationStaff.getSegment().getClefAtTime(lastIncrement);
|
|
TimeSignature timeSignature;
|
|
|
|
int startBar = getComposition()->getBarNumber(startTime);
|
|
|
|
TDEConfig *config = kapp->config();
|
|
config->setGroup("Notation Options");
|
|
bool showInvisibles = config->readBoolEntry("showinvisibles", true);
|
|
|
|
for (BarPositionList::iterator bpi = m_barPositions.begin();
|
|
bpi != m_barPositions.end(); ++bpi) {
|
|
|
|
int barNo = bpi->first;
|
|
if (!isFullLayout && barNo < startBar)
|
|
continue;
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::looking for bar "
|
|
<< bpi->first << endl;
|
|
BarDataList::iterator bdi = barList.find(barNo);
|
|
if (bdi == barList.end())
|
|
continue;
|
|
barX = bpi->second;
|
|
|
|
NotationElementList::iterator from = bdi->second.basicData.start;
|
|
NotationElementList::iterator to;
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::layout(): starting bar " << barNo << ", x = " << barX << ", width = " << bdi->second.sizeData.idealWidth << ", time = " << (from == notes->end() ? -1 : (*from)->getViewAbsoluteTime()) << endl;
|
|
|
|
BarDataList::iterator nbdi(bdi);
|
|
if (++nbdi == barList.end()) {
|
|
to = notes->end();
|
|
} else {
|
|
to = nbdi->second.basicData.start;
|
|
}
|
|
|
|
if (from == notes->end()) {
|
|
NOTATION_DEBUG << "Start is end" << endl;
|
|
}
|
|
if (from == to) {
|
|
NOTATION_DEBUG << "Start is to" << endl;
|
|
}
|
|
|
|
if (!bdi->second.layoutData.needsLayout) {
|
|
|
|
double offset = barX - bdi->second.layoutData.x;
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::layout(): bar " << barNo << " has needsLayout false and offset of " << offset << endl;
|
|
|
|
if (offset > -0.1 && offset < 0.1) {
|
|
NOTATION_DEBUG << "NotationHLayout::layout(): no offset, ignoring" << endl;
|
|
continue;
|
|
}
|
|
|
|
bdi->second.layoutData.x += offset;
|
|
|
|
if (bdi->second.basicData.newTimeSig)
|
|
bdi->second.layoutData.timeSigX += (int)offset;
|
|
|
|
for (NotationElementList::iterator it = from;
|
|
it != to && it != notes->end(); ++it) {
|
|
|
|
NotationElement* nel = static_cast<NotationElement*>(*it);
|
|
NOTATION_DEBUG << "NotationHLayout::layout(): shifting element's x to " << ((*it)->getLayoutX() + offset) << " (was " << (*it)->getLayoutX() << ")" << endl;
|
|
nel->setLayoutX((*it)->getLayoutX() + offset);
|
|
double airX, airWidth;
|
|
nel->getLayoutAirspace(airX, airWidth);
|
|
nel->setLayoutAirspace(airX + offset, airWidth);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
bdi->second.layoutData.x = barX;
|
|
// x = barX + getPostBarMargin();
|
|
|
|
bool timeSigToPlace = false;
|
|
if (bdi->second.basicData.newTimeSig) {
|
|
timeSignature = bdi->second.basicData.timeSignature;
|
|
timeSigToPlace = !bdi->second.basicData.timeSignature.isHidden();
|
|
}
|
|
if (timeSigToPlace) {
|
|
NOTATION_DEBUG << "NotationHLayout::layout(): there's a time sig in this bar" << endl;
|
|
}
|
|
|
|
bool repeatClefAndKey = false;
|
|
if (bdi->second.sizeData.clefKeyWidth > 0) {
|
|
repeatClefAndKey = true;
|
|
}
|
|
if (repeatClefAndKey) {
|
|
NOTATION_DEBUG << "NotationHLayout::layout(): need to repeat clef & key in this bar" << endl;
|
|
}
|
|
|
|
double barInset = notationStaff.getBarInset(barNo, repeatClefAndKey);
|
|
|
|
NotationElement *lastDynamicText = 0;
|
|
int fretboardCount = 0;
|
|
int count = 0;
|
|
|
|
double offset = 0.0;
|
|
double reconciledWidth = bdi->second.sizeData.reconciledWidth;
|
|
|
|
if (repeatClefAndKey) {
|
|
offset = bdi->second.sizeData.clefKeyWidth;
|
|
reconciledWidth -= offset;
|
|
}
|
|
|
|
if (bdi->second.basicData.newTimeSig ||
|
|
!bdi->second.basicData.timeSignature.hasHiddenBars()) {
|
|
offset += getPostBarMargin();
|
|
}
|
|
|
|
ChunkList &chunks = bdi->second.chunks;
|
|
ChunkList::iterator chunkitr = chunks.begin();
|
|
double reconcileRatio = 1.0;
|
|
if (bdi->second.sizeData.idealWidth > 0.0) {
|
|
reconcileRatio = reconciledWidth / bdi->second.sizeData.idealWidth;
|
|
}
|
|
|
|
NOTATION_DEBUG << "have " << chunks.size() << " chunks, reconciledWidth " << bdi->second.sizeData.reconciledWidth << ", idealWidth " << bdi->second.sizeData.idealWidth << ", ratio " << reconcileRatio << endl;
|
|
|
|
double delta = 0;
|
|
float sigx = 0.f;
|
|
|
|
for (NotationElementList::iterator it = from; it != to; ++it) {
|
|
|
|
NotationElement *el = static_cast<NotationElement*>(*it);
|
|
delta = 0;
|
|
float fixed = 0;
|
|
|
|
if (el->event()->isa(Note::EventType)) {
|
|
long pitch = 0;
|
|
el->event()->get<Int>(PITCH, pitch);
|
|
NOTATION_DEBUG << "element is a " << el->event()->getType() << " (pitch " << pitch << ")" << endl;
|
|
} else {
|
|
NOTATION_DEBUG << "element is a " << el->event()->getType() << endl;
|
|
}
|
|
|
|
bool invisible = false;
|
|
if (el->event()->get<Bool>(INVISIBLE, invisible) && invisible) {
|
|
if (!showInvisibles)
|
|
continue;
|
|
}
|
|
|
|
// float sigx = 0;
|
|
|
|
if (chunkitr != chunks.end()) {
|
|
NOTATION_DEBUG << "new chunk: addr " << &(*chunkitr) << " duration=" << (*chunkitr).duration << " subordering=" << (*chunkitr).subordering << " fixed=" << (*chunkitr).fixed << " stretchy=" << (*chunkitr).stretchy << " x=" << (*chunkitr).x << endl;
|
|
x = barX + offset + reconcileRatio * (*chunkitr).x;
|
|
fixed = (*chunkitr).fixed;
|
|
// sigx = barX + offset - fixed;
|
|
// sigx = x - fixed;
|
|
NOTATION_DEBUG << "adjusted x is " << x << ", fixed is " << fixed << endl;
|
|
|
|
if (timeSigToPlace) {
|
|
if (el->event()->isa(Clef::EventType) ||
|
|
el->event()->isa(Rosegarden::Key::EventType)) {
|
|
sigx = x + (*chunkitr).fixed + (*chunkitr).stretchy;
|
|
}
|
|
}
|
|
|
|
ChunkList::iterator chunkscooter(chunkitr);
|
|
if (++chunkscooter != chunks.end()) {
|
|
delta = (*chunkscooter).x - (*chunkitr).x;
|
|
} else {
|
|
delta = reconciledWidth -
|
|
bdi->second.sizeData.fixedWidth - (*chunkitr).x;
|
|
}
|
|
delta *= reconcileRatio;
|
|
|
|
++chunkitr;
|
|
} else {
|
|
x = barX + reconciledWidth - getPreBarMargin();
|
|
// sigx = x;
|
|
delta = 0;
|
|
}
|
|
|
|
if (timeSigToPlace &&
|
|
!el->event()->isa(Clef::EventType) &&
|
|
!el->event()->isa(::Rosegarden::Key::EventType)) {
|
|
|
|
if (sigx == 0.f) {
|
|
sigx = barX + offset;
|
|
}
|
|
|
|
// NOTATION_DEBUG << "Placing timesig at " << (x - fixed) << endl;
|
|
// bdi->second.layoutData.timeSigX = (int)(x - fixed);
|
|
NOTATION_DEBUG << "Placing timesig at " << sigx << " (would previously have been " << int(x-fixed) << "?)" << endl;
|
|
bdi->second.layoutData.timeSigX = (int)sigx;
|
|
double shift = getFixedItemSpacing() +
|
|
m_npf->getTimeSigWidth(timeSignature);
|
|
offset += shift;
|
|
x += shift;
|
|
NOTATION_DEBUG << "and moving next elt to " << x << endl;
|
|
timeSigToPlace = false;
|
|
}
|
|
|
|
if (barInset >= 1.0) {
|
|
if (el->event()->isa(Clef::EventType) ||
|
|
el->event()->isa(::Rosegarden::Key::EventType)) {
|
|
NOTATION_DEBUG << "Pulling clef/key back by " << getPreBarMargin() << endl;
|
|
x -= getPostBarMargin() * 2 / 3;
|
|
} else {
|
|
barInset = 0.0;
|
|
}
|
|
}
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::layout(): setting element's x to " << x << " (was " << el->getLayoutX() << ")" << endl;
|
|
|
|
double displacedX = 0.0;
|
|
long dxRaw = 0;
|
|
el->event()->get<Int>(DISPLACED_X, dxRaw);
|
|
displacedX = double(dxRaw * m_npf->getNoteBodyWidth()) / 1000.0;
|
|
|
|
el->setLayoutX(x + displacedX);
|
|
el->setLayoutAirspace(x, int(delta));
|
|
|
|
// #704958 (multiple tuplet spanners created when entering
|
|
// triplet chord) -- only do this here for non-notes,
|
|
// notes get it from positionChord
|
|
if (!el->isNote()) {
|
|
sampleGroupElement(staff, clef, key, it);
|
|
}
|
|
|
|
if (el->isNote()) {
|
|
|
|
// This modifies "it" and "tieMap"
|
|
positionChord(staff, it, clef, key, tieMap, to);
|
|
|
|
} else if (el->isRest()) {
|
|
|
|
// nothing to do
|
|
|
|
} else if (el->event()->isa(Clef::EventType)) {
|
|
|
|
clef = Clef(*el->event());
|
|
|
|
} else if (el->event()->isa(::Rosegarden::Key::EventType)) {
|
|
|
|
key = ::Rosegarden::Key(*el->event());
|
|
|
|
} else if (el->event()->isa(Text::EventType)) {
|
|
|
|
// if it's a dynamic, make a note of it in case a
|
|
// hairpin immediately follows it
|
|
|
|
if (el->event()->has(Text::TextTypePropertyName) &&
|
|
el->event()->get<String>(Text::TextTypePropertyName) ==
|
|
Text::Dynamic) {
|
|
lastDynamicText = el;
|
|
}
|
|
|
|
} else if (el->event()->isa(Indication::EventType)) {
|
|
|
|
std::string type;
|
|
double ix = x;
|
|
|
|
// Check for a dynamic text at the same time as the
|
|
// indication and if found, move the indication to the
|
|
// right to make room. We know the text should have
|
|
// preceded the indication in the staff because it has
|
|
// a smaller subordering
|
|
|
|
if (el->event()->get<String>
|
|
(Indication::IndicationTypePropertyName, type) &&
|
|
(type == Indication::Crescendo ||
|
|
type == Indication::Decrescendo) &&
|
|
lastDynamicText &&
|
|
lastDynamicText->getViewAbsoluteTime() ==
|
|
el->getViewAbsoluteTime()) {
|
|
|
|
ix = x + m_npf->getTextWidth
|
|
(Text(*lastDynamicText->event())) +
|
|
m_npf->getNoteBodyWidth() / 4;
|
|
}
|
|
|
|
el->setLayoutX(ix + displacedX);
|
|
el->setLayoutAirspace(ix, delta - (ix - x));
|
|
|
|
} else if (el->event()->isa(Guitar::Chord::EventType)) {
|
|
|
|
int guitarChordWidth = m_npf->getLineSpacing() * 6;
|
|
el->setLayoutX(x - (guitarChordWidth / 2)
|
|
+ fretboardCount * (guitarChordWidth +
|
|
m_npf->getNoteBodyWidth()/2)
|
|
+ displacedX);
|
|
++fretboardCount;
|
|
|
|
} else {
|
|
|
|
// nothing else
|
|
}
|
|
|
|
if (m_timePerProgressIncrement > 0 && (++count == 100)) {
|
|
count = 0;
|
|
timeT sinceIncrement = el->getViewAbsoluteTime() - lastIncrement;
|
|
if (sinceIncrement > m_timePerProgressIncrement) {
|
|
emit incrementProgress
|
|
(sinceIncrement / m_timePerProgressIncrement);
|
|
lastIncrement +=
|
|
(sinceIncrement / m_timePerProgressIncrement)
|
|
* m_timePerProgressIncrement;
|
|
throwIfCancelled();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (timeSigToPlace) {
|
|
// no other events in this bar, so we never managed to place it
|
|
x = barX + offset;
|
|
NOTATION_DEBUG << "Placing timesig reluctantly at " << x << endl;
|
|
bdi->second.layoutData.timeSigX = (int)(x);
|
|
timeSigToPlace = false;
|
|
}
|
|
|
|
for (NotationGroupMap::iterator mi = m_groupsExtant.begin();
|
|
mi != m_groupsExtant.end(); ++mi) {
|
|
mi->second->applyBeam(notationStaff);
|
|
mi->second->applyTuplingLine(notationStaff);
|
|
delete mi->second;
|
|
}
|
|
m_groupsExtant.clear();
|
|
|
|
bdi->second.layoutData.needsLayout = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
NotationHLayout::sampleGroupElement(Staff &staff,
|
|
const Clef &clef,
|
|
const ::Rosegarden::Key &key,
|
|
const NotationElementList::iterator &itr)
|
|
{
|
|
NotationElement *el = static_cast<NotationElement *>(*itr);
|
|
|
|
if (el->event()->has(BEAMED_GROUP_ID)) {
|
|
|
|
//!!! Gosh. We need some clever logic to establish whether
|
|
// one group is happening while another has not yet ended --
|
|
// perhaps we decide one has ended if we see another, and then
|
|
// re-open the case of the first if we meet another note that
|
|
// claims to be in it. Then we need to hint to both of the
|
|
// groups that they should choose appropriate stem directions
|
|
// -- we could just use HEIGHT_ON_STAFF of their first notes
|
|
// to determine this, as if that doesn't work, nothing will
|
|
|
|
long groupId = el->event()->get<Int>(BEAMED_GROUP_ID);
|
|
NOTATION_DEBUG << "group id: " << groupId << endl;
|
|
if (m_groupsExtant.find(groupId) == m_groupsExtant.end()) {
|
|
NOTATION_DEBUG << "(new group)" << endl;
|
|
m_groupsExtant[groupId] =
|
|
new NotationGroup(*staff.getViewElementList(),
|
|
m_notationQuantizer,
|
|
m_properties, clef, key);
|
|
}
|
|
m_groupsExtant[groupId]->sample(itr, true);
|
|
}
|
|
}
|
|
|
|
timeT
|
|
NotationHLayout::getSpacingDuration(Staff &staff,
|
|
const NotationElementList::iterator &i)
|
|
{
|
|
SegmentNotationHelper helper(staff.getSegment());
|
|
timeT t((*i)->getViewAbsoluteTime());
|
|
timeT d((*i)->getViewDuration());
|
|
|
|
if (d > 0 && (*i)->event()->getDuration() == 0) return d; // grace note
|
|
|
|
NotationElementList::iterator j(i), e(staff.getViewElementList()->end());
|
|
while (j != e && ((*j)->getViewAbsoluteTime() == t ||
|
|
(*j)->getViewDuration() == 0)) {
|
|
++j;
|
|
}
|
|
if (j == e) {
|
|
return d;
|
|
} else {
|
|
return (*j)->getViewAbsoluteTime() - (*i)->getViewAbsoluteTime();
|
|
}
|
|
}
|
|
|
|
timeT
|
|
NotationHLayout::getSpacingDuration(Staff &staff,
|
|
const NotationChord &chord)
|
|
{
|
|
SegmentNotationHelper helper(staff.getSegment());
|
|
|
|
NotationElementList::iterator i = chord.getShortestElement();
|
|
timeT d((*i)->getViewDuration());
|
|
|
|
if (d > 0 && (*i)->event()->getDuration() == 0) return d; // grace note
|
|
|
|
NotationElementList::iterator j(i), e(staff.getViewElementList()->end());
|
|
while (j != e && (chord.contains(j) || (*j)->getViewDuration() == 0))
|
|
++j;
|
|
|
|
if (j != e) {
|
|
d = (*j)->getViewAbsoluteTime() - (*i)->getViewAbsoluteTime();
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
void
|
|
NotationHLayout::positionChord(Staff &staff,
|
|
NotationElementList::iterator &itr,
|
|
const Clef &clef, const ::Rosegarden::Key &key,
|
|
TieMap &tieMap,
|
|
NotationElementList::iterator &to)
|
|
{
|
|
NotationChord chord(*staff.getViewElementList(), itr, m_notationQuantizer,
|
|
m_properties, clef, key);
|
|
double baseX, delta;
|
|
(static_cast<NotationElement *>(*itr))->getLayoutAirspace(baseX, delta);
|
|
|
|
bool barEndsInChord = false;
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::positionChord: x = " << baseX << endl;
|
|
|
|
// #938545 (Broken notation: Duplicated note can float outside
|
|
// stave) -- We need to iterate over all elements in the chord
|
|
// range here, not just the ordered set of notes actually in the
|
|
// chord. They all have the same x-coord, so there's no
|
|
// particular complication here.
|
|
|
|
for (NotationElementList::iterator citr = chord.getInitialElement();
|
|
citr != staff.getViewElementList()->end(); ++citr) {
|
|
|
|
if (citr == to)
|
|
barEndsInChord = true;
|
|
|
|
// #704958 (multiple tuplet spanners created when entering
|
|
// triplet chord) -- layout() updates the beamed group data
|
|
// for non-notes, but we have to do it for notes so as to
|
|
// ensure every note in the chord is accounted for
|
|
sampleGroupElement(staff, clef, key, citr);
|
|
|
|
NotationElement *elt = static_cast<NotationElement*>(*citr);
|
|
|
|
double displacedX = 0.0;
|
|
long dxRaw = 0;
|
|
elt->event()->get<Int>(DISPLACED_X, dxRaw);
|
|
displacedX = double(dxRaw * m_npf->getNoteBodyWidth()) / 1000.0;
|
|
|
|
elt->setLayoutX(baseX + displacedX);
|
|
elt->setLayoutAirspace(baseX, delta);
|
|
|
|
NOTATION_DEBUG << "NotationHLayout::positionChord: assigned x to elt at " << elt->getViewAbsoluteTime() << endl;
|
|
|
|
if (citr == chord.getFinalElement())
|
|
break;
|
|
}
|
|
|
|
// Check for any ties going back, and if so work out how long they
|
|
// must have been and assign accordingly.
|
|
|
|
for (NotationElementList::iterator citr = chord.getInitialElement();
|
|
citr != staff.getViewElementList()->end(); ++citr) {
|
|
|
|
NotationElement *note = static_cast<NotationElement*>(*citr);
|
|
if (!note->isNote()) {
|
|
if (citr == chord.getFinalElement())
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
bool tiedForwards = false;
|
|
bool tiedBack = false;
|
|
|
|
note->event()->get<Bool>(TIED_FORWARD, tiedForwards);
|
|
note->event()->get<Bool>(TIED_BACKWARD, tiedBack);
|
|
|
|
if (!note->event()->has(PITCH))
|
|
continue;
|
|
int pitch = note->event()->get<Int>(PITCH);
|
|
|
|
if (tiedBack) {
|
|
TieMap::iterator ti(tieMap.find(pitch));
|
|
|
|
if (ti != tieMap.end()) {
|
|
NotationElementList::iterator otherItr(ti->second);
|
|
|
|
if ((*otherItr)->getViewAbsoluteTime() +
|
|
(*otherItr)->getViewDuration() ==
|
|
note->getViewAbsoluteTime()) {
|
|
|
|
NOTATION_DEBUG << "Second note in tie at " << note->getViewAbsoluteTime() << ": found first note, it matches" << endl;
|
|
|
|
(*otherItr)->event()->setMaybe<Int>
|
|
(m_properties.TIE_LENGTH,
|
|
(int)(baseX - (*otherItr)->getLayoutX()));
|
|
|
|
} else {
|
|
NOTATION_DEBUG << "Second note in tie at " << note->getViewAbsoluteTime() << ": found first note but it ends at " << ((*otherItr)->getViewAbsoluteTime() + (*otherItr)->getViewDuration()) << endl;
|
|
|
|
tieMap.erase(pitch);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tiedForwards) {
|
|
note->event()->setMaybe<Int>(m_properties.TIE_LENGTH, 0);
|
|
tieMap[pitch] = citr;
|
|
} else {
|
|
note->event()->unset(m_properties.TIE_LENGTH);
|
|
}
|
|
|
|
if (citr == chord.getFinalElement())
|
|
break;
|
|
}
|
|
|
|
itr = chord.getFinalElement();
|
|
if (barEndsInChord) {
|
|
to = itr;
|
|
++to;
|
|
}
|
|
}
|
|
|
|
float
|
|
NotationHLayout::getLayoutWidth(ViewElement &ve,
|
|
NotePixmapFactory *npf,
|
|
const ::Rosegarden::Key &previousKey) const
|
|
{
|
|
NotationElement& e = static_cast<NotationElement&>(ve);
|
|
|
|
if ((e.isNote() || e.isRest()) && e.event()->has(NOTE_TYPE)) {
|
|
|
|
long noteType = e.event()->get<Int>(NOTE_TYPE);
|
|
long dots = 0;
|
|
(void)e.event()->get<Int>(NOTE_DOTS, dots);
|
|
|
|
double bw = 0;
|
|
|
|
if (e.isNote()) {
|
|
bw = m_npf->getNoteBodyWidth(noteType)
|
|
+ m_npf->getDotWidth() * dots;
|
|
} else {
|
|
bw = m_npf->getRestWidth(Note(noteType, dots));
|
|
}
|
|
|
|
double multiplier = double(Note(noteType, dots).getDuration()) /
|
|
double(Note(Note::Quaver).getDuration());
|
|
multiplier -= 1.0;
|
|
multiplier *= m_proportion / 100.0;
|
|
multiplier += 1.0;
|
|
|
|
double gap = m_npf->getNoteBodyWidth(noteType) * multiplier;
|
|
|
|
NOTATION_DEBUG << "note type " << noteType << ", isNote " << e.isNote() << ", dots " << dots << ", multiplier " << multiplier << ", gap " << gap << ", result " << (bw + gap * m_spacing / 100.0) << endl;
|
|
|
|
gap = gap * m_spacing / 100.0;
|
|
return bw + gap;
|
|
|
|
} else {
|
|
|
|
double w = getFixedItemSpacing();
|
|
|
|
if (e.event()->isa(Clef::EventType)) {
|
|
|
|
w += m_npf->getClefWidth(Clef(*e.event()));
|
|
|
|
} else if (e.event()->isa(::Rosegarden::Key::EventType)) {
|
|
|
|
::Rosegarden::Key key(*e.event());
|
|
|
|
::Rosegarden::Key cancelKey = previousKey;
|
|
|
|
if (m_keySigCancelMode == 0) { // only when entering C maj / A min
|
|
|
|
if (key.getAccidentalCount() != 0)
|
|
cancelKey = ::Rosegarden::Key();
|
|
|
|
} else if (m_keySigCancelMode == 1) { // only when reducing acc count
|
|
|
|
if (!(key.isSharp() == cancelKey.isSharp() &&
|
|
key.getAccidentalCount() < cancelKey.getAccidentalCount())) {
|
|
cancelKey = ::Rosegarden::Key();
|
|
}
|
|
}
|
|
|
|
w += m_npf->getKeyWidth(key, cancelKey);
|
|
|
|
} else if (e.event()->isa(Indication::EventType) ||
|
|
e.event()->isa(Text::EventType)) {
|
|
|
|
w = 0;
|
|
|
|
} else {
|
|
// NOTATION_DEBUG << "NotationHLayout::getLayoutWidth(): no case for event type " << e.event()->getType() << endl;
|
|
// w += 24;
|
|
w = 0;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
}
|
|
|
|
int NotationHLayout::getBarMargin() const
|
|
{
|
|
return (int)(m_npf->getBarMargin() * m_spacing / 100.0);
|
|
}
|
|
|
|
int NotationHLayout::getPreBarMargin() const
|
|
{
|
|
return getBarMargin() / 3;
|
|
}
|
|
|
|
int NotationHLayout::getPostBarMargin() const
|
|
{
|
|
return getBarMargin() - getPreBarMargin();
|
|
}
|
|
|
|
int NotationHLayout::getFixedItemSpacing() const
|
|
{
|
|
return (int)((m_npf->getNoteBodyWidth() * 2.0 / 3.0) * m_spacing / 100.0);
|
|
}
|
|
|
|
void
|
|
NotationHLayout::reset()
|
|
{
|
|
for (BarDataMap::iterator i = m_barData.begin();
|
|
i != m_barData.end(); ++i) {
|
|
clearBarList(*i->first);
|
|
}
|
|
|
|
m_barData.clear();
|
|
m_barPositions.clear();
|
|
m_totalWidth = 0;
|
|
}
|
|
|
|
void
|
|
NotationHLayout::resetStaff(Staff &staff, timeT startTime, timeT endTime)
|
|
{
|
|
if (startTime == endTime) {
|
|
getBarData(staff).clear();
|
|
m_totalWidth = 0;
|
|
}
|
|
}
|
|
|
|
int
|
|
NotationHLayout::getFirstVisibleBar() const
|
|
{
|
|
int bar = 0;
|
|
bool haveBar = false;
|
|
for (BarDataMap::const_iterator i = m_barData.begin(); i != m_barData.end(); ++i) {
|
|
if (i->second.begin() == i->second.end())
|
|
continue;
|
|
int barHere = i->second.begin()->first;
|
|
if (barHere < bar || !haveBar) {
|
|
bar = barHere;
|
|
haveBar = true;
|
|
}
|
|
}
|
|
|
|
// NOTATION_DEBUG << "NotationHLayout::getFirstVisibleBar: returning " << bar << endl;
|
|
|
|
return bar;
|
|
}
|
|
|
|
int
|
|
NotationHLayout::getFirstVisibleBarOnStaff(Staff &staff)
|
|
{
|
|
BarDataList &bdl(getBarData(staff));
|
|
|
|
int bar = 0;
|
|
if (bdl.begin() != bdl.end())
|
|
bar = bdl.begin()->first;
|
|
|
|
// NOTATION_DEBUG << "NotationHLayout::getFirstVisibleBarOnStaff: returning " << bar << endl;
|
|
|
|
return bar;
|
|
}
|
|
|
|
int
|
|
NotationHLayout::getLastVisibleBar() const
|
|
{
|
|
int bar = 0;
|
|
bool haveBar = false;
|
|
for (BarDataMap::const_iterator i = m_barData.begin();
|
|
i != m_barData.end(); ++i) {
|
|
if (i->second.begin() == i->second.end())
|
|
continue;
|
|
int barHere = getLastVisibleBarOnStaff(*i->first);
|
|
if (barHere > bar || !haveBar) {
|
|
bar = barHere;
|
|
haveBar = true;
|
|
}
|
|
}
|
|
|
|
// NOTATION_DEBUG << "NotationHLayout::getLastVisibleBar: returning " << bar << endl;
|
|
|
|
return bar;
|
|
}
|
|
|
|
int
|
|
NotationHLayout::getLastVisibleBarOnStaff(Staff &staff) const
|
|
{
|
|
const BarDataList &bdl(getBarData(staff));
|
|
int bar = 0;
|
|
|
|
if (bdl.begin() != bdl.end()) {
|
|
BarDataList::const_iterator i = bdl.end();
|
|
bar = ((--i)->first) + 1; // last visible bar_line_
|
|
}
|
|
|
|
// NOTATION_DEBUG << "NotationHLayout::getLastVisibleBarOnStaff: returning " << bar << endl;
|
|
|
|
return bar;
|
|
}
|
|
|
|
double
|
|
NotationHLayout::getBarPosition(int bar) const
|
|
{
|
|
double position = 0.0;
|
|
|
|
BarPositionList::const_iterator i = m_barPositions.find(bar);
|
|
|
|
if (i != m_barPositions.end()) {
|
|
|
|
position = i->second;
|
|
|
|
} else {
|
|
|
|
i = m_barPositions.begin();
|
|
if (i != m_barPositions.end()) {
|
|
if (bar < i->first)
|
|
position = i->second;
|
|
else {
|
|
i = m_barPositions.end();
|
|
--i;
|
|
if (bar > i->first)
|
|
position = i->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTATION_DEBUG << "NotationHLayout::getBarPosition: returning " << position << " for bar " << bar << endl;
|
|
|
|
return position;
|
|
}
|
|
|
|
bool
|
|
NotationHLayout::isBarCorrectOnStaff(Staff &staff, int i)
|
|
{
|
|
BarDataList &bdl(getBarData(staff));
|
|
++i;
|
|
|
|
BarDataList::iterator bdli(bdl.find(i));
|
|
if (bdli != bdl.end())
|
|
return bdli->second.basicData.correct;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
bool NotationHLayout::getTimeSignaturePosition(Staff &staff,
|
|
int i,
|
|
TimeSignature &timeSig,
|
|
double &timeSigX)
|
|
{
|
|
BarDataList &bdl(getBarData(staff));
|
|
|
|
BarDataList::iterator bdli(bdl.find(i));
|
|
if (bdli != bdl.end()) {
|
|
timeSig = bdli->second.basicData.timeSignature;
|
|
timeSigX = (double)(bdli->second.layoutData.timeSigX);
|
|
return bdli->second.basicData.newTimeSig;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
timeT
|
|
NotationHLayout::getTimeForX(double x) const
|
|
{
|
|
return RulerScale::getTimeForX(x);
|
|
}
|
|
|
|
double
|
|
NotationHLayout::getXForTime(timeT t) const
|
|
{
|
|
return RulerScale::getXForTime(t);
|
|
}
|
|
|
|
double
|
|
NotationHLayout::getXForTimeByEvent(timeT time) const
|
|
{
|
|
// NOTATION_DEBUG << "NotationHLayout::getXForTime(" << time << ")" << endl;
|
|
|
|
for (BarDataMap::const_iterator i = m_barData.begin(); i != m_barData.end(); ++i) {
|
|
|
|
Staff *staff = i->first;
|
|
|
|
if (staff->getSegment().getStartTime() <= time &&
|
|
staff->getSegment().getEndMarkerTime() > time) {
|
|
|
|
ViewElementList::iterator vli =
|
|
staff->getViewElementList()->findNearestTime(time);
|
|
|
|
bool found = false;
|
|
double x = 0.0, dx = 0.0;
|
|
timeT t = 0, dt = 0;
|
|
|
|
while (!found) {
|
|
if (vli == staff->getViewElementList()->end())
|
|
break;
|
|
NotationElement *element = static_cast<NotationElement *>(*vli);
|
|
if (element->getCanvasItem()) {
|
|
x = element->getLayoutX();
|
|
double temp;
|
|
element->getLayoutAirspace(temp, dx);
|
|
t = element->event()->getNotationAbsoluteTime();
|
|
dt = element->event()->getNotationDuration();
|
|
found = true;
|
|
break;
|
|
}
|
|
++vli;
|
|
}
|
|
|
|
if (found) {
|
|
if (time > t) {
|
|
|
|
while (vli != staff->getViewElementList()->end() &&
|
|
((*vli)->event()->getNotationAbsoluteTime() < time ||
|
|
!((static_cast<NotationElement *>(*vli))->getCanvasItem())))
|
|
++vli;
|
|
|
|
if (vli != staff->getViewElementList()->end()) {
|
|
NotationElement *element = static_cast<NotationElement *>(*vli);
|
|
dx = element->getLayoutX() - x;
|
|
dt = element->event()->getNotationAbsoluteTime() - t;
|
|
}
|
|
|
|
if (dt > 0 && dx > 0) {
|
|
return x + dx * (time - t) / dt;
|
|
}
|
|
}
|
|
|
|
return x - 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
return RulerScale::getXForTime(time);
|
|
}
|
|
|
|
std::vector<int> NotationHLayout::m_availableSpacings;
|
|
std::vector<int> NotationHLayout::m_availableProportions;
|
|
|
|
}
|