diff options
Diffstat (limited to 'src/gui/editors/notation')
75 files changed, 32497 insertions, 0 deletions
diff --git a/src/gui/editors/notation/ClefInserter.cpp b/src/gui/editors/notation/ClefInserter.cpp new file mode 100644 index 0000000..f39327e --- /dev/null +++ b/src/gui/editors/notation/ClefInserter.cpp @@ -0,0 +1,132 @@ +/* -*- 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 "ClefInserter.h" + +#include <klocale.h> +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/ViewElement.h" +#include "commands/notation/ClefInsertionCommand.h" +#include "gui/general/EditTool.h" +#include "gui/general/LinedStaff.h" +#include "NotationElement.h" +#include "NotationTool.h" +#include "NotationView.h" +#include "NotePixmapFactory.h" +#include <kaction.h> +#include <qiconset.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +ClefInserter::ClefInserter(NotationView* view) + : NotationTool("ClefInserter", view), + m_clef(Clef::Treble) +{ + QIconSet icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("select"))); + new KAction(i18n("Switch to Select Tool"), icon, 0, this, + SLOT(slotSelectSelected()), actionCollection(), + "select"); + + new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this, + SLOT(slotEraseSelected()), actionCollection(), + "erase"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("crotchet"))); + new KAction(i18n("Switch to Inserting Notes"), icon, 0, this, + SLOT(slotNotesSelected()), actionCollection(), + "notes"); + + createMenu("clefinserter.rc"); +} + +void ClefInserter::slotNotesSelected() +{ + m_nParentView->slotLastNoteAction(); +} + +void ClefInserter::slotEraseSelected() +{ + m_parentView->actionCollection()->action("erase")->activate(); +} + +void ClefInserter::slotSelectSelected() +{ + m_parentView->actionCollection()->action("select")->activate(); +} + +void ClefInserter::ready() +{ + m_nParentView->setCanvasCursor(Qt::crossCursor); + m_nParentView->setHeightTracking(false); +} + +void ClefInserter::setClef(std::string clefType) +{ + m_clef = clefType; +} + +void ClefInserter::handleLeftButtonPress(timeT, + int, + int staffNo, + QMouseEvent* e, + ViewElement*) +{ + if (staffNo < 0) + return ; + Event *clef = 0, *key = 0; + + LinedStaff *staff = m_nParentView->getLinedStaff(staffNo); + + NotationElementList::iterator closestElement = + staff->getClosestElementToCanvasCoords(e->x(), (int)e->y(), + clef, key, false, -1); + + if (closestElement == staff->getViewElementList()->end()) + return ; + + timeT time = (*closestElement)->event()->getAbsoluteTime(); // not getViewAbsoluteTime() + + + ClefInsertionCommand *command = + new ClefInsertionCommand(staff->getSegment(), time, m_clef); + + m_nParentView->addCommandToHistory(command); + + Event *event = command->getLastInsertedEvent(); + if (event) + m_nParentView->setSingleSelectedEvent(staffNo, event); +} + +const QString ClefInserter::ToolName = "clefinserter"; + +} +#include "ClefInserter.moc" diff --git a/src/gui/editors/notation/ClefInserter.h b/src/gui/editors/notation/ClefInserter.h new file mode 100644 index 0000000..460bfa5 --- /dev/null +++ b/src/gui/editors/notation/ClefInserter.h @@ -0,0 +1,83 @@ + +/* -*- 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. +*/ + +#ifndef _RG_CLEFINSERTER_H_ +#define _RG_CLEFINSERTER_H_ + +#include "base/NotationTypes.h" +#include "NotationTool.h" +#include <qstring.h> +#include "base/Event.h" + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class ViewElement; +class NotationView; + + +/** + * This tool will insert clefs on mouse click events + */ +class ClefInserter : public NotationTool +{ + Q_OBJECT + + friend class NotationToolBox; + +public: + void setClef(std::string clefType); + + virtual void ready(); + + virtual void handleLeftButtonPress(timeT, + int height, + int staffNo, + QMouseEvent*, + ViewElement* el); + static const QString ToolName; + +protected slots: + void slotNotesSelected(); + void slotEraseSelected(); + void slotSelectSelected(); + +protected: + ClefInserter(NotationView*); + + //--------------- Data members --------------------------------- + + Clef m_clef; +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/FontViewFrame.cpp b/src/gui/editors/notation/FontViewFrame.cpp new file mode 100644 index 0000000..ab0498f --- /dev/null +++ b/src/gui/editors/notation/FontViewFrame.cpp @@ -0,0 +1,252 @@ +/* -*- 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 "FontViewFrame.h" +#include <kapplication.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <qfontmetrics.h> +#include <qframe.h> +#include <qsize.h> +#include <qstring.h> +#include <qwidget.h> +#include <qpainter.h> + +#ifdef HAVE_XFT +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_OUTLINE_H +#include FT_GLYPH_H +#include <X11/Xft/Xft.h> +#endif + +namespace Rosegarden +{ + +FontViewFrame::FontViewFrame( int pixelSize, QWidget* parent, const char* name ) : + QFrame(parent, name), + m_fontSize(pixelSize), + m_tableFont(0) +{ + setBackgroundMode(PaletteBase); + setFrameStyle(Panel | Sunken); + setMargin(8); + setRow(0); +} + +FontViewFrame::~FontViewFrame() +{ + // empty +} + +void +FontViewFrame::setFont(QString font) +{ + m_fontName = font; + loadFont(); + update(); +} + +void +FontViewFrame::loadFont() +{ +#ifdef HAVE_XFT + if (m_tableFont) { + XftFontClose(x11AppDisplay(), (XftFont *)m_tableFont); + } + m_tableFont = 0; + + static bool haveDir = false; + if (!haveDir) { + FcConfigAppFontAddDir(FcConfigGetCurrent(), + (const FcChar8 *)"/opt/kde3/share/apps/rosegarden/fonts"); + haveDir = true; + } + + FcPattern *pattern = FcPatternCreate(); + FcPatternAddString(pattern, FC_FAMILY, (FcChar8 *)m_fontName.latin1()); + FcPatternAddInteger(pattern, FC_PIXEL_SIZE, m_fontSize); + + FcConfigSubstitute(FcConfigGetCurrent(), pattern, FcMatchPattern); + + FcResult result = FcResultMatch; + FcPattern *match = FcFontMatch(FcConfigGetCurrent(), pattern, &result); + FcPatternDestroy(pattern); + + if (!match || result != FcResultMatch) { + KMessageBox::error(this, i18n("Error: Unable to match font name %1").arg(m_fontName)); + return ; + } + + FcChar8 *matchFamily; + FcPatternGetString(match, FC_FAMILY, 0, &matchFamily); + + if (QString((const char *)matchFamily).lower() != m_fontName.lower()) { + KMessageBox::sorry(this, i18n("Warning: No good match for font name %1 (best is %2)"). + arg(m_fontName).arg(QString((const char *)matchFamily))); + m_fontName = (const char *)matchFamily; + } + + m_tableFont = XftFontOpenPattern(x11AppDisplay(), match); + + if (!m_tableFont) { + KMessageBox::error(this, i18n("Error: Unable to open best-match font %1"). + arg(QString((const char *)matchFamily))); + } +#endif +} + +void FontViewFrame::setGlyphs(bool glyphs) +{ + m_glyphs = glyphs; + update(); +} + +QSize FontViewFrame::sizeHint() const +{ + return QSize(16 * m_fontSize * 3 / 2 + margin() + 2 * frameWidth(), + 16 * m_fontSize * 3 / 2 + margin() + 2 * frameWidth()); +} + +QSize FontViewFrame::cellSize() const +{ + QFontMetrics fm = fontMetrics(); + return QSize( fm.maxWidth(), fm.lineSpacing() + 1 ); +} + +void FontViewFrame::paintEvent( QPaintEvent* e ) +{ +#ifdef HAVE_XFT + if (!m_tableFont) + return ; + + QFrame::paintEvent(e); + QPainter p(this); + + int ll = 25; + int ml = frameWidth() + margin() + ll + 1; + int mt = frameWidth() + margin(); + QSize cell((width() - 16 - ml) / 17, (height() - 16 - mt) / 17); + + if ( !cell.width() || !cell.height() ) + return ; + + QColor body(255, 255, 192); + QColor negative(255, 192, 192); + QColor positive(192, 192, 255); + QColor rnegative(255, 128, 128); + QColor rpositive(128, 128, 255); + + Drawable drawable = (Drawable)handle(); + XftDraw *draw = XftDrawCreate(x11AppDisplay(), drawable, + (Visual *)x11Visual(), x11Colormap()); + + QColor pen(Qt::black); + XftColor col; + col.color.red = pen.red () | pen.red() << 8; + col.color.green = pen.green () | pen.green() << 8; + col.color.blue = pen.blue () | pen.blue() << 8; + col.color.alpha = 0xffff; + col.pixel = pen.pixel(); + + for (int j = 0; j <= 16; j++) { + for (int i = 0; i <= 16; i++) { + + int x = i * cell.width(); + int y = j * cell.height(); + + x += ml; + y += mt; // plus ascent + + if (i == 0) { + if (j == 0) + continue; + p.setFont(kapp->font()); + QFontMetrics afm(kapp->font()); + QString s = QString("%1").arg(m_row * 256 + (j - 1) * 16); + p.drawText(x - afm.width(s), y, s); + p.setPen(QColor(190, 190, 255)); + p.drawLine(0, y, width(), y); + p.setPen(Qt::black); + continue; + } else if (j == 0) { + p.setFont(kapp->font()); + QString s = QString("%1").arg(i - 1); + p.drawText(x, y, s); + p.setPen(QColor(190, 190, 255)); + p.drawLine(x, 0, x, height()); + p.setPen(Qt::black); + continue; + } + + p.save(); + + if (m_glyphs) { + FT_UInt ui = m_row * 256 + (j - 1) * 16 + i - 1; + XftDrawGlyphs(draw, &col, (XftFont *)m_tableFont, x, y, &ui, 1); + } else { + FcChar32 ch = m_row * 256 + (j - 1) * 16 + i - 1; + if (XftCharExists(x11AppDisplay(), (XftFont *)m_tableFont, ch)) { + XftDrawString32(draw, &col, (XftFont *)m_tableFont, x, y, &ch, 1); + } + } + + p.restore(); + } + } +#endif +} + +bool +FontViewFrame::hasRow(int r) const +{ +#ifdef HAVE_XFT + if (m_glyphs) { + + if (r < 256) + return true; + + } else { + + for (int c = 0; c < 256; ++c) { + FcChar32 ch = r * 256 + c; + if (XftCharExists(x11AppDisplay(), (XftFont *)m_tableFont, ch)) { + return true; + } + } + } +#endif + return false; +} + +void FontViewFrame::setRow(int row) +{ + m_row = row; + update(); +} + +} +#include "FontViewFrame.moc" diff --git a/src/gui/editors/notation/FontViewFrame.h b/src/gui/editors/notation/FontViewFrame.h new file mode 100644 index 0000000..8a1a946 --- /dev/null +++ b/src/gui/editors/notation/FontViewFrame.h @@ -0,0 +1,77 @@ + +/* -*- 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. +*/ + +#ifndef _RG_FONTVIEWFRAME_H_ +#define _RG_FONTVIEWFRAME_H_ + +#include <qframe.h> +#include <qsize.h> +#include <qstring.h> + + +class QWidget; +class QPaintEvent; + + +namespace Rosegarden +{ + + + +class FontViewFrame : public QFrame +{ + Q_OBJECT + +public: + FontViewFrame(int pixelSize, QWidget *parent = 0, const char *name = 0); + virtual ~FontViewFrame(); + + QSize sizeHint() const; + bool hasRow(int row) const; + +public slots: + void setFont(QString name); + void setRow(int); + void setGlyphs(bool glyphs); + +protected: + QSize cellSize() const; + void paintEvent( QPaintEvent* ); + void loadFont(); + +private: + QString m_fontName; + int m_fontSize; + void *m_tableFont; + int m_row; + bool m_glyphs; +}; + + + + +} + +#endif diff --git a/src/gui/editors/notation/GuitarChordInserter.cpp b/src/gui/editors/notation/GuitarChordInserter.cpp new file mode 100644 index 0000000..2482b87 --- /dev/null +++ b/src/gui/editors/notation/GuitarChordInserter.cpp @@ -0,0 +1,185 @@ +/* -*- 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 "GuitarChordInserter.h" + +#include <klocale.h> +#include "base/Event.h" +#include "base/Exception.h" +#include "base/Staff.h" +#include "base/ViewElement.h" +#include "commands/notation/EraseEventCommand.h" +#include "commands/notation/GuitarChordInsertionCommand.h" +#include "gui/general/EditTool.h" +#include "gui/general/LinedStaff.h" +#include "gui/editors/guitar/GuitarChordSelectorDialog.h" +#include "misc/Debug.h" +#include "NotationElement.h" +#include "NotationTool.h" +#include "NotationView.h" +#include "NotePixmapFactory.h" +#include <kaction.h> +#include <qdialog.h> +#include <qiconset.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +GuitarChordInserter::GuitarChordInserter(NotationView* view) + : NotationTool("GuitarChordInserter", view), + m_guitarChordSelector(0) +{ + QIconSet icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("select"))); + + new KAction(i18n("Switch to Select Tool"), icon, 0, this, + SLOT(slotSelectSelected()), actionCollection(), + "select"); + + new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this, + SLOT(slotEraseSelected()), actionCollection(), + "erase"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("crotchet"))); + + new KAction(i18n("Switch to Inserting Notes"), icon, 0, this, + SLOT(slotNoteSelected()), actionCollection(), + "notes"); + + m_guitarChordSelector = new GuitarChordSelectorDialog(m_nParentView); + m_guitarChordSelector->init(); + createMenu("guitarchordinserter.rc"); +} + +void GuitarChordInserter::slotGuitarChordSelected() +{ + // Switch to last selected Guitar Chord + // m_nParentView->slotLastGuitarChordAction(); +} + +void GuitarChordInserter::slotEraseSelected() +{ + m_parentView->actionCollection()->action("erase")->activate(); +} + +void GuitarChordInserter::slotSelectSelected() +{ + m_parentView->actionCollection()->action("select")->activate(); +} + +void GuitarChordInserter::handleLeftButtonPress(timeT, + int, + int staffNo, + QMouseEvent* e, + ViewElement *element) +{ + NOTATION_DEBUG << "GuitarChordInserter::handleLeftButtonPress" << endl; + + if (staffNo < 0) { + return ; + } + + Staff *staff = m_nParentView->getStaff(staffNo); + + if (element && element->event()->isa(Guitar::Chord::EventType)) { + handleSelectedGuitarChord (element, staff); + } else { + createNewGuitarChord (element, staff, e); + } +} + +bool GuitarChordInserter::processDialog( Staff* staff, + timeT& insertionTime) +{ + bool result = false; + + if (m_guitarChordSelector->exec() == QDialog::Accepted) { + Guitar::Chord chord = m_guitarChordSelector->getChord(); + + GuitarChordInsertionCommand *command = + new GuitarChordInsertionCommand + (staff->getSegment(), insertionTime, chord); + + m_nParentView->addCommandToHistory(command); + result = true; + } + + return result; +} + +void GuitarChordInserter::handleSelectedGuitarChord (ViewElement* element, Staff *staff) +{ + NOTATION_DEBUG << "GuitarChordInserter::handleSelectedGuitarChord" << endl; + + + // Get time of where guitar chord is inserted + timeT insertionTime = element->event()->getAbsoluteTime(); // not getViewAbsoluteTime() + + // edit an existing guitar chord, if that's what we clicked on + try { + Guitar::Chord chord(*(element->event())); + + m_guitarChordSelector->setChord(chord); + + if ( processDialog( staff, insertionTime ) ) { + // Erase old guitar chord + EraseEventCommand *command = + new EraseEventCommand(staff->getSegment(), + element->event(), + false); + + m_nParentView->addCommandToHistory(command); + } + } catch (Exception e) {} +} + +void GuitarChordInserter::createNewGuitarChord (ViewElement* element, Staff *staff, QMouseEvent* e) +{ + NOTATION_DEBUG << "GuitarChordInserter::createNewGuitarChord" << endl; + Event *clef = 0, *key = 0; + + LinedStaff *s = dynamic_cast<LinedStaff *>(staff); + + NotationElementList::iterator closestElement = + s->getClosestElementToCanvasCoords(e->x(), (int)e->y(), + clef, key, false, -1); + + if (closestElement == staff->getViewElementList()->end()) { + return ; + } + + timeT insertionTime = (*closestElement)->event()->getAbsoluteTime(); // not getViewAbsoluteTime() + + processDialog( staff, insertionTime ); +} + +const QString GuitarChordInserter::ToolName = "guitarchordinserter"; + +} +#include "GuitarChordInserter.moc" diff --git a/src/gui/editors/notation/GuitarChordInserter.h b/src/gui/editors/notation/GuitarChordInserter.h new file mode 100644 index 0000000..3bd5660 --- /dev/null +++ b/src/gui/editors/notation/GuitarChordInserter.h @@ -0,0 +1,96 @@ + +/* -*- 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. +*/ + +#ifndef _RG_GUITAR_CHORD_INSERTER_H_ +#define _RG_GUITAR_CHORD_INSERTER_H_ + +#include "NotationTool.h" +#include <qstring.h> +#include "base/Event.h" + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class ViewElement; +class Staff; +class NotationView; +class GuitarChordSelectorDialog; + +/** + * This tool will insert guitar chord on mouse click events +*/ +class GuitarChordInserter : public NotationTool +{ + Q_OBJECT + + friend class NotationToolBox; + +public: + + virtual void handleLeftButtonPress(timeT t, + int height, + int staffNo, + QMouseEvent* e, + ViewElement *element); + +/* + virtual void handleMouseDoubleClick(timeT, + int height, int staffNo, + QMouseEvent*, + ViewElement* el); +*/ + + static const QString ToolName; + +protected slots: + void slotGuitarChordSelected(); + void slotEraseSelected(); + void slotSelectSelected(); + +protected: + GuitarChordSelectorDialog* m_guitarChordSelector; + + GuitarChordInserter(NotationView*); + +private: + void handleSelectedGuitarChord (ViewElement* element, + Staff *staff); + + void createNewGuitarChord (ViewElement* element, + Staff *staff, + QMouseEvent* e); + + bool processDialog (Staff *staff, + timeT& insertionTime); +}; + + +} + +#endif diff --git a/src/gui/editors/notation/HeadersGroup.cpp b/src/gui/editors/notation/HeadersGroup.cpp new file mode 100644 index 0000000..c0a2de0 --- /dev/null +++ b/src/gui/editors/notation/HeadersGroup.cpp @@ -0,0 +1,160 @@ +/* -*- 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]> + + This file is Copyright 2007-2008 + Yves Guillemot <[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 <limits> +#include <qsize.h> +#include <qwidget.h> +#include <qvbox.h> +#include <qlabel.h> + +#include "HeadersGroup.h" +#include "TrackHeader.h" +#include "NotationView.h" +#include "NotePixmapFactory.h" + + +namespace Rosegarden +{ + + +HeadersGroup:: +HeadersGroup(QWidget *parent, NotationView * nv, Composition * comp) : + QVBox(parent), + m_notationView(nv), + m_composition(comp), + m_usedHeight(0), + m_filler(0), + m_lastX(INT_MIN), + m_lastWidth(-1) +{ +} + +void +HeadersGroup::removeAllHeaders() +{ + TrackHeaderVector::iterator i; + for (i=m_headers.begin(); i!=m_headers.end(); i++) { + delete *i; + } + m_headers.erase(m_headers.begin(), m_headers.end()); + + if (m_filler) { + delete m_filler; + m_filler = 0; + } + m_usedHeight = 0; + m_lastWidth = -1; +} + +void +HeadersGroup::addHeader(int trackId, int height, int ypos, double xcur) +{ + TrackHeader * sh = new TrackHeader(this, trackId, height, ypos); + m_headers.push_back(sh); + m_usedHeight += height; +} + +void +HeadersGroup::completeToHeight(int height) +{ + if (height > m_usedHeight) { + if (!m_filler) m_filler = new QLabel(this); + m_filler->setFixedHeight(height - m_usedHeight); + } +} + +void +HeadersGroup::slotUpdateAllHeaders(int x, int y, bool force) +{ + // Minimum header width + int headerMinWidth = m_notationView->getHeadersTopFrameMinWidth(); + + // Maximum header width (may be overriden by clef and key width) + int headerMaxWidth = (m_notationView->getCanvasVisibleWidth() * 10) / 100; + + if ((x != m_lastX) || force) { + m_lastX = x; + TrackHeaderVector::iterator i; + int neededWidth = 0; + + // Pass 1 : get the max width needed + for (i=m_headers.begin(); i!=m_headers.end(); i++) { + int w = (*i)->lookAtStaff(x, headerMaxWidth); + if (w > neededWidth) neededWidth = w; + } + + if (neededWidth < headerMinWidth) neededWidth = headerMinWidth; + + // Only when m_lastWidth is valid (the first time, m_lastWidth = -1) + if (m_lastWidth > 0) { + // Don't redraw the headers when change of width is very small + const int treshold = 10; // Treshold value should be refined ... + int deltaWidth = m_lastWidth - neededWidth; + if ((deltaWidth < treshold) && (deltaWidth > -treshold)) + neededWidth = m_lastWidth; + } + + // Pass 2 : redraw the headers when necessary + for (i=m_headers.begin(); i!=m_headers.end(); i++) { + (*i)->updateHeader(neededWidth); + } + + if (neededWidth != m_lastWidth) { + setFixedWidth(neededWidth); + m_lastWidth = neededWidth; + + // Suppress vertical white stripes on canvas when headers + // width changes while scrolling + /// TODO : Limit "setChanged()" to the useful part of canvas + m_notationView->canvas()->setAllChanged(); + m_notationView->canvas()->update(); + } + } +} + + + + +void +HeadersGroup::setCurrent(TrackId trackId) +{ + TrackHeaderVector::iterator i; + for (i=m_headers.begin(); i!=m_headers.end(); i++) + (*i)->setCurrent((*i)->getId() == trackId); +} + +void +HeadersGroup::resizeEvent(QResizeEvent * ev) +{ + // Needed to avoid gray zone at the right of headers + // when width is decreasing + emit headersResized(ev->size().width()); +} + +} +#include "HeadersGroup.moc" diff --git a/src/gui/editors/notation/HeadersGroup.h b/src/gui/editors/notation/HeadersGroup.h new file mode 100644 index 0000000..22d25da --- /dev/null +++ b/src/gui/editors/notation/HeadersGroup.h @@ -0,0 +1,144 @@ + +/* -*- 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]> + + This file is Copyright 2007-2008 + Yves Guillemot <[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. +*/ + + +#ifndef _RG_HEADERSGROUP_H_ +#define _RG_HEADERSGROUP_H_ + +#include "base/Track.h" + +#include <vector> +#include <qsize.h> +#include <qwidget.h> +#include <qvbox.h> + + +class QLabel; +class QResizeEvent; + + +namespace Rosegarden +{ + + +class NotationView; +class Composition; +class TrackHeader; + + +class HeadersGroup : public QVBox +{ + Q_OBJECT +public: + /** + * Create an empty headers group + */ + HeadersGroup(QWidget *parent, NotationView * nv, Composition * comp); + + void removeAllHeaders(); + + void addHeader(int trackId, int height, int ypos, double xcur); + + /** + * Resize a filler at bottom of group to set the headersGroup height + * to the value specified in parameter. + * (Used to give to the headers group exactly the same height as the + * canvas. Necessary to get synchronous vertical scroll.) + */ + void completeToHeight(int height); + + NotationView * getNotationView() + { return m_notationView; + } + + Composition * getComposition() + { return m_composition; + } + + /** + * Return the total height of all the headers (without the filler). + */ + int getUsedHeight() + { return m_usedHeight; + } + + /** + * Highlight as "current" the header of the specified track. + */ + void setCurrent(TrackId trackId); + + /** + * Highlight as "current" the header of the specified track. + */ + int getWidth() + { + return m_lastWidth; + } + + typedef enum { ShowNever, ShowWhenNeeded, ShowAlways } ShowHeadersModeType; + + // Used to ensure to have one default value and only one. + static const ShowHeadersModeType DefaultShowMode = ShowAlways; + + // Useful in configuration dialog. + static bool isValidShowMode(int mode) + { + return ((mode >= ShowNever) && (mode <= ShowAlways)); + } + +public slots : + /** + * Called when notation canvas moves. + * Setting force to true forces the headers to be redrawn even + * if x has not changed since the last call. + */ + void slotUpdateAllHeaders(int x, int y, bool force = false); + +signals : + void headersResized(int newWidth); + +private: + void resizeEvent(QResizeEvent * ev); + + NotationView * m_notationView; + Composition * m_composition; + + typedef std::vector<TrackHeader *> TrackHeaderVector; + TrackHeaderVector m_headers; + + int m_usedHeight; + QLabel * m_filler; + int m_lastX; + int m_lastWidth; + +}; + + +} + +#endif diff --git a/src/gui/editors/notation/NotationCanvasView.cpp b/src/gui/editors/notation/NotationCanvasView.cpp new file mode 100644 index 0000000..55e63ac --- /dev/null +++ b/src/gui/editors/notation/NotationCanvasView.cpp @@ -0,0 +1,485 @@ +/* -*- 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 "NotationCanvasView.h" +#include "misc/Debug.h" + +#include "misc/Strings.h" +#include "gui/general/LinedStaffManager.h" +#include "gui/general/RosegardenCanvasView.h" +#include "gui/kdeext/QCanvasGroupableItem.h" +#include "gui/kdeext/QCanvasSimpleSprite.h" +#include "NotationElement.h" +#include "NotationProperties.h" +#include "NotationStaff.h" +#include <qcanvas.h> +#include <qcolor.h> +#include <qpainter.h> +#include <qpen.h> +#include <qpoint.h> +#include <qrect.h> +#include <qsize.h> +#include <qstring.h> +#include <qwidget.h> + + +namespace Rosegarden +{ + +NotationCanvasView::NotationCanvasView(const LinedStaffManager &staffmgr, + QCanvas *viewing, QWidget *parent, + const char *name, WFlags f) : + RosegardenCanvasView(viewing, parent, name, f), + m_linedStaffManager(staffmgr), + m_lastYPosNearStaff(0), + m_currentStaff(0), + m_currentHeight( -1000), + m_legerLineOffset(false), + m_heightTracking(false) +{ + // -- switching mandolin-sonatina first staff to page mode: + // default params (I think 16,100): render 1000ms position 1070ms + // 64,100: 1000ms 980ms + // 8, 100: 1140ms 1140ms + // 128, 100: 1060ms 980ms + // 256, 100: 1060ms 980ms / 930ms 920ms + + // canvas()->retune(256, 100); + + viewport()->setMouseTracking(true); + + m_heightMarker = new QCanvasItemGroup(viewing); + + m_vert1 = new QCanvasLineGroupable(viewing, m_heightMarker); + m_vert1->setPoints(0, 0, 0, 8); + m_vert1->setPen(QPen(QColor(64, 64, 64), 1)); + + m_vert2 = new QCanvasLineGroupable(viewing, m_heightMarker); + m_vert2->setPoints(17, 0, 17, 8); + m_vert2->setPen(QPen(QColor(64, 64, 64), 1)); + + m_heightMarker->hide(); +} + +NotationCanvasView::~NotationCanvasView() +{ + // All canvas items are deleted in ~NotationView() +} + +void +NotationCanvasView::setHeightTracking(bool t) +{ + m_heightTracking = t; + if (!t) { + m_heightMarker->hide(); + canvas()->update(); + } +} + +void +NotationCanvasView::contentsMouseReleaseEvent(QMouseEvent *e) +{ + emit mouseReleased(e); +} + +void +NotationCanvasView::contentsMouseMoveEvent(QMouseEvent *e) +{ + NotationStaff *prevStaff = m_currentStaff; + int prevHeight = m_currentHeight; + + m_currentStaff = dynamic_cast<NotationStaff *> + (m_linedStaffManager.getStaffForCanvasCoords(e->x(), e->y())); + + if (!m_currentStaff) { + + emit hoveredOverNoteChanged(QString::null); + if (prevStaff) { + m_heightMarker->hide(); + canvas()->update(); + } + + } else { + + m_currentHeight = m_currentStaff->getHeightAtCanvasCoords(e->x(), e->y()); + + int x = e->x() - 8; // magic based on mouse cursor size + bool needUpdate = (m_heightTracking && (m_heightMarker->x() != x)); + m_heightMarker->setX(x); + + if (prevStaff != m_currentStaff || + prevHeight != m_currentHeight) { + + if (m_heightTracking) { + setHeightMarkerHeight(e); + m_heightMarker->show(); + needUpdate = true; + } + + emit hoveredOverNoteChanged + (strtoqstr + (m_currentStaff->getNoteNameAtCanvasCoords(e->x(), e->y()))); + } + + if (needUpdate) + canvas()->update(); + } + + NotationElement *elt = getElementAtXCoord(e); + if (elt) { + emit hoveredOverAbsoluteTimeChanged(elt->getViewAbsoluteTime()); + } + + // if(tracking) ?? + emit mouseMoved(e); +} + +void NotationCanvasView::contentsMousePressEvent(QMouseEvent *e) +{ + NOTATION_DEBUG << "NotationCanvasView::contentsMousePressEvent() - btn : " + << e->button() << " - state : " << e->state() + << endl; + + QCanvasItemList itemList = canvas()->collisions(e->pos()); + + // We don't want to use m_currentStaff/Height, because we want + // to make sure the event happens at the point we clicked at + // rather than the last point for which contentsMouseMoveEvent + // happened to be called + + NotationStaff *staff = dynamic_cast<NotationStaff *> + (m_linedStaffManager.getStaffForCanvasCoords(e->x(), e->y())); + + QCanvasItemList::Iterator it; + NotationElement *clickedNote = 0; + NotationElement *clickedVagueNote = 0; + NotationElement *clickedNonNote = 0; + + bool haveClickHeight = false; + int clickHeight = 0; + if (staff) { + clickHeight = staff->getHeightAtCanvasCoords(e->x(), e->y()); + haveClickHeight = true; + } + + for (it = itemList.begin(); it != itemList.end(); ++it) { + + if ((*it)->active()) { + emit activeItemPressed(e, *it); + return ; + } + + QCanvasNotationSprite *sprite = + dynamic_cast<QCanvasNotationSprite*>(*it); + if (!sprite) { + if (dynamic_cast<QCanvasNonElementSprite *>(*it)) { + emit nonNotationItemPressed(e, *it); + return ; + } else if (dynamic_cast<QCanvasText *>(*it)) { + emit textItemPressed(e, *it); + return ; + } + continue; + } + + NotationElement &el = sprite->getNotationElement(); + + // #957364 (Notation: Hard to select upper note in chords of + // seconds) -- adjust x-coord for shifted note head + + double cx = el.getCanvasX(); + int nbw = 10; + + if (staff) { + nbw = staff->getNotePixmapFactory(false).getNoteBodyWidth(); + bool shifted = false; + + if (el.event()->get + <Bool> + (staff->getProperties().NOTE_HEAD_SHIFTED, shifted) && shifted) { + cx += nbw; + } + } + + if (el.isNote() && haveClickHeight) { + long eventHeight = 0; + if (el.event()->get + <Int> + (NotationProperties::HEIGHT_ON_STAFF, eventHeight)) { + + if (eventHeight == clickHeight) { + + if (!clickedNote && + e->x() >= cx && + e->x() <= cx + nbw) { + clickedNote = ⪙ + } else if (!clickedVagueNote && + e->x() >= cx - 2 && + e->x() <= cx + nbw + 2) { + clickedVagueNote = ⪙ + } + + } else if (eventHeight - 1 == clickHeight || + eventHeight + 1 == clickHeight) { + if (!clickedVagueNote) + clickedVagueNote = ⪙ + } + } + } else if (!el.isNote()) { + if (!clickedNonNote) + clickedNonNote = ⪙ + } + } + + int staffNo = -1; + if (staff) + staffNo = staff->getId(); + + if (clickedNote) + handleMousePress(clickHeight, staffNo, e, clickedNote); + else if (clickedNonNote) + handleMousePress(clickHeight, staffNo, e, clickedNonNote); + else if (clickedVagueNote) + handleMousePress(clickHeight, staffNo, e, clickedVagueNote); + else + handleMousePress(clickHeight, staffNo, e); +} + +void NotationCanvasView::contentsMouseDoubleClickEvent(QMouseEvent* e) +{ + NOTATION_DEBUG << "NotationCanvasView::contentsMouseDoubleClickEvent()\n"; + + contentsMousePressEvent(e); +} + +void +NotationCanvasView::processActiveItems(QMouseEvent* e, + QCanvasItemList itemList) +{ + QCanvasItem* pressedItem = 0; + QCanvasItemList::Iterator it; + + for (it = itemList.begin(); it != itemList.end(); ++it) { + + QCanvasItem *item = *it; + if (item->active() && !pressedItem) { + NOTATION_DEBUG << "mousepress : got active item\n"; + pressedItem = item; + } + } + + if (pressedItem) + emit activeItemPressed(e, pressedItem); + +} + +void +NotationCanvasView::handleMousePress(int height, + int staffNo, + QMouseEvent *e, + NotationElement *el) +{ + NOTATION_DEBUG << "NotationCanvasView::handleMousePress() at height " + << height << endl; + + emit itemPressed(height, staffNo, e, el); +} + +bool +NotationCanvasView::posIsTooFarFromStaff(const QPoint &pos) +{ + // return true if pos.y is more than m_staffLineThreshold away from + // the last pos for which a collision was detected + // + return (pos.y() > m_lastYPosNearStaff) ? + (pos.y() - m_lastYPosNearStaff) > (int)m_staffLineThreshold : + (m_lastYPosNearStaff - pos.y()) > (int)m_staffLineThreshold; + +} + +int +NotationCanvasView::getLegerLineCount(int height, bool &offset) +{ + //!!! This is far too specifically notation-related to be here, really + + if (height < 0) { + + offset = (( -height % 2) == 1); + return height / 2; + + } else if (height > 8) { + + offset = ((height % 2) == 1); + return (height - 8) / 2; + } + + return 0; +} + +void +NotationCanvasView::setHeightMarkerHeight(QMouseEvent *e) +{ + NotationStaff *staff = dynamic_cast<NotationStaff *> + (m_linedStaffManager.getStaffForCanvasCoords(e->x(), e->y())); + + int height = staff->getHeightAtCanvasCoords(e->x(), e->y()); + int lineY = staff->getCanvasYForHeight(height, e->x(), e->y()); + + // NOTATION_DEBUG << "NotationCanvasView::setHeightMarkerHeight: " + // << e->y() << " snapped to line -> " << lineY + // << " (height " << height << ")" << endl; + + int spacing = staff->getLineSpacing() - 1; + + m_staffLineThreshold = spacing; + m_vert1->setPoints(0, -spacing / 2, 0, spacing / 2); + m_vert2->setPoints(17, -spacing / 2, 17, spacing / 2); // magic based on mouse cursor size + m_heightMarker->setY(lineY); + + bool legerLineOffset = false; + int legerLineCount = getLegerLineCount(height, legerLineOffset); + + if (legerLineCount != (int)m_legerLines.size() || + legerLineOffset != m_legerLineOffset) { + + bool above = false; + if (legerLineCount < 0) { + above = true; + legerLineCount = -legerLineCount; + } + + int i; + for (i = 0; i < (int)m_legerLines.size(); ++i) { + delete m_legerLines[i]; + } + m_legerLines.clear(); + + for (i = 0; i < legerLineCount; ++i) { + + QCanvasLineGroupable *line = + new QCanvasLineGroupable(canvas(), m_heightMarker); + + line->setPen(QPen(QColor(64, 64, 64), 1)); + + int y = (int)m_heightMarker->y() + + (above ? -1 : 1) * (i * (spacing + 1)); + int x = (int)m_heightMarker->x() + 1; + + if (legerLineOffset) { + if (above) + y -= spacing / 2 + 1; + else + y += spacing / 2; + } + + line->setPoints(x, y, x + 15, y); // magic based on mouse cursor size + m_legerLines.push_back(line); + } + + m_legerLineOffset = legerLineOffset; + } +} + +NotationElement * +NotationCanvasView::getElementAtXCoord(QMouseEvent *e) // any old element +{ + QRect threshold(e->pos(), QSize(4, 100)); //!!! + threshold.moveCenter(e->pos()); + + QCanvasItemList itemList = canvas()->collisions(threshold); + + QCanvasItemList::Iterator it; + QCanvasNotationSprite* sprite = 0; + + for (it = itemList.begin(); it != itemList.end(); ++it) + { + + QCanvasItem *item = *it; + + if ((sprite = dynamic_cast<QCanvasNotationSprite*>(item))) { + return & (sprite->getNotationElement()); + } + } + + return 0; +} + +void +NotationCanvasView::viewportPaintEvent(QPaintEvent *e) +{ + int cx(e->rect().x()), + cy(e->rect().y()), + cw(e->rect().width()) /*, + ch(e->rect().height())*/; + // NOTATION_DEBUG << "NotationCanvasView::viewportPaintEvent: (" << cx << "," + // << cy << ") size (" << cw << "x" << ch << ")" << endl; + QCanvasView::viewportPaintEvent(e); + + cx += contentsX(); + cy += contentsY(); + m_lastRender = e->rect(); + emit renderRequired(std::min(contentsX(), cx), + std::max(contentsX() + visibleWidth(), cx + cw)); +} + +void +NotationCanvasView::drawContents(QPainter *p, int cx, int cy, int cw, int ch) +{ + /* + m_lastRender = QRect(cx, cy, cw, ch); + NOTATION_DEBUG << "NotationCanvasView::drawContents: (" << cx << "," + << cy << ") size (" << cw << "x" << ch << ")" << endl; + */ + QCanvasView::drawContents(p, cx, cy, cw, ch); + /* + emit renderRequired(std::min(contentsX(), cx), + std::max(contentsX() + visibleWidth(), cx + cw)); + */ +} + +void +NotationCanvasView::slotRenderComplete() +{ + /* QPainter painter(viewport()); + int cx(m_lastRender.x()), + cy(m_lastRender.y()), + cw(m_lastRender.width()), + ch(m_lastRender.height()); + NOTATION_DEBUG << "NotationCanvasView::slotRenderComplete: (" << cx << "," + << cy << ") size (" << cw << "x" << ch << ")" << endl; + QCanvasView::drawContents(&painter, cx, cy, cw, ch); + */ + QPaintEvent ev(m_lastRender); + QCanvasView::viewportPaintEvent(&ev); +} + +void +NotationCanvasView::slotExternalWheelEvent(QWheelEvent* e) +{ + wheelEvent(e); +} + +} +#include "NotationCanvasView.moc" diff --git a/src/gui/editors/notation/NotationCanvasView.h b/src/gui/editors/notation/NotationCanvasView.h new file mode 100644 index 0000000..5c88fb0 --- /dev/null +++ b/src/gui/editors/notation/NotationCanvasView.h @@ -0,0 +1,218 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONCANVASVIEW_H_ +#define _RG_NOTATIONCANVASVIEW_H_ + +#include "gui/general/RosegardenCanvasView.h" +#include <qrect.h> +#include <vector> + + +class QWidget; +class QString; +class QPoint; +class QPaintEvent; +class QPainter; +class QMouseEvent; +class QCanvasLineGroupable; +class QCanvasItemGroup; +class QCanvasItem; +class QCanvas; + + +namespace Rosegarden +{ + +class NotationStaff; +class NotationElement; +class LinedStaffManager; + + +/** + * Central widget for the NotationView window + * + * This class only takes care of the event handling + * (see the various signals). + * + * It does not deal with any canvas element. All elements are added by + * the NotationView. + * + *@see NotationView + */ + +class NotationCanvasView : public RosegardenCanvasView +{ + Q_OBJECT + +public: + NotationCanvasView(const LinedStaffManager &staffmgr, + QCanvas *viewing, QWidget *parent=0, + const char *name=0, WFlags f=0); + + ~NotationCanvasView(); + + void setHeightTracking(bool t); + +signals: + + /** + * Emitted when the user clicks on a staff (e.g. mouse button press) + * \a pitch is set to the MIDI pitch on which the click occurred + * \a staffNo is set to the staff on which the click occurred + * \a point is set to the coordinates of the click event + * \a el points to the NotationElement which was clicked on, if any + */ + void itemPressed(int pitch, int staffNo, + QMouseEvent*, + NotationElement* el); + + /** + * Emitted when the user clicks on a QCanvasItem which is active + * + * @see QCanvasItem#setActive + */ + void activeItemPressed(QMouseEvent*, + QCanvasItem* item); + + /** + * Emitted when the user clicks on a QCanvasItem which is neither + * active nor a notation element + */ + void nonNotationItemPressed(QMouseEvent *, + QCanvasItem *); + + /** + * Emitted when the user clicks on a QCanvasItem which is a + * plain QCanvasText + */ + void textItemPressed(QMouseEvent *, + QCanvasItem *); + + /** + * Emitted when the mouse cursor moves to a different height + * on the staff + * + * \a noteName contains the MIDI name of the corresponding note + */ + void hoveredOverNoteChanged(const QString ¬eName); + + /** + * Emitted when the mouse cursor moves to a note which is at a + * different time + * + * \a time is set to the absolute time of the note the cursor is + * hovering on + */ + void hoveredOverAbsoluteTimeChanged(unsigned int time); + + /** + * Emitted when the mouse cursor moves (used by the selection tool) + */ + void mouseMoved(QMouseEvent*); + + /** + * Emitted when the mouse button is released + */ + void mouseReleased(QMouseEvent*); + + /** + * Emitted when a region is about to be drawn by the canvas view. + * Indicates that any on-demand rendering for that region should + * be carried out. + */ + void renderRequired(double cx0, double cx1); + +public slots: + void slotRenderComplete(); + + void slotExternalWheelEvent(QWheelEvent* e); + +protected: + + virtual void viewportPaintEvent(QPaintEvent *e); + virtual void drawContents(QPainter *p, int cx, int cy, int cw, int ch); + + const LinedStaffManager &m_linedStaffManager; + + /** + * Callback for a mouse button press event in the canvas + */ + virtual void contentsMousePressEvent(QMouseEvent*); + + /** + * Callback for a mouse button release event in the canvas + */ + virtual void contentsMouseReleaseEvent(QMouseEvent*); + + /** + * Callback for a mouse move event in the canvas + */ + virtual void contentsMouseMoveEvent(QMouseEvent*); + + /** + * Callback for a mouse double click event in the canvas + */ + virtual void contentsMouseDoubleClickEvent(QMouseEvent*); + + void processActiveItems(QMouseEvent*, QCanvasItemList); + + void handleMousePress(int height, int staffNo, + QMouseEvent*, + NotationElement* pressedNotationElement = 0); + + bool posIsTooFarFromStaff(const QPoint &pos); + + int getLegerLineCount(int height, bool &offset); + + void setHeightMarkerHeight(QMouseEvent *e); + + NotationElement *getElementAtXCoord(QMouseEvent *e); + + //--------------- Data members --------------------------------- + + int m_lastYPosNearStaff; + + unsigned int m_staffLineThreshold; + + NotationStaff *m_currentStaff; + int m_currentHeight; + + QCanvasItemGroup *m_heightMarker; + QCanvasLineGroupable *m_vert1; + QCanvasLineGroupable *m_vert2; + std::vector<QCanvasLineGroupable *> m_legerLines; + bool m_legerLineOffset; + + bool m_heightTracking; + + QRect m_lastRender; +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/NotationChord.cpp b/src/gui/editors/notation/NotationChord.cpp new file mode 100644 index 0000000..7b0a263 --- /dev/null +++ b/src/gui/editors/notation/NotationChord.cpp @@ -0,0 +1,335 @@ +/* -*- 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 "NotationChord.h" + +#include "base/Sets.h" +#include "base/Event.h" +#include "base/NotationRules.h" +#include "base/NotationTypes.h" +#include "base/Quantizer.h" +#include "NotationProperties.h" +#include "NoteStyleFactory.h" + +namespace Rosegarden +{ + +template <> +Event * +AbstractSet<NotationElement, NotationElementList>::getAsEvent(const NotationElementList::iterator &i) +{ + return (*i)->event(); +} + +NotationChord::NotationChord(NotationElementList &c, + NotationElementList::iterator i, + const Quantizer *quantizer, + const NotationProperties &properties, + const Clef &clef, + const ::Rosegarden::Key &key) : + GenericChord < NotationElement, + NotationElementList, true > (c, i, quantizer, + NotationProperties::STEM_UP), + m_properties(properties), + m_clef(clef), + m_key(key) +{ + // nothing else +} + +int +NotationChord::getHeight(const Iterator &i) const +{ + //!!! We use HEIGHT_ON_STAFF in preference to the passed clef/key, + //but what if the clef/key changed since HEIGHT_ON_STAFF was + //written? Who updates the properties then? Check this. + + long h = 0; + if (getAsEvent(i)->get + <Int>(NotationProperties::HEIGHT_ON_STAFF, h)) { + return h; + } + + try { + Pitch pitch(*getAsEvent(i)); + h = pitch.getHeightOnStaff(m_clef, m_key); + } catch (...) { + // no pitch! + } + + // set non-persistent, not setMaybe, as we know the property is absent: + getAsEvent(i)->set + <Int>(NotationProperties::HEIGHT_ON_STAFF, h, false); + return h; +} + +bool +NotationChord::hasStem() const +{ + // true if any of the notes is stemmed + + Iterator i(getInitialNote()); + for (;;) { + long note; + if (!getAsEvent(i)->get + <Int>(BaseProperties::NOTE_TYPE, note)) return true; + if (NoteStyleFactory::getStyleForEvent(getAsEvent(i))->hasStem(note)) + return true; + if (i == getFinalNote()) + return false; + ++i; + } + return false; +} + +bool +NotationChord::hasStemUp() const +{ + NotationRules rules; + + // believe anything found in any of the notes, if in a persistent + // property or a property apparently set by the beaming algorithm + + Iterator i(getInitialNote()); + + for (;;) { + Event *e = getAsEvent(i); + /*!!! + if (e->has(m_properties.VIEW_LOCAL_STEM_UP)) { + return e->get<Bool>(m_properties.VIEW_LOCAL_STEM_UP); + } + */ + if (e->has(NotationProperties::STEM_UP)) { + return e->get + <Bool>(NotationProperties::STEM_UP); + } + + if (e->has(NotationProperties::BEAM_ABOVE)) { + if (e->has(NotationProperties::BEAMED) && + e->get + <Bool>(NotationProperties::BEAMED)) { + return e->get + <Bool>(NotationProperties::BEAM_ABOVE); + } + else { + return !e->get + <Bool>(NotationProperties::BEAM_ABOVE); + } + } + + if (i == getFinalNote()) + break; + ++i; + } + + return rules.isStemUp(getHighestNoteHeight(),getLowestNoteHeight()); +} + +bool +NotationChord::hasNoteHeadShifted() const +{ + int ph = 10000; + + for (unsigned int i = 0; i < size(); ++i) { + int h = getHeight((*this)[i]); + if (h == ph + 1) + return true; + ph = h; + } + + return false; +} + +bool +NotationChord::isNoteHeadShifted(const Iterator &itr) const +{ + unsigned int i; + for (i = 0; i < size(); ++i) { + if ((*this)[i] == itr) + break; + } + + if (i == size()) { + std::cerr << "NotationChord::isNoteHeadShifted: Warning: Unable to find note head " << getAsEvent(itr) << std::endl; + return false; + } + + int h = getHeight((*this)[i]); + + if (hasStemUp()) { + if ((i > 0) && (h == getHeight((*this)[i - 1]) + 1)) { + return (!isNoteHeadShifted((*this)[i - 1])); + } + } else { + if ((i < size() - 1) && (h == getHeight((*this)[i + 1]) - 1)) { + return (!isNoteHeadShifted((*this)[i + 1])); + } + } + + return false; +} + +void +NotationChord::applyAccidentalShiftProperties() +{ + // Some rules: + // + // The top accidental always gets the minimum shift (i.e. normally + // right next to the note head or stem). + // + // The bottom accidental gets the next least: the same, if the + // interval is more than a sixth, or the next shift out otherwise. + // + // We then progress up from the bottom accidental upwards. + // + // These rules aren't really enough, but they might do for now! + + //!!! Uh-oh... we have a catch-22 here. We can't determine the + // proper minimum shift until we know which way the stem goes, + // because if we have a shifted note head and the stem goes down, + // we need to shift one place further than otherwise. But we + // don't know for sure which way the stem goes until we've + // calculated the beam, and we don't do that until after we've + // worked out the x-coordinates based on (among other things) the + // accidental shift. + + int minShift = 0; + bool extra = false; + + if (!hasStemUp() && hasNoteHeadShifted()) { + minShift = 1; // lazy + extra = true; + } + + int lastShift = minShift; + int lastHeight = 0, maxHeight = 999; + int lastWidth = 1; + + for (iterator i = end(); i != begin(); ) { + + --i; + Event *e = getAsEvent(*i); + + Accidental acc; + if (e->get + <String>(m_properties.DISPLAY_ACCIDENTAL, acc) && + acc != Accidentals::NoAccidental) { + e->setMaybe<Int>(m_properties.ACCIDENTAL_SHIFT, minShift); + e->setMaybe<Bool>(m_properties.ACCIDENTAL_EXTRA_SHIFT, extra); + maxHeight = lastHeight = getHeight(*i); + break; + } + } + + if (maxHeight == 999) { + return ; + } + + for (iterator i = begin(); i != end(); ++i) { + + Event *e = getAsEvent(*i); + int height = getHeight(*i); + + if (height == maxHeight && e->has(m_properties.ACCIDENTAL_SHIFT)) { + // finished -- we've come around to the highest one again + break; + } + + Accidental acc; + + if (e->get + <String>(m_properties.DISPLAY_ACCIDENTAL, acc) && + acc != Accidentals::NoAccidental) { + + int shift = lastShift; + + if (height < lastHeight) { // lastHeight was the first, up top + if (lastHeight - height < 6) { + shift = lastShift + lastWidth; + } + } else { + if (height - lastHeight >= 6) { + if (maxHeight - height >= 6) { + shift = minShift; + } else { + shift = minShift + 1; + } + } else { + shift = lastShift + lastWidth; + } + } + + e->setMaybe<Int>(m_properties.ACCIDENTAL_SHIFT, shift); + + lastHeight = height; + lastShift = shift; + + lastWidth = 1; + bool c = false; + if (e->get + <Bool>(m_properties.DISPLAY_ACCIDENTAL_IS_CAUTIONARY, c) + && c) { + lastWidth = 2; + } + } + } +} + +int +NotationChord::getMaxAccidentalShift(bool &extra) const +{ + int maxShift = 0; + + for (const_iterator i = begin(); i != end(); ++i) { + Event *e = getAsEvent(*i); + if (e->has(m_properties.ACCIDENTAL_SHIFT)) { + int shift = e->get + <Int>(m_properties.ACCIDENTAL_SHIFT); + if (shift > maxShift) { + maxShift = shift; + e->get + <Bool>(m_properties.ACCIDENTAL_EXTRA_SHIFT, extra); + } + } + } + + return maxShift; +} + +int +NotationChord::getAccidentalShift(const Iterator &i, bool &extra) const +{ + if (getAsEvent(i)->has(m_properties.ACCIDENTAL_SHIFT)) { + int shift = getAsEvent(i)->get + <Int>(m_properties.ACCIDENTAL_SHIFT); + getAsEvent(i)->get + <Bool>(m_properties.ACCIDENTAL_EXTRA_SHIFT, extra); + return shift; + } else { + return 0; + } +} + +} diff --git a/src/gui/editors/notation/NotationChord.h b/src/gui/editors/notation/NotationChord.h new file mode 100644 index 0000000..7ce12fd --- /dev/null +++ b/src/gui/editors/notation/NotationChord.h @@ -0,0 +1,90 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONCHORD_H_ +#define _RG_NOTATIONCHORD_H_ + +#include "base/NotationTypes.h" +#include "base/Sets.h" +#include "NotationElement.h" + +class Iterator; + + +namespace Rosegarden +{ + +class Quantizer; +class NotationProperties; + + +class NotationChord : public GenericChord<NotationElement, + NotationElementList, + true> +{ +public: + NotationChord(NotationElementList &c, + NotationElementList::iterator i, + const Quantizer *quantizer, + const NotationProperties &properties, + const Clef &clef = Clef::DefaultClef, + const Key &key = Key::DefaultKey); + + virtual ~NotationChord() { } + + virtual int getHighestNoteHeight() const { + return getHeight(getHighestNote()); + } + virtual int getLowestNoteHeight() const { + return getHeight(getLowestNote()); + } + + virtual bool hasStem() const; + virtual bool hasStemUp() const; + + virtual bool hasNoteHeadShifted() const; + virtual bool isNoteHeadShifted(const NotationElementList::iterator &itr) + const; + + void applyAccidentalShiftProperties(); + + virtual int getMaxAccidentalShift(bool &extra) const; + virtual int getAccidentalShift(const NotationElementList::iterator &itr, + bool &extra) const; + +protected: + const NotationProperties &m_properties; + Clef m_clef; + Key m_key; + + + int getHeight(const Iterator&) const; +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/NotationElement.cpp b/src/gui/editors/notation/NotationElement.cpp new file mode 100644 index 0000000..7df1cd5 --- /dev/null +++ b/src/gui/editors/notation/NotationElement.cpp @@ -0,0 +1,198 @@ +/* -*- 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 "NotationElement.h" +#include "misc/Debug.h" + +#include "base/BaseProperties.h" +#include "base/Event.h" +#include "base/Exception.h" +#include "base/NotationTypes.h" +#include "base/ViewElement.h" + +#include <qcanvas.h> + +namespace Rosegarden +{ + +NotationElement::NotationElement(Event *event) + : ViewElement(event), + m_recentlyRegenerated(false), + m_isColliding(false), + m_canvasItem(0), + m_extraItems(0) +{ + // NOTATION_DEBUG << "new NotationElement " + // << this << " wrapping " << event << endl; +} + +NotationElement::~NotationElement() +{ + removeCanvasItem(); +} + +timeT +NotationElement::getViewAbsoluteTime() const +{ + return event()->getNotationAbsoluteTime(); +} + +timeT +NotationElement::getViewDuration() const +{ + return event()->getNotationDuration(); +} + +double +NotationElement::getCanvasX() +{ + if (m_canvasItem) + return m_canvasItem->x(); + else { + std::cerr << "ERROR: No canvas item for this notation element:"; + event()->dump(std::cerr); + throw NoCanvasItem("No canvas item for notation element of type " + + event()->getType(), __FILE__, __LINE__); + } +} + +double +NotationElement::getCanvasY() +{ + if (m_canvasItem) + return m_canvasItem->y(); + else { + std::cerr << "ERROR: No canvas item for this notation element:"; + event()->dump(std::cerr); + throw NoCanvasItem("No canvas item for notation element of type " + + event()->getType(), __FILE__, __LINE__); + } +} + +bool +NotationElement::isRest() const +{ + return event()->isa(Note::EventRestType); +} + +bool +NotationElement::isNote() const +{ + return event()->isa(Note::EventType); +} + +bool +NotationElement::isTuplet() const +{ + return event()->has(BaseProperties::BEAMED_GROUP_TUPLET_BASE); +} + +bool +NotationElement::isGrace() const +{ + return event()->has(BaseProperties::IS_GRACE_NOTE) && + event()->get + <Bool>(BaseProperties::IS_GRACE_NOTE); +} + +void +NotationElement::setCanvasItem(QCanvasItem *e, double canvasX, double canvasY) +{ + removeCanvasItem(); + m_recentlyRegenerated = true; + m_canvasItem = e; + e->move(canvasX, canvasY); +} + +void +NotationElement::addCanvasItem(QCanvasItem *e, double canvasX, double canvasY) +{ + if (!m_canvasItem) { + std::cerr << "ERROR: Attempt to add extra canvas item to element without main canvas item:"; + event()->dump(std::cerr); + throw NoCanvasItem("No canvas item for notation element of type " + + event()->getType(), __FILE__, __LINE__); + } + if (!m_extraItems) { + m_extraItems = new ItemList; + } + e->move(canvasX, canvasY); + m_extraItems->push_back(e); +} + +void +NotationElement::removeCanvasItem() +{ + m_recentlyRegenerated = false; + + delete m_canvasItem; + m_canvasItem = 0; + + if (m_extraItems) { + + for (ItemList::iterator i = m_extraItems->begin(); + i != m_extraItems->end(); ++i) + delete *i; + m_extraItems->clear(); + + delete m_extraItems; + m_extraItems = 0; + } +} + +void +NotationElement::reposition(double canvasX, double canvasY) +{ + m_recentlyRegenerated = false; + if (!m_canvasItem) + return ; + + double dx = canvasX - m_canvasItem->x(); + double dy = canvasY - m_canvasItem->y(); + m_canvasItem->move(canvasX, canvasY); + + if (m_extraItems) { + for (ItemList::iterator i = m_extraItems->begin(); + i != m_extraItems->end(); ++i) { + (*i)->moveBy(dx, dy); + } + } +} + +bool +NotationElement::isSelected() +{ + return m_canvasItem ? m_canvasItem->selected() : false; +} + +void +NotationElement::setSelected(bool selected) +{ + m_recentlyRegenerated = false; + if (m_canvasItem) + m_canvasItem->setSelected(selected); +} + +} diff --git a/src/gui/editors/notation/NotationElement.h b/src/gui/editors/notation/NotationElement.h new file mode 100644 index 0000000..c756641 --- /dev/null +++ b/src/gui/editors/notation/NotationElement.h @@ -0,0 +1,176 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONELEMENT_H_ +#define _RG_NOTATIONELEMENT_H_ + +#include "base/Exception.h" +#include "base/ViewElement.h" +#include <vector> +#include "base/Event.h" + + +class QCanvasItem; +class ItemList; + + +namespace Rosegarden +{ + +class Event; + + +/** + * The Notation H and V layout is performed on a + * NotationElementList. Once this is done, each NotationElement is + * affected a QCanvasItem which is set at these coords. + * + * @see NotationView#showElements() + */ + +class NotationElement : public ViewElement +{ +public: + typedef Exception NoCanvasItem; + + NotationElement(Event *event); + + ~NotationElement(); + + virtual timeT getViewAbsoluteTime() const; + virtual timeT getViewDuration() const; + + void getLayoutAirspace(double &x, double &width) { + x = m_airX; + width = m_airWidth; + } + + void getCanvasAirspace(double &x, double &width) { + x = m_airX - getLayoutX() + getCanvasX(); + width = m_airWidth; + } + + /// returns the x pos of the associated canvas item + double getCanvasX(); + + /// returns the y pos of the associated canvas item + double getCanvasY(); + + /** + * Sets the X coordinate and width of the space "underneath" + * this element, i.e. the extents within which a mouse click + * or some such might be considered to be interested in this + * element as opposed to any other. These are layout coords + */ + void setLayoutAirspace(double x, double width) { + m_airX = x; m_airWidth = width; + } + + /// Returns true if the wrapped event is a rest + bool isRest() const; + + /// Returns true if the wrapped event is a note + bool isNote() const; + + /// Returns true if the wrapped event is a tuplet + bool isTuplet() const; + + /// Returns true if the wrapped event is a grace note + bool isGrace() const; + + /** + * Sets the canvas item representing this notation element on screen. + * + * NOTE: The object takes ownership of its canvas item. + */ + void setCanvasItem(QCanvasItem *e, double canvasX, double canvasY); + + /** + * Add an extra canvas item associated with this element, for + * example where an element has been split across two or more + * staff rows. + * + * The element will take ownership of these canvas items and + * delete them when it deletes the main canvas item. + */ + void addCanvasItem(QCanvasItem *e, double canvasX, double canvasY); + + /** + * Remove the main canvas item and any additional ones. + */ + void removeCanvasItem(); + + /** + * Reset the position of the canvas item (which is assumed to + * exist already). + */ + void reposition(double canvasX, double canvasY); + + /** + * Return true if setCanvasItem has been called more recently + * than reposition. If true, any code that positions this + * element will probably not need to regenerate its sprite as + * well, even if other indications suggest otherwise. + */ + bool isRecentlyRegenerated() { return m_recentlyRegenerated; } + + bool isSelected(); + void setSelected(bool selected); + + /** + * Return true if the element is a note which lies at the exactly + * same place than another note. + * Only valid after NotationVLayout::scanStaff() call. + * Only a returned true is meaningful (when 2 notes are colliding, the + * first element returns false and the second one returns true). + */ + bool isColliding() { return m_isColliding; } + + void setIsColliding(bool value) { m_isColliding = value; } + + /// Returns the associated canvas item + QCanvasItem* getCanvasItem() { return m_canvasItem; } + +protected: + //--------------- Data members --------------------------------- + + double m_airX; + double m_airWidth; + bool m_recentlyRegenerated; + bool m_isColliding; + + QCanvasItem *m_canvasItem; + + typedef std::vector<QCanvasItem *> ItemList; + ItemList *m_extraItems; +}; + +typedef ViewElementList NotationElementList; + + + +} + +#endif diff --git a/src/gui/editors/notation/NotationEraser.cpp b/src/gui/editors/notation/NotationEraser.cpp new file mode 100644 index 0000000..4124e44 --- /dev/null +++ b/src/gui/editors/notation/NotationEraser.cpp @@ -0,0 +1,115 @@ +/* -*- 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 "NotationEraser.h" +#include <kapplication.h> + +#include <klocale.h> +#include "document/ConfigGroups.h" +#include "base/ViewElement.h" +#include "commands/notation/EraseEventCommand.h" +#include "gui/general/EditTool.h" +#include "NotationTool.h" +#include "NotationView.h" +#include "NotePixmapFactory.h" +#include <kaction.h> +#include <kconfig.h> +#include <qiconset.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +NotationEraser::NotationEraser(NotationView* view) + : NotationTool("NotationEraser", view), + m_collapseRest(false) +{ + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + m_collapseRest = config->readBoolEntry("collapse", false); + + new KToggleAction(i18n("Collapse rests after erase"), 0, this, + SLOT(slotToggleRestCollapse()), actionCollection(), + "toggle_rest_collapse"); + + QIconSet icon + (NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("crotchet"))); + new KAction(i18n("Switch to Insert Tool"), icon, 0, this, + SLOT(slotInsertSelected()), actionCollection(), + "insert"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("select"))); + new KAction(i18n("Switch to Select Tool"), icon, 0, this, + SLOT(slotSelectSelected()), actionCollection(), + "select"); + + createMenu("notationeraser.rc"); +} + +void NotationEraser::ready() +{ + m_nParentView->setCanvasCursor(Qt::pointingHandCursor); + m_nParentView->setHeightTracking(false); +} + +void NotationEraser::handleLeftButtonPress(timeT, + int, + int staffNo, + QMouseEvent*, + ViewElement* element) +{ + if (!element || staffNo < 0) + return ; + + EraseEventCommand *command = + new EraseEventCommand(m_nParentView->getStaff(staffNo)->getSegment(), + element->event(), + m_collapseRest); + + m_nParentView->addCommandToHistory(command); +} + +void NotationEraser::slotToggleRestCollapse() +{ + m_collapseRest = !m_collapseRest; +} + +void NotationEraser::slotInsertSelected() +{ + m_nParentView->slotLastNoteAction(); +} + +void NotationEraser::slotSelectSelected() +{ + m_parentView->actionCollection()->action("select")->activate(); +} + +const QString NotationEraser::ToolName = "notationeraser"; + +} +#include "NotationEraser.moc" diff --git a/src/gui/editors/notation/NotationEraser.h b/src/gui/editors/notation/NotationEraser.h new file mode 100644 index 0000000..9efdd13 --- /dev/null +++ b/src/gui/editors/notation/NotationEraser.h @@ -0,0 +1,81 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONERASER_H_ +#define _RG_NOTATIONERASER_H_ + +#include "NotationTool.h" +#include <qstring.h> +#include "base/Event.h" + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class ViewElement; +class NotationView; + + +/** + * This tool will erase a note on mouse click events + */ +class NotationEraser : public NotationTool +{ + Q_OBJECT + + friend class NotationToolBox; + +public: + + virtual void ready(); + + virtual void handleLeftButtonPress(timeT, + int height, + int staffNo, + QMouseEvent*, + ViewElement* el); + static const QString ToolName; + +public slots: + void slotToggleRestCollapse(); + + void slotInsertSelected(); + void slotSelectSelected(); + +protected: + NotationEraser(NotationView*); + + //--------------- Data members --------------------------------- + + bool m_collapseRest; +}; + + +} + +#endif diff --git a/src/gui/editors/notation/NotationGroup.cpp b/src/gui/editors/notation/NotationGroup.cpp new file mode 100644 index 0000000..78525d9 --- /dev/null +++ b/src/gui/editors/notation/NotationGroup.cpp @@ -0,0 +1,979 @@ +/* -*- 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 "NotationGroup.h" +#include "misc/Debug.h" + +#include "base/Equation.h" +#include "base/Event.h" +#include "base/NotationRules.h" +#include "base/NotationTypes.h" +#include "base/Quantizer.h" +#include "NotationChord.h" +#include "NotationElement.h" +#include "NotationProperties.h" +#include "NotationStaff.h" +#include "NoteStyleFactory.h" +#include "NotePixmapFactory.h" + + +namespace Rosegarden +{ + +NotationGroup::NotationGroup(NotationElementList &nel, + NELIterator i, const Quantizer *q, + std::pair<timeT, timeT> barRange, + const NotationProperties &p, + const Clef &clef, const Key &key) : + AbstractSet<NotationElement, NotationElementList>(nel, i, q), + m_barRange(barRange), + //!!! What if the clef and/or key change in the course of the group? + m_clef(clef), + m_key(key), + m_weightAbove(0), + m_weightBelow(0), + m_userSamples(false), + m_type(Beamed), + m_properties(p) +{ + if (!(*i)->event()->get<Int> + (BaseProperties::BEAMED_GROUP_ID, m_groupNo)) m_groupNo = -1; + + initialise(); + + /* + NOTATION_DEBUG << "NotationGroup::NotationGroup: id is " << m_groupNo << endl; + i = getInitialElement(); + while (i != getContainer().end()) { + long gid = -1; + (*i)->event()->get<Int>(BEAMED_GROUP_ID, gid); + NOTATION_DEBUG << "Found element with group id " + << gid << endl; + if (i == getFinalElement()) break; + ++i; + } + */ +} + +NotationGroup::NotationGroup(NotationElementList &nel, + const Quantizer *q, + const NotationProperties &p, + const Clef &clef, const Key &key) : + AbstractSet<NotationElement, NotationElementList>(nel, nel.end(), q), + m_barRange(0, 0), + //!!! What if the clef and/or key change in the course of the group? + m_clef(clef), + m_key(key), + m_weightAbove(0), + m_weightBelow(0), + m_userSamples(true), + m_groupNo( -1), + m_type(Beamed), + m_properties(p) +{ + //... +} + +NotationGroup::~NotationGroup() +{} + +bool NotationGroup::test(const NELIterator &i) +{ + // An event is a candidate for being within the bounds of the + // set if it's simply within the same bar as the original event. + // (Groups may contain other groups, so our bounds may enclose + // events that aren't members of the group: we reject those in + // sample rather than test, so as to avoid erroneously ending + // the group too soon.) + + return ((*i)->getViewAbsoluteTime() >= m_barRange.first && + (*i)->getViewAbsoluteTime() < m_barRange.second); +} + +bool +NotationGroup::sample(const NELIterator &i, bool goingForwards) +{ + if (m_baseIterator == getContainer().end()) { + m_baseIterator = i; + if (m_userSamples) + m_initial = i; + } + if (m_userSamples) + m_final = i; + + std::string t; + if (!(*i)->event()->get<String>(BaseProperties::BEAMED_GROUP_TYPE, t)) { + NOTATION_DEBUG << "NotationGroup::NotationGroup: Rejecting sample() for non-beamed element" << endl; + return false; + } + + long n; + if (!(*i)->event()->get<Int>(BaseProperties::BEAMED_GROUP_ID, n)) return false; + if (m_groupNo == -1) { + m_groupNo = n; + } else if (n != m_groupNo) { + NOTATION_DEBUG << "NotationGroup::NotationGroup: Rejecting sample() for event with group id " << n << " (mine is " << m_groupNo << ")" << endl; + return false; + } + + if (t == BaseProperties::GROUP_TYPE_BEAMED) { + m_type = Beamed; + } else if (t == BaseProperties::GROUP_TYPE_TUPLED) { + m_type = Tupled; + } else if (t == BaseProperties::GROUP_TYPE_GRACE) { + std::cerr << "NotationGroup::NotationGroup: WARNING: Obsolete group type Grace found" << std::endl; + return false; + } else { + NOTATION_DEBUG << "NotationGroup::NotationGroup: Warning: Rejecting sample() for unknown GroupType \"" << t << "\"" << endl; + return false; + } + + NOTATION_DEBUG << "NotationGroup::sample: group id is " << m_groupNo << endl; + + AbstractSet<NotationElement, NotationElementList>::sample + (i, goingForwards); + + // If the sum of the distances from the middle line to the notes + // above the middle line exceeds the sum of the distances from the + // middle line to the notes below, then the beam goes below. We + // can calculate the weightings here, as we construct the group. + + if (!static_cast<NotationElement*>(*i)->isNote()) + return true; + if (m_userSamples) { + if (m_initialNote == getContainer().end()) m_initialNote = i; + m_finalNote = i; + } + + // The code that uses the Group should not rely on the presence of + // e.g. BEAM_GRADIENT to indicate that a beam should be drawn; + // it's possible the gradient might be left over from a previous + // calculation and the group might have changed since. Instead it + // should test BEAMED, which may be false even if there is a + // gradient present. + (*i)->event()->setMaybe<Bool>(NotationProperties::BEAMED, false); + + int h = height(i); + if (h > 4) + m_weightAbove += h - 4; + if (h < 4) + m_weightBelow += 4 - h; + + return true; +} + +bool +NotationGroup::contains(const NELIterator &i) const +{ + NELIterator j(getInitialElement()), + k( getFinalElement()); + + for (;;) { + if (j == i) + return true; + if (j == k) + return false; + ++j; + } +} + +int +NotationGroup::height(const NELIterator &i) const +{ + long h = 0; + if ((*i)->event()->get<Int>(NotationProperties::HEIGHT_ON_STAFF, h)) { + return h; + } + + //!!! int pitch = (*i)->event()->get<Int>(PITCH); + // NotationDisplayPitch p(pitch, m_clef, m_key); + // h = p.getHeightOnStaff(); + + try { + Pitch pitch(*getAsEvent(i)); + h = pitch.getHeightOnStaff(m_clef, m_key); + } catch (...) { + // no pitch! + } + + // not setMaybe, as we know the property is absent: + (*i)->event()->set<Int>(NotationProperties::HEIGHT_ON_STAFF, h, false); + return h; +} + +void +NotationGroup::applyStemProperties() +{ + NotationRules rules; + + NELIterator + initialNote(getInitialNote()), + finalNote(getFinalNote()); + + if (initialNote == getContainer().end() || + initialNote == finalNote) { + //!!! This is not true -- if initialNote == finalNote there is + // one note in the group, not none. But we still won't have a + // beam. + NOTATION_DEBUG << "NotationGroup::applyStemProperties: no notes in group" + << endl; + return; // no notes, no case to answer + } + + if (getHighestNote() == getContainer().end()) { + std::cerr << "ERROR: NotationGroup::applyStemProperties: no highest note!" << std::endl; + abort(); + } + + if (getLowestNote() == getContainer().end()) { + std::cerr << "ERROR: NotationGroup::applyStemProperties: no lowest note!" << std::endl; + abort(); + } + + int up = 0, down = 0; + + for (NELIterator i = initialNote; i != getContainer().end(); ++i) { + NotationElement* el = static_cast<NotationElement*>(*i); + if (el->isNote()) { + if (el->event()->has(NotationProperties::STEM_UP)) { + if (el->event()->get<Bool>(NotationProperties::STEM_UP)) ++up; + else ++down; + } + } + + if (i == finalNote) break; + } + + NOTATION_DEBUG << "NotationGroup::applyStemProperties: weightAbove " + << m_weightAbove << ", weightBelow " << m_weightBelow + << ", up " << up << ", down " << down << endl; + + bool aboveNotes = rules.isBeamAbove(height(getHighestNote()), + height(getLowestNote()), + m_weightAbove, + m_weightBelow); + if (up != down) { + if (up > down) aboveNotes = true; + else aboveNotes = false; + } + + NOTATION_DEBUG << "NotationGroup::applyStemProperties: hence aboveNotes " + << aboveNotes << endl; + + /*!!! + if ((*initialNote)->event()->has(STEM_UP) && + (*initialNote)->event()->isPersistent<Bool>(STEM_UP)) { + aboveNotes = (*initialNote)->event()->get<Bool>(STEM_UP); + } + + if ((*initialNote)->event()->has(NotationProperties::BEAM_ABOVE) && + (*initialNote)->event()->isPersistent<Bool>(NotationProperties::BEAM_ABOVE)) { + aboveNotes = (*initialNote)->event()->get<Bool> + (NotationProperties::BEAM_ABOVE); + } + */ + for (NELIterator i = initialNote; i != getContainer().end(); ++i) { + + NotationElement* el = static_cast<NotationElement*>(*i); + + el->event()->setMaybe<Bool>(NotationProperties::BEAM_ABOVE, aboveNotes); + + if (el->isNote() && + el->event()->has(BaseProperties::NOTE_TYPE) && + el->event()->get<Int>(BaseProperties::NOTE_TYPE) < Note::Crotchet && + el->event()->has(BaseProperties::BEAMED_GROUP_ID) && + el->event()->get<Int>(BaseProperties::BEAMED_GROUP_ID) == m_groupNo) { + + el->event()->setMaybe<Bool>(NotationProperties::BEAMED, true); + // el->event()->setMaybe<Bool>(m_properties.VIEW_LOCAL_STEM_UP, aboveNotes); + + } else if (el->isNote()) { + + if (i == initialNote || i == finalNote) { + (*i)->event()->setMaybe<Bool> + (m_properties.VIEW_LOCAL_STEM_UP, aboveNotes); + } else { + (*i)->event()->setMaybe<Bool> + (m_properties.VIEW_LOCAL_STEM_UP, !aboveNotes); + } + } + + if (i == finalNote) break; + } +} + +bool +NotationGroup::haveInternalRest() +const +{ + bool inside = false; + bool found = false; + + for (NELIterator i = getInitialNote(); i != getContainer().end(); ++i) { + NotationElement* el = static_cast<NotationElement*>(*i); + + if (el->isNote() && + el->event()->has(BaseProperties::NOTE_TYPE) && + el->event()->get<Int>(BaseProperties::NOTE_TYPE) < Note::Crotchet && + el->event()->has(BaseProperties::BEAMED_GROUP_ID) && + el->event()->get<Int>(BaseProperties::BEAMED_GROUP_ID) == m_groupNo) { + if (found) return true; // a rest is wholly enclosed by beamed notes + inside = true; + } + + if (el->isRest() && inside) found = true; + + if (i == getFinalNote()) break; + } + + return false; +} + +NotationGroup::Beam +NotationGroup::calculateBeam(NotationStaff &staff) +{ + NotationRules rules; + + Beam beam; + beam.aboveNotes = true; + beam.startY = 0; + beam.gradient = 0; + beam.necessary = false; + + NELIterator + initialNote(getInitialNote()), + finalNote(getFinalNote()); + + if (initialNote == getContainer().end() || + initialNote == finalNote) { + return beam; // no notes, or at most one: no case to answer + } + + beam.aboveNotes = rules.isBeamAbove(height(getHighestNote()), + height(getLowestNote()), + m_weightAbove, + m_weightBelow); + + if ((*initialNote)->event()->has(NotationProperties::BEAM_ABOVE)) { + beam.aboveNotes = (*initialNote)->event()->get + <Bool> + (NotationProperties::BEAM_ABOVE); + } + + timeT crotchet = Note(Note::Crotchet).getDuration(); + + beam.necessary = + (*initialNote)->getViewDuration() < crotchet && + (*finalNote)->getViewDuration() < crotchet; + + beam.necessary = beam.necessary && + (((*finalNote)->getViewAbsoluteTime() > + (*initialNote)->getViewAbsoluteTime()) || + (((*finalNote)->getViewAbsoluteTime() == + (*initialNote)->getViewAbsoluteTime()) && + ((*finalNote)->event()->getSubOrdering() > + (*initialNote)->event()->getSubOrdering()))); + + // We continue even if the beam is not necessary, because the + // same data is used to generate the tupling line in tupled + // groups that do not have beams + + // if (!beam.necessary) return beam; + + NOTATION_DEBUG << "NotationGroup::calculateBeam: beam necessariness: " << beam.necessary << endl; + + NotationChord initialChord(getContainer(), initialNote, &getQuantizer(), + m_properties, m_clef, m_key), + finalChord(getContainer(), finalNote, &getQuantizer(), + m_properties, m_clef, m_key); + + if (initialChord.getInitialElement() == finalChord.getInitialElement()) { + return beam; + } + + bool isGrace = + (*initialNote)->event()->has(BaseProperties::IS_GRACE_NOTE) && + (*initialNote)->event()->get<Bool>(BaseProperties::IS_GRACE_NOTE); + + int initialHeight, finalHeight, extremeHeight; + NELIterator extremeNote; + + if (beam.aboveNotes) { + + initialHeight = height(initialChord.getHighestNote()); + finalHeight = height( finalChord.getHighestNote()); + extremeHeight = height( getHighestNote()); + extremeNote = getHighestNote(); + + } else { + initialHeight = height(initialChord.getLowestNote()); + finalHeight = height( finalChord.getLowestNote()); + extremeHeight = height( getLowestNote()); + extremeNote = getLowestNote(); + } + + int diff = initialHeight - finalHeight; + if (diff < 0) diff = -diff; + + bool linear = + (beam.aboveNotes ? + (extremeHeight <= std::max(initialHeight, finalHeight)) : + (extremeHeight >= std::min(initialHeight, finalHeight))); + + if (!linear) { + if (diff > 2) + diff = 1; + else + diff = 0; + } + + // Now, we need to judge the height of the beam such that the + // nearest note of the whole group, the nearest note of the first + // chord and the nearest note of the final chord are all at least + // two note-body-heights away from it, and at least one of the + // start and end points is at least the usual note stem-length + // away from it. This is a straight-line equation y = mx + c, + // where we have m and two x,y pairs and need to find c. + + //!!! If we find that making one extreme a sensible distance from + //the note head makes the other extreme way too far away from it + //in the direction of the gradient, then we should flatten the + //gradient. There may be a better heuristic for this. + + int initialX = (int)(*initialNote)->getLayoutX(); + int finalDX = (int) (*finalNote)->getLayoutX() - initialX; + int extremeDX = (int)(*extremeNote)->getLayoutX() - initialX; + + int spacing = staff.getNotePixmapFactory(isGrace).getLineSpacing(); + + beam.gradient = 0; + if (finalDX > 0) { + do { + if (diff == 0) + break; + else if (diff > 3) + diff = 3; + else + --diff; + beam.gradient = (diff * spacing * 100) / (finalDX * 2); + } while (beam.gradient > 18); + } else { + beam.gradient = 0; + } + if (initialHeight < finalHeight) + beam.gradient = -beam.gradient; + + int finalY = staff.getLayoutYForHeight(finalHeight); + int extremeY = staff.getLayoutYForHeight(extremeHeight); + + int c0 = staff.getLayoutYForHeight(initialHeight), c1, c2; + double dgrad = (double)beam.gradient / 100.0; + + Equation::solve(Equation::C, extremeY, dgrad, extremeDX, c1); + Equation::solve(Equation::C, finalY, dgrad, finalDX, c2); + + using std::max; + using std::min; + long shortestNoteType = Note::Quaver; + if (!(*getShortestElement())->event()->get + <Int>(BaseProperties::NOTE_TYPE, + shortestNoteType)) { + NOTATION_DEBUG << "NotationGroup::calculateBeam: WARNING: Shortest element has no note-type; should this be possible?" << endl; + NOTATION_DEBUG << "(Event dump follows)" << endl; + (*getShortestElement())->event()->dump(std::cerr); + } + + // minimal stem lengths at start, middle-extreme and end of beam + int sl = staff.getNotePixmapFactory(isGrace).getStemLength(); + int ml = spacing * 2; + int el = sl; + + NOTATION_DEBUG << "c0: " << c0 << ", c1: " << c1 << ", c2: " << c2 << endl; + NOTATION_DEBUG << "sl: " << sl << ", ml: " << ml << ", el: " << el << endl; + + // If the stems are down, we will need to ensure they end at + // heights lower than 0 if there's an internal rest -- likewise + // with stems up and an internal rest we need to ensure they end + // at higher than 8. + // [Avoid doing expensive haveInternalRest() test where possible] + + if (beam.aboveNotes) { + + int topY = staff.getLayoutYForHeight(8); + + if ((c0 - sl > topY) || (c1 - ml > topY) || (c2 - el > topY)) { + if (haveInternalRest()) { + if (c0 - sl > topY) sl = c0 - topY; + if (c1 - ml > topY) ml = c1 - topY; + if (c2 - el > topY) el = c2 - topY; + NOTATION_DEBUG << "made internal rest adjustment for above notes" << endl; + NOTATION_DEBUG << "sl: " << sl << ", ml: " << ml << ", el: " << el << endl; + } + } + } else { + int bottomY = staff.getLayoutYForHeight(0); + + if ((c0 + sl < bottomY) || (c1 + ml < bottomY) || (c2 + el < bottomY)) { + if (haveInternalRest()) { + if (c0 + sl < bottomY) sl = bottomY - c0; + if (c1 + ml < bottomY) ml = bottomY - c1; + if (c2 + el < bottomY) el = bottomY - c2; + NOTATION_DEBUG << "made internal rest adjustment for below notes" << endl; + NOTATION_DEBUG << "sl: " << sl << ", ml: " << ml << ", el: " << el << endl; + } + } + } + + + if (shortestNoteType < Note::Semiquaver) { + int off = spacing * (Note::Semiquaver - shortestNoteType); + sl += off; + el += off; + } + + if (shortestNoteType < Note::Quaver) { + int off = spacing * (Note::Quaver - shortestNoteType); + ml += off; + } + + + int midY = staff.getLayoutYForHeight(4); + + // ensure extended to middle line if necessary, and assign suitable stem length + if (beam.aboveNotes) { + if (c0 - sl > midY) sl = c0 - midY; + if (c1 - ml > midY) ml = c1 - midY; + if (c2 - el > midY) el = c2 - midY; + if (extremeDX > 1.0 || extremeDX < -1.0) { + // beam.gradient = int(100 * double(c2 - c0) / double(extremeDX)); + } + beam.startY = min(min(c0 - sl, c1 - ml), c2 - el); + } else { + if (c0 + sl < midY) sl = midY - c0; + if (c1 + ml < midY) ml = midY - c1; + if (c2 + el < midY) el = midY - c2; + if (extremeDX > 1.0 || extremeDX < -1.0) { + // beam.gradient = int(100 * double(c2 - c0) / double(extremeDX)); + } + beam.startY = max(max(c0 + sl, c1 + ml), c2 + el); + } + /* + NOTATION_DEBUG << "NotationGroup::calculateBeam: beam data:" << endl + << "gradient: " << beam.gradient << endl + << "(c0 " << c0 << ", c2 " << c2 << ", extremeDX " << extremeDX << ")" << endl + << "startY: " << beam.startY << endl + << "aboveNotes: " << beam.aboveNotes << endl + << "shortestNoteType: " << shortestNoteType << endl + << "necessary: " << beam.necessary << endl; + */ + return beam; +} + +void +NotationGroup::applyBeam(NotationStaff &staff) +{ + // NOTATION_DEBUG << "NotationGroup::applyBeam, group no is " << m_groupNo << endl; + /* + NOTATION_DEBUG << "\nNotationGroup::applyBeam" << endl; + NOTATION_DEBUG << "Group id: " << m_groupNo << ", type " << m_type << endl; + NOTATION_DEBUG << "Coverage:" << endl; + int i = 0; + for (NELIterator i = getInitialElement(); i != getContainer().end(); ++i) { + (*i)->event()->dump(cerr); + if (i == getFinalElement()) break; + } + { + NELIterator i(getInitialNote()); + NOTATION_DEBUG << "Initial note: " << (i == getContainer().end() ? -1 : (*i)->event()->getAbsoluteTime()) << endl; + } + { + NELIterator i(getFinalNote()); + NOTATION_DEBUG << "Final note: " << (i == getContainer().end() ? -1 : (*i)->event()->getAbsoluteTime()) << endl; + } + { + NELIterator i(getHighestNote()); + NOTATION_DEBUG << "Highest note: " << (i == getContainer().end() ? -1 : (*i)->event()->getAbsoluteTime()) << endl; + } + { + NELIterator i(getLowestNote()); + NOTATION_DEBUG << "Lowest note: " << (i == getContainer().end() ? -1 : (*i)->event()->getAbsoluteTime()) << endl; + } + */ + Beam beam(calculateBeam(staff)); + if (!beam.necessary) { + for (NELIterator i = getInitialNote(); i != getContainer().end(); ++i) { + (*i)->event()->unset(NotationProperties::BEAMED); + (*i)->event()->unset(m_properties.TUPLING_LINE_MY_Y); + if (i == getFinalNote()) + break; + } + return ; + } + + // NOTATION_DEBUG << "NotationGroup::applyBeam: Beam is necessary" << endl; + + NELIterator initialNote(getInitialNote()), + finalNote( getFinalNote()); + int initialX = (int)(*initialNote)->getLayoutX(); + timeT finalTime = (*finalNote)->getViewAbsoluteTime(); + + // For each chord in the group, we nominate the note head furthest + // from the beam as the primary note, the one that "owns" the stem + // and the section of beam up to the following chord. For this + // note, we need to: + // + // * Set the start height, start x-coord and gradient of the beam + // (we can't set the stem length for this note directly, because + // we don't know its y-coordinate yet) + // + // * Set width of this section of beam + // + // * Set the number of beams required for the following note (one + // slight complication here: a beamed group in which the very + // first chord is shorter than the following one. Here the first + // chord needs to know it's the first, or else it can't draw the + // part-beams immediately to its right correctly.) + // + // For the rest of the notes in the chord, we just need to + // indicate that they aren't part of the beam-drawing process and + // don't need to draw a stem. + + NELIterator prev = getContainer().end(), prevprev = getContainer().end(); + double gradient = (double)beam.gradient / 100.0; + + // NOTATION_DEBUG << "NotationGroup::applyBeam starting for group "<< this << endl; + + for (NELIterator i = getInitialNote(); i != getContainer().end(); ++i) { + NotationElement* el = static_cast<NotationElement*>(*i); + + // Clear tuplingness for all events in the group, to be + // reinstated by any subsequent call to applyTuplingLine. We + // do this because applyTuplingLine doesn't clear these + // properties from notes that don't need them; it only applies + // them to notes that do. + el->event()->unset(m_properties.TUPLING_LINE_MY_Y); + + if (el->isNote() && + el->event()->has(BaseProperties::NOTE_TYPE) && + el->event()->get + <Int>(BaseProperties::NOTE_TYPE) < Note::Crotchet && + el->event()->has(BaseProperties::BEAMED_GROUP_ID) && + el->event()->get<Int>(BaseProperties::BEAMED_GROUP_ID) == m_groupNo) { + + NotationChord chord(getContainer(), i, &getQuantizer(), + m_properties, m_clef, m_key); + unsigned int j; + + // NOTATION_DEBUG << "NotationGroup::applyBeam: Found chord" << endl; + + bool hasShifted = chord.hasNoteHeadShifted(); + + for (j = 0; j < chord.size(); ++j) { + NotationElement *el = static_cast<NotationElement*>(*chord[j]); + + el->event()->setMaybe<Bool> + (m_properties.CHORD_PRIMARY_NOTE, false); + + el->event()->setMaybe<Bool> + (m_properties.DRAW_FLAG, false); + + el->event()->setMaybe<Bool> + (NotationProperties::BEAMED, true); + + el->event()->setMaybe<Bool> + (NotationProperties::BEAM_ABOVE, beam.aboveNotes); + + el->event()->setMaybe<Bool> + (m_properties.VIEW_LOCAL_STEM_UP, beam.aboveNotes); + + bool shifted = chord.isNoteHeadShifted(chord[j]); + el->event()->setMaybe<Bool> + (m_properties.NOTE_HEAD_SHIFTED, shifted); + + long dots = 0; + (void)el->event()->get + <Int>(BaseProperties::NOTE_DOTS, dots); + + el->event()->setMaybe<Bool> + (m_properties.NOTE_DOT_SHIFTED, false); + if (hasShifted && beam.aboveNotes) { + long dots = 0; + (void)el->event()->get + <Int>(BaseProperties::NOTE_DOTS, dots); + if (dots > 0) { + el->event()->setMaybe<Bool> + (m_properties.NOTE_DOT_SHIFTED, true); + } + } + + el->event()->setMaybe<Bool> + (m_properties.NEEDS_EXTRA_SHIFT_SPACE, + chord.hasNoteHeadShifted() && !beam.aboveNotes); + } + + if (beam.aboveNotes) + j = 0; + else + j = chord.size() - 1; + + NotationElement *el = static_cast<NotationElement*>(*chord[j]); + el->event()->setMaybe<Bool>(NotationProperties::BEAMED, false); // set later + el->event()->setMaybe<Bool>(m_properties.DRAW_FLAG, true); // set later + + int x = (int)el->getLayoutX(); + int myY = (int)(gradient * (x - initialX)) + beam.startY; + + int beamCount = + NoteStyleFactory::getStyleForEvent(el->event())-> + getFlagCount(el->event()->get + <Int>(BaseProperties::NOTE_TYPE)); + + // If THIS_PART_BEAMS is true, then when drawing the + // chord, if it requires more beams than the following + // chord then they should be added as partial beams to the + // right of the stem. + + // If NEXT_PART_BEAMS is true, then when drawing the + // chord, if it requires fewer beams than the following + // chord then the difference should be added as partial + // beams to the left of the following chord's stem. + + // Procedure for setting these: If we have more beams than + // the preceding chord, then the preceding chord should + // have NEXT_PART_BEAMS set, until possibly unset again on + // the next iteration. If we have at least as many beams + // as the preceding chord, then the preceding chord should + // have THIS_PART_BEAMS unset and the one before it should + // have NEXT_PART_BEAMS unset. The first chord should + // have THIS_PART_BEAMS set, until possibly unset again on + // the next iteration. + + if (prev != getContainer().end()) { + + NotationElement *prevEl = static_cast<NotationElement*>(*prev); + int secWidth = x - (int)prevEl->getLayoutX(); + + // prevEl->event()->setMaybe<Int>(BEAM_NEXT_Y, myY); + + prevEl->event()->setMaybe<Int> + (m_properties.BEAM_SECTION_WIDTH, secWidth); + prevEl->event()->setMaybe<Int> + (m_properties.BEAM_NEXT_BEAM_COUNT, beamCount); + + int prevBeamCount = + NoteStyleFactory::getStyleForEvent(prevEl->event())-> + getFlagCount(prevEl->event()->get + <Int>(BaseProperties::NOTE_TYPE)); + + if ((beamCount > 0) && (prevBeamCount > 0)) { + el->event()->setMaybe<Bool>(m_properties.BEAMED, true); + el->event()->setMaybe<Bool>(m_properties.DRAW_FLAG, false); + prevEl->event()->setMaybe<Bool>(m_properties.BEAMED, true); + prevEl->event()->setMaybe<Bool>(m_properties.DRAW_FLAG, false); + } + + if (beamCount >= prevBeamCount) { + prevEl->event()->setMaybe<Bool> + (m_properties.BEAM_THIS_PART_BEAMS, false); + if (prevprev != getContainer().end()) { + (*prevprev)->event()->setMaybe<Bool> + (m_properties.BEAM_NEXT_PART_BEAMS, false); + } + } + + if (beamCount > prevBeamCount) { + prevEl->event()->setMaybe<Bool> + (m_properties.BEAM_NEXT_PART_BEAMS, true); + } + + } else { + el->event()->setMaybe<Bool>(m_properties.BEAM_THIS_PART_BEAMS, true); + } + + el->event()->setMaybe<Bool>(m_properties.CHORD_PRIMARY_NOTE, true); + + el->event()->setMaybe<Int>(m_properties.BEAM_MY_Y, myY); + el->event()->setMaybe<Int>(m_properties.BEAM_GRADIENT, beam.gradient); + + // until they're set next time around the loop, as (*prev)->... + // el->event()->setMaybe<Int>(m_properties.BEAM_NEXT_Y, myY); + el->event()->setMaybe<Int>(m_properties.BEAM_SECTION_WIDTH, 0); + el->event()->setMaybe<Int>(m_properties.BEAM_NEXT_BEAM_COUNT, 1); + + prevprev = prev; + prev = chord[j]; + i = chord.getFinalElement(); + + } + else if (el->isNote()) { + + //!!! should we really be setting these here as well as in + // applyStemProperties? + /* + if (i == initialNote || i == finalNote) { + (*i)->event()->setMaybe<Bool>(m_properties.VIEW_LOCAL_STEM_UP, beam.aboveNotes); + } else { + (*i)->event()->setMaybe<Bool>(m_properties.VIEW_LOCAL_STEM_UP, !beam.aboveNotes); + } + */ + } + + if (i == finalNote || el->getViewAbsoluteTime() > finalTime) break; + } +} + +void +NotationGroup::applyTuplingLine(NotationStaff &staff) +{ + // NOTATION_DEBUG << "NotationGroup::applyTuplingLine, group no is " << m_groupNo << ", group type is " << m_type << endl; + + if (m_type != Tupled) + return ; + + // NOTATION_DEBUG << "NotationGroup::applyTuplingLine: line is necessary" << endl; + + Beam beam(calculateBeam(staff)); + + NELIterator + initialNote(getInitialNote()), + finalNote(getFinalNote()), + initialElement(getInitialElement()), + finalElement(getFinalElement()); + + NELIterator initialNoteOrRest(initialElement); + NotationElement* initialNoteOrRestEl = static_cast<NotationElement*>(*initialNoteOrRest); + + while (initialNoteOrRest != finalElement && + !(initialNoteOrRestEl->isNote() || + initialNoteOrRestEl->isRest())) { + ++initialNoteOrRest; + initialNoteOrRestEl = static_cast<NotationElement*>(*initialNoteOrRest); + } + + if (!initialNoteOrRestEl->isRest()) { + initialNoteOrRest = initialNote; + initialNoteOrRestEl = static_cast<NotationElement*>(*initialNoteOrRest); + } + + if (initialNoteOrRest == staff.getViewElementList()->end()) return; + + bool isGrace = false; + if (initialNote != staff.getViewElementList()->end()) { + isGrace = + (*initialNote)->event()->has(BaseProperties::IS_GRACE_NOTE) && + (*initialNote)->event()->get<Bool>(BaseProperties::IS_GRACE_NOTE); + } + + // NOTATION_DEBUG << "NotationGroup::applyTuplingLine: first element is " << (initialNoteOrRestEl->isNote() ? "Note" : "Non-Note") << ", last is " << (static_cast<NotationElement*>(*finalElement)->isNote() ? "Note" : "Non-Note") << endl; + + int initialX = (int)(*initialNoteOrRest)->getLayoutX(); + int finalX = (int)(*finalElement)->getLayoutX(); + + if (initialNote == staff.getViewElementList()->end() && + finalNote == staff.getViewElementList()->end()) { + + Event *e = (*initialNoteOrRest)->event(); + e->setMaybe<Int>(m_properties.TUPLING_LINE_MY_Y, + staff.getLayoutYForHeight(12)); + e->setMaybe<Int>(m_properties.TUPLING_LINE_WIDTH, finalX - initialX); + e->setMaybe<Int>(m_properties.TUPLING_LINE_GRADIENT, 0); + + } else { + + // only notes have height + int initialY = staff.getLayoutYForHeight(height(initialNote)); + int finalY = staff.getLayoutYForHeight(height( finalNote)); + + // if we have a beam and both end-points of it are notes, + // place the tupling number over it (that is, make the tupling + // line follow the beam and say so); otherwise make the line + // follow the gradient a beam would have, but on the other + // side of the notes + bool followBeam = + (beam.necessary && + (*initialNoteOrRest)->event()->isa(Note::EventType) && + (finalNote == finalElement)); + + int startY = (followBeam ? beam.startY : + initialY - (beam.startY - initialY)); + + int endY = startY + (int)((finalX - initialX) * + ((double)beam.gradient / 100.0)); + + // NOTATION_DEBUG << "applyTuplingLine: beam.startY is " << beam.startY << ", initialY is " << initialY << " so my startY is " << startY << ", endY " << endY << ", beam.gradient " << beam.gradient << endl; + + int nh = staff.getNotePixmapFactory(isGrace).getNoteBodyHeight(); + + if (followBeam) { // adjust to move text slightly away from beam + + int maxEndBeamCount = 1; + long bc; + if ((*initialNoteOrRest)->event()->get<Int> + (m_properties.BEAM_NEXT_BEAM_COUNT, bc)) { + if (bc > maxEndBeamCount) + maxEndBeamCount = bc; + } + if ((*finalNote)->event()->get<Int> + (m_properties.BEAM_NEXT_BEAM_COUNT, bc)) { + if (bc > maxEndBeamCount) + maxEndBeamCount = bc; + } + + int extraBeamSpace = maxEndBeamCount * nh + nh / 2; + + if (beam.aboveNotes) { + startY -= extraBeamSpace; + endY -= extraBeamSpace; + finalX += nh; + } else { + startY += extraBeamSpace; + endY += extraBeamSpace; + finalX -= nh; + } + + } else { // adjust to place close to note heads + + if (startY < initialY) { + if (initialY - startY > nh * 3) + startY = initialY - nh * 3; + if ( finalY - endY < nh * 2) + startY -= nh * 2 - (finalY - endY); + } else { + if (startY - initialY > nh * 3) + startY = initialY + nh * 3; + if ( endY - finalY < nh * 2) + startY += nh * 2 - (endY - finalY); + } + } + + Event *e = (*initialNoteOrRest)->event(); + e->setMaybe<Int>(m_properties.TUPLING_LINE_MY_Y, startY); + e->setMaybe<Int>(m_properties.TUPLING_LINE_WIDTH, finalX - initialX); + e->setMaybe<Int>(m_properties.TUPLING_LINE_GRADIENT, beam.gradient); + e->setMaybe<Bool>(m_properties.TUPLING_LINE_FOLLOWS_BEAM, followBeam); + } +} + +} diff --git a/src/gui/editors/notation/NotationGroup.h b/src/gui/editors/notation/NotationGroup.h new file mode 100644 index 0000000..c7b1134 --- /dev/null +++ b/src/gui/editors/notation/NotationGroup.h @@ -0,0 +1,133 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONGROUP_H_ +#define _RG_NOTATIONGROUP_H_ + +#include "base/Sets.h" +#include <utility> +#include "base/Event.h" +#include "NotationElement.h" + + + + +namespace Rosegarden +{ + +class Quantizer; +class NotationStaff; +class NotationProperties; +class Key; +class Clef; + + +/// Several sorts of "Beamed Group" + +class NotationGroup : public AbstractSet<NotationElement, + NotationElementList> +{ +public: + typedef NotationElementList::iterator NELIterator; + + enum Type { Beamed, Tupled }; + + /// Group contents will be sampled from elements surrounding elementInGroup + NotationGroup(NotationElementList &nel, NELIterator elementInGroup, + const Quantizer *, + std::pair<timeT, timeT> barRange, + const NotationProperties &properties, + const Clef &clef, const Key &key); + + /// Caller intends to call sample() for each item in the group, _in order_ + NotationGroup(NotationElementList &nel, + const Quantizer *, + const NotationProperties &properties, + const Clef &clef, const Key &key); + + virtual ~NotationGroup(); + + Type getGroupType() const { return m_type; } + + /** + * Writes the BEAMED, BEAM_ABOVE, and STEM_UP properties into the + * notes in the group, as appropriate. Does not require layout x + * coordinates to have been set. + */ + void applyStemProperties(); + + /** + * Writes beam data into each note in the group. Notes' layout x + * coordinates must already have been set. Does not require + * applyStemProperties to have already been called. + */ + void applyBeam(NotationStaff &); + + /** + * Writes tupling line data into each note in the group. Notes' + * layout x coordinates must already have been set. Does nothing + * if this is not a tupled group. + */ + void applyTuplingLine(NotationStaff &); + + virtual bool contains(const NELIterator &) const; + + virtual bool sample(const NELIterator &i, bool goingForwards); + +protected: + virtual bool test(const NELIterator &i); + +private: + struct Beam + { // if a beam has a line equation y = mx + c, + int gradient; // -- then this is m*100 (i.e. a percentage) + int startY; // -- and this is c + bool aboveNotes; + bool necessary; + }; + + Beam calculateBeam(NotationStaff &); + + int height(const NELIterator&) const; + + bool haveInternalRest() const; + + //--------------- Data members --------------------------------- + + std::pair<timeT, timeT> m_barRange; + const Clef &m_clef; + const Key &m_key; + int m_weightAbove, m_weightBelow; + bool m_userSamples; + long m_groupNo; + Type m_type; + + const NotationProperties &m_properties; +}; + + +} + +#endif diff --git a/src/gui/editors/notation/NotationHLayout.cpp b/src/gui/editors/notation/NotationHLayout.cpp new file mode 100644 index 0000000..1b13765 --- /dev/null +++ b/src/gui/editors/notation/NotationHLayout.cpp @@ -0,0 +1,2110 @@ +/* -*- 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 "NotationHLayout.h" +#include "misc/Debug.h" +#include <kapplication.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 <kconfig.h> +#include <qobject.h> +#include <cmath> + +namespace Rosegarden +{ + +using namespace BaseProperties; + + +NotationHLayout::NotationHLayout(Composition *c, NotePixmapFactory *npf, + const NotationProperties &properties, + QObject* 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; + + KConfig *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; + + KConfig *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); + + KConfig *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; + +} diff --git a/src/gui/editors/notation/NotationHLayout.h b/src/gui/editors/notation/NotationHLayout.h new file mode 100644 index 0000000..9d7366b --- /dev/null +++ b/src/gui/editors/notation/NotationHLayout.h @@ -0,0 +1,446 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONHLAYOUT_H_ +#define _RG_NOTATIONHLAYOUT_H_ + +#include "base/LayoutEngine.h" +#include "base/NotationTypes.h" +#include "NotationElement.h" +#include "gui/general/ProgressReporter.h" +#include <map> +#include <vector> +#include "base/Event.h" + + +class TieMap; +class QObject; + + +namespace Rosegarden +{ + +class ViewElement; +class Staff; +class Quantizer; +class NotePixmapFactory; +class NotationProperties; +class NotationGroup; +class NotationChord; +class Key; +class Composition; +class Clef; +class AccidentalTable; + + +/** + * Horizontal notation layout + * + * computes the X coordinates of notation elements + */ + +class NotationHLayout : public ProgressReporter, + public HorizontalLayoutEngine +{ +public: + NotationHLayout(Composition *c, + NotePixmapFactory *npf, + const NotationProperties &properties, + QObject* parent, const char* name = 0); + + virtual ~NotationHLayout(); + + void setNotePixmapFactory(NotePixmapFactory *npf) { + m_npf = npf; + } + + /** + * Precomputes layout data for a single staff. The resulting data + * is stored in the BarDataMap, keyed from the staff reference; + * the entire map is then used by reconcileBars() and layout(). + * The map should be cleared (by calling reset()) before a full + * set of staffs is preparsed. + */ + virtual void scanStaff(Staff &staff, + timeT startTime = 0, + timeT endTime = 0); + + /** + * Resets internal data stores, notably the BarDataMap that is + * used to retain the data computed by scanStaff(). + */ + virtual void reset(); + + /** + * Resets internal data stores, notably the given staff's entry + * in the BarDataMap used to retain the data computed by scanStaff(). + */ + virtual void resetStaff(Staff &staff, + timeT startTime = 0, + timeT endTime = 0); + + /** + * Lays out all staffs that have been scanned + */ + virtual void finishLayout(timeT startTime = 0, + timeT endTime = 0); + + /** + * Set page mode + */ + virtual void setPageMode(bool pageMode) { m_pageMode = pageMode; } + + /** + * Get the page mode setting + */ + virtual bool isPageMode() { return m_pageMode; } + + /** + * Set a page width + */ + virtual void setPageWidth(double pageWidth) { m_pageWidth = pageWidth; } + + /** + * Get the page width + */ + virtual double getPageWidth() { return m_pageWidth; } + + /** + * Gets the current spacing factor (100 == "normal" spacing) + */ + int getSpacing() const { return m_spacing; } + + /** + * Sets the current spacing factor (100 == "normal" spacing) + */ + void setSpacing(int spacing) { m_spacing = spacing; } + + /** + * Gets the range of "standard" spacing factors (you can + * setSpacing() to anything you want, but it makes sense to + * have a standard list for GUI use). The only guaranteed + * property of the returned list is that 100 will be in it. + */ + static std::vector<int> getAvailableSpacings(); + + /** + * Gets the current proportion (100 == spaces proportional to + * durations, 0 == equal spacings) + */ + int getProportion() const { return m_proportion; } + + /** + * Sets the current proportion (100 == spaces proportional to + * durations, 0 == equal spacings) + */ + void setProportion(int proportion) { m_proportion = proportion; } + + /** + * Gets the range of "standard" proportion factors (you can + * setProportion() to anything you want, but it makes sense to + * have a standard list for GUI use). The only guaranteed + * property of the returned list is that 0, 100, and whatever the + * default proportion is will be in it. + */ + static std::vector<int> getAvailableProportions(); + + /** + * Returns the total length of all elements once layout is done + * This is the x-coord of the end of the last element on the longest + * staff, plus the space allocated to that element + */ + virtual double getTotalWidth() const { return m_totalWidth; } + + /** + * Returns the number of the first visible bar line on the given + * staff + */ + virtual int getFirstVisibleBarOnStaff(Staff &staff); + + /** + * Returns the number of the first visible bar line on any + * staff + */ + virtual int getFirstVisibleBar() const; + + /** + * Returns the number of the last visible bar line on the given + * staff + */ + virtual int getLastVisibleBarOnStaff(Staff &staff) const; + + /** + * Returns the number of the first visible bar line on any + * staff + */ + virtual int getLastVisibleBar() const; + + /** + * Returns the x-coordinate of the given bar number + */ + virtual double getBarPosition(int barNo) const; + + /** + * Returns the nearest time value to the given X coord. + */ + virtual timeT getTimeForX(double x) const; + + /** + * Returns the X coord corresponding to the given time value. + * This RulerScale method works by interpolating between bar lines + * (the inverse of the way getTimeForX works), and should be used + * for any rulers associated with the layout. + */ + virtual double getXForTime(timeT time) const; + + /** + * Returns the X coord corresponding to the given time value. + * This method works by interpolating between event positions, and + * should be used for position pointer tracking during playback. + */ + virtual double getXForTimeByEvent(timeT time) const; + + /** + * Returns true if the specified bar has the correct length + */ + virtual bool isBarCorrectOnStaff(Staff &staff, int barNo); + + /** + * Returns true if there is a new time signature in the given bar, + * setting timeSignature appropriately and setting timeSigX to its + * x-coord + */ + virtual bool getTimeSignaturePosition + (Staff &staff, int barNo, + TimeSignature &timeSig, double &timeSigX); + + /// purely optional, used only for progress reporting + void setStaffCount(int staffCount) { + m_staffCount = staffCount; + } + +protected: + + struct Chunk { + timeT duration; + short subordering; + float fixed; + float stretchy; + float x; + + Chunk(timeT d, short sub, float f, float s) : + duration(d), subordering(sub), fixed(f), stretchy(s), x(0) { } + Chunk(short sub, float f) : + duration(0), subordering(sub), fixed(f), stretchy(0), x(0) { } + }; + typedef std::vector<Chunk> ChunkList; + + /** + * Inner class for bar data, used by scanStaff() + */ + struct BarData + { + ChunkList chunks; + + struct BasicData + { // slots that can be filled at construction time + + NotationElementList::iterator start; // i.e. event following barline + bool correct; // bar preceding barline has correct duration + TimeSignature timeSignature; + bool newTimeSig; + + } basicData; + + struct SizeData + { // slots that can be filled when the following bar has been scanned + + float idealWidth; // theoretical width of bar following barline + float reconciledWidth; + float fixedWidth; // width of non-chunk items in bar + int clefKeyWidth; + timeT actualDuration; // may exceed nominal duration + + } sizeData; + + struct LayoutData + { // slots either assumed, or only known at layout time + bool needsLayout; + double x; // coordinate for display of barline + int timeSigX; + + } layoutData; + + BarData(NotationElementList::iterator i, + bool correct, TimeSignature timeSig, bool newTimeSig) { + basicData.start = i; + basicData.correct = correct; + basicData.timeSignature = timeSig; + basicData.newTimeSig = newTimeSig; + sizeData.idealWidth = 0; + sizeData.reconciledWidth = 0; + sizeData.fixedWidth = 0; + sizeData.clefKeyWidth = 0; + sizeData.actualDuration = 0; + layoutData.needsLayout = true; + layoutData.x = -1; + layoutData.timeSigX = -1; + } + }; + + typedef std::map<int, BarData> BarDataList; + typedef BarDataList::value_type BarDataPair; + typedef std::map<Staff *, BarDataList> BarDataMap; + typedef std::map<int, double> BarPositionList; + + typedef std::map<Staff *, int> StaffIntMap; + typedef std::map<long, NotationGroup *> NotationGroupMap; + + void clearBarList(Staff &); + + + /** + * Set the basic data for the given barNo. If barNo is + * beyond the end of the existing bar data list, create new + * records and/or fill with empty ones as appropriate. + */ + void setBarBasicData(Staff &staff, int barNo, + NotationElementList::iterator start, bool correct, + TimeSignature timeSig, bool newTimeSig); + + /** + * Set the size data for the given barNo. If barNo is + * beyond the end of the existing bar data list, create new + * records and/or fill with empty ones as appropriate. + */ + void setBarSizeData(Staff &staff, int barNo, + float fixedWidth, timeT actualDuration); + + /** + * Returns the bar positions for a given staff, provided that + * staff has been preparsed since the last reset + */ + BarDataList& getBarData(Staff &staff); + const BarDataList& getBarData(Staff &staff) const; + + /// Find the staff in which bar "barNo" is widest + Staff *getStaffWithWidestBar(int barNo); + + /// Find width of clef+key in the staff in which they're widest in this bar + int getMaxRepeatedClefAndKeyWidth(int barNo); + + /// For a single bar, makes sure synchronisation points align in all staves + void preSquishBar(int barNo); + + /// Tries to harmonize the bar positions for all the staves (linear mode) + void reconcileBarsLinear(); + + /// Tries to harmonize the bar positions for all the staves (page mode) + void reconcileBarsPage(); + + void layout(BarDataMap::iterator, + timeT startTime, + timeT endTime); + + /// Find earliest element with quantized time of t or greater + NotationElementList::iterator getStartOfQuantizedSlice + (NotationElementList *, timeT t) const; + + void scanChord + (NotationElementList *notes, NotationElementList::iterator &i, + const Clef &, const ::Rosegarden::Key &, + AccidentalTable &, float &lyricWidth, + ChunkList &chunks, NotePixmapFactory *, int ottavaShift, + NotationElementList::iterator &to); + + typedef std::map<int, NotationElementList::iterator> TieMap; + + // This modifies the NotationElementList::iterator passed to it, + // moving it on to the last note in the chord; updates the TieMap; + // and may modify the to-iterator if it turns out to point at a + // note within the chord + void positionChord + (Staff &staff, + NotationElementList::iterator &, const Clef &clef, + const ::Rosegarden::Key &key, TieMap &, NotationElementList::iterator &to); + + void sampleGroupElement + (Staff &staff, const Clef &clef, + const ::Rosegarden::Key &key, const NotationElementList::iterator &); + + /// Difference between absolute time of next event and of this + timeT getSpacingDuration + (Staff &staff, const NotationElementList::iterator &); + + /// Difference between absolute time of chord and of first event not in it + timeT getSpacingDuration + (Staff &staff, const NotationChord &); + + float getLayoutWidth(ViewElement &, + NotePixmapFactory *, + const ::Rosegarden::Key &) const; + + int getBarMargin() const; + int getPreBarMargin() const; + int getPostBarMargin() const; + int getFixedItemSpacing() const; + + NotePixmapFactory *getNotePixmapFactory(Staff &); + NotePixmapFactory *getGraceNotePixmapFactory(Staff &); + + //--------------- Data members --------------------------------- + + BarDataMap m_barData; + StaffIntMap m_staffNameWidths; + BarPositionList m_barPositions; + NotationGroupMap m_groupsExtant; + + double m_totalWidth; + bool m_pageMode; + double m_pageWidth; + int m_spacing; + int m_proportion; + int m_keySigCancelMode; + + //!!! This should not be here -- different staffs may have + //different sizes in principle, so we should always be referring + //to the npf of a particular staff + NotePixmapFactory *m_npf; + + static std::vector<int> m_availableSpacings; + static std::vector<int> m_availableProportions; + + const Quantizer *m_notationQuantizer; + const NotationProperties &m_properties; + + int m_timePerProgressIncrement; + std::map<Staff *, bool> m_haveOttavaSomewhere; + int m_staffCount; // purely for progress reporting +}; + + +} + +#endif diff --git a/src/gui/editors/notation/NotationProperties.cpp b/src/gui/editors/notation/NotationProperties.cpp new file mode 100644 index 0000000..8c87cc3 --- /dev/null +++ b/src/gui/editors/notation/NotationProperties.cpp @@ -0,0 +1,85 @@ +/* -*- 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 "NotationProperties.h" + +#include "base/PropertyName.h" + + +namespace Rosegarden +{ + +const PropertyName NotationProperties::NOTE_STYLE = "NoteStyle"; +const PropertyName NotationProperties::HEIGHT_ON_STAFF = "HeightOnStaff"; +const PropertyName NotationProperties::BEAMED = "Beamed"; +const PropertyName NotationProperties::BEAM_ABOVE = "BeamAbove"; +const PropertyName NotationProperties::SLASHES = "Slashes"; +const PropertyName NotationProperties::STEM_UP = "NoteStemUp"; +const PropertyName NotationProperties::USE_CAUTIONARY_ACCIDENTAL = "UseCautionaryAccidental"; +const PropertyName NotationProperties::OTTAVA_SHIFT = "OttavaShift"; +const PropertyName NotationProperties::SLUR_ABOVE = "SlurAbove"; + +NotationProperties::NotationProperties(const std::string &prefix) : + + VIEW_LOCAL_STEM_UP (prefix + "StemUp"), + + MIN_WIDTH (prefix + "MinWidth"), + + CALCULATED_ACCIDENTAL (prefix + "NoteCalculatedAccidental"), + DISPLAY_ACCIDENTAL (prefix + "NoteDisplayAccidental"), + DISPLAY_ACCIDENTAL_IS_CAUTIONARY(prefix + "NoteDisplayAccidentalIsCautionary"), + ACCIDENTAL_SHIFT (prefix + "NoteAccidentalShift"), + ACCIDENTAL_EXTRA_SHIFT (prefix + "NoteAccidentalExtraShift"), + UNBEAMED_STEM_LENGTH (prefix + "UnbeamedStemLength"), + DRAW_FLAG (prefix + "NoteDrawFlag"), + NOTE_HEAD_SHIFTED (prefix + "NoteHeadShifted"), + NEEDS_EXTRA_SHIFT_SPACE (prefix + "NeedsExtraShiftSpace"), + NOTE_DOT_SHIFTED (prefix + "NoteDotShifted"), + CHORD_PRIMARY_NOTE (prefix + "ChordPrimaryNote"), + CHORD_MARK_COUNT (prefix + "ChordMarkCount"), + TIE_LENGTH (prefix + "TieLength"), + SLUR_Y_DELTA (prefix + "SlurYDelta"), + SLUR_LENGTH (prefix + "SlurLength"), + LYRIC_EXTRA_WIDTH (prefix + "LyricExtraWidth"), + REST_TOO_SHORT (prefix + "RestTooShort"), + REST_OUTSIDE_STAVE (prefix + "RestOutsideStave"), + + BEAM_GRADIENT (prefix + "BeamGradient"), + BEAM_SECTION_WIDTH (prefix + "BeamSectionWidth"), + BEAM_NEXT_BEAM_COUNT (prefix + "BeamNextBeamCount"), + BEAM_NEXT_PART_BEAMS (prefix + "BeamNextPartBeams"), + BEAM_THIS_PART_BEAMS (prefix + "BeamThisPartBeams"), + BEAM_MY_Y (prefix + "BeamMyY"), + + TUPLING_LINE_MY_Y (prefix + "TuplingLineMyY"), + TUPLING_LINE_WIDTH (prefix + "TuplingLineWidth"), + TUPLING_LINE_GRADIENT (prefix + "TuplingLineGradient"), + TUPLING_LINE_FOLLOWS_BEAM (prefix + "TuplingLineFollowsBeam") + +{ + // nothing else +} + +} diff --git a/src/gui/editors/notation/NotationProperties.h b/src/gui/editors/notation/NotationProperties.h new file mode 100644 index 0000000..69a26cf --- /dev/null +++ b/src/gui/editors/notation/NotationProperties.h @@ -0,0 +1,108 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONPROPERTIES_H_ +#define _RG_NOTATIONPROPERTIES_H_ + +#include "base/PropertyName.h" +#include <string> + + + + +namespace Rosegarden +{ + + + +/** + * Property names for properties that are computed and cached within + * the notation module, but that need not necessarily be saved with + * the file. + * + * If you add something here, remember to add the definition to + * notationproperties.cpp as well... + */ + +class NotationProperties +{ +public: + NotationProperties(const std::string &prefix); + + // These are only of interest to notation views, but are the + // same across all notation views. + + static const PropertyName HEIGHT_ON_STAFF; + static const PropertyName NOTE_STYLE; + static const PropertyName BEAMED; + static const PropertyName BEAM_ABOVE; + static const PropertyName SLASHES; + static const PropertyName STEM_UP; + static const PropertyName USE_CAUTIONARY_ACCIDENTAL; + static const PropertyName OTTAVA_SHIFT; + static const PropertyName SLUR_ABOVE; + + // The rest are, or may be, view-local + + const PropertyName VIEW_LOCAL_STEM_UP; + const PropertyName MIN_WIDTH; + const PropertyName CALCULATED_ACCIDENTAL; + const PropertyName DISPLAY_ACCIDENTAL; + const PropertyName DISPLAY_ACCIDENTAL_IS_CAUTIONARY; + const PropertyName ACCIDENTAL_SHIFT; + const PropertyName ACCIDENTAL_EXTRA_SHIFT; + const PropertyName UNBEAMED_STEM_LENGTH; + const PropertyName DRAW_FLAG; + const PropertyName NOTE_HEAD_SHIFTED; + const PropertyName NEEDS_EXTRA_SHIFT_SPACE; + const PropertyName NOTE_DOT_SHIFTED; + const PropertyName CHORD_PRIMARY_NOTE; + const PropertyName CHORD_MARK_COUNT; + const PropertyName TIE_LENGTH; + const PropertyName SLUR_Y_DELTA; + const PropertyName SLUR_LENGTH; + const PropertyName LYRIC_EXTRA_WIDTH; + const PropertyName REST_TOO_SHORT; + const PropertyName REST_OUTSIDE_STAVE; + + // Set in applyBeam in notationsets.cpp: + + const PropertyName BEAM_GRADIENT; + const PropertyName BEAM_SECTION_WIDTH; + const PropertyName BEAM_NEXT_BEAM_COUNT; + const PropertyName BEAM_NEXT_PART_BEAMS; + const PropertyName BEAM_THIS_PART_BEAMS; + const PropertyName BEAM_MY_Y; + const PropertyName TUPLING_LINE_MY_Y; + const PropertyName TUPLING_LINE_WIDTH; + const PropertyName TUPLING_LINE_GRADIENT; + const PropertyName TUPLING_LINE_FOLLOWS_BEAM; + +}; + + +} + +#endif diff --git a/src/gui/editors/notation/NotationSelectionPaster.cpp b/src/gui/editors/notation/NotationSelectionPaster.cpp new file mode 100644 index 0000000..3b008f2 --- /dev/null +++ b/src/gui/editors/notation/NotationSelectionPaster.cpp @@ -0,0 +1,89 @@ +/* -*- 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 "NotationSelectionPaster.h" + +#include <klocale.h> +#include "base/Event.h" +#include "base/Selection.h" +#include "base/ViewElement.h" +#include "commands/edit/PasteEventsCommand.h" +#include "gui/general/EditTool.h" +#include "gui/general/LinedStaff.h" +#include "document/RosegardenGUIDoc.h" +#include "NotationTool.h" +#include "NotationView.h" +#include "NotationElement.h" + + +namespace Rosegarden +{ + +NotationSelectionPaster::NotationSelectionPaster(EventSelection& es, + NotationView* view) + : NotationTool("NotationPaster", view), + m_selection(es) +{ + m_nParentView->setCanvasCursor(Qt::crossCursor); +} + +NotationSelectionPaster::~NotationSelectionPaster() +{} + +void NotationSelectionPaster::handleLeftButtonPress(timeT, + int, + int staffNo, + QMouseEvent* e, + ViewElement*) +{ + if (staffNo < 0) + return ; + Event *clef = 0, *key = 0; + + LinedStaff *staff = m_nParentView->getLinedStaff(staffNo); + + NotationElementList::iterator closestElement = + staff->getClosestElementToCanvasCoords(e->x(), (int)e->y(), + clef, key, false, -1); + + if (closestElement == staff->getViewElementList()->end()) + return ; + + timeT time = (*closestElement)->getViewAbsoluteTime(); + + Segment& segment = staff->getSegment(); + PasteEventsCommand *command = new PasteEventsCommand + (segment, m_parentView->getDocument()->getClipboard(), time, + PasteEventsCommand::Restricted); + + if (!command->isPossible()) { + m_parentView->slotStatusHelpMsg(i18n("Couldn't paste at this point")); + } else { + m_parentView->addCommandToHistory(command); + m_parentView->slotStatusHelpMsg(i18n("Ready.")); + } +} + +} diff --git a/src/gui/editors/notation/NotationSelectionPaster.h b/src/gui/editors/notation/NotationSelectionPaster.h new file mode 100644 index 0000000..e6a80dd --- /dev/null +++ b/src/gui/editors/notation/NotationSelectionPaster.h @@ -0,0 +1,72 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONSELECTIONPASTER_H_ +#define _RG_NOTATIONSELECTIONPASTER_H_ + +#include "NotationTool.h" +#include "base/Event.h" + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class ViewElement; +class NotationView; +class EventSelection; + + +/** + * Selection pasting - unused at the moment + */ +class NotationSelectionPaster : public NotationTool +{ +public: + + ~NotationSelectionPaster(); + + virtual void handleLeftButtonPress(timeT, + int height, int staffNo, + QMouseEvent*, + ViewElement* el); + +protected: + NotationSelectionPaster(EventSelection&, + NotationView*); + + //--------------- Data members --------------------------------- + + EventSelection& m_selection; + +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/NotationSelector.cpp b/src/gui/editors/notation/NotationSelector.cpp new file mode 100644 index 0000000..221fbe3 --- /dev/null +++ b/src/gui/editors/notation/NotationSelector.cpp @@ -0,0 +1,957 @@ +/* -*- 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 "NotationSelector.h" +#include "misc/Debug.h" + +#include <klocale.h> +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/PropertyName.h" +#include "base/Selection.h" +#include "base/ViewElement.h" +#include "base/BaseProperties.h" +#include "commands/edit/MoveAcrossSegmentsCommand.h" +#include "commands/edit/MoveCommand.h" +#include "commands/edit/TransposeCommand.h" +#include "commands/notation/IncrementDisplacementsCommand.h" +#include "gui/general/EditTool.h" +#include "gui/general/GUIPalette.h" +#include "gui/general/LinedStaff.h" +#include "gui/general/RosegardenCanvasView.h" +#include "gui/kdeext/QCanvasSimpleSprite.h" +#include "NotationElement.h" +#include "NotationProperties.h" +#include "NotationStaff.h" +#include "NotationTool.h" +#include "NotationView.h" +#include "NotePixmapFactory.h" +#include <kaction.h> +#include <qapplication.h> +#include <qiconset.h> +#include <qrect.h> +#include <qstring.h> +#include <qtimer.h> + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +NotationSelector::NotationSelector(NotationView* view) + : NotationTool("NotationSelector", view), + m_selectionRect(0), + m_updateRect(false), + m_selectedStaff(0), + m_clickedElement(0), + m_selectionToMerge(0), + m_justSelectedBar(false), + m_wholeStaffSelectionComplete(false) +{ + connect(m_parentView, SIGNAL(usedSelection()), + this, SLOT(slotHideSelection())); + + connect(this, SIGNAL(editElement(NotationStaff *, NotationElement *, bool)), + m_parentView, SLOT(slotEditElement(NotationStaff *, NotationElement *, bool))); + + QIconSet icon + (NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("crotchet"))); + new KToggleAction(i18n("Switch to Insert Tool"), icon, 0, this, + SLOT(slotInsertSelected()), actionCollection(), + "insert"); + + new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this, + SLOT(slotEraseSelected()), actionCollection(), + "erase"); + + // (this crashed, and it might be superfluous with ^N anyway, so I'm + // commenting it out, but leaving it here in case I change my mind about + // fooling with it.) (DMM) + // new KAction(i18n("Normalize Rests"), 0, 0, this, + // SLOT(slotCollapseRests()), actionCollection(), + // "collapse_rests"); + + new KAction(i18n("Collapse Rests"), 0, 0, this, + SLOT(slotCollapseRestsHard()), actionCollection(), + "collapse_rests_aggressively"); + + new KAction(i18n("Respell as Flat"), 0, 0, this, + SLOT(slotRespellFlat()), actionCollection(), + "respell_flat"); + + new KAction(i18n("Respell as Sharp"), 0, 0, this, + SLOT(slotRespellSharp()), actionCollection(), + "respell_sharp"); + + new KAction(i18n("Respell as Natural"), 0, 0, this, + SLOT(slotRespellNatural()), actionCollection(), + "respell_natural"); + + new KAction(i18n("Collapse Notes"), 0, 0, this, + SLOT(slotCollapseNotes()), actionCollection(), + "collapse_notes"); + + new KAction(i18n("Interpret"), 0, 0, this, + SLOT(slotInterpret()), actionCollection(), + "interpret"); + + new KAction(i18n("Move to Staff Above"), 0, 0, this, + SLOT(slotStaffAbove()), actionCollection(), + "move_events_up_staff"); + + new KAction(i18n("Move to Staff Below"), 0, 0, this, + SLOT(slotStaffBelow()), actionCollection(), + "move_events_down_staff"); + + new KAction(i18n("Make Invisible"), 0, 0, this, + SLOT(slotMakeInvisible()), actionCollection(), + "make_invisible"); + + new KAction(i18n("Make Visible"), 0, 0, this, + SLOT(slotMakeVisible()), actionCollection(), + "make_visible"); + + createMenu("notationselector.rc"); +} + +NotationSelector::~NotationSelector() +{ + delete m_selectionToMerge; +} + +void NotationSelector::handleLeftButtonPress(timeT t, + int height, + int staffNo, + QMouseEvent* e, + ViewElement *element) +{ + std::cerr << "NotationSelector::handleMousePress: time is " << t << ", staffNo is " + << staffNo << ", e and element are " << e << " and " << element << std::endl; + + if (m_justSelectedBar) { + handleMouseTripleClick(t, height, staffNo, e, element); + m_justSelectedBar = false; + return ; + } + + m_wholeStaffSelectionComplete = false; + + delete m_selectionToMerge; + const EventSelection *selectionToMerge = 0; + if (e->state() & ShiftButton) { + m_clickedShift = true; + selectionToMerge = m_nParentView->getCurrentSelection(); + } else { + m_clickedShift = false; + } + m_selectionToMerge = + (selectionToMerge ? new EventSelection(*selectionToMerge) : 0); + + m_clickedElement = dynamic_cast<NotationElement*>(element); + if (m_clickedElement) { + m_selectedStaff = getStaffForElement(m_clickedElement); + m_lastDragPitch = -400; + m_lastDragTime = m_clickedElement->event()->getNotationAbsoluteTime(); + } else { + m_selectedStaff = 0; // don't know yet; wait until we have an element + } + + m_selectionRect->setX(e->x()); + m_selectionRect->setY(e->y()); + m_selectionRect->setSize(0, 0); + + m_selectionRect->show(); + m_updateRect = true; + m_startedFineDrag = false; + + //m_parentView->setCursorPosition(p.x()); +} + +void NotationSelector::handleRightButtonPress(timeT t, + int height, + int staffNo, + QMouseEvent* e, + ViewElement *element) +{ + std::cerr << "NotationSelector::handleRightButtonPress" << std::endl; + + const EventSelection *sel = m_nParentView->getCurrentSelection(); + + if (!sel || sel->getSegmentEvents().empty()) { + + // if nothing selected, permit the possibility of selecting + // something before showing the menu + + if (element) { + m_clickedElement = dynamic_cast<NotationElement*>(element); + m_selectedStaff = getStaffForElement(m_clickedElement); + m_nParentView->setSingleSelectedEvent + (m_selectedStaff->getId(), m_clickedElement->event(), + true, true); + } + + handleLeftButtonPress(t, height, staffNo, e, element); + } + + EditTool::handleRightButtonPress(t, height, staffNo, e, element); +} + +void NotationSelector::slotClickTimeout() +{ + m_justSelectedBar = false; +} + +void NotationSelector::handleMouseDoubleClick(timeT, + int, + int staffNo, + QMouseEvent* e, + ViewElement *element) +{ + NOTATION_DEBUG << "NotationSelector::handleMouseDoubleClick" << endl; + m_clickedElement = dynamic_cast<NotationElement*>(element); + + NotationStaff *staff = m_nParentView->getNotationStaff(staffNo); + if (!staff) + return ; + m_selectedStaff = staff; + + bool advanced = (e->state() & ShiftButton); + + if (m_clickedElement) { + + emit editElement(staff, m_clickedElement, advanced); + + } else { + + QRect rect = staff->getBarExtents(e->x(), e->y()); + + m_selectionRect->setX(rect.x() + 1); + m_selectionRect->setY(rect.y()); + m_selectionRect->setSize(rect.width() - 1, rect.height()); + + m_selectionRect->show(); + m_updateRect = false; + + m_justSelectedBar = true; + QTimer::singleShot(QApplication::doubleClickInterval(), this, + SLOT(slotClickTimeout())); + } + + return ; +} + +void NotationSelector::handleMouseTripleClick(timeT t, + int height, + int staffNo, + QMouseEvent* e, + ViewElement *element) +{ + if (!m_justSelectedBar) + return ; + m_justSelectedBar = false; + + NOTATION_DEBUG << "NotationSelector::handleMouseTripleClick" << endl; + m_clickedElement = dynamic_cast<NotationElement*>(element); + + NotationStaff *staff = m_nParentView->getNotationStaff(staffNo); + if (!staff) + return ; + m_selectedStaff = staff; + + if (m_clickedElement) { + + // should be safe, as we've already set m_justSelectedBar false + handleLeftButtonPress(t, height, staffNo, e, element); + return ; + + } else { + + m_selectionRect->setX(staff->getX()); + m_selectionRect->setY(staff->getY()); + m_selectionRect->setSize(int(staff->getTotalWidth()) - 1, + staff->getTotalHeight() - 1); + + m_selectionRect->show(); + m_updateRect = false; + } + + m_wholeStaffSelectionComplete = true; + + return ; +} + +int NotationSelector::handleMouseMove(timeT, int, + QMouseEvent* e) +{ + if (!m_updateRect) + return RosegardenCanvasView::NoFollow; + + int w = int(e->x() - m_selectionRect->x()); + int h = int(e->y() - m_selectionRect->y()); + + if (m_clickedElement /* && !m_clickedElement->isRest() */) { + + if (m_startedFineDrag) { + dragFine(e->x(), e->y(), false); + } else if (m_clickedShift) { + if (w > 2 || w < -2 || h > 2 || h < -2) { + dragFine(e->x(), e->y(), false); + } + } else if (w > 3 || w < -3 || h > 3 || h < -3) { + drag(e->x(), e->y(), false); + } + + } else { + + // Qt rectangle dimensions appear to be 1-based + if (w > 0) + ++w; + else + --w; + if (h > 0) + ++h; + else + --h; + + m_selectionRect->setSize(w, h); + setViewCurrentSelection(true); + m_nParentView->canvas()->update(); + } + + return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical; +} + +void NotationSelector::handleMouseRelease(timeT, int, QMouseEvent *e) +{ + NOTATION_DEBUG << "NotationSelector::handleMouseRelease" << endl; + m_updateRect = false; + + NOTATION_DEBUG << "selectionRect width, height: " << m_selectionRect->width() + << ", " << m_selectionRect->height() << endl; + + // Test how far we've moved from the original click position -- not + // how big the rectangle is (if we were dragging an event, the + // rectangle size will still be zero). + + if (((e->x() - m_selectionRect->x()) > -3 && + (e->x() - m_selectionRect->x()) < 3 && + (e->y() - m_selectionRect->y()) > -3 && + (e->y() - m_selectionRect->y()) < 3) && + !m_startedFineDrag) { + + if (m_clickedElement != 0 && m_selectedStaff) { + + // If we didn't drag out a meaningful area, but _did_ + // click on an individual event, then select just that + // event + + if (m_selectionToMerge && + m_selectionToMerge->getSegment() == + m_selectedStaff->getSegment()) { + + // if the event was already part of the selection, we want to + // remove it + if (m_selectionToMerge->contains(m_clickedElement->event())) { + m_selectionToMerge->removeEvent(m_clickedElement->event()); + } else { + m_selectionToMerge->addEvent(m_clickedElement->event()); + } + + m_nParentView->setCurrentSelection(m_selectionToMerge, + true, true); + m_selectionToMerge = 0; + + } else { + + m_nParentView->setSingleSelectedEvent + (m_selectedStaff->getId(), m_clickedElement->event(), + true, true); + } + /* + } else if (m_selectedStaff) { + + // If we clicked on no event but on a staff, move the + // insertion cursor to the point where we clicked. + // Actually we only really want this to happen if + // we aren't double-clicking -- consider using a timer + // to establish whether a double-click is going to happen + + m_nParentView->slotSetInsertCursorPosition(e->x(), (int)e->y()); + */ + } else { + setViewCurrentSelection(false); + } + + } else { + + if (m_startedFineDrag) { + dragFine(e->x(), e->y(), true); + } else if (m_clickedElement /* && !m_clickedElement->isRest() */) { + drag(e->x(), e->y(), true); + } else { + setViewCurrentSelection(false); + } + } + + m_clickedElement = 0; + m_selectionRect->hide(); + m_wholeStaffSelectionComplete = false; + m_nParentView->canvas()->update(); +} + +void NotationSelector::drag(int x, int y, bool final) +{ + NOTATION_DEBUG << "NotationSelector::drag " << x << ", " << y << endl; + + if (!m_clickedElement || !m_selectedStaff) + return ; + + EventSelection *selection = m_nParentView->getCurrentSelection(); + if (!selection || !selection->contains(m_clickedElement->event())) { + selection = new EventSelection(m_selectedStaff->getSegment()); + selection->addEvent(m_clickedElement->event()); + } + m_nParentView->setCurrentSelection(selection); + + LinedStaff *targetStaff = m_nParentView->getStaffForCanvasCoords(x, y); + if (!targetStaff) + targetStaff = m_selectedStaff; + + // Calculate time and height + + timeT clickedTime = m_clickedElement->event()->getNotationAbsoluteTime(); + + Accidental clickedAccidental = Accidentals::NoAccidental; + (void)m_clickedElement->event()->get<String>(ACCIDENTAL, clickedAccidental); + + long clickedPitch = 0; + (void)m_clickedElement->event()->get<Int>(PITCH, clickedPitch); + + long clickedHeight = 0; + (void)m_clickedElement->event()->get<Int> + (NotationProperties::HEIGHT_ON_STAFF, clickedHeight); + + Event *clefEvt = 0, *keyEvt = 0; + Clef clef; + ::Rosegarden::Key key; + + timeT dragTime = clickedTime; + double layoutX = m_clickedElement->getLayoutX(); + timeT duration = m_clickedElement->getViewDuration(); + + NotationElementList::iterator itr = + targetStaff->getElementUnderCanvasCoords(x, y, clefEvt, keyEvt); + + if (itr != targetStaff->getViewElementList()->end()) { + + NotationElement *elt = dynamic_cast<NotationElement *>(*itr); + dragTime = elt->getViewAbsoluteTime(); + layoutX = elt->getLayoutX(); + + if (elt->isRest() && duration > 0 && elt->getCanvasItem()) { + + double restX = 0, restWidth = 0; + elt->getCanvasAirspace(restX, restWidth); + + timeT restDuration = elt->getViewDuration(); + + if (restWidth > 0 && + restDuration >= duration * 2) { + + int parts = restDuration / duration; + double encroachment = x - restX; + NOTATION_DEBUG << "encroachment is " << encroachment << ", restWidth is " << restWidth << endl; + int part = (int)((encroachment / restWidth) * parts); + if (part >= parts) + part = parts - 1; + + dragTime += part * restDuration / parts; + layoutX += part * restWidth / parts + + (restX - elt->getCanvasX()); + } + } + } + + if (clefEvt) + clef = Clef(*clefEvt); + if (keyEvt) + key = ::Rosegarden::Key(*keyEvt); + + int height = targetStaff->getHeightAtCanvasCoords(x, y); + int pitch = clickedPitch; + + if (height != clickedHeight) + pitch = + Pitch + (height, clef, key, clickedAccidental).getPerformancePitch(); + + if (pitch < clickedPitch) { + if (height < -10) { + height = -10; + pitch = Pitch + (height, clef, key, clickedAccidental).getPerformancePitch(); + } + } else if (pitch > clickedPitch) { + if (height > 18) { + height = 18; + pitch = Pitch + (height, clef, key, clickedAccidental).getPerformancePitch(); + } + } + + bool singleNonNotePreview = !m_clickedElement->isNote() && + selection->getSegmentEvents().size() == 1; + + if (!final && !singleNonNotePreview) { + + if ((pitch != m_lastDragPitch || dragTime != m_lastDragTime) && + m_clickedElement->isNote()) { + + m_nParentView->showPreviewNote(targetStaff->getId(), + layoutX, pitch, height, + Note::getNearestNote(duration), + m_clickedElement->isGrace()); + m_lastDragPitch = pitch; + m_lastDragTime = dragTime; + } + + } else { + + m_nParentView->clearPreviewNote(); + + KMacroCommand *command = new KMacroCommand(MoveCommand::getGlobalName()); + bool haveSomething = false; + + MoveCommand *mc = 0; + Event *lastInsertedEvent = 0; + + if (pitch != clickedPitch && m_clickedElement->isNote()) { + command->addCommand(new TransposeCommand(pitch - clickedPitch, + *selection)); + haveSomething = true; + } + + if (targetStaff != m_selectedStaff) { + command->addCommand(new MoveAcrossSegmentsCommand + (m_selectedStaff->getSegment(), + targetStaff->getSegment(), + dragTime - clickedTime + selection->getStartTime(), + true, + *selection)); + haveSomething = true; + } else { + if (dragTime != clickedTime) { + mc = new MoveCommand + (m_selectedStaff->getSegment(), //!!!sort + dragTime - clickedTime, true, *selection); + command->addCommand(mc); + haveSomething = true; + } + } + + if (haveSomething) { + + m_nParentView->addCommandToHistory(command); + + if (mc && singleNonNotePreview) { + + lastInsertedEvent = mc->getLastInsertedEvent(); + + if (lastInsertedEvent) { + m_nParentView->setSingleSelectedEvent(targetStaff->getId(), + lastInsertedEvent); + + ViewElementList::iterator vli = + targetStaff->findEvent(lastInsertedEvent); + + if (vli != targetStaff->getViewElementList()->end()) { + m_clickedElement = dynamic_cast<NotationElement *>(*vli); + } else { + m_clickedElement = 0; + } + + m_selectionRect->setX(x); + m_selectionRect->setY(y); + } + } + } else { + delete command; + } + } +} + +void NotationSelector::dragFine(int x, int y, bool final) +{ + NOTATION_DEBUG << "NotationSelector::drag " << x << ", " << y << endl; + + if (!m_clickedElement || !m_selectedStaff) + return ; + + EventSelection *selection = m_nParentView->getCurrentSelection(); + if (!selection) + selection = new EventSelection(m_selectedStaff->getSegment()); + if (!selection->contains(m_clickedElement->event())) + selection->addEvent(m_clickedElement->event()); + m_nParentView->setCurrentSelection(selection); + + // Fine drag modifies the DISPLACED_X and DISPLACED_Y properties on + // each event. The modifications have to be relative to the previous + // values of these properties, not to zero, so for each event we need + // to store the previous value at the time the drag starts. + + static PropertyName xProperty("temporary-displaced-x"); + static PropertyName yProperty("temporary-displaced-y"); + + if (!m_startedFineDrag) { + // back up original properties + + for (EventSelection::eventcontainer::iterator i = + selection->getSegmentEvents().begin(); + i != selection->getSegmentEvents().end(); ++i) { + long prevX = 0, prevY = 0; + (*i)->get + <Int>(DISPLACED_X, prevX); + (*i)->get + <Int>(DISPLACED_Y, prevY); + (*i)->setMaybe<Int>(xProperty, prevX); + (*i)->setMaybe<Int>(yProperty, prevY); + } + + m_startedFineDrag = true; + } + + // We want the displacements in 1/1000ths of a staff space + + double dx = x - m_selectionRect->x(); + double dy = y - m_selectionRect->y(); + + double noteBodyWidth = m_nParentView->getNotePixmapFactory()->getNoteBodyWidth(); + double lineSpacing = m_nParentView->getNotePixmapFactory()->getLineSpacing(); + dx = (1000.0 * dx) / noteBodyWidth; + dy = (1000.0 * dy) / lineSpacing; + + if (final) { + + // reset original values (and remove backup values) before + // applying command + + for (EventSelection::eventcontainer::iterator i = + selection->getSegmentEvents().begin(); + i != selection->getSegmentEvents().end(); ++i) { + long prevX = 0, prevY = 0; + (*i)->get + <Int>(xProperty, prevX); + (*i)->get + <Int>(yProperty, prevY); + (*i)->setMaybe<Int>(DISPLACED_X, prevX); + (*i)->setMaybe<Int>(DISPLACED_Y, prevY); + (*i)->unset(xProperty); + (*i)->unset(yProperty); + } + + IncrementDisplacementsCommand *command = new IncrementDisplacementsCommand + (*selection, long(dx), long(dy)); + m_nParentView->addCommandToHistory(command); + + } else { + + timeT startTime = 0, endTime = 0; + + for (EventSelection::eventcontainer::iterator i = + selection->getSegmentEvents().begin(); + i != selection->getSegmentEvents().end(); ++i) { + long prevX = 0, prevY = 0; + (*i)->get + <Int>(xProperty, prevX); + (*i)->get + <Int>(yProperty, prevY); + (*i)->setMaybe<Int>(DISPLACED_X, prevX + long(dx)); + (*i)->setMaybe<Int>(DISPLACED_Y, prevY + long(dy)); + if (i == selection->getSegmentEvents().begin()) { + startTime = (*i)->getAbsoluteTime(); + } + endTime = (*i)->getAbsoluteTime() + (*i)->getDuration(); + } + + if (startTime == endTime) + ++endTime; + selection->getSegment().updateRefreshStatuses(startTime, endTime); + m_nParentView->update(); + } +} + +void NotationSelector::ready() +{ + m_selectionRect = new QCanvasRectangle(m_nParentView->canvas()); + + m_selectionRect->hide(); + m_selectionRect->setPen(GUIPalette::getColour(GUIPalette::SelectionRectangle)); + + m_nParentView->setCanvasCursor(Qt::arrowCursor); + m_nParentView->setHeightTracking(false); +} + +void NotationSelector::stow() +{ + delete m_selectionRect; + m_selectionRect = 0; + m_nParentView->canvas()->update(); +} + +void NotationSelector::slotHideSelection() +{ + if (!m_selectionRect) + return ; + m_selectionRect->hide(); + m_selectionRect->setSize(0, 0); + m_nParentView->canvas()->update(); +} + +void NotationSelector::slotInsertSelected() +{ + m_nParentView->slotLastNoteAction(); +} + +void NotationSelector::slotEraseSelected() +{ + m_parentView->actionCollection()->action("erase")->activate(); +} + +void NotationSelector::slotCollapseRestsHard() +{ + m_parentView->actionCollection()->action("collapse_rests_aggressively")->activate(); +} + +void NotationSelector::slotRespellFlat() +{ + m_parentView->actionCollection()->action("respell_flat")->activate(); +} + +void NotationSelector::slotRespellSharp() +{ + m_parentView->actionCollection()->action("respell_sharp")->activate(); +} + +void NotationSelector::slotRespellNatural() +{ + m_parentView->actionCollection()->action("respell_natural")->activate(); +} + +void NotationSelector::slotCollapseNotes() +{ + m_parentView->actionCollection()->action("collapse_notes")->activate(); +} + +void NotationSelector::slotInterpret() +{ + m_parentView->actionCollection()->action("interpret")->activate(); +} + +void NotationSelector::slotStaffAbove() +{ + m_parentView->actionCollection()->action("move_events_up_staff")->activate(); +} + +void NotationSelector::slotStaffBelow() +{ + m_parentView->actionCollection()->action("move_events_down_staff")->activate(); +} + +void NotationSelector::slotMakeInvisible() +{ + m_parentView->actionCollection()->action("make_invisible")->activate(); +} + +void NotationSelector::slotMakeVisible() +{ + m_parentView->actionCollection()->action("make_visible")->activate(); +} + +void NotationSelector::setViewCurrentSelection(bool preview) +{ + EventSelection *selection = getSelection(); + + if (m_selectionToMerge) { + if (selection && + m_selectionToMerge->getSegment() == selection->getSegment()) { + selection->addFromSelection(m_selectionToMerge); + } else { + return ; + } + } + + m_nParentView->setCurrentSelection(selection, preview, true); +} + +NotationStaff * +NotationSelector::getStaffForElement(NotationElement *elt) +{ + for (int i = 0; i < m_nParentView->getStaffCount(); ++i) { + NotationStaff *staff = m_nParentView->getNotationStaff(i); + if (staff->getSegment().findSingle(elt->event()) != + staff->getSegment().end()) + return staff; + } + return 0; +} + +EventSelection* NotationSelector::getSelection() +{ + // If selection rect is not visible or too small, + // return 0 + // + if (!m_selectionRect->visible()) return 0; + + // NOTATION_DEBUG << "Selection x,y: " << m_selectionRect->x() << "," + // << m_selectionRect->y() << "; w,h: " << m_selectionRect->width() << "," << m_selectionRect->height() << endl; + + if (m_selectionRect->width() > -3 && + m_selectionRect->width() < 3 && + m_selectionRect->height() > -3 && + m_selectionRect->height() < 3) return 0; + + QCanvasItemList itemList = m_selectionRect->collisions(false); + QCanvasItemList::Iterator it; + + QRect rect = m_selectionRect->rect().normalize(); + QCanvasNotationSprite *sprite = 0; + + if (!m_selectedStaff) { + + // Scan the list of collisions, looking for a valid notation + // element; if we find one, initialise m_selectedStaff from it. + // If we don't find one, we have no selection. This is a little + // inefficient but we only do it for the first event in the + // selection. + + for (it = itemList.begin(); it != itemList.end(); ++it) { + + if ((sprite = dynamic_cast<QCanvasNotationSprite*>(*it))) { + + NotationElement &el = sprite->getNotationElement(); + + NotationStaff *staff = getStaffForElement(&el); + if (!staff) continue; + + int x = (int)(*it)->x(); + bool shifted = false; + int nbw = staff->getNotePixmapFactory(false).getNoteBodyWidth(); + + + // #957364 (Notation: Hard to select upper note in + // chords of seconds) -- adjust x-coord for shifted + // note head + if (el.event()->get<Rosegarden::Bool> + (staff->getProperties().NOTE_HEAD_SHIFTED, shifted) && shifted) { + x += nbw; + } + + if (!rect.contains(x, int((*it)->y()), true)) { + // #988217 (Notation: Special column of pixels + // prevents sweep selection) -- for notes, test + // again with centred x-coord + if (!el.isNote() || !rect.contains(x + nbw/2, int((*it)->y()), true)) { + continue; + } + } + + m_selectedStaff = staff; + break; + } + } + } + + if (!m_selectedStaff) return 0; + Segment& originalSegment = m_selectedStaff->getSegment(); + + // If we selected the whole staff, force that to happen explicitly + // rather than relying on collisions with the rectangle -- because + // events way off the currently visible area might not even have + // been drawn yet, and so will not appear in the collision list. + // (We did still need the collision list to determine which staff + // to use though.) + + if (m_wholeStaffSelectionComplete) { + EventSelection *selection = new EventSelection(originalSegment, + originalSegment.getStartTime(), + originalSegment.getEndMarkerTime()); + return selection; + } + + EventSelection* selection = new EventSelection(originalSegment); + + for (it = itemList.begin(); it != itemList.end(); ++it) { + + if ((sprite = dynamic_cast<QCanvasNotationSprite*>(*it))) { + + NotationElement &el = sprite->getNotationElement(); + + int x = (int)(*it)->x(); + bool shifted = false; + int nbw = m_selectedStaff->getNotePixmapFactory(false).getNoteBodyWidth(); + + // #957364 (Notation: Hard to select upper note in chords + // of seconds) -- adjust x-coord for shifted note head + if (el.event()->get<Rosegarden::Bool> + (m_selectedStaff->getProperties().NOTE_HEAD_SHIFTED, shifted) + && shifted) { + x += nbw; + } + + // check if the element's rect + // is actually included in the selection rect. + // + if (!rect.contains(x, int((*it)->y()), true)) { + // #988217 (Notation: Special column of pixels + // prevents sweep selection) -- for notes, test again + // with centred x-coord + if (!el.isNote() || !rect.contains(x + nbw/2, int((*it)->y()), true)) { + continue; + } + } + + // must be in the same segment as we first started on, + // we can't select events across multiple segments + if (selection->getSegment().findSingle(el.event()) != + selection->getSegment().end()) { + selection->addEvent(el.event()); + } + } + } + + if (selection->getAddedEvents() > 0) { + return selection; + } else { + delete selection; + return 0; + } +} + +const QString NotationSelector::ToolName = "notationselector"; + +} +#include "NotationSelector.moc" diff --git a/src/gui/editors/notation/NotationSelector.h b/src/gui/editors/notation/NotationSelector.h new file mode 100644 index 0000000..7266fd5 --- /dev/null +++ b/src/gui/editors/notation/NotationSelector.h @@ -0,0 +1,197 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONSELECTOR_H_ +#define _RG_NOTATIONSELECTOR_H_ + +#include "NotationTool.h" +#include "NotationElement.h" +#include <qstring.h> +#include "base/Event.h" + + +class QMouseEvent; +class QCanvasRectangle; +class m_clickedElement; + + +namespace Rosegarden +{ + +class ViewElement; +class NotationView; +class NotationStaff; +class NotationElement; +class EventSelection; +class Event; + + +/** + * Rectangular note selection + */ +class NotationSelector : public NotationTool +{ + Q_OBJECT + + friend class NotationToolBox; + +public: + + ~NotationSelector(); + + virtual void handleLeftButtonPress(timeT, + int height, + int staffNo, + QMouseEvent*, + ViewElement* el); + + virtual void handleRightButtonPress(timeT time, + int height, + int staffNo, + QMouseEvent*, + ViewElement*); + + virtual int handleMouseMove(timeT, + int height, + QMouseEvent*); + + virtual void handleMouseRelease(timeT time, + int height, + QMouseEvent*); + + virtual void handleMouseDoubleClick(timeT, + int height, + int staffNo, + QMouseEvent*, + ViewElement*); + + virtual void handleMouseTripleClick(timeT, + int height, + int staffNo, + QMouseEvent*, + ViewElement*); + + /** + * Create the selection rect + * + * We need this because NotationView deletes all QCanvasItems + * along with it. This happens before the NotationSelector is + * deleted, so we can't delete the selection rect in + * ~NotationSelector because that leads to double deletion. + */ + virtual void ready(); + + /** + * Delete the selection rect. + */ + virtual void stow(); + + /** + * Returns the currently selected events + * + * The returned result is owned by the caller + */ + EventSelection* getSelection(); + + /** + * Respond to an event being deleted -- it may be the one the tool + * is remembering as the current event. + */ + virtual void handleEventRemoved(Event *event) { + if (m_clickedElement && m_clickedElement->event() == event) { + m_clickedElement = 0; + } + } + + static const QString ToolName; + +signals: + void editElement(NotationStaff *, NotationElement *, bool advanced); + +public slots: + /** + * Hide the selection rectangle + * + * Should be called after a cut or a copy has been + * performed + */ + void slotHideSelection(); + + void slotInsertSelected(); + void slotEraseSelected(); +// void slotCollapseRests(); + void slotCollapseRestsHard(); + void slotRespellFlat(); + void slotRespellSharp(); + void slotRespellNatural(); + void slotCollapseNotes(); + void slotInterpret(); + void slotStaffAbove(); + void slotStaffBelow(); + void slotMakeInvisible(); + void slotMakeVisible(); + + void slotClickTimeout(); + +protected: + NotationSelector(NotationView*); + + /** + * Set the current selection on the parent NotationView + */ + void setViewCurrentSelection(bool preview); + + /** + * Look up the staff containing the given notation element + */ + NotationStaff *getStaffForElement(NotationElement *elt); + + void drag(int x, int y, bool final); + void dragFine(int x, int y, bool final); + + //--------------- Data members --------------------------------- + + QCanvasRectangle* m_selectionRect; + bool m_updateRect; + + NotationStaff *m_selectedStaff; + NotationElement *m_clickedElement; + bool m_clickedShift; + bool m_startedFineDrag; + + EventSelection *m_selectionToMerge; + + long m_lastDragPitch; + timeT m_lastDragTime; + + bool m_justSelectedBar; + bool m_wholeStaffSelectionComplete; +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/NotationStaff.cpp b/src/gui/editors/notation/NotationStaff.cpp new file mode 100644 index 0000000..c5219b4 --- /dev/null +++ b/src/gui/editors/notation/NotationStaff.cpp @@ -0,0 +1,2300 @@ +/* -*- 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 "NotationStaff.h" +#include "misc/Debug.h" +#include <kapplication.h> + +#include <klocale.h> +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/Composition.h" +#include "base/Device.h" +#include "base/Event.h" +#include "base/Exception.h" +#include "base/Instrument.h" +#include "base/MidiDevice.h" +#include "base/MidiTypes.h" +#include "base/NotationQuantizer.h" +#include "base/NotationRules.h" +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/SnapGrid.h" +#include "base/Staff.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "base/ViewElement.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/editors/guitar/Chord.h" +#include "gui/general/LinedStaff.h" +#include "gui/general/PixmapFunctions.h" +#include "gui/general/ProgressReporter.h" +#include "gui/kdeext/QCanvasSimpleSprite.h" +#include "NotationChord.h" +#include "NotationElement.h" +#include "NotationProperties.h" +#include "NotationView.h" +#include "NoteFontFactory.h" +#include "NotePixmapFactory.h" +#include "NotePixmapParameters.h" +#include "NoteStyleFactory.h" +#include <kconfig.h> +#include <kmessagebox.h> +#include <qcanvas.h> +#include <qpainter.h> +#include <qpoint.h> +#include <qrect.h> + + +namespace Rosegarden +{ + +NotationStaff::NotationStaff(QCanvas *canvas, Segment *segment, + SnapGrid *snapGrid, int id, + NotationView *view, + std::string fontName, int resolution) : + ProgressReporter(0), + LinedStaff(canvas, segment, snapGrid, id, resolution, + resolution / 16 + 1, // line thickness + LinearMode, 0, 0, // pageMode, pageWidth and pageHeight set later + 0 // row spacing + ), + m_notePixmapFactory(0), + m_graceNotePixmapFactory(0), + m_previewSprite(0), + m_staffName(0), + m_notationView(view), + m_legerLineCount(8), + m_barNumbersEvery(0), + m_colourQuantize(true), + m_showUnknowns(true), + m_showRanges(true), + m_showCollisions(true), + m_printPainter(0), + m_ready(false), + m_lastRenderedBar(0) +{ + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + m_colourQuantize = config->readBoolEntry("colourquantize", false); + + // Shouldn't change these during the lifetime of the staff, really: + m_showUnknowns = config->readBoolEntry("showunknowns", false); + m_showRanges = config->readBoolEntry("showranges", true); + m_showCollisions = config->readBoolEntry("showcollisions", true); + + m_keySigCancelMode = config->readNumEntry("keysigcancelmode", 1); + + changeFont(fontName, resolution); +} + +NotationStaff::~NotationStaff() +{ + deleteTimeSignatures(); + delete m_notePixmapFactory; + delete m_graceNotePixmapFactory; +} + +void +NotationStaff::changeFont(std::string fontName, int size) +{ + setResolution(size); + + delete m_notePixmapFactory; + m_notePixmapFactory = new NotePixmapFactory(fontName, size); + + std::vector<int> sizes = NoteFontFactory::getScreenSizes(fontName); + int graceSize = size; + for (unsigned int i = 0; i < sizes.size(); ++i) { + if (sizes[i] == size || sizes[i] > size*3 / 4) + break; + graceSize = sizes[i]; + } + delete m_graceNotePixmapFactory; + m_graceNotePixmapFactory = new NotePixmapFactory(fontName, graceSize); + + setLineThickness(m_notePixmapFactory->getStaffLineThickness()); +} + +void +NotationStaff::insertTimeSignature(double layoutX, + const TimeSignature &timeSig) +{ + if (timeSig.isHidden()) + return ; + + m_notePixmapFactory->setSelected(false); + QCanvasPixmap *pixmap = m_notePixmapFactory->makeTimeSigPixmap(timeSig); + QCanvasTimeSigSprite *sprite = + new QCanvasTimeSigSprite(layoutX, pixmap, m_canvas); + + LinedStaffCoords sigCoords = + getCanvasCoordsForLayoutCoords(layoutX, getLayoutYForHeight(4)); + + sprite->move(sigCoords.first, (double)sigCoords.second); + sprite->show(); + m_timeSigs.insert(sprite); +} + +void +NotationStaff::deleteTimeSignatures() +{ + // NOTATION_DEBUG << "NotationStaff::deleteTimeSignatures()" << endl; + + for (SpriteSet::iterator i = m_timeSigs.begin(); + i != m_timeSigs.end(); ++i) { + delete *i; + } + + m_timeSigs.clear(); +} + +void +NotationStaff::insertRepeatedClefAndKey(double layoutX, int barNo) +{ + bool needClef = false, needKey = false; + timeT t; + + timeT barStart = getSegment().getComposition()->getBarStart(barNo); + + Clef clef = getSegment().getClefAtTime(barStart, t); + if (t < barStart) + needClef = true; + + ::Rosegarden::Key key = getSegment().getKeyAtTime(barStart, t); + if (t < barStart) + needKey = true; + + double dx = m_notePixmapFactory->getBarMargin() / 2; + + if (!m_notationView->isInPrintMode()) + m_notePixmapFactory->setShaded(true); + + if (needClef) { + + int layoutY = getLayoutYForHeight(clef.getAxisHeight()); + + LinedStaffCoords coords = + getCanvasCoordsForLayoutCoords(layoutX + dx, layoutY); + + QCanvasPixmap *pixmap = m_notePixmapFactory->makeClefPixmap(clef); + + QCanvasNonElementSprite *sprite = + new QCanvasNonElementSprite(pixmap, m_canvas); + + sprite->move(coords.first, coords.second); + sprite->show(); + m_repeatedClefsAndKeys.insert(sprite); + + dx += pixmap->width() + m_notePixmapFactory->getNoteBodyWidth() * 2 / 3; + } + + if (needKey) { + + int layoutY = getLayoutYForHeight(12); + + LinedStaffCoords coords = + getCanvasCoordsForLayoutCoords(layoutX + dx, layoutY); + + QCanvasPixmap *pixmap = m_notePixmapFactory->makeKeyPixmap(key, clef); + + QCanvasNonElementSprite *sprite = + new QCanvasNonElementSprite(pixmap, m_canvas); + + sprite->move(coords.first, coords.second); + sprite->show(); + m_repeatedClefsAndKeys.insert(sprite); + + dx += pixmap->width(); + } + + /* attempt to blot out things like slurs & ties that overrun this area: doesn't work + + if (m_notationView->isInPrintMode() && (needClef || needKey)) { + + int layoutY = getLayoutYForHeight(14); + int h = getLayoutYForHeight(-8) - layoutY; + + LinedStaffCoords coords = + getCanvasCoordsForLayoutCoords(layoutX, layoutY); + + QCanvasRectangle *rect = new QCanvasRectangle(coords.first, coords.second, + dx, h, m_canvas); + rect->setPen(Qt::black); + rect->setBrush(Qt::white); + rect->setZ(1); + rect->show(); + + m_repeatedClefsAndKeys.insert(rect); + } + */ + + m_notePixmapFactory->setShaded(false); +} + +void +NotationStaff::deleteRepeatedClefsAndKeys() +{ + for (ItemSet::iterator i = m_repeatedClefsAndKeys.begin(); + i != m_repeatedClefsAndKeys.end(); ++i) { + delete *i; + } + + m_repeatedClefsAndKeys.clear(); +} + +void +NotationStaff::drawStaffName() +{ + delete m_staffName; + + m_staffNameText = + getSegment().getComposition()-> + getTrackById(getSegment().getTrack())->getLabel(); + + QCanvasPixmap *map = + m_notePixmapFactory->makeTextPixmap + (Text(m_staffNameText, Text::StaffName)); + + m_staffName = new QCanvasStaffNameSprite(map, m_canvas); + + int layoutY = getLayoutYForHeight(3); + LinedStaffCoords coords = getCanvasCoordsForLayoutCoords(0, layoutY); + m_staffName->move(getX() + getMargin() + m_notePixmapFactory->getNoteBodyWidth(), + coords.second - map->height() / 2); + m_staffName->show(); +} + +bool +NotationStaff::isStaffNameUpToDate() +{ + return (m_staffNameText == + getSegment().getComposition()-> + getTrackById(getSegment().getTrack())->getLabel()); +} + +timeT +NotationStaff::getTimeAtCanvasCoords(double cx, int cy) const +{ + LinedStaffCoords layoutCoords = getLayoutCoordsForCanvasCoords(cx, cy); + RulerScale * rs = m_notationView->getHLayout(); + return rs->getTimeForX(layoutCoords.first); +} + +void +NotationStaff::getClefAndKeyAtCanvasCoords(double cx, int cy, + Clef &clef, + ::Rosegarden::Key &key) const +{ + LinedStaffCoords layoutCoords = getLayoutCoordsForCanvasCoords(cx, cy); + int i; + + for (i = 0; i < m_clefChanges.size(); ++i) { + if (m_clefChanges[i].first > layoutCoords.first) + break; + clef = m_clefChanges[i].second; + } + + for (i = 0; i < m_keyChanges.size(); ++i) { + if (m_keyChanges[i].first > layoutCoords.first) + break; + key = m_keyChanges[i].second; + } +} + +ViewElementList::iterator +NotationStaff::getClosestElementToLayoutX(double x, + Event *&clef, + Event *&key, + bool notesAndRestsOnly, + int proximityThreshold) +{ + START_TIMING; + + double minDist = 10e9, prevDist = 10e9; + + NotationElementList *notes = getViewElementList(); + NotationElementList::iterator it, result; + + // TODO: this is grossly inefficient + + for (it = notes->begin(); it != notes->end(); ++it) { + NotationElement *el = static_cast<NotationElement*>(*it); + + bool before = ((*it)->getLayoutX() < x); + + if (!el->isNote() && !el->isRest()) { + if (before) { + if ((*it)->event()->isa(Clef::EventType)) { + clef = (*it)->event(); + } else if ((*it)->event()->isa(::Rosegarden::Key::EventType)) { + key = (*it)->event(); + } + } + if (notesAndRestsOnly) + continue; + } + + double dx = x - (*it)->getLayoutX(); + if (dx < 0) + dx = -dx; + + if (dx < minDist) { + minDist = dx; + result = it; + } else if (!before) { + break; + } + + prevDist = dx; + } + + if (proximityThreshold > 0 && minDist > proximityThreshold) { + NOTATION_DEBUG << "NotationStaff::getClosestElementToLayoutX() : element is too far away : " + << minDist << endl; + return notes->end(); + } + + NOTATION_DEBUG << "NotationStaff::getClosestElementToLayoutX: found element at layout " << (*result)->getLayoutX() << " - we're at layout " << x << endl; + + PRINT_ELAPSED("NotationStaff::getClosestElementToLayoutX"); + + return result; +} + +ViewElementList::iterator +NotationStaff::getElementUnderLayoutX(double x, + Event *&clef, + Event *&key) +{ + NotationElementList *notes = getViewElementList(); + NotationElementList::iterator it; + + // TODO: this is grossly inefficient + + for (it = notes->begin(); it != notes->end(); ++it) { + NotationElement* el = static_cast<NotationElement*>(*it); + + bool before = ((*it)->getLayoutX() <= x); + + if (!el->isNote() && !el->isRest()) { + if (before) { + if ((*it)->event()->isa(Clef::EventType)) { + clef = (*it)->event(); + } else if ((*it)->event()->isa(::Rosegarden::Key::EventType)) { + key = (*it)->event(); + } + } + } + + double airX, airWidth; + el->getLayoutAirspace(airX, airWidth); + if (x >= airX && x < airX + airWidth) { + return it; + } else if (!before) { + if (it != notes->begin()) + --it; + return it; + } + } + + return notes->end(); +} + +std::string +NotationStaff::getNoteNameAtCanvasCoords(double x, int y, + Accidental) const +{ + Clef clef; + ::Rosegarden::Key key; + getClefAndKeyAtCanvasCoords(x, y, clef, key); + + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + int baseOctave = config->readNumEntry("midipitchoctave", -2); + + Pitch p(getHeightAtCanvasCoords(x, y), clef, key); + //!!! i18n() how? + return p.getAsString(key.isSharp(), true, baseOctave); +} + +void +NotationStaff::renderElements(NotationElementList::iterator from, + NotationElementList::iterator to) +{ + // NOTATION_DEBUG << "NotationStaff " << this << "::renderElements()" << endl; + Profiler profiler("NotationStaff::renderElements"); + + emit setOperationName(i18n("Rendering staff %1...").arg(getId() + 1)); + emit setProgress(0); + + throwIfCancelled(); + + // These are only used when rendering keys, and we don't have the + // right start data here so we choose not to render keys at all in + // this method (see below) so that we can pass bogus clef and key + // data to renderSingleElement + Clef currentClef; + ::Rosegarden::Key currentKey; + + int elementCount = 0; + timeT endTime = + (to != getViewElementList()->end() ? (*to)->getViewAbsoluteTime() : + getSegment().getEndMarkerTime()); + timeT startTime = (from != to ? (*from)->getViewAbsoluteTime() : endTime); + + for (NotationElementList::iterator it = from, nextIt = from; + it != to; it = nextIt) { + + ++nextIt; + + if (isDirectlyPrintable(*it)) { + // notes are renderable direct to the printer, so don't render + // them to the canvas here + continue; + } + + if ((*it)->event()->isa(::Rosegarden::Key::EventType)) { + // force rendering in positionElements instead + NotationElement* el = static_cast<NotationElement*>(*it); + el->removeCanvasItem(); + continue; + } + + bool selected = isSelected(it); + // NOTATION_DEBUG << "Rendering at " << (*it)->getAbsoluteTime() + // << " (selected = " << selected << ")" << endl; + + renderSingleElement(it, currentClef, currentKey, selected); + + if ((endTime > startTime) && + (++elementCount % 200 == 0)) { + + timeT myTime = (*it)->getViewAbsoluteTime(); + emit setProgress((myTime - startTime) * 100 / (endTime - startTime)); + throwIfCancelled(); + } + } + + // NOTATION_DEBUG << "NotationStaff " << this << "::renderElements: " + // << elementCount << " elements rendered" << endl; +} + +void +NotationStaff::renderPrintable(timeT from, timeT to) +{ + if (!m_printPainter) + return ; + + Profiler profiler("NotationStaff::renderElements"); + + emit setOperationName(i18n("Rendering notes on staff %1...").arg(getId() + 1)); + emit setProgress(0); + + throwIfCancelled(); + + // These are only used when rendering keys, and we don't do that + // here, so we don't care what they are + Clef currentClef; + ::Rosegarden::Key currentKey; + + Composition *composition = getSegment().getComposition(); + NotationElementList::iterator beginAt = + getViewElementList()->findTime(composition->getBarStartForTime(from)); + NotationElementList::iterator endAt = + getViewElementList()->findTime(composition->getBarEndForTime(to)); + + int elementCount = 0; + + for (NotationElementList::iterator it = beginAt, nextIt = beginAt; + it != endAt; it = nextIt) { + + ++nextIt; + + if (!isDirectlyPrintable(*it)) { + continue; + } + + bool selected = isSelected(it); + // NOTATION_DEBUG << "Rendering at " << (*it)->getAbsoluteTime() + // << " (selected = " << selected << ")" << endl; + + renderSingleElement(it, currentClef, currentKey, selected); + + if ((to > from) && (++elementCount % 200 == 0)) { + + timeT myTime = (*it)->getViewAbsoluteTime(); + emit setProgress((myTime - from) * 100 / (to - from)); + throwIfCancelled(); + } + } + + // NOTATION_DEBUG << "NotationStaff " << this << "::renderElements: " + // << elementCount << " elements rendered" << endl; +} + +const NotationProperties & +NotationStaff::getProperties() const +{ + return m_notationView->getProperties(); +} + +void +NotationStaff::positionElements(timeT from, timeT to) +{ + // NOTATION_DEBUG << "NotationStaff " << this << "::positionElements()" + // << from << " -> " << to << endl; + Profiler profiler("NotationStaff::positionElements"); + + // Following 4 lines are a workaround to not have m_clefChanges and + // m_keyChanges truncated when positionElements() is called with + // args outside current segment. + // Maybe a better fix would be not to call positionElements() with + // such args ... + int startTime = getSegment().getStartTime(); + if (from < startTime) from = startTime; + if (to < startTime) to = startTime; + if (to == from) return; + + emit setOperationName(i18n("Positioning staff %1...").arg(getId() + 1)); + emit setProgress(0); + throwIfCancelled(); + + const NotationProperties &properties(getProperties()); + + int elementsPositioned = 0; + int elementsRendered = 0; // diagnostic + + Composition *composition = getSegment().getComposition(); + + timeT nextBarTime = composition->getBarEndForTime(to); + + NotationElementList::iterator beginAt = + getViewElementList()->findTime(composition->getBarStartForTime(from)); + + NotationElementList::iterator endAt = + getViewElementList()->findTime(composition->getBarEndForTime(to)); + + if (beginAt == getViewElementList()->end()) + return ; + + truncateClefsAndKeysAt(static_cast<int>((*beginAt)->getLayoutX())); + + Clef currentClef; // used for rendering key sigs + bool haveCurrentClef = false; + + ::Rosegarden::Key currentKey; + bool haveCurrentKey = false; + + for (NotationElementList::iterator it = beginAt, nextIt = beginAt; + it != endAt; it = nextIt) { + + NotationElement * el = static_cast<NotationElement*>(*it); + + ++nextIt; + + if (el->event()->isa(Clef::EventType)) { + + currentClef = Clef(*el->event()); + m_clefChanges.push_back(ClefChange(int(el->getLayoutX()), + currentClef)); + haveCurrentClef = true; + + } else if (el->event()->isa(::Rosegarden::Key::EventType)) { + + m_keyChanges.push_back + (KeyChange(int(el->getLayoutX()), + ::Rosegarden::Key(*el->event()))); + + if (!haveCurrentClef) { // need this to know how to present the key + currentClef = getSegment().getClefAtTime + (el->event()->getAbsoluteTime()); + haveCurrentClef = true; + } + + if (!haveCurrentKey) { // stores the key _before_ this one + currentKey = getSegment().getKeyAtTime + (el->event()->getAbsoluteTime() - 1); + haveCurrentKey = true; + } + + } else if (isDirectlyPrintable(el)) { + // these are rendered by renderPrintable for printing + continue; + } + + bool selected = isSelected(it); + bool needNewSprite = (selected != el->isSelected()); + + if (!el->getCanvasItem()) { + + needNewSprite = true; + + } else if (el->isNote() && !el->isRecentlyRegenerated()) { + + // If the note's y-coordinate has changed, we should + // redraw it -- its stem direction may have changed, or it + // may need leger lines. This will happen e.g. if the + // user inserts a new clef; unfortunately this means + // inserting clefs is rather slow. + + needNewSprite = needNewSprite || !elementNotMovedInY(el); + + if (!needNewSprite) { + + // If the event is a beamed or tied-forward note, then + // we might need a new sprite if the distance from + // this note to the next has changed (because the beam + // or tie is part of the note's sprite). + + bool spanning = false; + (void)(el->event()->get + <Bool> + (properties.BEAMED, spanning)); + if (!spanning) { + (void)(el->event()->get + <Bool>(BaseProperties::TIED_FORWARD, spanning)); + } + + if (spanning) { + needNewSprite = + (el->getViewAbsoluteTime() < nextBarTime || + !elementShiftedOnly(it)); + } + } + + } else if (el->event()->isa(Indication::EventType) && + !el->isRecentlyRegenerated()) { + needNewSprite = true; + } + + if (needNewSprite) { + renderSingleElement(it, currentClef, currentKey, selected); + ++elementsRendered; + } + + if (el->event()->isa(::Rosegarden::Key::EventType)) { + // update currentKey after rendering, not before + currentKey = ::Rosegarden::Key(*el->event()); + } + + if (!needNewSprite) { + LinedStaffCoords coords = getCanvasCoordsForLayoutCoords + (el->getLayoutX(), (int)el->getLayoutY()); + el->reposition(coords.first, (double)coords.second); + } + + el->setSelected(selected); + + if ((to > from) && + (++elementsPositioned % 300 == 0)) { + timeT myTime = el->getViewAbsoluteTime(); + emit setProgress((myTime - from) * 100 / (to - from)); + throwIfCancelled(); + } + } + + // NOTATION_DEBUG << "NotationStaff " << this << "::positionElements " + // << from << " -> " << to << ": " + // << elementsPositioned << " elements positioned, " + // << elementsRendered << " re-rendered" + // << endl; + + // NotePixmapFactory::dumpStats(std::cerr); +} + +void +NotationStaff::truncateClefsAndKeysAt(int x) +{ + for (FastVector<ClefChange>::iterator i = m_clefChanges.begin(); + i != m_clefChanges.end(); ++i) { + if (i->first >= x) { + m_clefChanges.erase(i, m_clefChanges.end()); + break; + } + } + + for (FastVector<KeyChange>::iterator i = m_keyChanges.begin(); + i != m_keyChanges.end(); ++i) { + if (i->first >= x) { + m_keyChanges.erase(i, m_keyChanges.end()); + break; + } + } +} + +NotationElementList::iterator +NotationStaff::findUnchangedBarStart(timeT from) +{ + NotationElementList *nel = (NotationElementList *)getViewElementList(); + + // Track back bar-by-bar until we find one whose start position + // hasn't changed + + NotationElementList::iterator beginAt = nel->begin(); + do { + from = getSegment().getComposition()->getBarStartForTime(from - 1); + beginAt = nel->findTime(from); + } while (beginAt != nel->begin() && + (beginAt == nel->end() || !elementNotMoved(static_cast<NotationElement*>(*beginAt)))); + + return beginAt; +} + +NotationElementList::iterator +NotationStaff::findUnchangedBarEnd(timeT to) +{ + NotationElementList *nel = (NotationElementList *)getViewElementList(); + + // Track forward to the end, similarly. Here however it's very + // common for all the positions to have changed right up to the + // end of the piece; so we save time by assuming that to be the + // case if we get more than (arbitrary) 3 changed bars. + + // We also record the start of the bar following the changed + // section, for later use. + + NotationElementList::iterator endAt = nel->end(); + + int changedBarCount = 0; + NotationElementList::iterator candidate = nel->end(); + do { + candidate = nel->findTime(getSegment().getBarEndForTime(to)); + if (candidate != nel->end()) { + to = (*candidate)->getViewAbsoluteTime(); + } + ++changedBarCount; + } while (changedBarCount < 4 && + candidate != nel->end() && + !elementNotMoved(static_cast<NotationElement*>(*candidate))); + + if (changedBarCount < 4) + return candidate; + else + return endAt; +} + +bool +NotationStaff::elementNotMoved(NotationElement *elt) +{ + if (!elt->getCanvasItem()) + return false; + + LinedStaffCoords coords = getCanvasCoordsForLayoutCoords + (elt->getLayoutX(), (int)elt->getLayoutY()); + + bool ok = + (int)(elt->getCanvasX()) == (int)(coords.first) && + (int)(elt->getCanvasY()) == (int)(coords.second); + + if (!ok) { + NOTATION_DEBUG + << "elementNotMoved: elt at " << elt->getViewAbsoluteTime() << + ", ok is " << ok << endl; + NOTATION_DEBUG << "(cf " << (int)(elt->getCanvasX()) << " vs " + << (int)(coords.first) << ", " + << (int)(elt->getCanvasY()) << " vs " + << (int)(coords.second) << ")" << endl; + } else { + NOTATION_DEBUG << "elementNotMoved: elt at " << elt->getViewAbsoluteTime() + << " is ok" << endl; + } + + return ok; +} + +bool +NotationStaff::elementNotMovedInY(NotationElement *elt) +{ + if (!elt->getCanvasItem()) + return false; + + LinedStaffCoords coords = getCanvasCoordsForLayoutCoords + (elt->getLayoutX(), (int)elt->getLayoutY()); + + bool ok = (int)(elt->getCanvasY()) == (int)(coords.second); + + // if (!ok) { + // NOTATION_DEBUG + // << "elementNotMovedInY: elt at " << elt->getAbsoluteTime() << + // ", ok is " << ok << endl; + // NOTATION_DEBUG << "(cf " << (int)(elt->getCanvasY()) << " vs " + // << (int)(coords.second) << ")" << std::endl; + // } + return ok; +} + +bool +NotationStaff::elementShiftedOnly(NotationElementList::iterator i) +{ + int shift = 0; + bool ok = false; + + for (NotationElementList::iterator j = i; + j != getViewElementList()->end(); ++j) { + + NotationElement *elt = static_cast<NotationElement*>(*j); + if (!elt->getCanvasItem()) + break; + + LinedStaffCoords coords = getCanvasCoordsForLayoutCoords + (elt->getLayoutX(), (int)elt->getLayoutY()); + + // regard any shift in y as suspicious + if ((int)(elt->getCanvasY()) != (int)(coords.second)) + break; + + int myShift = (int)(elt->getCanvasX()) - (int)(coords.first); + if (j == i) + shift = myShift; + else if (myShift != shift) + break; + + if (elt->getViewAbsoluteTime() > (*i)->getViewAbsoluteTime()) { + // all events up to and including this one have passed + ok = true; + break; + } + } + + if (!ok) { + NOTATION_DEBUG + << "elementShiftedOnly: elt at " << (*i)->getViewAbsoluteTime() + << ", ok is " << ok << endl; + } + + return ok; +} + +bool +NotationStaff::isDirectlyPrintable(ViewElement *velt) +{ + if (!m_printPainter) + return false; + return (velt->event()->isa(Note::EventType) || + velt->event()->isa(Note::EventRestType) || + velt->event()->isa(Text::EventType) || + velt->event()->isa(Indication::EventType)); +} + +void +NotationStaff::renderSingleElement(ViewElementList::iterator &vli, + const Clef ¤tClef, + const ::Rosegarden::Key ¤tKey, + bool selected) +{ + const NotationProperties &properties(getProperties()); + static NotePixmapParameters restParams(Note::Crotchet, 0); + + NotationElement* elt = static_cast<NotationElement*>(*vli); + + bool invisible = false; + if (elt->event()->get + <Bool>(BaseProperties::INVISIBLE, invisible) && invisible) { + if (m_printPainter) + return ; + KConfig *config = kapp->config(); + config->setGroup("Notation Options"); + bool showInvisibles = config->readBoolEntry("showinvisibles", true); + if (!showInvisibles) + return ; + } + + try { + m_notePixmapFactory->setNoteStyle + (NoteStyleFactory::getStyleForEvent(elt->event())); + + } catch (NoteStyleFactory::StyleUnavailable u) { + + std::cerr << "WARNING: Note style unavailable: " + << u.getMessage() << std::endl; + + static bool warned = false; + if (!warned) { + KMessageBox::error(0, i18n(strtoqstr(u.getMessage()))); + warned = true; + } + } + + try { + + QCanvasPixmap *pixmap = 0; + + m_notePixmapFactory->setSelected(selected); + m_notePixmapFactory->setShaded(invisible); + int z = selected ? 3 : 0; + + // these are actually only used for the printer stuff + LinedStaffCoords coords; + if (m_printPainter) + coords = getCanvasCoordsForLayoutCoords + (elt->getLayoutX(), (int)elt->getLayoutY()); + + FitPolicy policy = PretendItFittedAllAlong; + + if (elt->isNote()) { + + renderNote(vli); + + } else if (elt->isRest()) { + + bool ignoreRest = false; + // NotationHLayout sets this property if it finds the rest + // in the middle of a chord -- Quantizer still sometimes gets + // this wrong + elt->event()->get + <Bool>(properties.REST_TOO_SHORT, ignoreRest); + + if (!ignoreRest) { + + Note::Type note = elt->event()->get + <Int>(BaseProperties::NOTE_TYPE); + int dots = elt->event()->get + <Int>(BaseProperties::NOTE_DOTS); + restParams.setNoteType(note); + restParams.setDots(dots); + setTuplingParameters(elt, restParams); + restParams.setQuantized(false); + bool restOutside = false; + elt->event()->get + <Bool>(properties.REST_OUTSIDE_STAVE, + restOutside); + restParams.setRestOutside(restOutside); + if (restOutside) { + NOTATION_DEBUG << "NotationStaff::renderSingleElement() : rest outside staff" << endl; + if (note == Note::DoubleWholeNote) { + NOTATION_DEBUG << "NotationStaff::renderSingleElement() : breve rest needs leger lines" << endl; + restParams.setLegerLines(5); + } + } + + if (m_printPainter) { + m_notePixmapFactory->drawRest + (restParams, + *m_printPainter, int(coords.first), coords.second); + } else { + pixmap = m_notePixmapFactory->makeRestPixmap(restParams); + } + } + + } else if (elt->event()->isa(Clef::EventType)) { + + pixmap = m_notePixmapFactory->makeClefPixmap + (Clef(*elt->event())); + + } else if (elt->event()->isa(::Rosegarden::Key::EventType)) { + + ::Rosegarden::Key key(*elt->event()); + ::Rosegarden::Key cancelKey = currentKey; + + 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(); + } + } + + pixmap = m_notePixmapFactory->makeKeyPixmap + (key, currentClef, cancelKey); + + } else if (elt->event()->isa(Text::EventType)) { + + policy = MoveBackToFit; + + if (elt->event()->has(Text::TextTypePropertyName) && + elt->event()->get + <String> + (Text::TextTypePropertyName) == + Text::Annotation && + !m_notationView->areAnnotationsVisible()) { + + // nothing I guess + + } + else if (elt->event()->has(Text::TextTypePropertyName) && + elt->event()->get + <String> + (Text::TextTypePropertyName) == + Text::LilyPondDirective && + !m_notationView->areLilyPondDirectivesVisible()) { + + // nothing here either + + } + else { + + try { + if (m_printPainter) { + Text text(*elt->event()); + int length = m_notePixmapFactory->getTextWidth(text); + for (double w = -1, inc = 0; w != 0; inc += w) { + w = setPainterClipping(m_printPainter, + elt->getLayoutX(), + int(elt->getLayoutY()), + int(inc), length, coords, + policy); + m_notePixmapFactory->drawText + (text, *m_printPainter, int(coords.first), coords.second); + m_printPainter->restore(); + } + } else { + pixmap = m_notePixmapFactory->makeTextPixmap + (Text(*elt->event())); + } + } catch (Exception e) { // Text ctor failed + NOTATION_DEBUG << "Bad text event" << endl; + } + } + + } else if (elt->event()->isa(Indication::EventType)) { + + policy = SplitToFit; + + try { + Indication indication(*elt->event()); + + timeT indicationDuration = indication.getIndicationDuration(); + timeT indicationEndTime = + elt->getViewAbsoluteTime() + indicationDuration; + + NotationElementList::iterator indicationEnd = + getViewElementList()->findTime(indicationEndTime); + + std::string indicationType = indication.getIndicationType(); + + int length, y1; + + if ((indicationType == Indication::Slur || + indicationType == Indication::PhrasingSlur) && + indicationEnd != getViewElementList()->begin()) { + --indicationEnd; + } + + if ((indicationType != Indication::Slur && + indicationType != Indication::PhrasingSlur) && + indicationEnd != getViewElementList()->begin() && + (indicationEnd == getViewElementList()->end() || + indicationEndTime == + getSegment().getBarStartForTime(indicationEndTime))) { + + while (indicationEnd == getViewElementList()->end() || + (*indicationEnd)->getViewAbsoluteTime() >= indicationEndTime) + --indicationEnd; + + double x, w; + static_cast<NotationElement *>(*indicationEnd)-> + getLayoutAirspace(x, w); + length = (int)(x + w - elt->getLayoutX() - + m_notePixmapFactory->getBarMargin()); + + } else { + + length = (int)((*indicationEnd)->getLayoutX() - + elt->getLayoutX()); + + if (indication.isOttavaType()) { + length -= m_notePixmapFactory->getNoteBodyWidth(); + } + } + + y1 = (int)(*indicationEnd)->getLayoutY(); + + if (length < m_notePixmapFactory->getNoteBodyWidth()) { + length = m_notePixmapFactory->getNoteBodyWidth(); + } + + if (indicationType == Indication::Crescendo || + indicationType == Indication::Decrescendo) { + + if (m_printPainter) { + for (double w = -1, inc = 0; w != 0; inc += w) { + w = setPainterClipping(m_printPainter, + elt->getLayoutX(), + int(elt->getLayoutY()), + int(inc), length, coords, + policy); + m_notePixmapFactory->drawHairpin + (length, indicationType == Indication::Crescendo, + *m_printPainter, int(coords.first), coords.second); + m_printPainter->restore(); + } + } else { + pixmap = m_notePixmapFactory->makeHairpinPixmap + (length, indicationType == Indication::Crescendo); + } + + } else if (indicationType == Indication::Slur || + indicationType == Indication::PhrasingSlur) { + + bool above = true; + long dy = 0; + long length = 10; + + elt->event()->get + <Bool>(properties.SLUR_ABOVE, above); + elt->event()->get + <Int>(properties.SLUR_Y_DELTA, dy); + elt->event()->get + <Int>(properties.SLUR_LENGTH, length); + + if (m_printPainter) { + for (double w = -1, inc = 0; w != 0; inc += w) { + w = setPainterClipping(m_printPainter, + elt->getLayoutX(), + int(elt->getLayoutY()), + int(inc), length, coords, + policy); + m_notePixmapFactory->drawSlur + (length, dy, above, + indicationType == Indication::PhrasingSlur, + *m_printPainter, int(coords.first), coords.second); + m_printPainter->restore(); + } + } else { + pixmap = m_notePixmapFactory->makeSlurPixmap + (length, dy, above, + indicationType == Indication::PhrasingSlur); + } + + } else { + + int octaves = indication.getOttavaShift(); + + if (octaves != 0) { + if (m_printPainter) { + for (double w = -1, inc = 0; w != 0; inc += w) { + w = setPainterClipping(m_printPainter, + elt->getLayoutX(), + int(elt->getLayoutY()), + int(inc), length, coords, + policy); + m_notePixmapFactory->drawOttava + (length, octaves, + *m_printPainter, int(coords.first), coords.second); + m_printPainter->restore(); + } + } else { + pixmap = m_notePixmapFactory->makeOttavaPixmap + (length, octaves); + } + } else { + + NOTATION_DEBUG + << "Unrecognised indicationType " << indicationType << endl; + if (m_showUnknowns) { + pixmap = m_notePixmapFactory->makeUnknownPixmap(); + } + } + } + } catch (...) { + NOTATION_DEBUG << "Bad indication!" << endl; + } + + } else if (elt->event()->isa(Controller::EventType)) { + + bool isSustain = false; + + long controlNumber = 0; + elt->event()->get + <Int>(Controller::NUMBER, controlNumber); + + Studio *studio = &m_notationView->getDocument()->getStudio(); + Track *track = getSegment().getComposition()->getTrackById + (getSegment().getTrack()); + + if (track) { + + Instrument *instrument = studio->getInstrumentById + (track->getInstrument()); + if (instrument) { + MidiDevice *device = dynamic_cast<MidiDevice *> + (instrument->getDevice()); + if (device) { + for (ControlList::const_iterator i = + device->getControlParameters().begin(); + i != device->getControlParameters().end(); ++i) { + if (i->getType() == Controller::EventType && + i->getControllerValue() == controlNumber) { + if (i->getName() == "Sustain" || + strtoqstr(i->getName()) == i18n("Sustain")) { + isSustain = true; + } + break; + } + } + } else if (instrument->getDevice() && + instrument->getDevice()->getType() == Device::SoftSynth) { + if (controlNumber == 64) { + isSustain = true; + } + } + } + } + + if (isSustain) { + long value = 0; + elt->event()->get + <Int>(Controller::VALUE, value); + if (value > 0) { + pixmap = m_notePixmapFactory->makePedalDownPixmap(); + } else { + pixmap = m_notePixmapFactory->makePedalUpPixmap(); + } + + } else { + + if (m_showUnknowns) { + pixmap = m_notePixmapFactory->makeUnknownPixmap(); + } + } + } else if (elt->event()->isa(Guitar::Chord::EventType)) { + + // Create a guitar chord pixmap + try { + + Guitar::Chord chord (*elt->event()); + + /* UNUSED - for printing, just use a large pixmap as below + if (m_printPainter) { + + int length = m_notePixmapFactory->getTextWidth(text); + for (double w = -1, inc = 0; w != 0; inc += w) { + w = setPainterClipping(m_printPainter, + elt->getLayoutX(), + int(elt->getLayoutY()), + int(inc), length, coords, + policy); + m_notePixmapFactory->drawText + (text, *m_printPainter, int(coords.first), coords.second); + m_printPainter->restore(); + } + } else { + */ + + pixmap = m_notePixmapFactory->makeGuitarChordPixmap (chord.getFingering(), + int(coords.first), + coords.second); + // } + } catch (Exception e) { // GuitarChord ctor failed + NOTATION_DEBUG << "Bad guitar chord event" << endl; + } + + } else { + + if (m_showUnknowns) { + pixmap = m_notePixmapFactory->makeUnknownPixmap(); + } + } + + // Show the result, one way or another + + if (elt->isNote()) { + + // No need, we already set and showed it in renderNote + + } else if (pixmap) { + + setPixmap(elt, pixmap, z, policy); + + } else { + elt->removeCanvasItem(); + } + + // NOTATION_DEBUG << "NotationStaff::renderSingleElement: Setting selected at " << elt->getAbsoluteTime() << " to " << selected << endl; + + } catch (...) { + std::cerr << "Event lacks the proper properties: " + << std::endl; + elt->event()->dump(std::cerr); + } + + m_notePixmapFactory->setSelected(false); + m_notePixmapFactory->setShaded(false); +} + +double +NotationStaff::setPainterClipping(QPainter *painter, double lx, int ly, + double dx, double w, LinedStaffCoords &coords, + FitPolicy policy) +{ + painter->save(); + + // NOTATION_DEBUG << "NotationStaff::setPainterClipping: lx " << lx << ", dx " << dx << ", w " << w << endl; + + coords = getCanvasCoordsForLayoutCoords(lx + dx, ly); + int row = getRowForLayoutX(lx + dx); + double rightMargin = getCanvasXForRightOfRow(row); + double available = rightMargin - coords.first; + + // NOTATION_DEBUG << "NotationStaff::setPainterClipping: row " << row << ", rightMargin " << rightMargin << ", available " << available << endl; + + switch (policy) { + + case SplitToFit: { + bool fit = (w - dx <= available + m_notePixmapFactory->getNoteBodyWidth()); + if (dx > 0.01 || !fit) { + int clipLeft = int(coords.first), clipWidth = int(available); + if (dx < 0.01) { + // never clip the left side of the first part of something + clipWidth += clipLeft; + clipLeft = 0; + } + QRect clip(clipLeft, coords.second - getRowSpacing() / 2, + clipWidth, getRowSpacing()); + painter->setClipRect(clip, QPainter::CoordPainter); + coords.first -= dx; + } + if (fit) { + return 0.0; + } + return available; + } + + case MoveBackToFit: + if (w - dx > available + m_notePixmapFactory->getNoteBodyWidth()) { + coords.first -= (w - dx) - available; + } + return 0.0; + + default: + return 0.0; + } +} + +void +NotationStaff::setPixmap(NotationElement *elt, QCanvasPixmap *pixmap, int z, + FitPolicy policy) +{ + double layoutX = elt->getLayoutX(); + int layoutY = (int)elt->getLayoutY(); + + elt->removeCanvasItem(); + + while (1) { + + LinedStaffCoords coords = + getCanvasCoordsForLayoutCoords(layoutX, layoutY); + + double canvasX = coords.first; + int canvasY = coords.second; + + QCanvasItem *item = 0; + + if (m_pageMode == LinearMode || policy == PretendItFittedAllAlong) { + + item = new QCanvasNotationSprite(*elt, pixmap, m_canvas); + + } else { + + int row = getRowForLayoutX(layoutX); + double rightMargin = getCanvasXForRightOfRow(row); + double extent = canvasX + pixmap->width(); + + // NOTATION_DEBUG << "NotationStaff::setPixmap: row " << row << ", right margin " << rightMargin << ", extent " << extent << endl; + + if (extent > rightMargin + m_notePixmapFactory->getNoteBodyWidth()) { + + if (policy == SplitToFit) { + + // NOTATION_DEBUG << "splitting at " << (rightMargin-canvasX) << endl; + + std::pair<QPixmap, QPixmap> split = + PixmapFunctions::splitPixmap(*pixmap, + int(rightMargin - canvasX)); + + QCanvasPixmap *leftCanvasPixmap = new QCanvasPixmap + (split.first, QPoint(pixmap->offsetX(), pixmap->offsetY())); + + QCanvasPixmap *rightCanvasPixmap = new QCanvasPixmap + (split.second, QPoint(0, pixmap->offsetY())); + + item = new QCanvasNotationSprite(*elt, leftCanvasPixmap, m_canvas); + item->setZ(z); + + if (elt->getCanvasItem()) { + elt->addCanvasItem(item, canvasX, canvasY); + } else { + elt->setCanvasItem(item, canvasX, canvasY); + } + + item->show(); + + delete pixmap; + pixmap = rightCanvasPixmap; + + layoutX += rightMargin - canvasX + 0.01; // ensure flip to next row + + continue; + + } else { // policy == MoveBackToFit + + item = new QCanvasNotationSprite(*elt, pixmap, m_canvas); + elt->setLayoutX(elt->getLayoutX() - (extent - rightMargin)); + coords = getCanvasCoordsForLayoutCoords(layoutX, layoutY); + canvasX = coords.first; + } + } else { + item = new QCanvasNotationSprite(*elt, pixmap, m_canvas); + } + } + + item->setZ(z); + if (elt->getCanvasItem()) { + elt->addCanvasItem(item, canvasX, canvasY); + } else { + elt->setCanvasItem(item, canvasX, canvasY); + } + item->show(); + break; + } +} + +void +NotationStaff::renderNote(ViewElementList::iterator &vli) +{ + NotationElement* elt = static_cast<NotationElement*>(*vli); + + const NotationProperties &properties(getProperties()); + static NotePixmapParameters params(Note::Crotchet, 0); + + Note::Type note = elt->event()->get + <Int>(BaseProperties::NOTE_TYPE); + int dots = elt->event()->get + <Int>(BaseProperties::NOTE_DOTS); + + Accidental accidental = Accidentals::NoAccidental; + (void)elt->event()->get + <String>(properties.DISPLAY_ACCIDENTAL, accidental); + + bool cautionary = false; + if (accidental != Accidentals::NoAccidental) { + (void)elt->event()->get + <Bool>(properties.DISPLAY_ACCIDENTAL_IS_CAUTIONARY, + cautionary); + } + + bool up = true; + // (void)(elt->event()->get<Bool>(properties.STEM_UP, up)); + (void)(elt->event()->get + <Bool>(properties.VIEW_LOCAL_STEM_UP, up)); + + bool flag = true; + (void)(elt->event()->get + <Bool>(properties.DRAW_FLAG, flag)); + + bool beamed = false; + (void)(elt->event()->get + <Bool>(properties.BEAMED, beamed)); + + bool shifted = false; + (void)(elt->event()->get + <Bool>(properties.NOTE_HEAD_SHIFTED, shifted)); + + bool dotShifted = false; + (void)(elt->event()->get + <Bool>(properties.NOTE_DOT_SHIFTED, dotShifted)); + + long stemLength = m_notePixmapFactory->getNoteBodyHeight(); + (void)(elt->event()->get + <Int>(properties.UNBEAMED_STEM_LENGTH, stemLength)); + + long heightOnStaff = 0; + int legerLines = 0; + + (void)(elt->event()->get + <Int>(properties.HEIGHT_ON_STAFF, heightOnStaff)); + if (heightOnStaff < 0) { + legerLines = heightOnStaff; + } else if (heightOnStaff > 8) { + legerLines = heightOnStaff - 8; + } + + long slashes = 0; + (void)(elt->event()->get + <Int>(properties.SLASHES, slashes)); + + bool quantized = false; + if (m_colourQuantize && !elt->isTuplet()) { + quantized = + (elt->getViewAbsoluteTime() != elt->event()->getAbsoluteTime() || + elt->getViewDuration() != elt->event()->getDuration()); + } + params.setQuantized(quantized); + + bool trigger = false; + if (elt->event()->has(BaseProperties::TRIGGER_SEGMENT_ID)) + trigger = true; + params.setTrigger(trigger); + + bool inRange = true; + Pitch p(*elt->event()); + Segment *segment = &getSegment(); + if (m_showRanges) { + int pitch = p.getPerformancePitch(); + if (pitch > segment->getHighestPlayable() || + pitch < segment->getLowestPlayable()) { + inRange = false; + } + } + params.setInRange(inRange); + + params.setNoteType(note); + params.setDots(dots); + params.setAccidental(accidental); + params.setAccidentalCautionary(cautionary); + params.setNoteHeadShifted(shifted); + params.setNoteDotShifted(dotShifted); + params.setDrawFlag(flag); + params.setDrawStem(true); + params.setStemGoesUp(up); + params.setLegerLines(legerLines); + params.setSlashes(slashes); + params.setBeamed(false); + params.setIsOnLine(heightOnStaff % 2 == 0); + params.removeMarks(); + params.setSafeVertDistance(0); + + bool primary = false; + int safeVertDistance = 0; + + if (elt->event()->get + <Bool>(properties.CHORD_PRIMARY_NOTE, primary) + && primary) { + + long marks = 0; + elt->event()->get + <Int>(properties.CHORD_MARK_COUNT, marks); + if (marks) { + NotationChord chord(*getViewElementList(), vli, + m_segment.getComposition()->getNotationQuantizer(), + properties); + params.setMarks(chord.getMarksForChord()); + } + + // params.setMarks(Marks::getMarks(*elt->event())); + + if (up && note < Note::Semibreve) { + safeVertDistance = m_notePixmapFactory->getStemLength(); + safeVertDistance = std::max(safeVertDistance, int(stemLength)); + } + } + + long tieLength = 0; + (void)(elt->event()->get<Int>(properties.TIE_LENGTH, tieLength)); + if (tieLength > 0) { + params.setTied(true); + params.setTieLength(tieLength); + } else { + params.setTied(false); + } + + if (elt->event()->has(BaseProperties::TIE_IS_ABOVE)) { + params.setTiePosition + (true, elt->event()->get<Bool>(BaseProperties::TIE_IS_ABOVE)); + } else { + params.setTiePosition(false, false); // the default + } + + long accidentalShift = 0; + bool accidentalExtra = false; + if (elt->event()->get<Int>(properties.ACCIDENTAL_SHIFT, accidentalShift)) { + elt->event()->get<Bool>(properties.ACCIDENTAL_EXTRA_SHIFT, accidentalExtra); + } + params.setAccidentalShift(accidentalShift); + params.setAccExtraShift(accidentalExtra); + + double airX, airWidth; + elt->getLayoutAirspace(airX, airWidth); + params.setWidth(int(airWidth)); + + if (beamed) { + + if (elt->event()->get<Bool>(properties.CHORD_PRIMARY_NOTE, primary) + && primary) { + + int myY = elt->event()->get<Int>(properties.BEAM_MY_Y); + + stemLength = myY - (int)elt->getLayoutY(); + if (stemLength < 0) + stemLength = -stemLength; + + int nextBeamCount = + elt->event()->get + <Int>(properties.BEAM_NEXT_BEAM_COUNT); + int width = + elt->event()->get + <Int>(properties.BEAM_SECTION_WIDTH); + int gradient = + elt->event()->get + <Int>(properties.BEAM_GRADIENT); + + bool thisPartialBeams(false), nextPartialBeams(false); + (void)elt->event()->get + <Bool> + (properties.BEAM_THIS_PART_BEAMS, thisPartialBeams); + (void)elt->event()->get + <Bool> + (properties.BEAM_NEXT_PART_BEAMS, nextPartialBeams); + + params.setBeamed(true); + params.setNextBeamCount(nextBeamCount); + params.setThisPartialBeams(thisPartialBeams); + params.setNextPartialBeams(nextPartialBeams); + params.setWidth(width); + params.setGradient((double)gradient / 100.0); + if (up) + safeVertDistance = stemLength; + + } + else { + params.setBeamed(false); + params.setDrawStem(false); + } + } + + if (heightOnStaff < 7) { + int gap = (((7 - heightOnStaff) * m_notePixmapFactory->getLineSpacing()) / 2); + if (safeVertDistance < gap) + safeVertDistance = gap; + } + + params.setStemLength(stemLength); + params.setSafeVertDistance(safeVertDistance); + setTuplingParameters(elt, params); + + NotePixmapFactory *factory = m_notePixmapFactory; + + if (elt->isGrace()) { + // lift this code from elsewhere to fix #1930309, and it seems to work a + // treat, as y'all Wrongpondians are wont to say + params.setLegerLines(heightOnStaff < 0 ? heightOnStaff : + heightOnStaff > 8 ? heightOnStaff - 8 : 0); + m_graceNotePixmapFactory->setSelected(m_notePixmapFactory->isSelected()); + m_graceNotePixmapFactory->setShaded(m_notePixmapFactory->isShaded()); + factory = m_graceNotePixmapFactory; + } + + if (m_printPainter) { + + // Return no canvas item, but instead render straight to + // the printer. + + LinedStaffCoords coords = getCanvasCoordsForLayoutCoords + (elt->getLayoutX(), (int)elt->getLayoutY()); + + // We don't actually know how wide the note drawing will be, + // but we should be able to use a fairly pessimistic estimate + // without causing any problems + int length = tieLength + 10 * m_notePixmapFactory->getNoteBodyWidth(); + + for (double w = -1, inc = 0; w != 0; inc += w) { + + w = setPainterClipping(m_printPainter, + elt->getLayoutX(), + int(elt->getLayoutY()), + int(inc), length, coords, + SplitToFit); + + factory->drawNote + (params, *m_printPainter, int(coords.first), coords.second); + + m_printPainter->restore(); // save() called by setPainterClipping + } + + } else { + + // The normal on-screen case + + bool collision = false; + QCanvasItem * haloItem = 0; + if (m_showCollisions) { + collision = elt->isColliding(); + if (collision) { + // Make collision halo + QCanvasPixmap *haloPixmap = factory->makeNoteHaloPixmap(params); + haloItem = new QCanvasNotationSprite(*elt, haloPixmap, m_canvas); + haloItem->setZ(-1); + } + } + + QCanvasPixmap *pixmap = factory->makeNotePixmap(params); + + int z = 0; + if (factory->isSelected()) + z = 3; + else if (quantized) + z = 2; + + setPixmap(elt, pixmap, z, SplitToFit); + + if (collision) { + // Display collision halo + LinedStaffCoords coords = + getCanvasCoordsForLayoutCoords(elt->getLayoutX(), + elt->getLayoutY()); + double canvasX = coords.first; + int canvasY = coords.second; + elt->addCanvasItem(haloItem, canvasX, canvasY); + haloItem->show(); + } + } +} + +void +NotationStaff::setTuplingParameters(NotationElement *elt, + NotePixmapParameters ¶ms) +{ + const NotationProperties &properties(getProperties()); + + params.setTupletCount(0); + long tuplingLineY = 0; + bool tupled = (elt->event()->get + <Int>(properties.TUPLING_LINE_MY_Y, tuplingLineY)); + + if (tupled) { + + long tuplingLineWidth = 0; + if (!elt->event()->get + <Int>(properties.TUPLING_LINE_WIDTH, tuplingLineWidth)) { + std::cerr << "WARNING: Tupled event at " << elt->event()->getAbsoluteTime() << " has no tupling line width" << std::endl; + } + + long tuplingLineGradient = 0; + if (!(elt->event()->get + <Int>(properties.TUPLING_LINE_GRADIENT, + tuplingLineGradient))) { + std::cerr << "WARNING: Tupled event at " << elt->event()->getAbsoluteTime() << " has no tupling line gradient" << std::endl; + } + + bool tuplingLineFollowsBeam = false; + elt->event()->get + <Bool>(properties.TUPLING_LINE_FOLLOWS_BEAM, + tuplingLineFollowsBeam); + + long tupletCount; + if (elt->event()->get<Int> + (BaseProperties::BEAMED_GROUP_UNTUPLED_COUNT, tupletCount)) { + + params.setTupletCount(tupletCount); + params.setTuplingLineY(tuplingLineY - (int)elt->getLayoutY()); + params.setTuplingLineWidth(tuplingLineWidth); + params.setTuplingLineGradient(double(tuplingLineGradient) / 100.0); + params.setTuplingLineFollowsBeam(tuplingLineFollowsBeam); + } + } +} + +bool +NotationStaff::isSelected(NotationElementList::iterator it) +{ + const EventSelection *selection = + m_notationView->getCurrentSelection(); + return selection && selection->contains((*it)->event()); +} + +void +NotationStaff::showPreviewNote(double layoutX, int heightOnStaff, + const Note ¬e, bool grace) +{ + NotePixmapFactory *npf = m_notePixmapFactory; + if (grace) npf = m_graceNotePixmapFactory; + + NotePixmapParameters params(note.getNoteType(), note.getDots()); + NotationRules rules; + + params.setAccidental(Accidentals::NoAccidental); + params.setNoteHeadShifted(false); + params.setDrawFlag(true); + params.setDrawStem(true); + params.setStemGoesUp(rules.isStemUp(heightOnStaff)); + params.setLegerLines(heightOnStaff < 0 ? heightOnStaff : + heightOnStaff > 8 ? heightOnStaff - 8 : 0); + params.setBeamed(false); + params.setIsOnLine(heightOnStaff % 2 == 0); + params.setTied(false); + params.setBeamed(false); + params.setTupletCount(0); + params.setSelected(false); + params.setHighlighted(true); + + delete m_previewSprite; + m_previewSprite = new QCanvasSimpleSprite + (npf->makeNotePixmap(params), m_canvas); + + int layoutY = getLayoutYForHeight(heightOnStaff); + LinedStaffCoords coords = getCanvasCoordsForLayoutCoords(layoutX, layoutY); + + m_previewSprite->move(coords.first, (double)coords.second); + m_previewSprite->setZ(4); + m_previewSprite->show(); + m_canvas->update(); +} + +void +NotationStaff::clearPreviewNote() +{ + delete m_previewSprite; + m_previewSprite = 0; +} + +bool +NotationStaff::wrapEvent(Event *e) +{ + bool wrap = true; + + /*!!! always wrap unknowns, just don't necessarily render them? + + if (!m_showUnknowns) { + std::string etype = e->getType(); + if (etype != Note::EventType && + etype != Note::EventRestType && + etype != Clef::EventType && + etype != Key::EventType && + etype != Indication::EventType && + etype != Text::EventType) { + wrap = false; + } + } + */ + + if (wrap) + wrap = Staff::wrapEvent(e); + + return wrap; +} + +void +NotationStaff::eventRemoved(const Segment *segment, + Event *event) +{ + LinedStaff::eventRemoved(segment, event); + m_notationView->handleEventRemoved(event); +} + +void +NotationStaff::markChanged(timeT from, timeT to, bool movedOnly) +{ + // first time through this, m_ready is false -- we mark it true + + NOTATION_DEBUG << "NotationStaff::markChanged (" << from << " -> " << to << ") " << movedOnly << endl; + + drawStaffName();//!!! + + if (from == to) { + + m_status.clear(); + + if (!movedOnly && m_ready) { // undo all the rendering we've already done + for (NotationElementList::iterator i = getViewElementList()->begin(); + i != getViewElementList()->end(); ++i) { + static_cast<NotationElement *>(*i)->removeCanvasItem(); + } + + m_clefChanges.clear(); + m_keyChanges.clear(); + } + + drawStaffName(); + + } else { + + Segment *segment = &getSegment(); + Composition *composition = segment->getComposition(); + + NotationElementList::iterator unchanged = findUnchangedBarEnd(to); + + int finalBar; + if (unchanged == getViewElementList()->end()) { + finalBar = composition->getBarNumber(segment->getEndMarkerTime()); + } else { + finalBar = composition->getBarNumber((*unchanged)->getViewAbsoluteTime()); + } + + int fromBar = composition->getBarNumber(from); + int toBar = composition->getBarNumber(to); + if (finalBar < toBar) + finalBar = toBar; + + for (int bar = fromBar; bar <= finalBar; ++bar) { + + if (bar > toBar) + movedOnly = true; + + // NOTATION_DEBUG << "bar " << bar << " status " << m_status[bar] << endl; + + if (bar >= m_lastRenderCheck.first && + bar <= m_lastRenderCheck.second) { + + // NOTATION_DEBUG << "bar " << bar << " rendering and positioning" << endl; + + if (!movedOnly || m_status[bar] == UnRendered) { + renderElements + (getViewElementList()->findTime(composition->getBarStart(bar)), + getViewElementList()->findTime(composition->getBarEnd(bar))); + } + positionElements(composition->getBarStart(bar), + composition->getBarEnd(bar)); + m_status[bar] = Positioned; + + } else if (!m_ready) { + // NOTATION_DEBUG << "bar " << bar << " rendering and positioning" << endl; + + // first time through -- we don't need a separate render phase, + // only to mark as not yet positioned + m_status[bar] = Rendered; + + } else if (movedOnly) { + if (m_status[bar] == Positioned) { + // NOTATION_DEBUG << "bar " << bar << " marking unpositioned" << endl; + m_status[bar] = Rendered; + } + + } else { + // NOTATION_DEBUG << "bar " << bar << " marking unrendered" << endl; + + m_status[bar] = UnRendered; + } + } + } + + m_ready = true; +} + +void +NotationStaff::setPrintPainter(QPainter *painter) +{ + m_printPainter = painter; +} + +bool +NotationStaff::checkRendered(timeT from, timeT to) +{ + if (!m_ready) + return false; + Composition *composition = getSegment().getComposition(); + if (!composition) { + NOTATION_DEBUG << "NotationStaff::checkRendered: warning: segment has no composition -- is my paint event late?" << endl; + return false; + } + + // NOTATION_DEBUG << "NotationStaff::checkRendered: " << from << " -> " << to << endl; + + int fromBar = composition->getBarNumber(from); + int toBar = composition->getBarNumber(to); + bool something = false; + + if (fromBar > toBar) + std::swap(fromBar, toBar); + + for (int bar = fromBar; bar <= toBar; ++bar) { + // NOTATION_DEBUG << "NotationStaff::checkRendered: bar " << bar << " status " + // << m_status[bar] << endl; + + switch (m_status[bar]) { + + case UnRendered: + renderElements + (getViewElementList()->findTime(composition->getBarStart(bar)), + getViewElementList()->findTime(composition->getBarEnd(bar))); + + case Rendered: + positionElements + (composition->getBarStart(bar), + composition->getBarEnd(bar)); + m_lastRenderedBar = bar; + + something = true; + + case Positioned: + break; + } + + m_status[bar] = Positioned; + } + + m_lastRenderCheck = std::pair<int, int>(fromBar, toBar); + return something; +} + +bool +NotationStaff::doRenderWork(timeT from, timeT to) +{ + if (!m_ready) + return true; + Composition *composition = getSegment().getComposition(); + + int fromBar = composition->getBarNumber(from); + int toBar = composition->getBarNumber(to); + + if (fromBar > toBar) + std::swap(fromBar, toBar); + + for (int bar = fromBar; bar <= toBar; ++bar) { + + switch (m_status[bar]) { + + case UnRendered: + renderElements + (getViewElementList()->findTime(composition->getBarStart(bar)), + getViewElementList()->findTime(composition->getBarEnd(bar))); + m_status[bar] = Rendered; + return true; + + case Rendered: + positionElements + (composition->getBarStart(bar), + composition->getBarEnd(bar)); + m_status[bar] = Positioned; + m_lastRenderedBar = bar; + return true; + + case Positioned: + // The bars currently displayed are rendered before the others. + // Later, when preceding bars are rendered, truncateClefsAndKeysAt() + // is called and possible clefs and/or keys from the bars previously + // rendered may be lost. Following code should restore these clefs + // and keys in m_clefChanges and m_keyChanges lists. + if (bar > m_lastRenderedBar) + checkAndCompleteClefsAndKeys(bar); + continue; + } + } + + return false; +} + +void +NotationStaff::checkAndCompleteClefsAndKeys(int bar) +{ + // Look for Clef or Key in current bar + Composition *composition = getSegment().getComposition(); + timeT barStartTime = composition->getBarStart(bar); + timeT barEndTime = composition->getBarEnd(bar); + + for (ViewElementList::iterator it = + getViewElementList()->findTime(barStartTime); + (it != getViewElementList()->end()) + && ((*it)->getViewAbsoluteTime() < barEndTime); ++it) { + if ((*it)->event()->isa(Clef::EventType)) { + // Clef found + Clef clef = *(*it)->event(); + + // Is this clef already in m_clefChanges list ? + int xClef = int((*it)->getLayoutX()); + bool found = false; + for (int i = 0; i < m_clefChanges.size(); ++i) { + if ( (m_clefChanges[i].first == xClef) + && (m_clefChanges[i].second == clef)) { + found = true; + break; + } + } + + // If not, add it + if (!found) { + m_clefChanges.push_back(ClefChange(xClef, clef)); + } + + } else if ((*it)->event()->isa(::Rosegarden::Key::EventType)) { + ::Rosegarden::Key key = *(*it)->event(); + + // Is this key already in m_keyChanges list ? + int xKey = int((*it)->getLayoutX()); + bool found = false; + for (int i = 0; i < m_keyChanges.size(); ++i) { + if ( (m_keyChanges[i].first == xKey) + && (m_keyChanges[i].second == key)) { + found = true; + break; + } + } + + // If not, add it + if (!found) { + m_keyChanges.push_back(KeyChange(xKey, key)); + } + } + } +} + +LinedStaff::BarStyle +NotationStaff::getBarStyle(int barNo) const +{ + const Segment *s = &getSegment(); + Composition *c = s->getComposition(); + + int firstBar = c->getBarNumber(s->getStartTime()); + int lastNonEmptyBar = c->getBarNumber(s->getEndMarkerTime() - 1); + + // Currently only the first and last bar in a segment have any + // possibility of getting special treatment: + if (barNo > firstBar && barNo <= lastNonEmptyBar) + return PlainBar; + + // First and last bar in a repeating segment get repeat bars. + + if (s->isRepeating()) { + if (barNo == firstBar) + return RepeatStartBar; + else if (barNo == lastNonEmptyBar + 1) + return RepeatEndBar; + } + + if (barNo <= lastNonEmptyBar) + return PlainBar; + + // Last bar on a given track gets heavy double bars. Exploit the + // fact that Composition's iterator returns segments in track + // order. + + Segment *lastSegmentOnTrack = 0; + + for (Composition::iterator i = c->begin(); i != c->end(); ++i) { + if ((*i)->getTrack() == s->getTrack()) { + lastSegmentOnTrack = *i; + } else if (lastSegmentOnTrack != 0) { + break; + } + } + + if (&getSegment() == lastSegmentOnTrack) + return HeavyDoubleBar; + else + return PlainBar; +} + +double +NotationStaff::getBarInset(int barNo, bool isFirstBarInRow) const +{ + LinedStaff::BarStyle style = getBarStyle(barNo); + + NOTATION_DEBUG << "getBarInset(" << barNo << "," << isFirstBarInRow << ")" << endl; + + if (!(style == RepeatStartBar || style == RepeatBothBar)) + return 0.0; + + const Segment &s = getSegment(); + Composition *composition = s.getComposition(); + timeT barStart = composition->getBarStart(barNo); + + double inset = 0.0; + + NOTATION_DEBUG << "ready" << endl; + + bool haveKey = false, haveClef = false; + + ::Rosegarden::Key key; + ::Rosegarden::Key cancelKey; + Clef clef; + + for (Segment::iterator i = s.findTime(barStart); + s.isBeforeEndMarker(i) && ((*i)->getNotationAbsoluteTime() == barStart); + ++i) { + + NOTATION_DEBUG << "type " << (*i)->getType() << " at " << (*i)->getNotationAbsoluteTime() << endl; + + if ((*i)->isa(::Rosegarden::Key::EventType)) { + + try { + key = ::Rosegarden::Key(**i); + + if (barNo > composition->getBarNumber(s.getStartTime())) { + cancelKey = s.getKeyAtTime(barStart - 1); + } + + 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(); + } + } + + haveKey = true; + + } catch (...) { + NOTATION_DEBUG << "getBarInset: Bad key in event" << endl; + } + + } else if ((*i)->isa(Clef::EventType)) { + + try { + clef = Clef(**i); + haveClef = true; + } catch (...) { + NOTATION_DEBUG << "getBarInset: Bad clef in event" << endl; + } + } + } + + if (isFirstBarInRow) { + if (!haveKey) { + key = s.getKeyAtTime(barStart); + haveKey = true; + } + if (!haveClef) { + clef = s.getClefAtTime(barStart); + haveClef = true; + } + } + + if (haveKey) { + inset += m_notePixmapFactory->getKeyWidth(key, cancelKey); + } + if (haveClef) { + inset += m_notePixmapFactory->getClefWidth(clef); + } + if (haveClef || haveKey) { + inset += m_notePixmapFactory->getBarMargin() / 3; + } + if (haveClef && haveKey) { + inset += m_notePixmapFactory->getNoteBodyWidth() / 2; + } + + NOTATION_DEBUG << "getBarInset(" << barNo << "," << isFirstBarInRow << "): inset " << inset << endl; + + + return inset; +} + +Rosegarden::ViewElement* NotationStaff::makeViewElement(Rosegarden::Event* e) +{ + return new NotationElement(e); +} + +} diff --git a/src/gui/editors/notation/NotationStaff.h b/src/gui/editors/notation/NotationStaff.h new file mode 100644 index 0000000..4a0302c --- /dev/null +++ b/src/gui/editors/notation/NotationStaff.h @@ -0,0 +1,488 @@ +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONSTAFF_H_ +#define _RG_NOTATIONSTAFF_H_ + +#include "base/FastVector.h" +#include "base/Staff.h" +#include "base/ViewElement.h" +#include "gui/general/LinedStaff.h" +#include "gui/general/ProgressReporter.h" +#include <map> +#include <set> +#include <string> +#include <utility> +#include "base/Event.h" +#include "NotationElement.h" + + +class QPainter; +class QCanvasPixmap; +class QCanvasItem; +class QCanvas; +class LinedStaffCoords; + + +namespace Rosegarden +{ + +class ViewElement; +class TimeSignature; +class SnapGrid; +class Segment; +class QCanvasSimpleSprite; +class NotePixmapParameters; +class NotePixmapFactory; +class Note; +class NotationView; +class NotationProperties; +class Key; +class Event; +class Clef; + + +/** + * The Staff is a repository for information about the notation + * representation of a single Segment. This includes all of the + * NotationElements representing the Events on that Segment, the staff + * lines, as well as basic positional and size data. This class + * used to be in gui/staff.h, but it's been moved and renamed + * following the introduction of the core Staff base class, and + * much of the functionality has been extracted into the LinedStaff + * base class. + */ + +class NotationStaff : public ProgressReporter, public LinedStaff +{ +public: + + /** + * Creates a new NotationStaff for the specified Segment + * \a id is the id of the staff in the NotationView + */ + NotationStaff(QCanvas *, Segment *, SnapGrid *, + int id, NotationView *view, + std::string fontName, int resolution); + virtual ~NotationStaff(); + + /** + * Changes the resolution of the note pixmap factory and the + * staff lines, etc + */ + virtual void changeFont(std::string fontName, int resolution); + + void setLegerLineCount(int legerLineCount) { + if (legerLineCount == -1) m_legerLineCount = 8; + else m_legerLineCount = legerLineCount; + } + + void setBarNumbersEvery(int barNumbersEvery) { + m_barNumbersEvery = barNumbersEvery; + } + + LinedStaff::setPageMode; + LinedStaff::setPageWidth; + LinedStaff::setRowsPerPage; + LinedStaff::setRowSpacing; + LinedStaff::setConnectingLineLength; + + /** + * Gets a read-only reference to the pixmap factory used by the + * staff. (For use by NotationHLayout, principally.) This + * reference isn't const because the NotePixmapFactory maintains + * too much state for its methods to be const, but you should + * treat the returned reference as if it were const anyway. + */ + virtual NotePixmapFactory& getNotePixmapFactory(bool grace) { + return grace ? *m_graceNotePixmapFactory : *m_notePixmapFactory; + } + + /** + * Generate or re-generate sprites for all the elements between + * from and to. Call this when you've just made a change, + * specifying the extents of the change in the from and to + * parameters. + * + * This method does not reposition any elements outside the given + * range -- so after any edit that may change the visible extents + * of a range, you will then need to call positionElements for the + * changed range and the entire remainder of the staff. + */ + virtual void renderElements(NotationElementList::iterator from, + NotationElementList::iterator to); + + + /** + * Assign suitable coordinates to the elements on the staff, + * based entirely on the layout X and Y coordinates they were + * given by the horizontal and vertical layout processes. + * + * This is necessary because the sprites that are being positioned + * may have been created either after the layout process completed + * (by renderElements) or before (by the previous renderElements + * call, if the sprites are unchanged but have moved) -- so + * neither the layout nor renderElements can authoritatively set + * their final positions. + * + * This method also updates the selected-ness of any elements it + * sees (i.e. it turns the selected ones blue and the unselected + * ones black), and re-generates sprites for any elements for + * which it seems necessary. In general it will only notice a + * element needs regenerating if its position has changed, not if + * the nature of the element has changed, so this is no substitute + * for calling renderElements. + * + * The from and to arguments are used to indicate the extents of a + * changed area within the staff. The actual area within which the + * elements end up being repositioned will begin at the start of + * the bar containing the changed area's start, and will end at the + * start of the next bar whose first element hasn't moved, after + * the changed area's end. + * + * Call this after renderElements, or after changing the selection, + * passing from and to arguments corresponding to the times of those + * passed to renderElements. + */ + virtual void positionElements(timeT from, + timeT to); + + /** + * Re-render and position elements as necessary, based on the + * given extents and any information obtained from calls to + * markChanged(). This provides a render-on-demand mechanism. If + * you are going to use this rendering mechanism, it's generally + * wise to avoid explicitly calling + * renderElements/positionElements as well. + * + * Returns true if something needed re-rendering. + */ + virtual bool checkRendered(timeT from, + timeT to); + + /** + * Find something between the given times that has not yet been + * rendered, and render a small amount of it. Return true if it + * found something to do. This is to be used as a background work + * procedure for rendering not-yet-visible areas of notation. + */ + virtual bool doRenderWork(timeT from, + timeT to); + + /** + * Mark a region of staff as changed, for use by the on-demand + * rendering mechanism. If fromBar == toBar == -1, mark the + * entire staff as changed (and recover the memory used for its + * elements). Pass movedOnly as true to indicate that elements + * have not changed but only been repositioned, for example as a + * consequence of a modification on another staff that caused a + * relayout. + */ + virtual void markChanged(timeT from = 0, + timeT to = 0, + bool movedOnly = false); + + /** + * Set a painter as the printer output. If this painter is + * non-null, subsequent renderElements calls will only render + * those elements that cannot be rendered directly to a print + * painter; those that can, will be rendered by renderPrintable() + * instead. + */ + virtual void setPrintPainter(QPainter *painter); + + /** + * Render to the current print painter those elements that can be + * rendered directly to a print painter. If no print painter is + * set, do nothing. + */ + virtual void renderPrintable(timeT from, + timeT to); + + /** + * Insert time signature at x-coordinate \a x. + */ + virtual void insertTimeSignature(double layoutX, + const TimeSignature &timeSig); + + /** + * Delete all time signatures + */ + virtual void deleteTimeSignatures(); + + /** + * Insert repeated clef and key at start of new line, at x-coordinate \a x. + */ + virtual void insertRepeatedClefAndKey(double layoutX, int barNo); + + /** + * Delete all repeated clefs and keys. + */ + virtual void deleteRepeatedClefsAndKeys(); + + /** + * (Re)draw the staff name from the track's current name + */ + virtual void drawStaffName(); + + /** + * Return true if the staff name as currently drawn is up-to-date + * with that in the composition + */ + virtual bool isStaffNameUpToDate(); + + /** + * Return the clef and key in force at the given canvas + * coordinates + */ + virtual void getClefAndKeyAtCanvasCoords(double x, int y, + Clef &clef, + ::Rosegarden::Key &key) const; + + /** + * Return the note name (C4, Bb3, whatever) corresponding to the + * given canvas coordinates + */ + virtual std::string getNoteNameAtCanvasCoords + (double x, int y, + Accidental accidental = + Accidentals::NoAccidental) const; + + /** + * Find the NotationElement whose layout x-coord is closest to x, + * without regard to its y-coord. + * + * If notesAndRestsOnly is true, will return the closest note + * or rest but will never return any other kind of element. + * + * If the closest event is further than \a proximityThreshold + * horizontally away from x, in pixels, end() is returned. + * (If proximityThreshold is negative, there will be no limit + * to the distances that will be considered.) + * + * Also returns the clef and key in force at the given coordinate. + */ + virtual ViewElementList::iterator getClosestElementToLayoutX + (double x, Event *&clef, Event *&key, + bool notesAndRestsOnly = false, + int proximityThreshold = 10); + + /** + * Find the NotationElement "under" the given layout x-coord, + * without regard to its y-coord. + * + * Also returns the clef and key in force at the given coordinates. + */ + virtual ViewElementList::iterator getElementUnderLayoutX + (double x, Event *&clef, Event *&key); + + /** + * Draw a note on the staff to show an insert position prior to + * an insert. + */ + virtual void showPreviewNote(double layoutX, int heightOnStaff, + const Note ¬e, bool grace); + + /** + * Remove any visible preview note. + */ + virtual void clearPreviewNote(); + + /** + * Overridden from Staff<T>. + * We want to avoid wrapping things like controller events, if + * our showUnknowns preference is off + */ + virtual bool wrapEvent(Event *); + + /** + * Override from Staff<T> + * Let tools know if their current element has gone + */ + virtual void eventRemoved(const Segment *, Event *); + + /** + * Return the view-local PropertyName definitions for this staff's view + */ + const NotationProperties &getProperties() const; + + virtual double getBarInset(int barNo, bool isFirstBarInRow) const; + + /** + * Return the time at the given canvas coordinates + */ + timeT getTimeAtCanvasCoords(double x, int y) const; + +protected: + + virtual ViewElement* makeViewElement(Event*); + + // definition of staff + virtual int getLineCount() const { return 5; } + virtual int getLegerLineCount() const { return m_legerLineCount; } + virtual int getBottomLineHeight() const { return 0; } + virtual int getHeightPerLine() const { return 2; } + virtual int showBarNumbersEvery() const { return m_barNumbersEvery; } + + virtual BarStyle getBarStyle(int barNo) const; + + /** + * Assign a suitable sprite to the given element (the clef is + * needed in case it's a key event, in which case we need to judge + * the correct pitch for the key) + */ + virtual void renderSingleElement(ViewElementList::iterator &, + const Clef &, + const ::Rosegarden::Key &, + bool selected); + + bool isDirectlyPrintable(ViewElement *elt); + + void setTuplingParameters(NotationElement *, NotePixmapParameters &); + + /** + * Set a sprite representing the given note event to the given notation element + */ + virtual void renderNote(ViewElementList::iterator &); + + /** + * Return a NotationElementList::iterator pointing to the + * start of a bar prior to the given time that doesn't appear + * to have been affected by any changes around that time + */ + NotationElementList::iterator findUnchangedBarStart(timeT); + + /** + * Return a NotationElementList::iterator pointing to the + * end of a bar subsequent to the given time that doesn't appear + * to have been affected by any changes around that time + */ + NotationElementList::iterator findUnchangedBarEnd(timeT); + + /** + * Return true if the element has a canvas item that is already + * at the correct coordinates + */ + virtual bool elementNotMoved(NotationElement *); + + /** + * Return true if the element has a canvas item that is already + * at the correct y-coordinate + */ + virtual bool elementNotMovedInY(NotationElement *); + + /** + * Returns true if the item at the given iterator appears to have + * moved horizontally without the spacing around it changing. + * + * In practice, calculates the offset between the intended layout + * and current canvas coordinates of the item at the given + * iterator, and returns true if this offset is equal to those of + * all other following iterators at the same time as well as the + * first iterator found at a greater time. + */ + virtual bool elementShiftedOnly(NotationElementList::iterator); + + enum FitPolicy { + PretendItFittedAllAlong = 0, + MoveBackToFit, + SplitToFit + }; + + /** + * Prepare a painter to draw an object of logical width w at + * layout-x coord x, starting at offset dx into the object, by + * setting the painter's clipping so as to crop the object at the + * right edge of the row if it would otherwise overrun. The + * return value is the amount of the object visible on this row + * (i.e. the increment in offset for the next call to this method) + * or zero if no crop was necessary. The canvas coords at which + * the object should subsequently be drawn are returned in coords. + * + * This function calls painter.save(), and the caller must call + * painter.restore() after use. + */ + virtual double setPainterClipping(QPainter *, double layoutX, int layoutY, + double dx, double w, LinedStaffCoords &coords, + FitPolicy policy); + + /** + * Set a single pixmap to a notation element, or split it into + * bits if it overruns the end of a row and set the bits + * separately. + */ + virtual void setPixmap(NotationElement *, QCanvasPixmap *, int z, + FitPolicy policy); + + bool isSelected(NotationElementList::iterator); + + typedef std::set<QCanvasSimpleSprite *> SpriteSet; + SpriteSet m_timeSigs; + + typedef std::set<QCanvasItem *> ItemSet; + ItemSet m_repeatedClefsAndKeys; + + typedef std::pair<int, Clef> ClefChange; + FastVector<ClefChange> m_clefChanges; + + typedef std::pair<int, ::Rosegarden::Key> KeyChange; + FastVector<KeyChange> m_keyChanges; + + void truncateClefsAndKeysAt(int); + + /** Verify that a possible Clef or Key in bar is already inserted + * in m_clefChange or m_keyChange. + * If not, do the insertion. + */ + void checkAndCompleteClefsAndKeys(int bar); + + NotePixmapFactory *m_notePixmapFactory; + NotePixmapFactory *m_graceNotePixmapFactory; + QCanvasSimpleSprite *m_previewSprite; + QCanvasSimpleSprite *m_staffName; + std::string m_staffNameText; + NotationView *m_notationView; + int m_legerLineCount; + int m_barNumbersEvery; + bool m_colourQuantize; + bool m_showUnknowns; + bool m_showRanges; + bool m_showCollisions; + int m_keySigCancelMode; + + QPainter *m_printPainter; + + enum BarStatus { UnRendered = 0, Rendered, Positioned }; + typedef std::map<int, BarStatus> BarStatusMap; + BarStatusMap m_status; + std::pair<int, int> m_lastRenderCheck; + bool m_ready; + + int m_lastRenderedBar; +}; + + +} + +#endif diff --git a/src/gui/editors/notation/NotationStrings.cpp b/src/gui/editors/notation/NotationStrings.cpp new file mode 100644 index 0000000..6f8defd --- /dev/null +++ b/src/gui/editors/notation/NotationStrings.cpp @@ -0,0 +1,301 @@ +/* -*- 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 "NotationStrings.h" +#include <kapplication.h> + +#include <klocale.h> +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/Exception.h" +#include "base/NotationTypes.h" +#include "gui/configuration/GeneralConfigurationPage.h" +#include <kconfig.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +QString +NotationStrings::addDots(QString s, int dots, + bool hyphenate, bool internationalize) +{ + if (!dots) + return s; + + if (internationalize) { + if (dots > 1) { + if (hyphenate) + return i18n("%1-dotted-%2").arg(dots).arg(s); + else + return i18n("%1-dotted %2").arg(dots).arg(s); + } else { + if (hyphenate) + return i18n("dotted-%1").arg(s); + else + return i18n("dotted %1").arg(s); + } + } else { + if (dots > 1) { + if (hyphenate) + return QString("%1-dotted-%2").arg(dots).arg(s); + else + return QString("%1-dotted %2").arg(dots).arg(s); + } else { + if (hyphenate) + return QString("dotted-%1").arg(s); + else + return QString("dotted %1").arg(s); + } + } +} + +QString +NotationStrings::getNoteName(Note note, bool plural, bool triplet) +{ + Note::Type type = note.getNoteType(); + int dots = note.getDots(); + + static const QString names[] = { + i18n("sixty-fourth note"), i18n("thirty-second note"), + i18n("sixteenth note"), i18n("eighth note"), + i18n("quarter note"), i18n("half note"), + i18n("whole note"), i18n("double whole note") + }; + static const QString pluralnames[] = { + i18n("sixty-fourth notes"), i18n("thirty-second notes"), + i18n("sixteenth notes"), i18n("eighth notes"), + i18n("quarter notes"), i18n("half notes"), + i18n("whole notes"), i18n("double whole notes") + }; + + if (plural && triplet) { + return addDots(i18n("%1 triplets").arg(names[type]), dots, false, true); // TODO PLURAL - this is broken because it assumes there's only 1 plural form + } else if (plural) { + return addDots(pluralnames[type], dots, false, true); + } else if (triplet) { + return addDots(i18n("%1 triplet").arg(names[type]), dots, false, true); + } else { + return addDots(names[type], dots, false, true); + } +} + +QString +NotationStrings::getAmericanName(Note note, bool plural, bool triplet) +{ + Note::Type type = note.getNoteType(); + int dots = note.getDots(); + + static const QString names[] = { + "sixty-fourth note", "thirty-second note", + "sixteenth note", "eighth note", + "quarter note", "half note", + "whole note", "double whole note" + }; + static const QString pluralnames[] = { + "sixty-fourth notes", "thirty-second notes", + "sixteenth notes", "eighth notes", + "quarter notes", "half notes", + "whole notes", "double whole notes" + }; + + if (plural && triplet) { + return addDots(QString("%1 triplets").arg(names[type]), dots, false, false); + } else if (plural) { + return addDots(pluralnames[type], dots, false, false); + } else if (triplet) { + return addDots(QString("%1 triplet").arg(names[type]), dots, false, false); + } else { + return addDots(names[type], dots, false, false); + } +} + +QString +NotationStrings::getShortNoteName(Note note, bool plural, bool triplet) +{ + Note::Type type = note.getNoteType(); + int dots = note.getDots(); + + static const QString names[] = { + i18n("64th"), i18n("32nd"), i18n("16th"), i18n("8th"), + i18n("quarter"), i18n("half"), i18n("whole"), + i18n("double whole") + }; + static const QString pluralnames[] = { + i18n("64ths"), i18n("32nds"), i18n("16ths"), i18n("8ths"), + i18n("quarters"), i18n("halves"), i18n("wholes"), + i18n("double wholes") + }; + + if (plural && triplet) { + return addDots(i18n("%1 triplets").arg(names[type]), dots, false, true); // TODO - this is broken because it assumes there's only 1 plural form + } else if (plural) { + return addDots(pluralnames[type], dots, false, true); + } else if (triplet) { + return addDots(i18n("%1 triplet").arg(names[type]), dots, false, true); + } else { + return addDots(names[type], dots, false, true); + } +} + +QString +NotationStrings::getReferenceName(Note note, bool isRest) +{ + Note::Type type = note.getNoteType(); + int dots = note.getDots(); + + static const QString names[] = { + "hemidemisemi", "demisemi", "semiquaver", + "quaver", "crotchet", "minim", "semibreve", "breve" + }; + + QString name(names[type]); + if (isRest) + name = "rest-" + name; + return addDots(name, dots, true, false); +} + +Note +NotationStrings::getNoteForName(QString name) +{ + std::string origName(qstrtostr(name)); + int pos = name.find('-'); + int dots = 0; + + if (pos > 0 && pos < 6 && pos < int(name.length()) - 1) { + dots = name.left(pos).toInt(); + name = name.right(name.length() - pos - 1); + if (dots < 2) { + throw MalformedNoteName("Non-numeric or invalid dot count in \"" + + origName + "\""); + } + } + + if (name.length() > 7 && + (name.left(7) == "dotted " || name.left(7) == "dotted-")) { + if (dots == 0) + dots = 1; + name = name.right(name.length() - 7); + } else { + if (dots > 1) { + throw MalformedNoteName("Dot count without dotted tag in \"" + + origName + "\""); + } + } + + if (name.length() > 5 && name.right(5) == " note") { + name = name.left(name.length() - 5); + } + + Note::Type type; + + static const char *names[][4] = { + { "64th", "sixty-fourth", "hemidemisemi", "hemidemisemiquaver" + }, + { "32nd", "thirty-second", "demisemi", "demisemiquaver" }, + { "16th", "sixteenth", "semi", "semiquaver" }, + { "8th", "eighth", 0, "quaver" }, + { "quarter", 0, 0, "crotchet", }, + { "half", 0, 0, "minim" }, + { "whole", 0, 0, "semibreve" }, + { "double whole", 0, 0, "breve" } + }; + + for (type = Note::Shortest; type <= Note::Longest; ++type) { + for (int i = 0; i < 4; ++i) { + if (!names[type][i]) + continue; + if (name == names[type][i]) + return Note(type, dots); + } + } + + throw MalformedNoteName("Can't parse note name \"" + origName + "\""); +} + +QString +NotationStrings::makeNoteMenuLabel(timeT duration, + bool brief, + timeT &errorReturn, + bool plural) +{ + Note nearestNote = Note::getNearestNote(duration); + bool triplet = false; + errorReturn = 0; + + if (duration == 0) + return "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(); + duration = nearestNote.getDuration(); + } + } + + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + GeneralConfigurationPage::NoteNameStyle noteNameStyle = + (GeneralConfigurationPage::NoteNameStyle) + config->readUnsignedNumEntry + ("notenamestyle", GeneralConfigurationPage::Local); + + if (brief) { + + timeT wholeNote = Note(Note::Semibreve).getDuration(); + if ((wholeNote / duration) * duration == wholeNote) { + return QString("1/%1").arg(wholeNote / duration); + } else if ((duration / wholeNote) * wholeNote == duration) { + return QString("%1/1").arg(duration / wholeNote); + } else { + return i18n("%1 ticks").arg(duration); + plural = false; + } + + } else { + QString noteName; + + switch (noteNameStyle) { + + case GeneralConfigurationPage::American: + noteName = getAmericanName(nearestNote, plural, triplet); + break; + + case GeneralConfigurationPage::Local: + noteName = getNoteName(nearestNote, plural, triplet); + break; + } + + // Already internationalised, if appropriate + return noteName; + } +} + +} diff --git a/src/gui/editors/notation/NotationStrings.h b/src/gui/editors/notation/NotationStrings.h new file mode 100644 index 0000000..d79dff3 --- /dev/null +++ b/src/gui/editors/notation/NotationStrings.h @@ -0,0 +1,121 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONSTRINGS_H_ +#define _RG_NOTATIONSTRINGS_H_ + +#include "base/Exception.h" +#include "base/NotationTypes.h" +#include <qstring.h> +#include "base/Event.h" + + + + +namespace Rosegarden +{ + + + +/** + * String factory for note names, etc. used in the GUI + * Replaces use of base/NotationTypes.h strings which should + * be used only for non-user purposes. + */ +class NotationStrings +{ +public: + NotationStrings(); + ~NotationStrings(); + + + /** + * Get the name of a note. The default return values are American + * (e.g. quarter note, dotted sixteenth note). If the app is + * internationalised, you will get return names local to your + * region. Note that this includes English note names- set your + * LC_LANG to en_GB. + */ + static QString getNoteName(Note note, + bool plural = false, bool triplet = false); + + /** + * Get the UNTRANSLATED American name of a note. This may be + * useful if the user has specified that they'd prefer American + * names to local names. + */ + static QString getAmericanName(Note note, + bool plural = false, bool triplet = false); + + /** + * Get the short name of a note. The default return values are + * American (e.g. quarter, dotted 16th). If the app is + * internationalised, you will get return names local to your + * region. Note that this includes English note names- set your + * LC_LANG to en_GB. + */ + static QString getShortNoteName(Note note, + bool plural = false, bool triplet = false); + + + /** + * Get the UNTRANSLATED reference name of a note or rest. This is the + * formal name used to name pixmap files and the like, so the exact + * values of these strings are pretty sensitive. + */ + static QString getReferenceName(Note note, bool isRest = false); + + typedef Exception MalformedNoteName; + + /** + * Get the note corresponding to the given string, which must be a + * reference name or an untranslated British, American or short name. + * May throw MalformedNoteName. + */ + static Note getNoteForName(QString name); + + /** + * Construct a label to describe the given duration as a note name in + * the proper locale. Uses the nearest available note to the duration + * and returns a non-zero value in errorReturn if it was not an exact + * match for the required duration. + */ + static QString makeNoteMenuLabel(timeT duration, + bool brief, + timeT &errorReturn, + bool plural = false); + +private: + /** + * Return a string representing the dotted version of the input str. + */ + static QString addDots(QString s, int dots, + bool hyphenate, bool internationalize); + +}; + +} + +#endif diff --git a/src/gui/editors/notation/NotationTool.cpp b/src/gui/editors/notation/NotationTool.cpp new file mode 100644 index 0000000..8e82107 --- /dev/null +++ b/src/gui/editors/notation/NotationTool.cpp @@ -0,0 +1,57 @@ +/* -*- 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 "NotationTool.h" +#include "misc/Debug.h" + +#include "gui/general/EditTool.h" +#include "NotationView.h" +#include <qstring.h> + + +namespace Rosegarden +{ + +NotationTool::NotationTool(const QString& menuName, NotationView* view) + : EditTool(menuName, view), + m_nParentView(view) +{} + +NotationTool::~NotationTool() +{ + NOTATION_DEBUG << "NotationTool::~NotationTool()" << endl; + + // delete m_menu; + // m_parentView->factory()->removeClient(this); + // m_instance = 0; +} + +void NotationTool::ready() +{ + m_nParentView->setCanvasCursor(Qt::arrowCursor); + m_nParentView->setHeightTracking(false); +} + +} diff --git a/src/gui/editors/notation/NotationTool.h b/src/gui/editors/notation/NotationTool.h new file mode 100644 index 0000000..ab1020a --- /dev/null +++ b/src/gui/editors/notation/NotationTool.h @@ -0,0 +1,93 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONTOOL_H_ +#define _RG_NOTATIONTOOL_H_ + +#include "gui/general/EditTool.h" + + +class QString; + + +namespace Rosegarden +{ + +class NotationView; + + +/** + * Notation tool base class. + * + * A NotationTool represents one of the items on the notation toolbars + * (notes, rests, clefs, eraser, etc...). It handle mouse click events + * for the NotationView ('State' design pattern). + * + * A NotationTool can have a menu, normally activated through a right + * mouse button click. This menu is defined in an XML file, see + * NoteInserter and noteinserter.rc for an example. + * + * This class is a "semi-singleton", that is, only one instance per + * NotationView window is created. This is because menu creation is + * slow, and the fact that a tool can trigger the setting of another + * tool through a menu choice). This is maintained with the + * NotationToolBox class This means we can't rely on the ctor/dtor to + * perform setting up, like mouse cursor changes for instance. Use the + * ready() and stow() method for this. + * + * @see NotationView#setTool() + * @see NotationToolBox + */ +class NotationTool : public EditTool +{ + friend class NotationToolBox; + +public: + virtual ~NotationTool(); + + /** + * Is called by NotationView when the tool is set as current + * Add any setup here + */ + virtual void ready(); + +protected: + /** + * Create a new NotationTool + * + * \a menuName : the name of the menu defined in the XML rc file + */ + NotationTool(const QString& menuName, NotationView*); + + //--------------- Data members --------------------------------- + + NotationView* m_nParentView; +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/NotationToolBox.cpp b/src/gui/editors/notation/NotationToolBox.cpp new file mode 100644 index 0000000..769bcaf --- /dev/null +++ b/src/gui/editors/notation/NotationToolBox.cpp @@ -0,0 +1,102 @@ +/* -*- 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 "NotationToolBox.h" + +#include "gui/general/EditToolBox.h" +#include "gui/general/EditTool.h" +#include "NotationView.h" +#include "NoteInserter.h" +#include "RestInserter.h" +#include "ClefInserter.h" +#include "TextInserter.h" +#include "GuitarChordInserter.h" +#include "NotationEraser.h" +#include "NotationSelector.h" + +#include <qstring.h> +#include <kmessagebox.h> + +namespace Rosegarden +{ + +NotationToolBox::NotationToolBox(NotationView *parent) + : EditToolBox(parent), + m_nParentView(parent) +{ + //m_tools.setAutoDelete(true); +} + +EditTool* NotationToolBox::createTool(const QString& toolName) +{ + NotationTool* tool = 0; + + QString toolNamelc = toolName.lower(); + + if (toolNamelc == NoteInserter::ToolName) + + tool = new NoteInserter(m_nParentView); + + else if (toolNamelc == RestInserter::ToolName) + + tool = new RestInserter(m_nParentView); + + else if (toolNamelc == ClefInserter::ToolName) + + tool = new ClefInserter(m_nParentView); + + else if (toolNamelc == TextInserter::ToolName) + + tool = new TextInserter(m_nParentView); + + else if (toolNamelc == GuitarChordInserter::ToolName) + + tool = new GuitarChordInserter(m_nParentView); + +/* else if (toolNamelc == LilyPondDirectiveInserter::ToolName) + + tool = new LilyPondDirectiveInserter(m_nParentView);*/ + + else if (toolNamelc == NotationEraser::ToolName) + + tool = new NotationEraser(m_nParentView); + + else if (toolNamelc == NotationSelector::ToolName) + + tool = new NotationSelector(m_nParentView); + + else { + KMessageBox::error(0, QString("NotationToolBox::createTool : unrecognised toolname %1 (%2)") + .arg(toolName).arg(toolNamelc)); + return 0; + } + + m_tools.insert(toolName, tool); + + return tool; +} + +} +#include "NotationToolBox.moc" diff --git a/src/gui/editors/notation/NotationToolBox.h b/src/gui/editors/notation/NotationToolBox.h new file mode 100644 index 0000000..48b1202 --- /dev/null +++ b/src/gui/editors/notation/NotationToolBox.h @@ -0,0 +1,65 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONTOOLBOX_H_ +#define _RG_NOTATIONTOOLBOX_H_ + +#include "gui/general/EditToolBox.h" + + +class QString; + + +namespace Rosegarden +{ + +class NotationView; +class EditTool; + + +/** + * NotationToolBox : maintains a single instance of each registered tool + * + * Tools are fetched from a name + */ +class NotationToolBox : public EditToolBox +{ + Q_OBJECT +public: + NotationToolBox(NotationView* parent); + +protected: + virtual EditTool* createTool(const QString& toolName); + + //--------------- Data members --------------------------------- + + NotationView* m_nParentView; +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/NotationVLayout.cpp b/src/gui/editors/notation/NotationVLayout.cpp new file mode 100644 index 0000000..c746a30 --- /dev/null +++ b/src/gui/editors/notation/NotationVLayout.cpp @@ -0,0 +1,731 @@ +/* -*- 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 "NotationVLayout.h" +#include "misc/Debug.h" + +#include <klocale.h> +#include "base/Composition.h" +#include "base/Event.h" +#include "base/LayoutEngine.h" +#include "base/NotationRules.h" +#include "base/NotationTypes.h" +#include "base/NotationQuantizer.h" +#include "base/Staff.h" +#include "gui/general/ProgressReporter.h" +#include "gui/editors/guitar/Chord.h" +#include "NotationChord.h" +#include "NotationElement.h" +#include "NotationProperties.h" +#include "NotationStaff.h" +#include "NotePixmapFactory.h" +#include <kmessagebox.h> +#include <qobject.h> +#include <qstring.h> +#include <qwidget.h> + + +namespace Rosegarden +{ + +using namespace BaseProperties; + + +NotationVLayout::NotationVLayout(Composition *c, NotePixmapFactory *npf, + const NotationProperties &properties, + QObject* parent, const char* name) : + ProgressReporter(parent, name), + m_composition(c), + m_npf(npf), + m_notationQuantizer(c->getNotationQuantizer()), + m_properties(properties) +{ + // empty +} + +NotationVLayout::~NotationVLayout() +{ + // empty +} + +NotationVLayout::SlurList & + +NotationVLayout::getSlurList(Staff &staff) +{ + SlurListMap::iterator i = m_slurs.find(&staff); + if (i == m_slurs.end()) { + m_slurs[&staff] = SlurList(); + } + + return m_slurs[&staff]; +} + +void +NotationVLayout::reset() +{ + m_slurs.clear(); +} + +void +NotationVLayout::resetStaff(Staff &staff, timeT, timeT) +{ + getSlurList(staff).clear(); +} + +void +NotationVLayout::scanStaff(Staff &staffBase, timeT, timeT) +{ + START_TIMING; + + NotationStaff &staff = dynamic_cast<NotationStaff &>(staffBase); + NotationElementList *notes = staff.getViewElementList(); + + NotationElementList::iterator from = notes->begin(); + NotationElementList::iterator to = notes->end(); + NotationElementList::iterator i; + + for (i = from; i != to; ++i) { + + NotationElement *el = static_cast<NotationElement*>(*i); + + // Displaced Y will only be used for certain events -- in + // particular not for notes, whose y-coord is obviously kind + // of meaningful. + double displacedY = 0.0; + long dyRaw = 0; + el->event()->get<Int>(DISPLACED_Y, dyRaw); + displacedY = double(dyRaw * m_npf->getLineSpacing()) / 1000.0; + + el->setLayoutY(staff.getLayoutYForHeight( -9) + displacedY); + + if (el->isRest()) { + + // rests for notes longer than the minim have hotspots + // aligned with the line above the middle line; the rest + // are aligned with the middle line + + long noteType; + bool hasNoteType = el->event()->get + <Int>(NOTE_TYPE, noteType); + if (hasNoteType && noteType > Note::Minim) { + el->setLayoutY(staff.getLayoutYForHeight(6) + displacedY); + } else { + el->setLayoutY(staff.getLayoutYForHeight(4) + displacedY); + } + + // Fix for bug 1090767 Rests outside staves have wrong glyphs + // by William <rosegarden4c AT orthoset.com> + // We use a "rest-outside-stave" glyph for any minim/semibreve/breve + // rest that has been displaced vertically e.g. by fine-positioning + // outside the stave. For small vertical displacements that keep + // the rest inside the stave, we use the "rest-inside-stave" glyph + // and also discretise the displacement into multiples of the + // stave-line spacing. The outside-stave glyphs match the character + // numbers 1D13A, 1D13B and 1D13C in the Unicode 4.0 standard. + + if (hasNoteType && (displacedY > 0.1 || displacedY < -0.1)) { + + // a fiddly check for transition from inside to outside: + + int min = -1, max = 1; + + switch (noteType) { + case Note::Breve: + min = -1; + max = 2; + break; + case Note::Semibreve: + min = -1; + max = 3; + break; + case Note::Minim: + min = -2; + max = 2; + break; + case Note::Crotchet: + min = -1; + max = 3; + break; + case Note::Quaver: + min = -2; + max = 3; + break; + case Note::Semiquaver: + min = -3; + max = 3; + break; + case Note::Demisemiquaver: + min = -3; + max = 4; + break; + case Note::Hemidemisemiquaver: + min = -4; + max = 4; + break; + } + + bool outside = false; + + if (noteType == Note::Breve) { + if (nearbyint(displacedY) < min * m_npf->getLineSpacing() || + nearbyint(displacedY) > max * m_npf->getLineSpacing()) { + outside = true; + } + } else { + if ((int)displacedY < min * m_npf->getLineSpacing() || + (int)displacedY > max * m_npf->getLineSpacing()) { + outside = true; + } + } + + el->event()->setMaybe<Bool>(m_properties.REST_OUTSIDE_STAVE, + outside); + + if (!outside) { + displacedY = (double)m_npf->getLineSpacing() * + (int(nearbyint((double)displacedY / + m_npf->getLineSpacing()))); + if (noteType > Note::Minim) + el->setLayoutY(staff.getLayoutYForHeight(6) + displacedY); + else + el->setLayoutY(staff.getLayoutYForHeight(4) + displacedY); + } + + // if (displacedY != 0.0) + // NOTATION_DEBUG << "REST_OUTSIDE_STAVE AFTER " + // << " : displacedY : " << displacedY + // << " line-spacing : " << m_npf->getLineSpacing() + // << " time : " << (el->getViewAbsoluteTime()) + // << endl; + } else { + el->event()->setMaybe<Bool>(m_properties.REST_OUTSIDE_STAVE, + false); + } + + } else if (el->isNote()) { + + NotationChord chord(*notes, i, m_notationQuantizer, m_properties); + if (chord.size() == 0) + continue; + + std::vector<int> h; + for (unsigned int j = 0; j < chord.size(); ++j) { + long height = 0; + if (!(*chord[j])->event()->get + <Int> + (m_properties.HEIGHT_ON_STAFF, height)) { + std::cerr << QString("ERROR: Event in chord at %1 has no HEIGHT_ON_STAFF property!\nThis is a bug (the program would previously have crashed by now)").arg((*chord[j])->getViewAbsoluteTime()) << std::endl; + (*chord[j])->event()->dump(std::cerr); + } + h.push_back(height); + } + bool stemmed = chord.hasStem(); + bool stemUp = chord.hasStemUp(); + bool hasNoteHeadShifted = chord.hasNoteHeadShifted(); + + unsigned int flaggedNote = (stemUp ? chord.size() - 1 : 0); + + bool hasShifted = chord.hasNoteHeadShifted(); + + double y0 = -1E50; // A very unlikely Y layout value + + for (unsigned int j = 0; j < chord.size(); ++j) { + + el = static_cast<NotationElement*>(*chord[j]); + el->setLayoutY(staff.getLayoutYForHeight(h[j])); + + // Look for collision + const double eps = 0.001; + Event *eel = el->event(); + double y = el->getLayoutY(); + if (eel->has("pitch")) { + el->setIsColliding(fabs(y - y0) < eps); + y0 = y; + } + + + // These calculations and assignments are pretty much final + // if the chord is not in a beamed group, but if it is then + // they will be reworked by NotationGroup::applyBeam, which + // is called from NotationHLayout::layout, which is called + // after this. Any inaccuracies here for beamed groups + // should be stamped out there. + + // el->event()->setMaybe<Bool>(STEM_UP, stemUp); + el->event()->setMaybe<Bool>(m_properties.VIEW_LOCAL_STEM_UP, stemUp); + + bool primary = + ((stemmed && stemUp) ? (j == 0) : (j == chord.size() - 1)); + el->event()->setMaybe<Bool> + (m_properties.CHORD_PRIMARY_NOTE, primary); + + if (primary) { + el->event()->setMaybe<Int> + (m_properties.CHORD_MARK_COUNT, chord.getMarkCountForChord()); + } + + bool shifted = chord.isNoteHeadShifted(chord[j]); + el->event()->setMaybe<Bool> + (m_properties.NOTE_HEAD_SHIFTED, shifted); + + el->event()->setMaybe<Bool> + (m_properties.NOTE_DOT_SHIFTED, false); + if (hasShifted && stemUp) { + long dots = 0; + (void)el->event()->get + <Int>(NOTE_DOTS, dots); + if (dots > 0) { + el->event()->setMaybe<Bool> + (m_properties.NOTE_DOT_SHIFTED, true); + } + } + + el->event()->setMaybe<Bool> + (m_properties.NEEDS_EXTRA_SHIFT_SPACE, + hasNoteHeadShifted && !stemUp); + + el->event()->setMaybe<Bool> + (m_properties.DRAW_FLAG, j == flaggedNote); + + int stemLength = -1; + if (j != flaggedNote) { + stemLength = staff.getLayoutYForHeight(h[flaggedNote]) - + staff.getLayoutYForHeight(h[j]); + if (stemLength < 0) + stemLength = -stemLength; + // NOTATION_DEBUG << "Setting stem length to " + // << stemLength << endl; + } else { + int minStemLength = stemLength; + if (h[j] < -2 && stemUp) { + //!!! needs tuning, & applying for beamed stems too + minStemLength = staff.getLayoutYForHeight(h[j]) - + staff.getLayoutYForHeight(4); + } else if (h[j] > 10 && !stemUp) { + minStemLength = staff.getLayoutYForHeight(4) - + staff.getLayoutYForHeight(h[j]); + } + stemLength = std::max(minStemLength, stemLength); + } + + el->event()->setMaybe<Int> + (m_properties.UNBEAMED_STEM_LENGTH, stemLength); + } + + + // #938545 (Broken notation: Duplicated note can float + // outside stave) -- Need to cope with the case where a + // note that's not a member of a chord (different stem + // direction &c) falls between notes that are members. + // Not optimal, as we can end up scanning the chord + // multiple times (we'll return to it after scanning the + // contained note). [We can't just iterate over all + // elements within the chord (as we can in hlayout) + // because we need them in height order.] + + i = chord.getFirstElementNotInChord(); + if (i == notes->end()) + i = chord.getFinalElement(); + else + --i; + + } else { + + if (el->event()->isa(Clef::EventType)) { + + // clef pixmaps have the hotspot placed to coincide + // with the pitch of the clef -- so the alto clef + // should be "on" the middle line, the treble clef + // "on" the line below the middle, etc + + el->setLayoutY(staff.getLayoutYForHeight + (Clef(*el->event()).getAxisHeight())); + + } else if (el->event()->isa(Rosegarden::Key::EventType)) { + + el->setLayoutY(staff.getLayoutYForHeight(12)); + + } else if (el->event()->isa(Text::EventType)) { + + std::string type = Text::UnspecifiedType; + el->event()->get<String>(Text::TextTypePropertyName, type); + + if (type == Text::Dynamic || + type == Text::LocalDirection || + type == Text::UnspecifiedType) { + el->setLayoutY(staff.getLayoutYForHeight(-7) + displacedY); + } else if (type == Text::Lyric) { + long verse = 0; + el->event()->get<Int>(Text::LyricVersePropertyName, verse); + el->setLayoutY(staff.getLayoutYForHeight(-10 - 3 * verse) + displacedY); + } else if (type == Text::Annotation) { + el->setLayoutY(staff.getLayoutYForHeight(-13) + displacedY); + } else { + el->setLayoutY(staff.getLayoutYForHeight(22) + displacedY); + } + + } else if (el->event()->isa(Indication::EventType)) { + + try { + std::string indicationType = + el->event()->get + <String>(Indication::IndicationTypePropertyName); + + if (indicationType == Indication::Slur || + indicationType == Indication::PhrasingSlur) { + getSlurList(staff).push_back(i); + } + + if (indicationType == Indication::OttavaUp || + indicationType == Indication::QuindicesimaUp) { + el->setLayoutY(staff.getLayoutYForHeight(15) + displacedY); + } else { + el->setLayoutY(staff.getLayoutYForHeight( -9) + displacedY); + } + } catch (...) { + el->setLayoutY(staff.getLayoutYForHeight( -9) + displacedY); + } + + } else if (el->event()->isa(Guitar::Chord::EventType)) { + + el->setLayoutY(staff.getLayoutYForHeight(22) + displacedY); + } + } + } + + PRINT_ELAPSED("NotationVLayout::scanStaff"); +} + +void +NotationVLayout::finishLayout(timeT, timeT) +{ + START_TIMING; + + for (SlurListMap::iterator mi = m_slurs.begin(); + mi != m_slurs.end(); ++mi) { + + for (SlurList::iterator si = mi->second.begin(); + si != mi->second.end(); ++si) { + + NotationElementList::iterator i = *si; + NotationStaff &staff = dynamic_cast<NotationStaff &>(*(mi->first)); + + positionSlur(staff, i); + } + } + + PRINT_ELAPSED("NotationVLayout::finishLayout"); +} + +void +NotationVLayout::positionSlur(NotationStaff &staff, + NotationElementList::iterator i) +{ + NotationRules rules; + + bool phrasing = ((*i)->event()->get + <String>(Indication::IndicationTypePropertyName) + == Indication::PhrasingSlur); + + NotationElementList::iterator scooter = i; + + timeT slurDuration = (*i)->event()->getDuration(); + if (slurDuration == 0 && (*i)->event()->has("indicationduration")) { + slurDuration = (*i)->event()->get + <Int>("indicationduration"); // obs property + } + timeT endTime = (*i)->getViewAbsoluteTime() + slurDuration; + + bool haveStart = false; + + int startTopHeight = 4, endTopHeight = 4, + startBottomHeight = 4, endBottomHeight = 4, + maxTopHeight = 4, minBottomHeight = 4, + maxCount = 0, minCount = 0; + + int startX = (int)(*i)->getLayoutX(), endX = startX + 10; + bool startStemUp = false, endStemUp = false; + long startMarks = 0, endMarks = 0; + bool startTied = false, endTied = false; + bool beamAbove = false, beamBelow = false; + bool dynamic = false; + + std::vector<Event *> stemUpNotes, stemDownNotes; + + // Scan the notes spanned by the slur, recording the top and + // bottom heights of the first and last chords, plus the presence + // of any troublesome beams and high or low notes in the body. + + while (scooter != staff.getViewElementList()->end()) { + + if ((*scooter)->getViewAbsoluteTime() >= endTime) + break; + Event *event = (*scooter)->event(); + + if (event->isa(Note::EventType)) { + + long h = 0; + if (!event->get + <Int>(m_properties.HEIGHT_ON_STAFF, h)) { + KMessageBox::sorry + ((QWidget *)parent(), i18n("Spanned note at %1 has no HEIGHT_ON_STAFF property!\nThis is a bug (the program would previously have crashed by now)").arg((*scooter)->getViewAbsoluteTime())); + event->dump(std::cerr); + } + + bool stemUp = rules.isStemUp(h); + event->get + <Bool>(m_properties.VIEW_LOCAL_STEM_UP, stemUp); + + bool beamed = false; + event->get + <Bool>(m_properties.BEAMED, beamed); + + bool primary = false; + + if (event->get + <Bool> + (m_properties.CHORD_PRIMARY_NOTE, primary) && primary) { + + NotationChord chord(*(staff.getViewElementList()), scooter, + m_notationQuantizer, m_properties); + + if (beamed) { + if (stemUp) + beamAbove = true; + else + beamBelow = true; + } + + if (!haveStart) { + + startBottomHeight = chord.getLowestNoteHeight(); + startTopHeight = chord.getHighestNoteHeight(); + minBottomHeight = startBottomHeight; + maxTopHeight = startTopHeight; + + startX = (int)(*scooter)->getLayoutX(); + startStemUp = stemUp; + startMarks = chord.getMarkCountForChord(); + + bool tied = false; + if ((event->get + <Bool>(TIED_FORWARD, tied) && tied) || + (event->get<Bool>(TIED_BACKWARD, tied) && tied)) { + startTied = true; + } + + haveStart = true; + + } else { + if (chord.getLowestNoteHeight() < minBottomHeight) { + minBottomHeight = chord.getLowestNoteHeight(); + ++minCount; + } + if (chord.getHighestNoteHeight() > maxTopHeight) { + maxTopHeight = chord.getHighestNoteHeight(); + ++maxCount; + } + } + + endBottomHeight = chord.getLowestNoteHeight(); + endTopHeight = chord.getHighestNoteHeight(); + endX = (int)(*scooter)->getLayoutX(); + endStemUp = stemUp; + endMarks = chord.getMarkCountForChord(); + + bool tied = false; + if ((event->get + <Bool>(TIED_FORWARD, tied) && tied) || + (event->get<Bool>(TIED_BACKWARD, tied) && tied)) { + endTied = true; + } + } + + if (!beamed) { + if (stemUp) + stemUpNotes.push_back(event); + else + stemDownNotes.push_back(event); + } + + } else if (event->isa(Indication::EventType)) { + + try { + std::string indicationType = + event->get + <String>(Indication::IndicationTypePropertyName); + + if (indicationType == Indication::Crescendo || + indicationType == Indication::Decrescendo) + dynamic = true; + } catch (...) { } + } + + ++scooter; + } + + bool above = true; + + if ((*i)->event()->has(NotationProperties::SLUR_ABOVE) && + (*i)->event()->isPersistent<Bool>(NotationProperties::SLUR_ABOVE)) { + + (*i)->event()->get + <Bool>(NotationProperties::SLUR_ABOVE, above); + + } else if (phrasing) { + + int score = 0; // for "above" + + if (dynamic) + score += 2; + + if (startStemUp == endStemUp) { + if (startStemUp) + score -= 2; + else + score += 2; + } else if (beamBelow != beamAbove) { + if (beamAbove) + score -= 2; + else + score += 2; + } + + if (maxTopHeight < 6) + score += 1; + else if (minBottomHeight > 2) + score -= 1; + + if (stemUpNotes.size() != stemDownNotes.size()) { + if (stemUpNotes.size() < stemDownNotes.size()) + score += 1; + else + score -= 1; + } + + above = (score >= 0); + + } else { + + if (startStemUp == endStemUp) { + above = !startStemUp; + } else if (beamBelow) { + above = true; + } else if (beamAbove) { + above = false; + } else if (stemUpNotes.size() != stemDownNotes.size()) { + above = (stemUpNotes.size() < stemDownNotes.size()); + } else { + above = ((startTopHeight - 4) + (endTopHeight - 4) + + (4 - startBottomHeight) + (4 - endBottomHeight) <= 8); + } + } + + // now choose the actual y-coord of the slur based on the side + // we've decided to put it on + + int startHeight, endHeight; + int startOffset = 2, endOffset = 2; + + if (above) { + + if (!startStemUp) + startOffset += startMarks * 2; + else + startOffset += 5; + + if (!endStemUp) + endOffset += startMarks * 2; + else + endOffset += 5; + + startHeight = startTopHeight + startOffset; + endHeight = endTopHeight + endOffset; + + bool maxRelevant = ((maxTopHeight != endTopHeight) || (maxCount > 1)); + if (maxRelevant) { + int midHeight = (startHeight + endHeight) / 2; + if (maxTopHeight > midHeight - 1) { + startHeight += maxTopHeight - midHeight + 1; + endHeight += maxTopHeight - midHeight + 1; + } + } + + } else { + + if (startStemUp) + startOffset += startMarks * 2; + else + startOffset += 5; + + if (endStemUp) + endOffset += startMarks * 2; + else + endOffset += 5; + + startHeight = startBottomHeight - startOffset; + endHeight = endBottomHeight - endOffset; + + bool minRelevant = ((minBottomHeight != endBottomHeight) || (minCount > 1)); + if (minRelevant) { + int midHeight = (startHeight + endHeight) / 2; + if (minBottomHeight < midHeight + 1) { + startHeight -= midHeight - minBottomHeight + 1; + endHeight -= midHeight - minBottomHeight + 1; + } + } + } + + int y0 = staff.getLayoutYForHeight(startHeight), + y1 = staff.getLayoutYForHeight(endHeight); + + int dy = y1 - y0; + int length = endX - startX; + int diff = staff.getLayoutYForHeight(0) - staff.getLayoutYForHeight(3); + if (length < diff*10) + diff /= 2; + if (length > diff*3) + length -= diff / 2; + startX += diff; + + (*i)->event()->setMaybe<Bool>(NotationProperties::SLUR_ABOVE, above); + (*i)->event()->setMaybe<Int>(m_properties.SLUR_Y_DELTA, dy); + (*i)->event()->setMaybe<Int>(m_properties.SLUR_LENGTH, length); + + double displacedX = 0.0, displacedY = 0.0; + + long dxRaw = 0; + (*i)->event()->get<Int>(DISPLACED_X, dxRaw); + displacedX = double(dxRaw * m_npf->getNoteBodyWidth()) / 1000.0; + + long dyRaw = 0; + (*i)->event()->get<Int>(DISPLACED_Y, dyRaw); + displacedY = double(dyRaw * m_npf->getLineSpacing()) / 1000.0; + + (*i)->setLayoutX(startX + displacedX); + (*i)->setLayoutY(y0 + displacedY); +} + +} diff --git a/src/gui/editors/notation/NotationVLayout.h b/src/gui/editors/notation/NotationVLayout.h new file mode 100644 index 0000000..83a16c1 --- /dev/null +++ b/src/gui/editors/notation/NotationVLayout.h @@ -0,0 +1,122 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONVLAYOUT_H_ +#define _RG_NOTATIONVLAYOUT_H_ + +#include "base/FastVector.h" +#include "base/LayoutEngine.h" +#include "gui/general/ProgressReporter.h" +#include <map> +#include "base/Event.h" + +#include "NotationElement.h" + + +class SlurList; +class QObject; + + +namespace Rosegarden +{ + +class Staff; +class Quantizer; +class Composition; +class NotePixmapFactory; +class NotationStaff; +class NotationProperties; +class Composition; + + +/** + * Vertical notation layout + * + * computes the Y coordinate of notation elements + */ + +class NotationVLayout : public ProgressReporter, + public VerticalLayoutEngine +{ +public: + NotationVLayout(Composition *c, NotePixmapFactory *npf, + const NotationProperties &properties, + QObject* parent, const char* name = 0); + + virtual ~NotationVLayout(); + + void setNotePixmapFactory(NotePixmapFactory *npf) { + m_npf = npf; + } + + /** + * Resets internal data stores for all staffs + */ + virtual void reset(); + + /** + * Resets internal data stores for a specific staff + */ + virtual void resetStaff(Staff &, + timeT = 0, + timeT = 0); + + /** + * Lay out a single staff. + */ + virtual void scanStaff(Staff &, + timeT = 0, + timeT = 0); + + /** + * Do any layout dependent on more than one staff. As it + * happens, we have none, but we do have some layout that + * depends on the final results from the horizontal layout + * (for slurs), so we should do that here + */ + virtual void finishLayout(timeT = 0, + timeT = 0); + +private: + void positionSlur(NotationStaff &staff, NotationElementList::iterator i); + + typedef FastVector<NotationElementList::iterator> SlurList; + typedef std::map<Staff *, SlurList> SlurListMap; + + //--------------- Data members --------------------------------- + + SlurListMap m_slurs; + SlurList &getSlurList(Staff &); + + Composition *m_composition; + NotePixmapFactory *m_npf; + const Quantizer *m_notationQuantizer; + const NotationProperties &m_properties; +}; + + +} + +#endif diff --git a/src/gui/editors/notation/NotationView.cpp b/src/gui/editors/notation/NotationView.cpp new file mode 100644 index 0000000..66cb4b3 --- /dev/null +++ b/src/gui/editors/notation/NotationView.cpp @@ -0,0 +1,7552 @@ +/* -*- 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 "NotationView.h" +#include <list> +#include <qlayout.h> +#include "misc/Debug.h" +#include <kapplication.h> + +#include "gui/editors/segment/TrackEditor.h" +#include "gui/editors/segment/TrackButtons.h" +#include "base/BaseProperties.h" +#include <klocale.h> +#include <kstddirs.h> +#include "misc/Strings.h" +#include "base/AnalysisTypes.h" +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "base/CompositionTimeSliceAdapter.h" +#include "base/Configuration.h" +#include "base/Device.h" +#include "base/Event.h" +#include "base/Exception.h" +#include "base/Instrument.h" +#include "base/MidiDevice.h" +#include "base/MidiTypes.h" +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "base/PropertyName.h" +#include "base/NotationQuantizer.h" +#include "base/RealTime.h" +#include "base/RulerScale.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/Staff.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "ClefInserter.h" +#include "commands/edit/AddDotCommand.h" +#include "commands/edit/ClearTriggersCommand.h" +#include "commands/edit/CollapseNotesCommand.h" +#include "commands/edit/CopyCommand.h" +#include "commands/edit/CutAndCloseCommand.h" +#include "commands/edit/CutCommand.h" +#include "commands/edit/EraseCommand.h" +#include "commands/edit/EventEditCommand.h" +#include "commands/edit/EventQuantizeCommand.h" +#include "commands/edit/InsertTriggerNoteCommand.h" +#include "commands/edit/PasteEventsCommand.h" +#include "commands/edit/SetLyricsCommand.h" +#include "commands/edit/SetNoteTypeCommand.h" +#include "commands/edit/SetTriggerCommand.h" +#include "commands/edit/TransposeCommand.h" +#include "commands/notation/AddFingeringMarkCommand.h" +#include "commands/notation/AddIndicationCommand.h" +#include "commands/notation/AddMarkCommand.h" +#include "commands/notation/AddSlashesCommand.h" +#include "commands/notation/AddTextMarkCommand.h" +#include "commands/notation/AutoBeamCommand.h" +#include "commands/notation/BeamCommand.h" +#include "commands/notation/BreakCommand.h" +#include "commands/notation/ChangeSlurPositionCommand.h" +#include "commands/notation/ChangeTiePositionCommand.h" +#include "commands/notation/ChangeStemsCommand.h" +#include "commands/notation/ChangeStyleCommand.h" +#include "commands/notation/ClefInsertionCommand.h" +#include "commands/notation/CollapseRestsCommand.h" +#include "commands/notation/DeCounterpointCommand.h" +#include "commands/notation/EraseEventCommand.h" +#include "commands/notation/FixNotationQuantizeCommand.h" +#include "commands/notation/IncrementDisplacementsCommand.h" +#include "commands/notation/InterpretCommand.h" +#include "commands/notation/KeyInsertionCommand.h" +#include "commands/notation/MakeAccidentalsCautionaryCommand.h" +#include "commands/notation/MakeChordCommand.h" +#include "commands/notation/MakeNotesViableCommand.h" +#include "commands/notation/MultiKeyInsertionCommand.h" +#include "commands/notation/NormalizeRestsCommand.h" +#include "commands/notation/RemoveFingeringMarksCommand.h" +#include "commands/notation/RemoveMarksCommand.h" +#include "commands/notation/RemoveNotationQuantizeCommand.h" +#include "commands/notation/ResetDisplacementsCommand.h" +#include "commands/notation/RespellCommand.h" +#include "commands/notation/RestoreSlursCommand.h" +#include "commands/notation/RestoreTiesCommand.h" +#include "commands/notation/RestoreStemsCommand.h" +#include "commands/notation/SetVisibilityCommand.h" +#include "commands/notation/SustainInsertionCommand.h" +#include "commands/notation/TextInsertionCommand.h" +#include "commands/notation/TieNotesCommand.h" +#include "commands/notation/TupletCommand.h" +#include "commands/notation/UntieNotesCommand.h" +#include "commands/notation/UnTupletCommand.h" +#include "commands/segment/PasteToTriggerSegmentCommand.h" +#include "commands/segment/SegmentSyncCommand.h" +#include "commands/segment/SegmentTransposeCommand.h" +#include "commands/segment/RenameTrackCommand.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "document/io/LilyPondExporter.h" +#include "GuitarChordInserter.h" +#include "gui/application/SetWaitCursor.h" +#include "gui/application/RosegardenGUIView.h" +#include "gui/dialogs/ClefDialog.h" +#include "gui/dialogs/EventEditDialog.h" +#include "gui/dialogs/InterpretDialog.h" +#include "gui/dialogs/IntervalDialog.h" +#include "gui/dialogs/KeySignatureDialog.h" +#include "gui/dialogs/LilyPondOptionsDialog.h" +#include "gui/dialogs/LyricEditDialog.h" +#include "gui/dialogs/MakeOrnamentDialog.h" +#include "gui/dialogs/PasteNotationDialog.h" +#include "gui/dialogs/QuantizeDialog.h" +#include "gui/dialogs/SimpleEventEditDialog.h" +#include "gui/dialogs/TextEventDialog.h" +#include "gui/dialogs/TupletDialog.h" +#include "gui/dialogs/UseOrnamentDialog.h" +#include "gui/rulers/StandardRuler.h" +#include "gui/general/ActiveItem.h" +#include "gui/general/ClefIndex.h" +#include "gui/general/EditViewBase.h" +#include "gui/general/EditView.h" +#include "gui/general/GUIPalette.h" +#include "gui/general/LinedStaff.h" +#include "gui/general/LinedStaffManager.h" +#include "gui/general/ProgressReporter.h" +#include "gui/general/PresetHandlerDialog.h" +#include "gui/general/RosegardenCanvasView.h" +#include "gui/kdeext/KTmpStatusMsg.h" +#include "gui/kdeext/QCanvasSimpleSprite.h" +#include "gui/rulers/ChordNameRuler.h" +#include "gui/rulers/RawNoteRuler.h" +#include "gui/rulers/TempoRuler.h" +#include "gui/rulers/LoopRuler.h" +#include "gui/studio/StudioControl.h" +#include "gui/dialogs/EventFilterDialog.h" +#include "gui/widgets/ProgressBar.h" +#include "gui/widgets/ProgressDialog.h" +#include "gui/widgets/ScrollBoxDialog.h" +#include "gui/widgets/ScrollBox.h" +#include "gui/widgets/QDeferScrollView.h" +#include "NotationCanvasView.h" +#include "NotationElement.h" +#include "NotationEraser.h" +#include "NotationHLayout.h" +#include "NotationProperties.h" +#include "NotationSelector.h" +#include "NotationStaff.h" +#include "NotationStrings.h" +#include "NotationToolBox.h" +#include "NotationVLayout.h" +#include "NoteFontFactory.h" +#include "NoteInserter.h" +#include "NotePixmapFactory.h" +#include "NoteStyleFactory.h" +#include "NoteStyle.h" +#include "RestInserter.h" +#include "sound/MappedEvent.h" +#include "TextInserter.h" +#include "HeadersGroup.h" +#include <kaction.h> +#include <kcombobox.h> +#include <kconfig.h> +#include <kglobal.h> +#include <klineeditdlg.h> +#include <kmessagebox.h> +#include <kprinter.h> +#include <kprocess.h> +#include <kprogress.h> +#include <kstatusbar.h> +#include <kstdaction.h> +#include <ktempfile.h> +#include <ktoolbar.h> +#include <kxmlguiclient.h> +#include <qbrush.h> +#include <qcanvas.h> +#include <qcursor.h> +#include <qdialog.h> +#include <qevent.h> +#include <qfont.h> +#include <qfontmetrics.h> +#include <qhbox.h> +#include <qiconset.h> +#include <qlabel.h> +#include <qobject.h> +#include <qpaintdevicemetrics.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qprinter.h> +#include <qrect.h> +#include <qregexp.h> +#include <qsize.h> +#include <qstring.h> +#include <qtimer.h> +#include <qwidget.h> +#include <qvalidator.h> +#include <algorithm> +#include <qpushbutton.h> +#include <qtooltip.h> + + +namespace Rosegarden +{ + +class NoteActionData +{ +public: + NoteActionData(); + NoteActionData(const QString& _title, + QString _actionName, + QString _pixmapName, + int _keycode, + bool _rest, + Note::Type _noteType, + int _dots); + + QString title; + QString actionName; + QString pixmapName; + int keycode; + bool rest; + Note::Type noteType; + int dots; +}; + +NoteActionData::NoteActionData() + : title(0), + actionName(0), + pixmapName(0), + keycode(0), + rest(false), + noteType(0), + dots(0) +{ +} + +NoteActionData::NoteActionData(const QString& _title, + QString _actionName, + QString _pixmapName, + int _keycode, + bool _rest, + Note::Type _noteType, + int _dots) + : title(_title), + actionName(_actionName), + pixmapName(_pixmapName), + keycode(_keycode), + rest(_rest), + noteType(_noteType), + dots(_dots) +{ +} + + +class NoteChangeActionData +{ +public: + NoteChangeActionData(); + NoteChangeActionData(const QString &_title, + QString _actionName, + QString _pixmapName, + int _keycode, + bool _notationOnly, + Note::Type _noteType); + + QString title; + QString actionName; + QString pixmapName; + int keycode; + bool notationOnly; + Note::Type noteType; +}; + +NoteChangeActionData::NoteChangeActionData() + : title(0), + actionName(0), + pixmapName(0), + keycode(0), + notationOnly(false), + noteType(0) +{ +} + +NoteChangeActionData::NoteChangeActionData(const QString& _title, + QString _actionName, + QString _pixmapName, + int _keycode, + bool _notationOnly, + Note::Type _noteType) + : title(_title), + actionName(_actionName), + pixmapName(_pixmapName), + keycode(_keycode), + notationOnly(_notationOnly), + noteType(_noteType) +{ +} + + +class MarkActionData +{ +public: + MarkActionData() : + title(0), + actionName(0), + keycode(0) { } + + MarkActionData(const QString &_title, + QString _actionName, + int _keycode, + Mark _mark) : + title(_title), + actionName(_actionName), + keycode(_keycode), + mark(_mark) { } + + QString title; + QString actionName; + int keycode; + Mark mark; +}; + + +NotationView::NotationView(RosegardenGUIDoc *doc, + std::vector<Segment *> segments, + QWidget *parent, + bool showProgressive) : + EditView(doc, segments, 2, parent, "notationview"), + m_properties(getViewLocalPropertyPrefix()), + m_selectionCounter(0), + m_insertModeLabel(0), + m_annotationsLabel(0), + m_lilyPondDirectivesLabel(0), + m_progressBar(0), + m_currentNotePixmap(0), + m_hoveredOverNoteName(0), + m_hoveredOverAbsoluteTime(0), + m_currentStaff( -1), + m_lastFinishingStaff( -1), + m_title(0), + m_subtitle(0), + m_composer(0), + m_copyright(0), + m_insertionTime(0), + m_deferredCursorMove(NoCursorMoveNeeded), + m_lastNoteAction("crotchet"), + m_fontName(NoteFontFactory::getDefaultFontName()), + m_fontSize(NoteFontFactory::getDefaultSize(m_fontName)), + m_pageMode(LinedStaff::LinearMode), + m_leftGutter(20), + m_notePixmapFactory(new NotePixmapFactory(m_fontName, m_fontSize)), + m_hlayout(new NotationHLayout(&doc->getComposition(), m_notePixmapFactory, + m_properties, this)), + m_vlayout(new NotationVLayout(&doc->getComposition(), m_notePixmapFactory, + m_properties, this)), + m_chordNameRuler(0), + m_tempoRuler(0), + m_rawNoteRuler(0), + m_annotationsVisible(false), + m_lilyPondDirectivesVisible(false), + m_selectDefaultNote(0), + m_fontCombo(0), + m_fontSizeCombo(0), + m_spacingCombo(0), + m_fontSizeActionMenu(0), + m_pannerDialog(new ScrollBoxDialog(this, ScrollBox::FixHeight)), + m_renderTimer(0), + m_playTracking(true), + m_progressDisplayer(PROGRESS_NONE), + m_inhibitRefresh(true), + m_ok(false), + m_printMode(false), + m_printSize(8), // set in positionStaffs + m_showHeadersGroup(0), + m_headersGroupView(0), + m_headersGroup(0), + m_headersTopFrame(0), + m_showHeadersMenuEntry(0) +{ + initActionDataMaps(); // does something only the 1st time it's called + + m_toolBox = new NotationToolBox(this); + + assert(segments.size() > 0); + NOTATION_DEBUG << "NotationView ctor" << endl; + + + // Initialise the display-related defaults that will be needed + // by both the actions and the layout toolbar + + m_config->setGroup(NotationViewConfigGroup); + + m_showHeadersGroup = m_config->readNumEntry("shownotationheader", + HeadersGroup::DefaultShowMode); + + m_fontName = qstrtostr(m_config->readEntry + ("notefont", + strtoqstr(NoteFontFactory::getDefaultFontName()))); + + try + { + (void)NoteFontFactory::getFont + (m_fontName, + NoteFontFactory::getDefaultSize(m_fontName)); + } catch (Exception e) + { + m_fontName = NoteFontFactory::getDefaultFontName(); + } + + m_fontSize = m_config->readUnsignedNumEntry + ((segments.size() > 1 ? "multistaffnotesize" : "singlestaffnotesize"), + NoteFontFactory::getDefaultSize(m_fontName)); + + int defaultSpacing = m_config->readNumEntry("spacing", 100); + m_hlayout->setSpacing(defaultSpacing); + + int defaultProportion = m_config->readNumEntry("proportion", 60); + m_hlayout->setProportion(defaultProportion); + + delete m_notePixmapFactory; + m_notePixmapFactory = new NotePixmapFactory(m_fontName, m_fontSize); + m_hlayout->setNotePixmapFactory(m_notePixmapFactory); + m_vlayout->setNotePixmapFactory(m_notePixmapFactory); + + setupActions(); + // setupAddControlRulerMenu(); - too early for notation, moved to end of ctor. + initLayoutToolbar(); + initStatusBar(); + + setBackgroundMode(PaletteBase); + + QCanvas *tCanvas = new QCanvas(this); + tCanvas->resize(width() * 2, height() * 2); + + setCanvasView(new NotationCanvasView(*this, tCanvas, getCentralWidget())); + + updateViewCaption(); + + m_chordNameRuler = new ChordNameRuler + (m_hlayout, doc, segments, m_leftGutter, 20, getCentralWidget()); + addRuler(m_chordNameRuler); + if (showProgressive) + m_chordNameRuler->show(); + + m_tempoRuler = new TempoRuler + (m_hlayout, doc, this, m_leftGutter, 24, false, getCentralWidget()); + addRuler(m_tempoRuler); + m_tempoRuler->hide(); + static_cast<TempoRuler *>(m_tempoRuler)->connectSignals(); + + m_rawNoteRuler = new RawNoteRuler + (m_hlayout, segments[0], m_leftGutter, 20, getCentralWidget()); + addRuler(m_rawNoteRuler); + m_rawNoteRuler->show(); + + // All toolbars should be created before this is called + setAutoSaveSettings("NotationView", true); + + // All rulers must have been created before this is called, + // or the program will crash + readOptions(); + + + setBottomStandardRuler(new StandardRuler(getDocument(), m_hlayout, m_leftGutter, 25, + true, getBottomWidget())); + + for (unsigned int i = 0; i < segments.size(); ++i) + { + m_staffs.push_back(new NotationStaff + (canvas(), segments[i], 0, // snap + i, this, + m_fontName, m_fontSize)); + } + + + // HeadersGroup ctor must not be called before m_staffs initialization + m_headersGroupView = new QDeferScrollView(getCentralWidget()); + QWidget * vport = m_headersGroupView->viewport(); + m_headersGroup = new HeadersGroup(vport, this, &doc->getComposition()); + m_headersGroupView->setVScrollBarMode(QScrollView::AlwaysOff); + m_headersGroupView->setHScrollBarMode(QScrollView::AlwaysOff); + m_headersGroupView->setFixedWidth(m_headersGroupView->contentsWidth()); + m_canvasView->setLeftFixedWidget(m_headersGroupView); + + // Add a close button just above the track headers. + // The grid layout is only here to maintain the button in a + // right place + m_headersTopFrame = new QFrame(getCentralWidget()); + QGridLayout * headersTopGrid + = new QGridLayout(m_headersTopFrame, 2, 2); + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QCanvasPixmap pixmap(pixmapDir + "/misc/close.xpm"); + QPushButton * hideHeadersButton + = new QPushButton(m_headersTopFrame); + headersTopGrid->addWidget(hideHeadersButton, 1, 1, + Qt::AlignRight | Qt::AlignBottom); + hideHeadersButton->setIconSet(QIconSet(pixmap)); + hideHeadersButton->setFlat(true); + QToolTip::add(hideHeadersButton, i18n("Close track headers")); + headersTopGrid->setMargin(4); + setTopStandardRuler(new StandardRuler(getDocument(), + m_hlayout, m_leftGutter, 25, + false, getCentralWidget()), m_headersTopFrame); + + m_topStandardRuler->getLoopRuler()->setBackgroundColor + (GUIPalette::getColour(GUIPalette::InsertCursorRuler)); + + connect(m_topStandardRuler->getLoopRuler(), SIGNAL(startMouseMove(int)), + m_canvasView, SLOT(startAutoScroll(int))); + connect(m_topStandardRuler->getLoopRuler(), SIGNAL(stopMouseMove()), + m_canvasView, SLOT(stopAutoScroll())); + + connect(m_bottomStandardRuler->getLoopRuler(), SIGNAL(startMouseMove(int)), + m_canvasView, SLOT(startAutoScroll(int))); + connect(m_bottomStandardRuler->getLoopRuler(), SIGNAL(stopMouseMove()), + m_canvasView, SLOT(stopAutoScroll())); + + // Following connection have to be done before calling setPageMode()) + connect(m_headersGroup, SIGNAL(headersResized(int)), + this, SLOT(slotHeadersWidthChanged(int))); + + + // + // layout + // + ProgressDialog* progressDlg = 0; + + if (showProgressive) + { + show(); + ProgressDialog::processEvents(); + + NOTATION_DEBUG << "NotationView : setting up progress dialog" << endl; + + progressDlg = new ProgressDialog(i18n("Starting..."), + 100, this); + progressDlg->setAutoClose(false); + progressDlg->setAutoReset(true); + progressDlg->setMinimumDuration(1000); + setupProgress(progressDlg); + + m_progressDisplayer = PROGRESS_DIALOG; + } + + m_chordNameRuler->setStudio(&getDocument()->getStudio()); + + m_currentStaff = 0; + m_staffs[0]->setCurrent(true); + + m_config->setGroup(NotationViewConfigGroup); + int layoutMode = m_config->readNumEntry("layoutmode", 0); + + try + { + + LinedStaff::PageMode mode = LinedStaff::LinearMode; + if (layoutMode == 1) + mode = LinedStaff::ContinuousPageMode; + else if (layoutMode == 2) + mode = LinedStaff::MultiPageMode; + + setPageMode(mode); + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + m_staffs[i]->getSegment().getRefreshStatus + (m_segmentsRefreshStatusIds[i]).setNeedsRefresh(false); + } + + m_ok = true; + + } catch (ProgressReporter::Cancelled c) + { + // when cancelled, m_ok is false -- checked by calling method + NOTATION_DEBUG << "NotationView ctor : layout Cancelled" << endl; + } + + NOTATION_DEBUG << "NotationView ctor : m_ok = " << m_ok << endl; + + delete progressDlg; + + // at this point we can return if operation was cancelled + if (!isOK()) + { + setOutOfCtor(); + return ; + } + + + // otherwise, carry on + setupDefaultProgress(); + + // + // Connect signals + // + + QObject::connect + (getCanvasView(), SIGNAL(renderRequired(double, double)), + this, SLOT(slotCheckRendered(double, double))); + + m_topStandardRuler->connectRulerToDocPointer(doc); + m_bottomStandardRuler->connectRulerToDocPointer(doc); + + // Disconnect the default connection for this signal from the + // top ruler, and connect our own instead + + QObject::disconnect + (m_topStandardRuler->getLoopRuler(), + SIGNAL(setPointerPosition(timeT)), 0, 0); + + QObject::connect + (m_topStandardRuler->getLoopRuler(), + SIGNAL(setPointerPosition(timeT)), + this, SLOT(slotSetInsertCursorPosition(timeT))); + + QObject::connect + (m_topStandardRuler, + SIGNAL(dragPointerToPosition(timeT)), + this, SLOT(slotSetInsertCursorPosition(timeT))); + + connect(m_bottomStandardRuler, SIGNAL(dragPointerToPosition(timeT)), + this, SLOT(slotSetPointerPosition(timeT))); + + QObject::connect + (getCanvasView(), SIGNAL(itemPressed(int, int, QMouseEvent*, NotationElement*)), + this, SLOT (slotItemPressed(int, int, QMouseEvent*, NotationElement*))); + + QObject::connect + (getCanvasView(), SIGNAL(activeItemPressed(QMouseEvent*, QCanvasItem*)), + this, SLOT (slotActiveItemPressed(QMouseEvent*, QCanvasItem*))); + + QObject::connect + (getCanvasView(), SIGNAL(nonNotationItemPressed(QMouseEvent*, QCanvasItem*)), + this, SLOT (slotNonNotationItemPressed(QMouseEvent*, QCanvasItem*))); + + QObject::connect + (getCanvasView(), SIGNAL(textItemPressed(QMouseEvent*, QCanvasItem*)), + this, SLOT (slotTextItemPressed(QMouseEvent*, QCanvasItem*))); + + QObject::connect + (getCanvasView(), SIGNAL(mouseMoved(QMouseEvent*)), + this, SLOT (slotMouseMoved(QMouseEvent*))); + + QObject::connect + (getCanvasView(), SIGNAL(mouseReleased(QMouseEvent*)), + this, SLOT (slotMouseReleased(QMouseEvent*))); + + QObject::connect + (getCanvasView(), SIGNAL(hoveredOverNoteChanged(const QString&)), + this, SLOT (slotHoveredOverNoteChanged(const QString&))); + + QObject::connect + (getCanvasView(), SIGNAL(hoveredOverAbsoluteTimeChanged(unsigned int)), + this, SLOT (slotHoveredOverAbsoluteTimeChanged(unsigned int))); + + QObject::connect + (getCanvasView(), SIGNAL(zoomIn()), this, SLOT(slotZoomIn())); + + QObject::connect + (getCanvasView(), SIGNAL(zoomOut()), this, SLOT(slotZoomOut())); + + QObject::connect + (m_pannerDialog->scrollbox(), SIGNAL(valueChanged(const QPoint &)), + getCanvasView(), SLOT(slotSetScrollPos(const QPoint &))); + + QObject::connect + (getCanvasView()->horizontalScrollBar(), SIGNAL(valueChanged(int)), + m_pannerDialog->scrollbox(), SLOT(setViewX(int))); + + QObject::connect + (getCanvasView()->verticalScrollBar(), SIGNAL(valueChanged(int)), + m_pannerDialog->scrollbox(), SLOT(setViewY(int))); + + QObject::connect + (doc, SIGNAL(pointerPositionChanged(timeT)), + this, SLOT(slotSetPointerPosition(timeT))); + + // + // Connect vertical scrollbars between canvas and notation header + QObject::connect + (getCanvasView()->verticalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(slotVerticalScrollHeadersGroup(int))); + + QObject::connect + (getCanvasView()->verticalScrollBar(), SIGNAL(sliderMoved(int)), + this, SLOT(slotVerticalScrollHeadersGroup(int))); + + QObject::connect + (m_headersGroupView, SIGNAL(gotWheelEvent(QWheelEvent*)), + getCanvasView(), SLOT(slotExternalWheelEvent(QWheelEvent*))); + + // Ensure notation header keeps the right bottom margin when user + // toggles the canvas view bottom rulers + connect(getCanvasView(), SIGNAL(bottomWidgetHeightChanged(int)), + this, SLOT(slotCanvasBottomWidgetHeightChanged(int))); + + // Signal canvas horizontal scroll to notation header + QObject::connect + (getCanvasView(), SIGNAL(contentsMoving(int, int)), + this, SLOT(slotUpdateHeaders(int, int))); + + // Connect the close notation headers button + QObject::connect(hideHeadersButton, SIGNAL(clicked()), + this, SLOT(slotHideHeadersGroup())); + + stateChanged("have_selection", KXMLGUIClient::StateReverse); + stateChanged("have_notes_in_selection", KXMLGUIClient::StateReverse); + stateChanged("have_rests_in_selection", KXMLGUIClient::StateReverse); + stateChanged("have_multiple_staffs", + (m_staffs.size() > 1 ? KXMLGUIClient::StateNoReverse : + KXMLGUIClient::StateReverse)); + stateChanged("rest_insert_tool_current", KXMLGUIClient::StateReverse); + slotTestClipboard(); + + if (getSegmentsOnlyRestsAndClefs()) + { + m_selectDefaultNote->activate(); + stateChanged("note_insert_tool_current", + KXMLGUIClient::StateNoReverse); + } else + { + actionCollection()->action("select")->activate(); + stateChanged("note_insert_tool_current", + KXMLGUIClient::StateReverse); + } + + timeT start = doc->getComposition().getLoopStart(); + timeT end = doc->getComposition().getLoopEnd(); + m_topStandardRuler->getLoopRuler()->slotSetLoopMarker(start, end); + m_bottomStandardRuler->getLoopRuler()->slotSetLoopMarker(start, end); + + slotSetInsertCursorPosition(0); + slotSetPointerPosition(doc->getComposition().getPosition()); + setCurrentSelection(0, false, true); + slotUpdateInsertModeStatus(); + m_chordNameRuler->repaint(); + m_tempoRuler->repaint(); + m_rawNoteRuler->repaint(); + m_inhibitRefresh = false; + + // slotCheckRendered(0, getCanvasView()->visibleWidth()); + // getCanvasView()->repaintContents(); + updateView(); + + QObject::connect + (this, SIGNAL(renderComplete()), + getCanvasView(), SLOT(slotRenderComplete())); + + if (parent) + { + const TrackButtons * trackLabels = + ((RosegardenGUIView*)parent)->getTrackEditor()->getTrackButtons(); + QObject::connect + (trackLabels, SIGNAL(nameChanged()), + this, SLOT(slotUpdateStaffName())); + } + + setConfigDialogPageIndex(3); + setOutOfCtor(); + + // Property and Control Rulers + // + if (getCurrentSegment()->getViewFeatures()) + slotShowVelocityControlRuler(); + setupControllerTabs(); + + setupAddControlRulerMenu(); + setRewFFwdToAutoRepeat(); + + slotCompositionStateUpdate(); + + NOTATION_DEBUG << "NotationView ctor exiting" << endl; +} + +NotationView::NotationView(RosegardenGUIDoc *doc, + std::vector<Segment *> segments, + QWidget *parent, + NotationView *referenceView) + : EditView(doc, segments, 1, 0, "printview"), + m_properties(getViewLocalPropertyPrefix()), + m_selectionCounter(0), + m_currentNotePixmap(0), + m_hoveredOverNoteName(0), + m_hoveredOverAbsoluteTime(0), + m_lastFinishingStaff( -1), + m_title(0), + m_subtitle(0), + m_composer(0), + m_copyright(0), + m_insertionTime(0), + m_deferredCursorMove(NoCursorMoveNeeded), + m_lastNoteAction("crotchet"), + m_fontName(NoteFontFactory::getDefaultFontName()), + m_fontSize(NoteFontFactory::getDefaultSize(m_fontName)), + m_pageMode(LinedStaff::LinearMode), + m_leftGutter(0), + m_notePixmapFactory(new NotePixmapFactory(m_fontName, m_fontSize)), + m_hlayout(new NotationHLayout(&doc->getComposition(), m_notePixmapFactory, + m_properties, this)), + m_vlayout(new NotationVLayout(&doc->getComposition(), m_notePixmapFactory, + m_properties, this)), + m_chordNameRuler(0), + m_tempoRuler(0), + m_rawNoteRuler(0), + m_annotationsVisible(false), + m_lilyPondDirectivesVisible(false), + m_selectDefaultNote(0), + m_fontCombo(0), + m_fontSizeCombo(0), + m_spacingCombo(0), + m_fontSizeActionMenu(0), + m_pannerDialog(0), + m_renderTimer(0), + m_playTracking(false), + m_progressDisplayer(PROGRESS_NONE), + m_inhibitRefresh(true), + m_ok(false), + m_printMode(true), + m_printSize(8), // set in positionStaffs + m_showHeadersGroup(0), + m_headersGroupView(0), + m_headersGroup(0), + m_headersTopFrame(0), + m_showHeadersMenuEntry(0) +{ + assert(segments.size() > 0); + NOTATION_DEBUG << "NotationView print ctor" << endl; + + + // Initialise the display-related defaults that will be needed + // by both the actions and the layout toolbar + + m_config->setGroup(NotationViewConfigGroup); + + if (referenceView) + { + m_fontName = referenceView->m_fontName; + } else + { + m_fontName = qstrtostr(m_config->readEntry + ("notefont", + strtoqstr(NoteFontFactory::getDefaultFontName()))); + } + + + // Force largest font size + std::vector<int> sizes = NoteFontFactory::getAllSizes(m_fontName); + m_fontSize = sizes[sizes.size() - 1]; + + if (referenceView) + { + m_hlayout->setSpacing(referenceView->m_hlayout->getSpacing()); + m_hlayout->setProportion(referenceView->m_hlayout->getProportion()); + } else + { + int defaultSpacing = m_config->readNumEntry("spacing", 100); + m_hlayout->setSpacing(defaultSpacing); + int defaultProportion = m_config->readNumEntry("proportion", 60); + m_hlayout->setProportion(defaultProportion); + } + + delete m_notePixmapFactory; + m_notePixmapFactory = new NotePixmapFactory(m_fontName, m_fontSize); + m_hlayout->setNotePixmapFactory(m_notePixmapFactory); + m_vlayout->setNotePixmapFactory(m_notePixmapFactory); + + setBackgroundMode(PaletteBase); + m_config->setGroup(NotationViewConfigGroup); + + QCanvas *tCanvas = new QCanvas(this); + tCanvas->resize(width() * 2, height() * 2); //!!! + + setCanvasView(new NotationCanvasView(*this, tCanvas, getCentralWidget())); + canvas()->retune(128); // tune for larger canvas + + for (unsigned int i = 0; i < segments.size(); ++i) + { + m_staffs.push_back(new NotationStaff(canvas(), segments[i], 0, // snap + i, this, + m_fontName, m_fontSize)); + } + + m_currentStaff = 0; + m_staffs[0]->setCurrent(true); + + ProgressDialog* progressDlg = 0; + + if (parent) + { + + ProgressDialog::processEvents(); + + NOTATION_DEBUG << "NotationView : setting up progress dialog" << endl; + + progressDlg = new ProgressDialog(i18n("Preparing to print..."), + 100, parent); + progressDlg->setAutoClose(false); + progressDlg->setAutoReset(true); + progressDlg->setMinimumDuration(1000); + setupProgress(progressDlg); + + m_progressDisplayer = PROGRESS_DIALOG; + } + + try + { + + setPageMode(LinedStaff::MultiPageMode); // also positions and renders the staffs! + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + m_staffs[i]->getSegment().getRefreshStatus + (m_segmentsRefreshStatusIds[i]).setNeedsRefresh(false); + } + + m_ok = true; + + } catch (ProgressReporter::Cancelled c) + { + // when cancelled, m_ok is false -- checked by calling method + NOTATION_DEBUG << "NotationView ctor : layout Cancelled" << endl; + } + + NOTATION_DEBUG << "NotationView ctor : m_ok = " << m_ok << endl; + + delete progressDlg; + + if (!isOK()) + { + setOutOfCtor(); + return ; // In case more code is added there later + } + + setOutOfCtor(); // keep this as last call in the ctor +} + +NotationView::~NotationView() +{ + NOTATION_DEBUG << "-> ~NotationView()" << endl; + + if (!m_printMode && m_ok) + slotSaveOptions(); + + delete m_chordNameRuler; + + delete m_renderTimer; + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + for (Segment::iterator j = m_staffs[i]->getSegment().begin(); + j != m_staffs[i]->getSegment().end(); ++j) { + removeViewLocalProperties(*j); + } + delete m_staffs[i]; // this will erase all "notes" canvas items + } + + PixmapArrayGC::deleteAll(); + Profiles::getInstance()->dump(); + + NOTATION_DEBUG << "<- ~NotationView()" << endl; +} + +void +NotationView::removeViewLocalProperties(Event *e) +{ + Event::PropertyNames names(e->getPropertyNames()); + std::string prefix(getViewLocalPropertyPrefix()); + + for (Event::PropertyNames::iterator i = names.begin(); + i != names.end(); ++i) { + if (i->getName().substr(0, prefix.size()) == prefix) { + e->unset(*i); + } + } +} + +const NotationProperties & +NotationView::getProperties() const +{ + return m_properties; +} + +void NotationView::positionStaffs() +{ + NOTATION_DEBUG << "NotationView::positionStaffs" << endl; + + m_config->setGroup(NotationViewConfigGroup); + m_printSize = m_config->readUnsignedNumEntry("printingnotesize", 5); + + int minTrack = 0, maxTrack = 0; + bool haveMinTrack = false; + typedef std::map<int, int> TrackIntMap; + TrackIntMap trackHeights; + TrackIntMap trackCoords; + + int pageWidth, pageHeight, leftMargin, topMargin; + pageWidth = getPageWidth(); + pageHeight = getPageHeight(); + leftMargin = 0, topMargin = 0; + getPageMargins(leftMargin, topMargin); + + int accumulatedHeight; + int rowsPerPage = 1; + int legerLines = 8; + if (m_pageMode != LinedStaff::LinearMode) + legerLines = 7; + int rowGapPercent = (m_staffs.size() > 1 ? 40 : 10); + int aimFor = -1; + + bool done = false; + + int titleHeight = 0; + + if (m_title) + delete m_title; + if (m_subtitle) + delete m_subtitle; + if (m_composer) + delete m_composer; + if (m_copyright) + delete m_copyright; + m_title = m_subtitle = m_composer = m_copyright = 0; + + if (m_pageMode == LinedStaff::MultiPageMode) { + + const Configuration &metadata = + getDocument()->getComposition().getMetadata(); + + QFont defaultFont(NotePixmapFactory::defaultSerifFontFamily); + m_config->setGroup(NotationViewConfigGroup); + QFont font = m_config->readFontEntry("textfont", &defaultFont); + font.setPixelSize(m_fontSize * 5); + QFontMetrics metrics(font); + + if (metadata.has(CompositionMetadataKeys::Title)) { + QString title(strtoqstr(metadata.get<String> + (CompositionMetadataKeys::Title))); + m_title = new QCanvasText(title, font, canvas()); + m_title->setX(m_leftGutter + pageWidth / 2 - metrics.width(title) / 2); + m_title->setY(20 + topMargin / 4 + metrics.ascent()); + m_title->show(); + titleHeight += metrics.height() * 3 / 2 + topMargin / 4; + } + + font.setPixelSize(m_fontSize * 3); + metrics = QFontMetrics(font); + + if (metadata.has(CompositionMetadataKeys::Subtitle)) { + QString subtitle(strtoqstr(metadata.get<String> + (CompositionMetadataKeys::Subtitle))); + m_subtitle = new QCanvasText(subtitle, font, canvas()); + m_subtitle->setX(m_leftGutter + pageWidth / 2 - metrics.width(subtitle) / 2); + m_subtitle->setY(20 + titleHeight + metrics.ascent()); + m_subtitle->show(); + titleHeight += metrics.height() * 3 / 2; + } + + if (metadata.has(CompositionMetadataKeys::Composer)) { + QString composer(strtoqstr(metadata.get<String> + (CompositionMetadataKeys::Composer))); + m_composer = new QCanvasText(composer, font, canvas()); + m_composer->setX(m_leftGutter + pageWidth - metrics.width(composer) - leftMargin); + m_composer->setY(20 + titleHeight + metrics.ascent()); + m_composer->show(); + titleHeight += metrics.height() * 3 / 2; + } + + font.setPixelSize(m_fontSize * 2); + metrics = QFontMetrics(font); + + if (metadata.has(CompositionMetadataKeys::Copyright)) { + QString copyright(strtoqstr(metadata.get<String> + (CompositionMetadataKeys::Copyright))); + m_copyright = new QCanvasText(copyright, font, canvas()); + m_copyright->setX(m_leftGutter + leftMargin); + m_copyright->setY(20 + pageHeight - topMargin - metrics.descent()); + m_copyright->show(); + } + } + + while (1) { + + accumulatedHeight = 0; + int maxTrackHeight = 0; + + trackHeights.clear(); + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + + m_staffs[i]->setLegerLineCount(legerLines); + + int height = m_staffs[i]->getHeightOfRow(); + TrackId trackId = m_staffs[i]->getSegment().getTrack(); + Track *track = + m_staffs[i]->getSegment().getComposition()-> + getTrackById(trackId); + + if (!track) + continue; // This Should Not Happen, My Friend + + int trackPosition = track->getPosition(); + + TrackIntMap::iterator hi = trackHeights.find(trackPosition); + if (hi == trackHeights.end()) { + trackHeights.insert(TrackIntMap::value_type + (trackPosition, height)); + } else if (height > hi->second) { + hi->second = height; + } + + if (height > maxTrackHeight) + maxTrackHeight = height; + + if (trackPosition < minTrack || !haveMinTrack) { + minTrack = trackPosition; + haveMinTrack = true; + } + if (trackPosition > maxTrack) { + maxTrack = trackPosition; + } + } + + for (int i = minTrack; i <= maxTrack; ++i) { + TrackIntMap::iterator hi = trackHeights.find(i); + if (hi != trackHeights.end()) { + trackCoords[i] = accumulatedHeight; + accumulatedHeight += hi->second; + } + } + + accumulatedHeight += maxTrackHeight * rowGapPercent / 100; + + if (done) + break; + + if (m_pageMode != LinedStaff::MultiPageMode) { + + rowsPerPage = 0; + done = true; + break; + + } else { + + // Check how well all this stuff actually fits on the + // page. If things don't fit as well as we'd like, modify + // at most one parameter so as to save some space, then + // loop around again and see if it worked. This iterative + // approach is inefficient but the time spent here is + // neglible in context, and it's a simple way to code it. + + int staffPageHeight = pageHeight - topMargin * 2 - titleHeight; + rowsPerPage = staffPageHeight / accumulatedHeight; + + if (rowsPerPage < 1) { + + if (legerLines > 5) + --legerLines; + else if (rowGapPercent > 20) + rowGapPercent -= 10; + else if (legerLines > 4) + --legerLines; + else if (rowGapPercent > 0) + rowGapPercent -= 10; + else if (legerLines > 3) + --legerLines; + else if (m_printSize > 3) + --m_printSize; + else { // just accept that we'll have to overflow + rowsPerPage = 1; + done = true; + } + + } else { + + if (aimFor == rowsPerPage) { + + titleHeight += + (staffPageHeight - (rowsPerPage * accumulatedHeight)) / 2; + + done = true; + + } else { + + if (aimFor == -1) + aimFor = rowsPerPage + 1; + + // we can perhaps accommodate another row, with care + if (legerLines > 5) + --legerLines; + else if (rowGapPercent > 20) + rowGapPercent -= 10; + else if (legerLines > 3) + --legerLines; + else if (rowGapPercent > 0) + rowGapPercent -= 10; + else { // no, we can't + rowGapPercent = 0; + legerLines = 8; + done = true; + } + } + } + } + } + + m_hlayout->setPageWidth(pageWidth - leftMargin * 2); + + int topGutter = 0; + + if (m_pageMode == LinedStaff::MultiPageMode) { + + topGutter = 20; + + } else if (m_pageMode == LinedStaff::ContinuousPageMode) { + + // fewer leger lines above staff than in linear mode -- + // compensate for this on the top staff + topGutter = m_notePixmapFactory->getLineSpacing() * 2; + } + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + + TrackId trackId = m_staffs[i]->getSegment().getTrack(); + Track *track = + m_staffs[i]->getSegment().getComposition()-> + getTrackById(trackId); + + if (!track) + continue; // Once Again, My Friend, You Should Never See Me Here + + int trackPosition = track->getPosition(); + + m_staffs[i]->setTitleHeight(titleHeight); + m_staffs[i]->setRowSpacing(accumulatedHeight); + + if (trackPosition < maxTrack) { + m_staffs[i]->setConnectingLineLength(trackHeights[trackPosition]); + } + + if (trackPosition == minTrack && + m_pageMode != LinedStaff::LinearMode) { + m_staffs[i]->setBarNumbersEvery(5); + } else { + m_staffs[i]->setBarNumbersEvery(0); + } + + m_staffs[i]->setX(m_leftGutter); + m_staffs[i]->setY(topGutter + trackCoords[trackPosition] + topMargin); + m_staffs[i]->setPageWidth(pageWidth - leftMargin * 2); + m_staffs[i]->setRowsPerPage(rowsPerPage); + m_staffs[i]->setPageMode(m_pageMode); + m_staffs[i]->setMargin(leftMargin); + + NOTATION_DEBUG << "NotationView::positionStaffs: set staff's page width to " + << (pageWidth - leftMargin * 2) << endl; + + } + + + if (!m_printMode) { + // Destroy then recreate all track headers + hideHeadersGroup(); + m_headersGroup->removeAllHeaders(); + if (m_pageMode == LinedStaff::LinearMode) { + for (int i = minTrack; i <= maxTrack; ++i) { + TrackIntMap::iterator hi = trackHeights.find(i); + if (hi != trackHeights.end()) { + TrackId trackId = getDocument()->getComposition() + .getTrackByPosition(i)->getId(); + m_headersGroup->addHeader(trackId, trackHeights[i], + trackCoords[i], getCanvasLeftX()); + } + } + + m_headersGroup->completeToHeight(canvas()->height()); + + m_headersGroupView->addChild(m_headersGroup); + + getCanvasView()->updateLeftWidgetGeometry(); + + if ( (m_showHeadersGroup == HeadersGroup::ShowAlways) + || ( (m_showHeadersGroup == HeadersGroup::ShowWhenNeeded) + && (m_headersGroup->getUsedHeight() + > getCanvasView()->visibleHeight()))) { + m_headersGroup->slotUpdateAllHeaders(getCanvasLeftX(), 0, true); + showHeadersGroup(); + + // Disable menu entry when headers are shown + m_showHeadersMenuEntry->setEnabled(false); + } else { + // Enable menu entry when headers are hidden + m_showHeadersMenuEntry->setEnabled(true); + } + } else { + // Disable menu entry when not in linear mode + m_showHeadersMenuEntry->setEnabled(false); + } + } +} + +void NotationView::slotCanvasBottomWidgetHeightChanged(int newHeight) +{ + getCanvasView()->updateLeftWidgetGeometry(); +} + +void NotationView::positionPages() +{ + if (m_printMode) + return ; + + QPixmap background; + QPixmap deskBackground; + bool haveBackground = false; + + m_config->setGroup(NotationViewConfigGroup); + if (m_config->readBoolEntry("backgroundtextures", true)) { + QString pixmapDir = + KGlobal::dirs()->findResource("appdata", "pixmaps/"); + if (background.load(QString("%1/misc/bg-paper-cream.xpm"). + arg(pixmapDir))) { + haveBackground = true; + } + // we're happy to ignore errors from this one: + deskBackground.load(QString("%1/misc/bg-desktop.xpm").arg(pixmapDir)); + } + + int pageWidth = getPageWidth(); + int pageHeight = getPageHeight(); + int leftMargin = 0, topMargin = 0; + getPageMargins(leftMargin, topMargin); + int maxPageCount = 1; + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + int pageCount = m_staffs[i]->getPageCount(); + if (pageCount > maxPageCount) + maxPageCount = pageCount; + } + + for (unsigned int i = 0; i < m_pages.size(); ++i) { + delete m_pages[i]; + delete m_pageNumbers[i]; + } + m_pages.clear(); + m_pageNumbers.clear(); + + if (m_pageMode != LinedStaff::MultiPageMode) { + if (haveBackground) { + canvas()->setBackgroundPixmap(background); + getCanvasView()->setBackgroundMode(Qt::FixedPixmap); + getCanvasView()->setPaletteBackgroundPixmap(background); + getCanvasView()->setErasePixmap(background); + } + } else { + if (haveBackground) { + canvas()->setBackgroundPixmap(deskBackground); + getCanvasView()->setBackgroundMode(Qt::FixedPixmap); + getCanvasView()->setPaletteBackgroundPixmap(background); + getCanvasView()->setErasePixmap(background); + } + + QFont pageNumberFont; + pageNumberFont.setPixelSize(m_fontSize * 2); + QFontMetrics pageNumberMetrics(pageNumberFont); + + for (int page = 0; page < maxPageCount; ++page) { + + int x = m_leftGutter + pageWidth * page + leftMargin / 4; + int y = 20; + int w = pageWidth - leftMargin / 2; + int h = pageHeight; + + QString str = QString("%1").arg(page + 1); + QCanvasText *text = new QCanvasText(str, pageNumberFont, canvas()); + text->setX(m_leftGutter + pageWidth * page + pageWidth - pageNumberMetrics.width(str) - leftMargin); + text->setY(y + h - pageNumberMetrics.descent() - topMargin); + text->setZ( -999); + text->show(); + m_pageNumbers.push_back(text); + + QCanvasRectangle *rect = new QCanvasRectangle(x, y, w, h, canvas()); + if (haveBackground) + rect->setBrush(QBrush(Qt::white, background)); + rect->setPen(Qt::black); + rect->setZ( -1000); + rect->show(); + m_pages.push_back(rect); + } + + updateThumbnails(false); + } + + m_config->setGroup(NotationViewConfigGroup); +} + +void NotationView::slotUpdateStaffName() +{ + LinedStaff *staff = getLinedStaff(m_currentStaff); + staff->drawStaffName(); + m_headersGroup->slotUpdateAllHeaders(getCanvasLeftX(), 0, true); +} + +void NotationView::slotSaveOptions() +{ + m_config->setGroup(NotationViewConfigGroup); + + m_config->writeEntry("Show Chord Name Ruler", getToggleAction("show_chords_ruler")->isChecked()); + m_config->writeEntry("Show Raw Note Ruler", getToggleAction("show_raw_note_ruler")->isChecked()); + m_config->writeEntry("Show Tempo Ruler", getToggleAction("show_tempo_ruler")->isChecked()); + m_config->writeEntry("Show Annotations", m_annotationsVisible); + m_config->writeEntry("Show LilyPond Directives", m_lilyPondDirectivesVisible); + + m_config->sync(); +} + +void NotationView::setOneToolbar(const char *actionName, + const char *toolbarName) +{ + KToggleAction *action = getToggleAction(actionName); + if (!action) { + std::cerr << "WARNING: No such action as " << actionName << std::endl; + return ; + } + QWidget *toolbar = toolBar(toolbarName); + if (!toolbar) { + std::cerr << "WARNING: No such toolbar as " << toolbarName << std::endl; + return ; + } + action->setChecked(!toolbar->isHidden()); +} + +void NotationView::readOptions() +{ + EditView::readOptions(); + + setOneToolbar("show_tools_toolbar", "Tools Toolbar"); + setOneToolbar("show_notes_toolbar", "Notes Toolbar"); + setOneToolbar("show_rests_toolbar", "Rests Toolbar"); + setOneToolbar("show_clefs_toolbar", "Clefs Toolbar"); + setOneToolbar("show_group_toolbar", "Group Toolbar"); + setOneToolbar("show_marks_toolbar", "Marks Toolbar"); + setOneToolbar("show_layout_toolbar", "Layout Toolbar"); + setOneToolbar("show_transport_toolbar", "Transport Toolbar"); + setOneToolbar("show_accidentals_toolbar", "Accidentals Toolbar"); + setOneToolbar("show_meta_toolbar", "Meta Toolbar"); + + m_config->setGroup(NotationViewConfigGroup); + + bool opt; + + opt = m_config->readBoolEntry("Show Chord Name Ruler", false); + getToggleAction("show_chords_ruler")->setChecked(opt); + slotToggleChordsRuler(); + + opt = m_config->readBoolEntry("Show Raw Note Ruler", true); + getToggleAction("show_raw_note_ruler")->setChecked(opt); + slotToggleRawNoteRuler(); + + opt = m_config->readBoolEntry("Show Tempo Ruler", true); + getToggleAction("show_tempo_ruler")->setChecked(opt); + slotToggleTempoRuler(); + + opt = m_config->readBoolEntry("Show Annotations", true); + m_annotationsVisible = opt; + getToggleAction("show_annotations")->setChecked(opt); + slotUpdateAnnotationsStatus(); + // slotToggleAnnotations(); + + opt = m_config->readBoolEntry("Show LilyPond Directives", true); + m_lilyPondDirectivesVisible = opt; + getToggleAction("show_lilypond_directives")->setChecked(opt); + slotUpdateLilyPondDirectivesStatus(); +} + +void NotationView::setupActions() +{ + KStdAction::print(this, SLOT(slotFilePrint()), actionCollection()); + KStdAction::printPreview(this, SLOT(slotFilePrintPreview()), + actionCollection()); + + new KAction(i18n("Print &with LilyPond..."), 0, 0, this, + SLOT(slotPrintLilyPond()), actionCollection(), + "file_print_lilypond"); + + new KAction(i18n("Preview with Lil&yPond..."), 0, 0, this, + SLOT(slotPreviewLilyPond()), actionCollection(), + "file_preview_lilypond"); + + EditViewBase::setupActions("notation.rc"); + EditView::setupActions(); + + KRadioAction* noteAction = 0; + + // View menu stuff + + KActionMenu *fontActionMenu = + new KActionMenu(i18n("Note &Font"), this, "note_font_actionmenu"); + + std::set + <std::string> fs(NoteFontFactory::getFontNames()); + std::vector<std::string> f(fs.begin(), fs.end()); + std::sort(f.begin(), f.end()); + + for (std::vector<std::string>::iterator i = f.begin(); i != f.end(); ++i) { + + QString fontQName(strtoqstr(*i)); + + KToggleAction *fontAction = + new KToggleAction + (fontQName, 0, this, SLOT(slotChangeFontFromAction()), + actionCollection(), "note_font_" + fontQName); + + fontAction->setChecked(*i == m_fontName); + fontActionMenu->insert(fontAction); + } + + actionCollection()->insert(fontActionMenu); + + m_fontSizeActionMenu = + new KActionMenu(i18n("Si&ze"), this, "note_font_size_actionmenu"); + setupFontSizeMenu(); + + actionCollection()->insert(m_fontSizeActionMenu); + + m_showHeadersMenuEntry + = new KAction(i18n("Show Track Headers"), 0, this, + SLOT(slotShowHeadersGroup()), + actionCollection(), "show_track_headers"); + + KActionMenu *spacingActionMenu = + new KActionMenu(i18n("S&pacing"), this, "stretch_actionmenu"); + + int defaultSpacing = m_hlayout->getSpacing(); + std::vector<int> spacings = NotationHLayout::getAvailableSpacings(); + + for (std::vector<int>::iterator i = spacings.begin(); + i != spacings.end(); ++i) { + + KToggleAction *spacingAction = + new KToggleAction + (QString("%1%").arg(*i), 0, this, + SLOT(slotChangeSpacingFromAction()), + actionCollection(), QString("spacing_%1").arg(*i)); + + spacingAction->setExclusiveGroup("spacing"); + spacingAction->setChecked(*i == defaultSpacing); + spacingActionMenu->insert(spacingAction); + } + + actionCollection()->insert(spacingActionMenu); + + KActionMenu *proportionActionMenu = + new KActionMenu(i18n("Du&ration Factor"), this, "proportion_actionmenu"); + + int defaultProportion = m_hlayout->getProportion(); + std::vector<int> proportions = NotationHLayout::getAvailableProportions(); + + for (std::vector<int>::iterator i = proportions.begin(); + i != proportions.end(); ++i) { + + QString name = QString("%1%").arg(*i); + if (*i == 0) + name = i18n("None"); + + KToggleAction *proportionAction = + new KToggleAction + (name, 0, this, + SLOT(slotChangeProportionFromAction()), + actionCollection(), QString("proportion_%1").arg(*i)); + + proportionAction->setExclusiveGroup("proportion"); + proportionAction->setChecked(*i == defaultProportion); + proportionActionMenu->insert(proportionAction); + } + + actionCollection()->insert(proportionActionMenu); + + KActionMenu *styleActionMenu = + new KActionMenu(i18n("Note &Style"), this, "note_style_actionmenu"); + + std::vector<NoteStyleName> styles + (NoteStyleFactory::getAvailableStyleNames()); + + for (std::vector<NoteStyleName>::iterator i = styles.begin(); + i != styles.end(); ++i) { + + QString styleQName(strtoqstr(*i)); + + KAction *styleAction = + new KAction + (styleQName, 0, this, SLOT(slotSetStyleFromAction()), + actionCollection(), "style_" + styleQName); + + styleActionMenu->insert(styleAction); + } + + actionCollection()->insert(styleActionMenu); + + KActionMenu *ornamentActionMenu = + new KActionMenu(i18n("Use Ornament"), this, "ornament_actionmenu"); + + + + new KAction + (i18n("Insert Rest"), Key_P, this, SLOT(slotInsertRest()), + actionCollection(), QString("insert_rest")); + + new KAction + (i18n("Switch from Note to Rest"), Key_T, this, + SLOT(slotSwitchFromNoteToRest()), + actionCollection(), QString("switch_from_note_to_rest")); + + new KAction + (i18n("Switch from Rest to Note"), Key_Y, this, + SLOT(slotSwitchFromRestToNote()), + actionCollection(), QString("switch_from_rest_to_note")); + + + // setup Notes menu & toolbar + QIconSet icon; + + for (NoteActionDataMap::Iterator actionDataIter = m_noteActionDataMap->begin(); + actionDataIter != m_noteActionDataMap->end(); + ++actionDataIter) { + + NoteActionData noteActionData = **actionDataIter; + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + (noteActionData.pixmapName))); + noteAction = new KRadioAction(noteActionData.title, + icon, + noteActionData.keycode, + this, + SLOT(slotNoteAction()), + actionCollection(), + noteActionData.actionName); + noteAction->setExclusiveGroup("notes"); + + if (noteActionData.noteType == Note::Crotchet && + noteActionData.dots == 0 && !noteActionData.rest) { + m_selectDefaultNote = noteAction; + } + } + + // Note duration change actions + for (NoteChangeActionDataMap::Iterator actionDataIter = m_noteChangeActionDataMap->begin(); + actionDataIter != m_noteChangeActionDataMap->end(); + ++actionDataIter) { + + NoteChangeActionData data = **actionDataIter; + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + (data.pixmapName))); + + KAction *action = new KAction(data.title, + icon, + data.keycode, + this, + SLOT(slotNoteChangeAction()), + actionCollection(), + data.actionName); + } + + // + // Accidentals + // + static QString actionsAccidental[][4] = + { + { i18n("No accidental"), "1slotNoAccidental()", "no_accidental", "accidental-none" }, + { i18n("Follow previous accidental"), "1slotFollowAccidental()", "follow_accidental", "accidental-follow" }, + { i18n("Sharp"), "1slotSharp()", "sharp_accidental", "accidental-sharp" }, + { i18n("Flat"), "1slotFlat()", "flat_accidental", "accidental-flat" }, + { i18n("Natural"), "1slotNatural()", "natural_accidental", "accidental-natural" }, + { i18n("Double sharp"), "1slotDoubleSharp()", "double_sharp_accidental", "accidental-doublesharp" }, + { i18n("Double flat"), "1slotDoubleFlat()", "double_flat_accidental", "accidental-doubleflat" } + }; + + for (unsigned int i = 0; + i < sizeof(actionsAccidental) / sizeof(actionsAccidental[0]); ++i) { + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + (actionsAccidental[i][3]))); + noteAction = new KRadioAction(actionsAccidental[i][0], icon, 0, this, + actionsAccidental[i][1], + actionCollection(), actionsAccidental[i][2]); + noteAction->setExclusiveGroup("accidentals"); + } + + + // + // Clefs + // + + // Treble + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-treble"))); + noteAction = new KRadioAction(i18n("&Treble Clef"), icon, 0, this, + SLOT(slotTrebleClef()), + actionCollection(), "treble_clef"); + noteAction->setExclusiveGroup("notes"); + + // Alto + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-alto"))); + noteAction = new KRadioAction(i18n("&Alto Clef"), icon, 0, this, + SLOT(slotAltoClef()), + actionCollection(), "alto_clef"); + noteAction->setExclusiveGroup("notes"); + + // Tenor + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-tenor"))); + noteAction = new KRadioAction(i18n("Te&nor Clef"), icon, 0, this, + SLOT(slotTenorClef()), + actionCollection(), "tenor_clef"); + noteAction->setExclusiveGroup("notes"); + + // Bass + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-bass"))); + noteAction = new KRadioAction(i18n("&Bass Clef"), icon, 0, this, + SLOT(slotBassClef()), + actionCollection(), "bass_clef"); + noteAction->setExclusiveGroup("notes"); + + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("text"))); + noteAction = new KRadioAction(i18n("&Text"), icon, Key_F8, this, + SLOT(slotText()), + actionCollection(), "text"); + noteAction->setExclusiveGroup("notes"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("guitarchord"))); + noteAction = new KRadioAction(i18n("&Guitar Chord"), icon, Key_F9, this, + SLOT(slotGuitarChord()), + actionCollection(), "guitarchord"); + noteAction->setExclusiveGroup("notes"); + + /* icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("lilypond"))); + noteAction = new KRadioAction(i18n("Lil&ypond Directive"), icon, Key_F9, this, + SLOT(slotLilyPondDirective()), + actionCollection(), "lilypond_directive"); + noteAction->setExclusiveGroup("notes"); */ + + + // + // Edition tools (eraser, selector...) + // + noteAction = new KRadioAction(i18n("&Erase"), "eraser", Key_F4, + this, SLOT(slotEraseSelected()), + actionCollection(), "erase"); + noteAction->setExclusiveGroup("notes"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("select"))); + noteAction = new KRadioAction(i18n("&Select and Edit"), icon, Key_F2, + this, SLOT(slotSelectSelected()), + actionCollection(), "select"); + noteAction->setExclusiveGroup("notes"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("step_by_step"))); + new KToggleAction(i18n("Ste&p Recording"), icon, 0, this, + SLOT(slotToggleStepByStep()), actionCollection(), + "toggle_step_by_step"); + + + // Edit menu + new KAction(i18n("Select from Sta&rt"), 0, this, + SLOT(slotEditSelectFromStart()), actionCollection(), + "select_from_start"); + + new KAction(i18n("Select to &End"), 0, this, + SLOT(slotEditSelectToEnd()), actionCollection(), + "select_to_end"); + + new KAction(i18n("Select Whole St&aff"), Key_A + CTRL, this, + SLOT(slotEditSelectWholeStaff()), actionCollection(), + "select_whole_staff"); + + new KAction(i18n("C&ut and Close"), CTRL + SHIFT + Key_X, this, + SLOT(slotEditCutAndClose()), actionCollection(), + "cut_and_close"); + + new KAction(i18n("Pa&ste..."), CTRL + SHIFT + Key_V, this, + SLOT(slotEditGeneralPaste()), actionCollection(), + "general_paste"); + + new KAction(i18n("De&lete"), Key_Delete, this, + SLOT(slotEditDelete()), actionCollection(), + "delete"); + + new KAction(i18n("Move to Staff Above"), 0, this, + SLOT(slotMoveEventsUpStaff()), actionCollection(), + "move_events_up_staff"); + + new KAction(i18n("Move to Staff Below"), 0, this, + SLOT(slotMoveEventsDownStaff()), actionCollection(), + "move_events_down_staff"); + + // + // Settings menu + // + int layoutMode = m_config->readNumEntry("layoutmode", 0); + + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + + QCanvasPixmap pixmap(pixmapDir + "/toolbar/linear-layout.xpm"); + icon = QIconSet(pixmap); + KRadioAction *linearModeAction = new KRadioAction + (i18n("&Linear Layout"), icon, 0, this, SLOT(slotLinearMode()), + actionCollection(), "linear_mode"); + linearModeAction->setExclusiveGroup("layoutMode"); + if (layoutMode == 0) + linearModeAction->setChecked(true); + + pixmap.load(pixmapDir + "/toolbar/continuous-page-mode.xpm"); + icon = QIconSet(pixmap); + KRadioAction *continuousPageModeAction = new KRadioAction + (i18n("&Continuous Page Layout"), icon, 0, this, SLOT(slotContinuousPageMode()), + actionCollection(), "continuous_page_mode"); + continuousPageModeAction->setExclusiveGroup("layoutMode"); + if (layoutMode == 1) + continuousPageModeAction->setChecked(true); + + pixmap.load(pixmapDir + "/toolbar/multi-page-mode.xpm"); + icon = QIconSet(pixmap); + KRadioAction *multiPageModeAction = new KRadioAction + (i18n("&Multiple Page Layout"), icon, 0, this, SLOT(slotMultiPageMode()), + actionCollection(), "multi_page_mode"); + multiPageModeAction->setExclusiveGroup("layoutMode"); + if (layoutMode == 2) + multiPageModeAction->setChecked(true); + + new KToggleAction(i18n("Show Ch&ord Name Ruler"), 0, this, + SLOT(slotToggleChordsRuler()), + actionCollection(), "show_chords_ruler"); + + new KToggleAction(i18n("Show Ra&w Note Ruler"), 0, this, + SLOT(slotToggleRawNoteRuler()), + actionCollection(), "show_raw_note_ruler"); + + new KToggleAction(i18n("Show &Tempo Ruler"), 0, this, + SLOT(slotToggleTempoRuler()), + actionCollection(), "show_tempo_ruler"); + + new KToggleAction(i18n("Show &Annotations"), 0, this, + SLOT(slotToggleAnnotations()), + actionCollection(), "show_annotations"); + + new KToggleAction(i18n("Show Lily&Pond Directives"), 0, this, + SLOT(slotToggleLilyPondDirectives()), + actionCollection(), "show_lilypond_directives"); + + new KAction(i18n("Open L&yric Editor"), 0, this, SLOT(slotEditLyrics()), + actionCollection(), "lyric_editor"); + + // + // Group menu + // + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("group-beam"))); + + new KAction(BeamCommand::getGlobalName(), icon, Key_B + CTRL, this, + SLOT(slotGroupBeam()), actionCollection(), "beam"); + + new KAction(AutoBeamCommand::getGlobalName(), 0, this, + SLOT(slotGroupAutoBeam()), actionCollection(), "auto_beam"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("group-unbeam"))); + + new KAction(BreakCommand::getGlobalName(), icon, Key_U + CTRL, this, + SLOT(slotGroupBreak()), actionCollection(), "break_group"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("group-simple-tuplet"))); + + new KAction(TupletCommand::getGlobalName(true), icon, Key_R + CTRL, this, + SLOT(slotGroupSimpleTuplet()), actionCollection(), "simple_tuplet"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("group-tuplet"))); + + new KAction(TupletCommand::getGlobalName(false), icon, Key_T + CTRL, this, + SLOT(slotGroupGeneralTuplet()), actionCollection(), "tuplet"); + + new KAction(UnTupletCommand::getGlobalName(), 0, this, + SLOT(slotGroupUnTuplet()), actionCollection(), "break_tuplets"); + + icon = QIconSet(NotePixmapFactory::toQPixmap + (NotePixmapFactory::makeToolbarPixmap("triplet"))); + (new KToggleAction(i18n("Trip&let Insert Mode"), icon, Key_G, + this, SLOT(slotUpdateInsertModeStatus()), + actionCollection(), "triplet_mode"))-> + setChecked(false); + + icon = QIconSet(NotePixmapFactory::toQPixmap + (NotePixmapFactory::makeToolbarPixmap("chord"))); + (new KToggleAction(i18n("C&hord Insert Mode"), icon, Key_H, + this, SLOT(slotUpdateInsertModeStatus()), + actionCollection(), "chord_mode"))-> + setChecked(false); + + icon = QIconSet(NotePixmapFactory::toQPixmap + (NotePixmapFactory::makeToolbarPixmap("group-grace"))); + (new KToggleAction(i18n("Grace Insert Mode"), icon, 0, + this, SLOT(slotUpdateInsertModeStatus()), + actionCollection(), "grace_mode"))-> + setChecked(false); +/*!!! + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("group-grace"))); + + new KAction(GraceCommand::getGlobalName(), icon, 0, this, + SLOT(slotGroupGrace()), actionCollection(), "grace"); + + new KAction(UnGraceCommand::getGlobalName(), 0, this, + SLOT(slotGroupUnGrace()), actionCollection(), "ungrace"); +*/ + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("group-slur"))); + + new KAction(AddIndicationCommand::getGlobalName + (Indication::Slur), icon, Key_ParenRight, this, + SLOT(slotGroupSlur()), actionCollection(), "slur"); + + new KAction(AddIndicationCommand::getGlobalName + (Indication::PhrasingSlur), 0, Key_ParenRight + CTRL, this, + SLOT(slotGroupPhrasingSlur()), actionCollection(), "phrasing_slur"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("group-glissando"))); + + new KAction(AddIndicationCommand::getGlobalName + (Indication::Glissando), icon, 0, this, + SLOT(slotGroupGlissando()), actionCollection(), "glissando"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("group-crescendo"))); + + new KAction(AddIndicationCommand::getGlobalName + (Indication::Crescendo), icon, Key_Less, this, + SLOT(slotGroupCrescendo()), actionCollection(), "crescendo"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("group-decrescendo"))); + + new KAction(AddIndicationCommand::getGlobalName + (Indication::Decrescendo), icon, Key_Greater, this, + SLOT(slotGroupDecrescendo()), actionCollection(), "decrescendo"); + + new KAction(AddIndicationCommand::getGlobalName + (Indication::QuindicesimaUp), 0, 0, this, + SLOT(slotGroupOctave2Up()), actionCollection(), "octave_2up"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("group-ottava"))); + + new KAction(AddIndicationCommand::getGlobalName + (Indication::OttavaUp), icon, 0, this, + SLOT(slotGroupOctaveUp()), actionCollection(), "octave_up"); + + new KAction(AddIndicationCommand::getGlobalName + (Indication::OttavaDown), 0, 0, this, + SLOT(slotGroupOctaveDown()), actionCollection(), "octave_down"); + + new KAction(AddIndicationCommand::getGlobalName + (Indication::QuindicesimaDown), 0, 0, this, + SLOT(slotGroupOctave2Down()), actionCollection(), "octave_2down"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("group-chord"))); + new KAction(MakeChordCommand::getGlobalName(), icon, 0, this, + SLOT(slotGroupMakeChord()), actionCollection(), "make_chord"); + + // setup Transforms menu + new KAction(NormalizeRestsCommand::getGlobalName(), Key_N + CTRL, this, + SLOT(slotTransformsNormalizeRests()), actionCollection(), + "normalize_rests"); + + new KAction(CollapseRestsCommand::getGlobalName(), 0, this, + SLOT(slotTransformsCollapseRests()), actionCollection(), + "collapse_rests_aggressively"); + + new KAction(CollapseNotesCommand::getGlobalName(), Key_Equal + CTRL, this, + SLOT(slotTransformsCollapseNotes()), actionCollection(), + "collapse_notes"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transforms-tie"))); + + new KAction(TieNotesCommand::getGlobalName(), icon, Key_AsciiTilde, this, + SLOT(slotTransformsTieNotes()), actionCollection(), + "tie_notes"); + + new KAction(UntieNotesCommand::getGlobalName(), 0, this, + SLOT(slotTransformsUntieNotes()), actionCollection(), + "untie_notes"); + + new KAction(MakeNotesViableCommand::getGlobalName(), 0, this, + SLOT(slotTransformsMakeNotesViable()), actionCollection(), + "make_notes_viable"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transforms-decounterpoint"))); + + new KAction(DeCounterpointCommand::getGlobalName(), icon, 0, this, + SLOT(slotTransformsDeCounterpoint()), actionCollection(), + "de_counterpoint"); + + new KAction(ChangeStemsCommand::getGlobalName(true), + 0, Key_PageUp + CTRL, this, + SLOT(slotTransformsStemsUp()), actionCollection(), + "stems_up"); + + new KAction(ChangeStemsCommand::getGlobalName(false), + 0, Key_PageDown + CTRL, this, + SLOT(slotTransformsStemsDown()), actionCollection(), + "stems_down"); + + new KAction(RestoreStemsCommand::getGlobalName(), 0, this, + SLOT(slotTransformsRestoreStems()), actionCollection(), + "restore_stems"); + + new KAction(ChangeSlurPositionCommand::getGlobalName(true), + 0, this, + SLOT(slotTransformsSlursAbove()), actionCollection(), + "slurs_above"); + + new KAction(ChangeSlurPositionCommand::getGlobalName(false), + 0, this, + SLOT(slotTransformsSlursBelow()), actionCollection(), + "slurs_below"); + + new KAction(RestoreSlursCommand::getGlobalName(), 0, this, + SLOT(slotTransformsRestoreSlurs()), actionCollection(), + "restore_slurs"); + + new KAction(ChangeTiePositionCommand::getGlobalName(true), + 0, this, + SLOT(slotTransformsTiesAbove()), actionCollection(), + "ties_above"); + + new KAction(ChangeTiePositionCommand::getGlobalName(false), + 0, this, + SLOT(slotTransformsTiesBelow()), actionCollection(), + "ties_below"); + + new KAction(RestoreTiesCommand::getGlobalName(), 0, this, + SLOT(slotTransformsRestoreTies()), actionCollection(), + "restore_ties"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("accmenu-doubleflat"))); + + new KAction(RespellCommand::getGlobalName + (RespellCommand::Set, Accidentals::DoubleFlat), + icon, 0, this, + SLOT(slotRespellDoubleFlat()), actionCollection(), + "respell_doubleflat"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("accmenu-flat"))); + + new KAction(RespellCommand::getGlobalName + (RespellCommand::Set, Accidentals::Flat), + icon, 0, this, + SLOT(slotRespellFlat()), actionCollection(), + "respell_flat"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("accmenu-natural"))); + + new KAction(RespellCommand::getGlobalName + (RespellCommand::Set, Accidentals::Natural), + icon, 0, this, + SLOT(slotRespellNatural()), actionCollection(), + "respell_natural"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("accmenu-sharp"))); + + new KAction(RespellCommand::getGlobalName + (RespellCommand::Set, Accidentals::Sharp), + icon, 0, this, + SLOT(slotRespellSharp()), actionCollection(), + "respell_sharp"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("accmenu-doublesharp"))); + + new KAction(RespellCommand::getGlobalName + (RespellCommand::Set, Accidentals::DoubleSharp), + icon, 0, this, + SLOT(slotRespellDoubleSharp()), actionCollection(), + "respell_doublesharp"); + + new KAction(RespellCommand::getGlobalName + (RespellCommand::Up, Accidentals::NoAccidental), + Key_Up + CTRL + SHIFT, this, + SLOT(slotRespellUp()), actionCollection(), + "respell_up"); + + new KAction(RespellCommand::getGlobalName + (RespellCommand::Down, Accidentals::NoAccidental), + Key_Down + CTRL + SHIFT, this, + SLOT(slotRespellDown()), actionCollection(), + "respell_down"); + + new KAction(RespellCommand::getGlobalName + (RespellCommand::Restore, Accidentals::NoAccidental), + 0, this, + SLOT(slotRespellRestore()), actionCollection(), + "respell_restore"); + + new KAction(MakeAccidentalsCautionaryCommand::getGlobalName(true), + 0, this, + SLOT(slotShowCautionary()), actionCollection(), + "show_cautionary"); + + new KAction(MakeAccidentalsCautionaryCommand::getGlobalName(false), + 0, this, + SLOT(slotCancelCautionary()), actionCollection(), + "cancel_cautionary"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("quantize"))); + + new KAction(EventQuantizeCommand::getGlobalName(), icon, Key_Equal, this, + SLOT(slotTransformsQuantize()), actionCollection(), + "quantize"); + + new KAction(FixNotationQuantizeCommand::getGlobalName(), 0, + this, SLOT(slotTransformsFixQuantization()), actionCollection(), + "fix_quantization"); + + new KAction(RemoveNotationQuantizeCommand::getGlobalName(), 0, + this, SLOT(slotTransformsRemoveQuantization()), actionCollection(), + "remove_quantization"); + + new KAction(InterpretCommand::getGlobalName(), 0, + this, SLOT(slotTransformsInterpret()), actionCollection(), + "interpret"); + + new KAction(i18n("&Dump selected events to stderr"), 0, this, + SLOT(slotDebugDump()), actionCollection(), "debug_dump"); + + for (MarkActionDataMap::Iterator i = m_markActionDataMap->begin(); + i != m_markActionDataMap->end(); ++i) { + + const MarkActionData &markActionData = **i; + + icon = QIconSet(NotePixmapFactory::toQPixmap + (NotePixmapFactory::makeMarkMenuPixmap(markActionData.mark))); + + new KAction(markActionData.title, + icon, + markActionData.keycode, + this, + SLOT(slotAddMark()), + actionCollection(), + markActionData.actionName); + } + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("text-mark"))); + + new KAction(AddTextMarkCommand::getGlobalName(), icon, 0, this, + SLOT(slotMarksAddTextMark()), actionCollection(), + "add_text_mark"); + + new KAction(AddFingeringMarkCommand::getGlobalName("0"), 0, Key_0 + ALT, this, + SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(), + "add_fingering_0"); + + new KAction(AddFingeringMarkCommand::getGlobalName("1"), 0, Key_1 + ALT, this, + SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(), + "add_fingering_1"); + + new KAction(AddFingeringMarkCommand::getGlobalName("2"), 0, Key_2 + ALT, this, + SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(), + "add_fingering_2"); + + new KAction(AddFingeringMarkCommand::getGlobalName("3"), 0, Key_3 + ALT, this, + SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(), + "add_fingering_3"); + + new KAction(AddFingeringMarkCommand::getGlobalName("4"), 0, Key_4 + ALT, this, + SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(), + "add_fingering_4"); + + new KAction(AddFingeringMarkCommand::getGlobalName("5"), 0, Key_5 + ALT, this, + SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(), + "add_fingering_5"); + + new KAction(AddFingeringMarkCommand::getGlobalName("+"), 0, Key_9 + ALT, this, + SLOT(slotMarksAddFingeringMarkFromAction()), actionCollection(), + "add_fingering_plus"); + + new KAction(AddFingeringMarkCommand::getGlobalName(), 0, 0, this, + SLOT(slotMarksAddFingeringMark()), actionCollection(), + "add_fingering_mark"); + + new KAction(RemoveMarksCommand::getGlobalName(), 0, this, + SLOT(slotMarksRemoveMarks()), actionCollection(), + "remove_marks"); + + new KAction(RemoveFingeringMarksCommand::getGlobalName(), 0, this, + SLOT(slotMarksRemoveFingeringMarks()), actionCollection(), + "remove_fingering_marks"); + + new KAction(i18n("Ma&ke Ornament..."), 0, this, + SLOT(slotMakeOrnament()), actionCollection(), + "make_ornament"); + + new KAction(i18n("Trigger &Ornament..."), 0, this, + SLOT(slotUseOrnament()), actionCollection(), + "use_ornament"); + + new KAction(i18n("Remove Ornament..."), 0, this, + SLOT(slotRemoveOrnament()), actionCollection(), + "remove_ornament"); + + static QString slashTitles[] = { + i18n("&None"), "&1", "&2", "&3", "&4", "&5" + }; + for (int i = 0; i <= 5; ++i) { + new KAction(slashTitles[i], 0, this, + SLOT(slotAddSlashes()), actionCollection(), + QString("slashes_%1").arg(i)); + } + + new KAction(ClefInsertionCommand::getGlobalName(), 0, this, + SLOT(slotEditAddClef()), actionCollection(), + "add_clef"); + + new KAction(KeyInsertionCommand::getGlobalName(), 0, this, + SLOT(slotEditAddKeySignature()), actionCollection(), + "add_key_signature"); + + new KAction(SustainInsertionCommand::getGlobalName(true), 0, this, + SLOT(slotEditAddSustainDown()), actionCollection(), + "add_sustain_down"); + + new KAction(SustainInsertionCommand::getGlobalName(false), 0, this, + SLOT(slotEditAddSustainUp()), actionCollection(), + "add_sustain_up"); + + new KAction(TransposeCommand::getDiatonicGlobalName(false), 0, this, + SLOT(slotEditTranspose()), actionCollection(), + "transpose_segment"); + + new KAction(i18n("Convert Notation For..."), 0, this, + SLOT(slotEditSwitchPreset()), actionCollection(), + "switch_preset"); + + + // setup Settings menu + static QString actionsToolbars[][4] = + { + { i18n("Show T&ools Toolbar"), "1slotToggleToolsToolBar()", "show_tools_toolbar", "palette-tools" }, + { i18n("Show &Notes Toolbar"), "1slotToggleNotesToolBar()", "show_notes_toolbar", "palette-notes" }, + { i18n("Show &Rests Toolbar"), "1slotToggleRestsToolBar()", "show_rests_toolbar", "palette-rests" }, + { i18n("Show &Accidentals Toolbar"), "1slotToggleAccidentalsToolBar()", "show_accidentals_toolbar", "palette-accidentals" }, + { i18n("Show Cle&fs Toolbar"), "1slotToggleClefsToolBar()", "show_clefs_toolbar", + "palette-clefs" }, + { i18n("Show &Marks Toolbar"), "1slotToggleMarksToolBar()", "show_marks_toolbar", + "palette-marks" }, + { i18n("Show &Group Toolbar"), "1slotToggleGroupToolBar()", "show_group_toolbar", + "palette-group" }, + { i18n("Show &Layout Toolbar"), "1slotToggleLayoutToolBar()", "show_layout_toolbar", + "palette-font" }, + { i18n("Show Trans&port Toolbar"), "1slotToggleTransportToolBar()", "show_transport_toolbar", + "palette-transport" }, + { i18n("Show M&eta Toolbar"), "1slotToggleMetaToolBar()", "show_meta_toolbar", + "palette-meta" } + }; + + for (unsigned int i = 0; + i < sizeof(actionsToolbars) / sizeof(actionsToolbars[0]); ++i) { + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap(actionsToolbars[i][3]))); + + new KToggleAction(actionsToolbars[i][0], icon, 0, + this, actionsToolbars[i][1], + actionCollection(), actionsToolbars[i][2]); + } + + new KAction(i18n("Cursor &Back"), 0, Key_Left, this, + SLOT(slotStepBackward()), actionCollection(), + "cursor_back"); + + new KAction(i18n("Cursor &Forward"), 0, Key_Right, this, + SLOT(slotStepForward()), actionCollection(), + "cursor_forward"); + + new KAction(i18n("Cursor Ba&ck Bar"), 0, Key_Left + CTRL, this, + SLOT(slotJumpBackward()), actionCollection(), + "cursor_back_bar"); + + new KAction(i18n("Cursor For&ward Bar"), 0, Key_Right + CTRL, this, + SLOT(slotJumpForward()), actionCollection(), + "cursor_forward_bar"); + + new KAction(i18n("Cursor Back and Se&lect"), SHIFT + Key_Left, this, + SLOT(slotExtendSelectionBackward()), actionCollection(), + "extend_selection_backward"); + + new KAction(i18n("Cursor Forward and &Select"), SHIFT + Key_Right, this, + SLOT(slotExtendSelectionForward()), actionCollection(), + "extend_selection_forward"); + + new KAction(i18n("Cursor Back Bar and Select"), SHIFT + CTRL + Key_Left, this, + SLOT(slotExtendSelectionBackwardBar()), actionCollection(), + "extend_selection_backward_bar"); + + new KAction(i18n("Cursor Forward Bar and Select"), SHIFT + CTRL + Key_Right, this, + SLOT(slotExtendSelectionForwardBar()), actionCollection(), + "extend_selection_forward_bar"); + + /*!!! not here yet + new KAction(i18n("Move Selection Left"), Key_Minus, this, + SLOT(slotMoveSelectionLeft()), actionCollection(), + "move_selection_left"); + */ + + new KAction(i18n("Cursor to St&art"), 0, + /* #1025717: conflicting meanings for ctrl+a - dupe with Select All + Key_A + CTRL, */ this, + SLOT(slotJumpToStart()), actionCollection(), + "cursor_start"); + + new KAction(i18n("Cursor to &End"), 0, Key_E + CTRL, this, + SLOT(slotJumpToEnd()), actionCollection(), + "cursor_end"); + + new KAction(i18n("Cursor &Up Staff"), 0, Key_Up + SHIFT, this, + SLOT(slotCurrentStaffUp()), actionCollection(), + "cursor_up_staff"); + + new KAction(i18n("Cursor &Down Staff"), 0, Key_Down + SHIFT, this, + SLOT(slotCurrentStaffDown()), actionCollection(), + "cursor_down_staff"); + + new KAction(i18n("Cursor Pre&vious Segment"), 0, Key_Prior + ALT, this, + SLOT(slotCurrentSegmentPrior()), actionCollection(), + "cursor_prior_segment"); + + new KAction(i18n("Cursor Ne&xt Segment"), 0, Key_Next + ALT, this, + SLOT(slotCurrentSegmentNext()), actionCollection(), + "cursor_next_segment"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transport-cursor-to-pointer"))); + new KAction(i18n("Cursor to &Playback Pointer"), icon, 0, this, + SLOT(slotJumpCursorToPlayback()), actionCollection(), + "cursor_to_playback_pointer"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transport-play"))); + KAction *play = new KAction(i18n("&Play"), icon, Key_Enter, this, + SIGNAL(play()), actionCollection(), "play"); + // Alternative shortcut for Play + KShortcut playShortcut = play->shortcut(); + playShortcut.append( KKey(Key_Return + CTRL) ); + play->setShortcut(playShortcut); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transport-stop"))); + new KAction(i18n("&Stop"), icon, Key_Insert, this, + SIGNAL(stop()), actionCollection(), "stop"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transport-rewind"))); + new KAction(i18n("Re&wind"), icon, Key_End, this, + SIGNAL(rewindPlayback()), actionCollection(), + "playback_pointer_back_bar"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transport-ffwd"))); + new KAction(i18n("&Fast Forward"), icon, Key_PageDown, this, + SIGNAL(fastForwardPlayback()), actionCollection(), + "playback_pointer_forward_bar"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transport-rewind-end"))); + new KAction(i18n("Rewind to &Beginning"), icon, 0, this, + SIGNAL(rewindPlaybackToBeginning()), actionCollection(), + "playback_pointer_start"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transport-ffwd-end"))); + new KAction(i18n("Fast Forward to &End"), icon, 0, this, + SIGNAL(fastForwardPlaybackToEnd()), actionCollection(), + "playback_pointer_end"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transport-pointer-to-cursor"))); + new KAction(i18n("Playback Pointer to &Cursor"), icon, 0, this, + SLOT(slotJumpPlaybackToCursor()), actionCollection(), + "playback_pointer_to_cursor"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transport-solo"))); + new KToggleAction(i18n("&Solo"), icon, 0, this, + SLOT(slotToggleSolo()), actionCollection(), + "toggle_solo"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transport-tracking"))); + (new KToggleAction(i18n("Scro&ll to Follow Playback"), icon, Key_Pause, this, + SLOT(slotToggleTracking()), actionCollection(), + "toggle_tracking"))->setChecked(m_playTracking); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap + ("transport-panic"))); + new KAction(i18n("Panic"), icon, Key_P + CTRL + ALT, this, + SIGNAL(panic()), actionCollection(), "panic"); + + new KAction(i18n("Set Loop to Selection"), Key_Semicolon + CTRL, this, + SLOT(slotPreviewSelection()), actionCollection(), + "preview_selection"); + + new KAction(i18n("Clear L&oop"), Key_Colon + CTRL, this, + SLOT(slotClearLoop()), actionCollection(), + "clear_loop"); + + new KAction(i18n("Clear Selection"), Key_Escape, this, + SLOT(slotClearSelection()), actionCollection(), + "clear_selection"); + + // QString pixmapDir = + // KGlobal::dirs()->findResource("appdata", "pixmaps/"); + // icon = QIconSet(QCanvasPixmap(pixmapDir + "/toolbar/eventfilter.xpm")); + new KAction(i18n("&Filter Selection"), "filter", Key_F + CTRL, this, + SLOT(slotFilterSelection()), actionCollection(), + "filter_selection"); + + new KAction(i18n("Push &Left"), 0, this, + SLOT(slotFinePositionLeft()), actionCollection(), + "fine_position_left"); + + new KAction(i18n("Push &Right"), 0, this, + SLOT(slotFinePositionRight()), actionCollection(), + "fine_position_right"); + + new KAction(i18n("Push &Up"), 0, this, + SLOT(slotFinePositionUp()), actionCollection(), + "fine_position_up"); + + new KAction(i18n("Push &Down"), 0, this, + SLOT(slotFinePositionDown()), actionCollection(), + "fine_position_down"); + + new KAction(i18n("&Restore Positions"), 0, this, + SLOT(slotFinePositionRestore()), actionCollection(), + "fine_position_restore"); + + new KAction(i18n("Make &Invisible"), 0, this, + SLOT(slotMakeInvisible()), actionCollection(), + "make_invisible"); + + new KAction(i18n("Make &Visible"), 0, this, + SLOT(slotMakeVisible()), actionCollection(), + "make_visible"); + + new KAction(i18n("Toggle Dot"), Key_Period, this, + SLOT(slotToggleDot()), actionCollection(), + "toggle_dot"); + + new KAction(i18n("Add Dot"), Key_Period + CTRL, this, + SLOT(slotAddDot()), actionCollection(), + "add_dot"); + + new KAction(i18n("Add Dot"), Key_Period + CTRL + ALT, this, + SLOT(slotAddDotNotationOnly()), actionCollection(), + "add_notation_dot"); + + createGUI(getRCFileName(), false); +} + +bool +NotationView::isInChordMode() +{ + return ((KToggleAction *)actionCollection()->action("chord_mode"))-> + isChecked(); +} + +bool +NotationView::isInTripletMode() +{ + return ((KToggleAction *)actionCollection()->action("triplet_mode"))-> + isChecked(); +} + +bool +NotationView::isInGraceMode() +{ + return ((KToggleAction *)actionCollection()->action("grace_mode"))-> + isChecked(); +} + +void +NotationView::setupFontSizeMenu(std::string oldFontName) +{ + if (oldFontName != "") { + + std::vector<int> sizes = NoteFontFactory::getScreenSizes(oldFontName); + + for (unsigned int i = 0; i < sizes.size(); ++i) { + KAction *action = + actionCollection()->action + (QString("note_font_size_%1").arg(sizes[i])); + m_fontSizeActionMenu->remove + (action); + + // Don't delete -- that could cause a crash when this + // function is called from the action itself. Instead + // we reuse and reinsert existing actions below. + } + } + + std::vector<int> sizes = NoteFontFactory::getScreenSizes(m_fontName); + + for (unsigned int i = 0; i < sizes.size(); ++i) { + + QString actionName = QString("note_font_size_%1").arg(sizes[i]); + + KToggleAction *sizeAction = dynamic_cast<KToggleAction *> + (actionCollection()->action(actionName)); + + if (!sizeAction) { + sizeAction = + new KToggleAction(i18n("1 pixel", "%n pixels", sizes[i]), + 0, this, + SLOT(slotChangeFontSizeFromAction()), + actionCollection(), actionName); + } + + sizeAction->setChecked(sizes[i] == m_fontSize); + m_fontSizeActionMenu->insert(sizeAction); + } +} + +LinedStaff * +NotationView::getLinedStaff(int i) +{ + return getNotationStaff(i); +} + +LinedStaff * +NotationView::getLinedStaff(const Segment &segment) +{ + return getNotationStaff(segment); +} + +NotationStaff * +NotationView::getNotationStaff(const Segment &segment) +{ + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + if (&(m_staffs[i]->getSegment()) == &segment) + return m_staffs[i]; + } + return 0; +} + +bool NotationView::isCurrentStaff(int i) +{ + return getCurrentSegment() == &(m_staffs[i]->getSegment()); +} + +void NotationView::initLayoutToolbar() +{ + KToolBar *layoutToolbar = toolBar("Layout Toolbar"); + + if (!layoutToolbar) { + std::cerr + << "NotationView::initLayoutToolbar() : layout toolbar not found" + << std::endl; + return ; + } + + new QLabel(i18n(" Font: "), layoutToolbar, "font label"); + + // + // font combo + // + m_fontCombo = new KComboBox(layoutToolbar); + m_fontCombo->setEditable(false); + + std::set + <std::string> fs(NoteFontFactory::getFontNames()); + std::vector<std::string> f(fs.begin(), fs.end()); + std::sort(f.begin(), f.end()); + + bool foundFont = false; + + for (std::vector<std::string>::iterator i = f.begin(); i != f.end(); ++i) { + + QString fontQName(strtoqstr(*i)); + + m_fontCombo->insertItem(fontQName); + if (fontQName.lower() == strtoqstr(m_fontName).lower()) { + m_fontCombo->setCurrentItem(m_fontCombo->count() - 1); + foundFont = true; + } + } + + if (!foundFont) { + KMessageBox::sorry + (this, i18n("Unknown font \"%1\", using default").arg + (strtoqstr(m_fontName))); + m_fontName = NoteFontFactory::getDefaultFontName(); + } + + connect(m_fontCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotChangeFont(const QString &))); + + new QLabel(i18n(" Size: "), layoutToolbar, "size label"); + + QString value; + + // + // font size combo + // + std::vector<int> sizes = NoteFontFactory::getScreenSizes(m_fontName); + m_fontSizeCombo = new KComboBox(layoutToolbar, "font size combo"); + + for (std::vector<int>::iterator i = sizes.begin(); i != sizes.end(); ++i) { + + value.setNum(*i); + m_fontSizeCombo->insertItem(value); + } + // set combo's current value to default + value.setNum(m_fontSize); + m_fontSizeCombo->setCurrentText(value); + + connect(m_fontSizeCombo, SIGNAL(activated(const QString&)), + this, SLOT(slotChangeFontSizeFromStringValue(const QString&))); + + new QLabel(i18n(" Spacing: "), layoutToolbar, "spacing label"); + + // + // spacing combo + // + int defaultSpacing = m_hlayout->getSpacing(); + std::vector<int> spacings = NotationHLayout::getAvailableSpacings(); + + m_spacingCombo = new KComboBox(layoutToolbar, "spacing combo"); + for (std::vector<int>::iterator i = spacings.begin(); i != spacings.end(); ++i) { + + value.setNum(*i); + value += "%"; + m_spacingCombo->insertItem(value); + } + // set combo's current value to default + value.setNum(defaultSpacing); + value += "%"; + m_spacingCombo->setCurrentText(value); + + connect(m_spacingCombo, SIGNAL(activated(const QString&)), + this, SLOT(slotChangeSpacingFromStringValue(const QString&))); +} + +void NotationView::initStatusBar() +{ + KStatusBar* sb = statusBar(); + + m_hoveredOverNoteName = new QLabel(sb); + m_hoveredOverNoteName->setMinimumWidth(32); + + m_hoveredOverAbsoluteTime = new QLabel(sb); + m_hoveredOverAbsoluteTime->setMinimumWidth(160); + + sb->addWidget(m_hoveredOverAbsoluteTime); + sb->addWidget(m_hoveredOverNoteName); + + QHBox *hbox = new QHBox(sb); + m_currentNotePixmap = new QLabel(hbox); + m_currentNotePixmap->setMinimumWidth(20); + m_insertModeLabel = new QLabel(hbox); + m_annotationsLabel = new QLabel(hbox); + m_lilyPondDirectivesLabel = new QLabel(hbox); + sb->addWidget(hbox); + + sb->insertItem(KTmpStatusMsg::getDefaultMsg(), + KTmpStatusMsg::getDefaultId(), 1); + sb->setItemAlignment(KTmpStatusMsg::getDefaultId(), + AlignLeft | AlignVCenter); + + m_selectionCounter = new QLabel(sb); + sb->addWidget(m_selectionCounter); + + m_progressBar = new ProgressBar(100, true, sb); + m_progressBar->setMinimumWidth(100); + sb->addWidget(m_progressBar); +} + +QSize NotationView::getViewSize() +{ + return canvas()->size(); +} + +void NotationView::setViewSize(QSize s) +{ + canvas()->resize(s.width(), s.height()); + + if ( (m_pageMode == LinedStaff::LinearMode) + && (m_showHeadersGroup != HeadersGroup::ShowNever)) { + m_headersGroup->completeToHeight(s.height()); + } +} + +void +NotationView::setPageMode(LinedStaff::PageMode pageMode) +{ + m_pageMode = pageMode; + + if (pageMode != LinedStaff::LinearMode) { + if (m_topStandardRuler) + m_topStandardRuler->hide(); + if (m_bottomStandardRuler) + m_bottomStandardRuler->hide(); + if (m_chordNameRuler) + m_chordNameRuler->hide(); + if (m_rawNoteRuler) + m_rawNoteRuler->hide(); + if (m_tempoRuler) + m_tempoRuler->hide(); + hideHeadersGroup(); + } else { + if (m_topStandardRuler) + m_topStandardRuler->show(); + if (m_bottomStandardRuler) + m_bottomStandardRuler->show(); + if (m_chordNameRuler && getToggleAction("show_chords_ruler")->isChecked()) + m_chordNameRuler->show(); + if (m_rawNoteRuler && getToggleAction("show_raw_note_ruler")->isChecked()) + m_rawNoteRuler->show(); + if (m_tempoRuler && getToggleAction("show_tempo_ruler")->isChecked()) + m_tempoRuler->show(); + showHeadersGroup(); + } + + stateChanged("linear_mode", + (pageMode == LinedStaff::LinearMode ? KXMLGUIClient::StateNoReverse : + KXMLGUIClient::StateReverse)); + + int pageWidth = getPageWidth(); + int topMargin = 0, leftMargin = 0; + getPageMargins(leftMargin, topMargin); + + m_hlayout->setPageMode(pageMode != LinedStaff::LinearMode); + m_hlayout->setPageWidth(pageWidth - leftMargin * 2); + + NOTATION_DEBUG << "NotationView::setPageMode: set layout's page width to " + << (pageWidth - leftMargin * 2) << endl; + + positionStaffs(); + + bool layoutApplied = applyLayout(); + if (!layoutApplied) + KMessageBox::sorry(0, "Couldn't apply layout"); + else { + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + m_staffs[i]->markChanged(); + } + } + + if (!m_printMode) { + // Layout is done : Time related to left of canvas should now + // correctly be determined and track headers contents be drawn. + m_headersGroup->slotUpdateAllHeaders(0, 0, true); + } + + positionPages(); + + if (!m_printMode) { + updateView(); + slotSetInsertCursorPosition(getInsertionTime(), false, false); + slotSetPointerPosition(getDocument()->getComposition().getPosition(), false); + } + + Profiles::getInstance()->dump(); +} + +int +NotationView::getPageWidth() +{ + if (m_pageMode != LinedStaff::MultiPageMode) { + + if (isInPrintMode() && getCanvasView() && getCanvasView()->canvas()) + return getCanvasView()->canvas()->width(); + + if (getCanvasView()) { + return + getCanvasView()->width() - + getCanvasView()->verticalScrollBar()->width() - + m_leftGutter - 10; + } + + return width() - 50; + + } else { + + //!!! For the moment we use A4 for this calculation + + double printSizeMm = 25.4 * ((double)m_printSize / 72.0); + double mmPerPixel = printSizeMm / (double)m_notePixmapFactory->getSize(); + return (int)(210.0 / mmPerPixel); + } +} + +int +NotationView::getPageHeight() +{ + if (m_pageMode != LinedStaff::MultiPageMode) { + + if (isInPrintMode() && getCanvasView() && getCanvasView()->canvas()) + return getCanvasView()->canvas()->height(); + + if (getCanvasView()) { + return getCanvasView()->height(); + } + + return (height() > 200 ? height() - 100 : height()); + + } else { + + //!!! For the moment we use A4 for this calculation + + double printSizeMm = 25.4 * ((double)m_printSize / 72.0); + double mmPerPixel = printSizeMm / (double)m_notePixmapFactory->getSize(); + return (int)(297.0 / mmPerPixel); + } +} + +void +NotationView::getPageMargins(int &left, int &top) +{ + if (m_pageMode != LinedStaff::MultiPageMode) { + + left = 0; + top = 0; + + } else { + + //!!! For the moment we use A4 for this calculation + + double printSizeMm = 25.4 * ((double)m_printSize / 72.0); + double mmPerPixel = printSizeMm / (double)m_notePixmapFactory->getSize(); + left = (int)(20.0 / mmPerPixel); + top = (int)(15.0 / mmPerPixel); + } +} + +void +NotationView::scrollToTime(timeT t) +{ + + double notationViewLayoutCoord = m_hlayout->getXForTime(t); + + // Doesn't appear to matter which staff we use + //!!! actually it probably does matter, if they don't have the same extents + double notationViewCanvasCoord = + getLinedStaff(0)->getCanvasCoordsForLayoutCoords + (notationViewLayoutCoord, 0).first; + + // HK: I could have sworn I saw a hard-coded scroll happen somewhere + // (i.e. a default extra scroll to make up for the staff not beginning on + // the left edge) but now I can't find it. + getCanvasView()->slotScrollHorizSmallSteps + (int(notationViewCanvasCoord)); // + DEFAULT_STAFF_OFFSET); +} + +RulerScale* +NotationView::getHLayout() +{ + return m_hlayout; +} + +void +NotationView::paintEvent(QPaintEvent *e) +{ + m_inPaintEvent = true; + + // This is duplicated here from EditViewBase, because (a) we need + // to know about the segment being removed before we try to check + // the staff names etc., and (b) it's not safe to call close() + // from EditViewBase::paintEvent if we're then going to try to do + // some more work afterwards in this function + + if (isCompositionModified()) { + + // Check if one of the segments we display has been removed + // from the composition. + // + // For the moment we'll have to close the view if any of the + // segments we handle has been deleted. + + for (unsigned int i = 0; i < m_segments.size(); ++i) { + + if (!m_segments[i]->getComposition()) { + // oops, I think we've been deleted + close(); + return ; + } + } + } + + int topMargin = 0, leftMargin = 0; + getPageMargins(leftMargin, topMargin); + + if (m_pageMode == LinedStaff::ContinuousPageMode) { + // relayout if the window width changes significantly in continuous page mode + int diff = int(getPageWidth() - leftMargin * 2 - m_hlayout->getPageWidth()); + if (diff < -10 || diff > 10) { + setPageMode(m_pageMode); + refreshSegment(0, 0, 0); + } + + } else if (m_pageMode == LinedStaff::LinearMode) { + // resize canvas again if the window height has changed significantly + if (getCanvasView() && getCanvasView()->canvas()) { + int diff = int(getPageHeight() - getCanvasView()->canvas()->height()); + if (diff > 10) { + readjustCanvasSize(); + } + } + } + + // check for staff name changes + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + if (!m_staffs[i]->isStaffNameUpToDate()) { + refreshSegment(0); + break; + } + } + + m_inPaintEvent = false; + + EditView::paintEvent(e); + + m_inPaintEvent = false; + + // now deal with any backlog of insertable notes that appeared + // during paint (because it's not safe to modify a segment from + // within a sub-event-loop in a processEvents call from a paint) + if (!m_pendingInsertableNotes.empty()) { + std::vector<std::pair<int, int> > notes = m_pendingInsertableNotes; + m_pendingInsertableNotes.clear(); + for (unsigned int i = 0; i < notes.size(); ++i) { + slotInsertableNoteEventReceived(notes[i].first, notes[i].second, true); + } + } + + slotSetOperationNameAndStatus(i18n(" Ready.")); +} + +bool NotationView::applyLayout(int staffNo, timeT startTime, timeT endTime) +{ + slotSetOperationNameAndStatus(i18n("Laying out score...")); + ProgressDialog::processEvents(); + + m_hlayout->setStaffCount(m_staffs.size()); + + Profiler profiler("NotationView::applyLayout"); + unsigned int i; + + for (i = 0; i < m_staffs.size(); ++i) { + + if (staffNo >= 0 && (int)i != staffNo) + continue; + + slotSetOperationNameAndStatus(i18n("Laying out staff %1...").arg(i + 1)); + ProgressDialog::processEvents(); + + m_hlayout->resetStaff(*m_staffs[i], startTime, endTime); + m_vlayout->resetStaff(*m_staffs[i], startTime, endTime); + m_hlayout->scanStaff(*m_staffs[i], startTime, endTime); + m_vlayout->scanStaff(*m_staffs[i], startTime, endTime); + } + + slotSetOperationNameAndStatus(i18n("Reconciling staffs...")); + ProgressDialog::processEvents(); + + m_hlayout->finishLayout(startTime, endTime); + m_vlayout->finishLayout(startTime, endTime); + + // find the last finishing staff for future use + + timeT lastFinishingStaffEndTime = 0; + bool haveEndTime = false; + m_lastFinishingStaff = -1; + + timeT firstStartingStaffStartTime = 0; + bool haveStartTime = false; + int firstStartingStaff = -1; + + for (i = 0; i < m_staffs.size(); ++i) { + + timeT thisStartTime = m_staffs[i]->getSegment().getStartTime(); + if (thisStartTime < firstStartingStaffStartTime || !haveStartTime) { + firstStartingStaffStartTime = thisStartTime; + haveStartTime = true; + firstStartingStaff = i; + } + + timeT thisEndTime = m_staffs[i]->getSegment().getEndTime(); + if (thisEndTime > lastFinishingStaffEndTime || !haveEndTime) { + lastFinishingStaffEndTime = thisEndTime; + haveEndTime = true; + m_lastFinishingStaff = i; + } + } + + readjustCanvasSize(); + if (m_topStandardRuler) { + m_topStandardRuler->update(); + } + if (m_bottomStandardRuler) { + m_bottomStandardRuler->update(); + } + if (m_tempoRuler && m_tempoRuler->isVisible()) { + m_tempoRuler->update(); + } + if (m_rawNoteRuler && m_rawNoteRuler->isVisible()) { + m_rawNoteRuler->update(); + } + + return true; +} + +void NotationView::setCurrentSelectedNote(const char *pixmapName, + bool rest, Note::Type n, int dots) +{ + NoteInserter* inserter = 0; + + if (rest) + inserter = dynamic_cast<NoteInserter*>(m_toolBox->getTool(RestInserter::ToolName)); + else + inserter = dynamic_cast<NoteInserter*>(m_toolBox->getTool(NoteInserter::ToolName)); + + inserter->slotSetNote(n); + inserter->slotSetDots(dots); + + setTool(inserter); + + m_currentNotePixmap->setPixmap + (NotePixmapFactory::toQPixmap + (NotePixmapFactory::makeToolbarPixmap(pixmapName, true))); + + emit changeCurrentNote(rest, n); +} + +void NotationView::setCurrentSelectedNote(const NoteActionData ¬eAction) +{ + setCurrentSelectedNote(noteAction.pixmapName, + noteAction.rest, + noteAction.noteType, + noteAction.dots); +} + +void NotationView::setCurrentSelection(EventSelection* s, bool preview, + bool redrawNow) +{ + //!!! rather too much here shared with matrixview -- could much of + // this be in editview? + + if (m_currentEventSelection == s) + return ; + NOTATION_DEBUG << "XXX " << endl; + + EventSelection *oldSelection = m_currentEventSelection; + m_currentEventSelection = s; + + // positionElements is overkill here, but we hope it's not too + // much overkill (if that's not a contradiction) + + timeT startA, endA, startB, endB; + + if (oldSelection) { + startA = oldSelection->getStartTime(); + endA = oldSelection->getEndTime(); + startB = s ? s->getStartTime() : startA; + endB = s ? s->getEndTime() : endA; + } else { + // we know they can't both be null -- first thing we tested above + startA = startB = s->getStartTime(); + endA = endB = s->getEndTime(); + } + + // refreshSegment takes start==end to mean refresh everything + if (startA == endA) + ++endA; + if (startB == endB) + ++endB; + + bool updateRequired = true; + + // play previews if appropriate -- also permits an optimisation + // for the case where the selection is unchanged (quite likely + // when sweeping) + + if (s && preview) { + + bool foundNewEvent = false; + + for (EventSelection::eventcontainer::iterator i = + s->getSegmentEvents().begin(); + i != s->getSegmentEvents().end(); ++i) { + + if (oldSelection && oldSelection->getSegment() == s->getSegment() + && oldSelection->contains(*i)) + continue; + + foundNewEvent = true; + + long pitch; + if (!(*i)->get + <Int>(BaseProperties::PITCH, + pitch)) continue; + + long velocity = -1; + (void)(*i)->get + <Int>(BaseProperties::VELOCITY, + velocity); + + if (!((*i)->has(BaseProperties::TIED_BACKWARD) && + (*i)->get + <Bool> + (BaseProperties::TIED_BACKWARD))) + playNote(s->getSegment(), pitch, velocity); + } + + if (!foundNewEvent) { + if (oldSelection && + oldSelection->getSegment() == s->getSegment() && + oldSelection->getSegmentEvents().size() == + s->getSegmentEvents().size()) + updateRequired = false; + } + } + + if (updateRequired) { + + if (!s || !oldSelection || + (endA >= startB && endB >= startA && + oldSelection->getSegment() == s->getSegment())) { + + // the regions overlap: use their union and just do one refresh + + Segment &segment(s ? s->getSegment() : + oldSelection->getSegment()); + + if (redrawNow) { + // recolour the events now + getLinedStaff(segment)->positionElements(std::min(startA, startB), + std::max(endA, endB)); + } else { + // mark refresh status and then request a repaint + segment.getRefreshStatus + (m_segmentsRefreshStatusIds + [getLinedStaff(segment)->getId()]). + push(std::min(startA, startB), std::max(endA, endB)); + } + + } else { + // do two refreshes, one for each -- here we know neither is null + + if (redrawNow) { + // recolour the events now + getLinedStaff(oldSelection->getSegment())->positionElements(startA, + endA); + + getLinedStaff(s->getSegment())->positionElements(startB, endB); + } else { + // mark refresh status and then request a repaint + + oldSelection->getSegment().getRefreshStatus + (m_segmentsRefreshStatusIds + [getLinedStaff(oldSelection->getSegment())->getId()]). + push(startA, endA); + + s->getSegment().getRefreshStatus + (m_segmentsRefreshStatusIds + [getLinedStaff(s->getSegment())->getId()]). + push(startB, endB); + } + } + + if (s) { + // make the staff containing the selection current + int staffId = getLinedStaff(s->getSegment())->getId(); + if (staffId != m_currentStaff) + slotSetCurrentStaff(staffId); + } + } + + delete oldSelection; + + statusBar()->changeItem(KTmpStatusMsg::getDefaultMsg(), + KTmpStatusMsg::getDefaultId()); + + if (s) { + int eventsSelected = s->getSegmentEvents().size(); + m_selectionCounter->setText + (i18n(" 1 event selected ", + " %n events selected ", eventsSelected)); + } else { + m_selectionCounter->setText(i18n(" No selection ")); + } + m_selectionCounter->update(); + + setMenuStates(); + + if (redrawNow) + updateView(); + else + update(); + + NOTATION_DEBUG << "XXX " << endl; +} + +void NotationView::setSingleSelectedEvent(int staffNo, Event *event, + bool preview, bool redrawNow) +{ + setSingleSelectedEvent(getStaff(staffNo)->getSegment(), event, + preview, redrawNow); +} + +void NotationView::setSingleSelectedEvent(Segment &segment, Event *event, + bool preview, bool redrawNow) +{ + EventSelection *selection = new EventSelection(segment); + selection->addEvent(event); + setCurrentSelection(selection, preview, redrawNow); +} + +bool NotationView::canPreviewAnotherNote() +{ + static time_t lastCutOff = 0; + static int sinceLastCutOff = 0; + + time_t now = time(0); + ++sinceLastCutOff; + + if ((now - lastCutOff) > 0) { + sinceLastCutOff = 0; + lastCutOff = now; + NOTATION_DEBUG << "NotationView::canPreviewAnotherNote: reset" << endl; + } else { + if (sinceLastCutOff >= 20) { + // don't permit more than 20 notes per second or so, to + // avoid gungeing up the sound drivers + NOTATION_DEBUG << "Rejecting preview (too busy)" << endl; + return false; + } + NOTATION_DEBUG << "NotationView::canPreviewAnotherNote: ok" << endl; + } + + return true; +} + +void NotationView::playNote(Segment &s, int pitch, int velocity) +{ + Composition &comp = getDocument()->getComposition(); + Studio &studio = getDocument()->getStudio(); + Track *track = comp.getTrackById(s.getTrack()); + + Instrument *ins = + studio.getInstrumentById(track->getInstrument()); + + // check for null instrument + // + if (ins == 0) + return ; + + if (!canPreviewAnotherNote()) + return ; + + if (velocity < 0) + velocity = MidiMaxValue; + + MappedEvent mE(ins->getId(), + MappedEvent::MidiNoteOneShot, + pitch + s.getTranspose(), + velocity, + RealTime::zeroTime, + RealTime(0, 250000000), + RealTime::zeroTime); + + StudioControl::sendMappedEvent(mE); +} + +void NotationView::showPreviewNote(int staffNo, double layoutX, + int pitch, int height, + const Note ¬e, bool grace, + int velocity) +{ + m_staffs[staffNo]->showPreviewNote(layoutX, height, note, grace); + playNote(m_staffs[staffNo]->getSegment(), pitch, velocity); +} + +void NotationView::clearPreviewNote() +{ + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + m_staffs[i]->clearPreviewNote(); + } +} + +void NotationView::setNotePixmapFactory(NotePixmapFactory* f) +{ + delete m_notePixmapFactory; + m_notePixmapFactory = f; + if (m_hlayout) + m_hlayout->setNotePixmapFactory(m_notePixmapFactory); + if (m_vlayout) + m_vlayout->setNotePixmapFactory(m_notePixmapFactory); +} + +Segment * +NotationView::getCurrentSegment() +{ + Staff *staff = getCurrentStaff(); + return (staff ? &staff->getSegment() : 0); +} + +bool +NotationView::hasSegment(Segment *segment) +{ + for (unsigned int i = 0; i < m_segments.size(); ++i) { + if (segment == m_segments[i]) return true; + } + return false; +} + + +LinedStaff * +NotationView::getCurrentLinedStaff() +{ + return getLinedStaff(m_currentStaff); +} + +LinedStaff * +NotationView::getStaffAbove() +{ + if (m_staffs.size() < 2) return 0; + + Composition *composition = + m_staffs[m_currentStaff]->getSegment().getComposition(); + + Track *track = composition-> + getTrackById(m_staffs[m_currentStaff]->getSegment().getTrack()); + if (!track) return 0; + + int position = track->getPosition(); + Track *newTrack = 0; + + while ((newTrack = composition->getTrackByPosition(--position))) { + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + if (m_staffs[i]->getSegment().getTrack() == newTrack->getId()) { + return m_staffs[i]; + } + } + } + + return 0; +} + +LinedStaff * +NotationView::getStaffBelow() +{ + if (m_staffs.size() < 2) return 0; + + Composition *composition = + m_staffs[m_currentStaff]->getSegment().getComposition(); + + Track *track = composition-> + getTrackById(m_staffs[m_currentStaff]->getSegment().getTrack()); + if (!track) return 0; + + int position = track->getPosition(); + Track *newTrack = 0; + + while ((newTrack = composition->getTrackByPosition(++position))) { + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + if (m_staffs[i]->getSegment().getTrack() == newTrack->getId()) { + return m_staffs[i]; + } + } + } + + return 0; +} + +timeT +NotationView::getInsertionTime() +{ + return m_insertionTime; +} + +timeT +NotationView::getInsertionTime(Clef &clef, + Rosegarden::Key &key) +{ + // This fuss is solely to recover the clef and key: we already + // set m_insertionTime to the right value when we first placed + // the insert cursor. We could get clef and key directly from + // the segment but the staff has a more efficient lookup + + LinedStaff *staff = m_staffs[m_currentStaff]; + double layoutX = staff->getLayoutXOfInsertCursor(); + if (layoutX < 0) layoutX = 0; + Event *clefEvt = 0, *keyEvt = 0; + (void)staff->getElementUnderLayoutX(layoutX, clefEvt, keyEvt); + + if (clefEvt) clef = Clef(*clefEvt); + else clef = Clef(); + + if (keyEvt) key = Rosegarden::Key(*keyEvt); + else key = Rosegarden::Key(); + + return m_insertionTime; +} + +LinedStaff* +NotationView::getStaffForCanvasCoords(int x, int y) const +{ + // (i) Do not change staff, if mouse was clicked within the current staff. + LinedStaff *s = m_staffs[m_currentStaff]; + if (s->containsCanvasCoords(x, y)) { + LinedStaff::LinedStaffCoords coords = + s->getLayoutCoordsForCanvasCoords(x, y); + + timeT t = m_hlayout->getTimeForX(coords.first); + // In order to find the correct starting and ending bar of the segment, + // make infinitesimal shifts (+1 and -1) towards its center. + timeT t0 = getDocument()->getComposition().getBarStartForTime(m_staffs[m_currentStaff]->getSegment().getStartTime()+1); + timeT t1 = getDocument()->getComposition().getBarEndForTime(m_staffs[m_currentStaff]->getSegment().getEndTime()-1); + if (t >= t0 && t < t1) { + return m_staffs[m_currentStaff]; + } + } + // (ii) Find staff under cursor, if clicked outside the current staff. + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + + LinedStaff *s = m_staffs[i]; + + if (s->containsCanvasCoords(x, y)) { + + LinedStaff::LinedStaffCoords coords = + s->getLayoutCoordsForCanvasCoords(x, y); + + timeT t = m_hlayout->getTimeForX(coords.first); + // In order to find the correct starting and ending bar of the segment, + // make infinitesimal shifts (+1 and -1) towards its center. + timeT t0 = getDocument()->getComposition().getBarStartForTime(m_staffs[i]->getSegment().getStartTime()+1); + timeT t1 = getDocument()->getComposition().getBarEndForTime(m_staffs[i]->getSegment().getEndTime()-1); + if (t >= t0 && t < t1) { + return m_staffs[i]; + } + } + } + + return 0; +} + +void NotationView::updateView() +{ + slotCheckRendered + (getCanvasView()->contentsX(), + getCanvasView()->contentsX() + getCanvasView()->visibleWidth()); + canvas()->update(); +} + +void NotationView::print(bool previewOnly) +{ + if (m_staffs.size() == 0) { + KMessageBox::error(0, "Nothing to print"); + return ; + } + + Profiler profiler("NotationView::print"); + + // We need to be in multi-page mode at this point + + int pageWidth = getPageWidth(); + int pageHeight = getPageHeight(); + int leftMargin = 0, topMargin = 0; + getPageMargins(leftMargin, topMargin); + int maxPageCount = 1; + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + int pageCount = m_staffs[i]->getPageCount(); + NOTATION_DEBUG << "NotationView::print(): staff " << i << " reports " << pageCount << " pages " << endl; + if (pageCount > maxPageCount) + maxPageCount = pageCount; + } + + KPrinter printer(true, QPrinter::HighResolution); + + printer.setPageSelection(KPrinter::ApplicationSide); + printer.setMinMax(1, maxPageCount + 1); + + if (previewOnly) + printer.setPreviewOnly(true); + else if (!printer.setup((QWidget *)parent())) + return ; + + QPaintDeviceMetrics pdm(&printer); + QPainter printpainter(&printer); + + // Ideally we should aim to retain the aspect ratio and to move the + // staffs so as to be centred after scaling. But because we haven't + // got around to the latter, let's lose the former too and just + // expand to fit. + + // Retain aspect ratio when scaling + double ratioX = (double)pdm.width() / (double)(pageWidth - leftMargin * 2), + ratioY = (double)pdm.height() / (double)(pageHeight - topMargin * 2); + double ratio = std::min(ratioX, ratioY); + printpainter.scale(ratio, ratio); + + // printpainter.scale((double)pdm.width() / (double)(pageWidth - leftMargin*2), + // (double)pdm.height() / (double)(pageHeight - topMargin*2)); + printpainter.translate( -leftMargin, -topMargin); + + QValueList<int> pages = printer.pageList(); + + for (QValueList<int>::Iterator pli = pages.begin(); + pli != pages.end(); ) { // incremented just below + + int page = *pli - 1; + ++pli; + if (page < 0 || page >= maxPageCount) + continue; + + NOTATION_DEBUG << "Printing page " << page << endl; + + QRect pageRect(m_leftGutter + leftMargin + pageWidth * page, + topMargin, + pageWidth - leftMargin, + pageHeight - topMargin); + + for (size_t i = 0; i < m_staffs.size(); ++i) { + + LinedStaff *staff = m_staffs[i]; + + LinedStaff::LinedStaffCoords cc0 = staff->getLayoutCoordsForCanvasCoords + (pageRect.x(), pageRect.y()); + + LinedStaff::LinedStaffCoords cc1 = staff->getLayoutCoordsForCanvasCoords + (pageRect.x() + pageRect.width(), pageRect.y() + pageRect.height()); + + timeT t0 = m_hlayout->getTimeForX(cc0.first); + timeT t1 = m_hlayout->getTimeForX(cc1.first); + + m_staffs[i]->setPrintPainter(&printpainter); + m_staffs[i]->checkRendered(t0, t1); + } + + // Supplying doublebuffer==true to this method appears to + // slow down printing considerably but without it we get + // all sorts of horrible artifacts (possibly related to + // mishandling of pixmap masks?) in qt-3.0. Let's permit + // it as a "hidden" option. + + m_config->setGroup(NotationViewConfigGroup); + + NOTATION_DEBUG << "NotationView::print: calling QCanvas::drawArea" << endl; + + { + Profiler profiler("NotationView::print(QCanvas::drawArea)"); + + if (m_config->readBoolEntry("forcedoublebufferprinting", false)) { + getCanvasView()->canvas()->drawArea(pageRect, &printpainter, true); + } else { +#if QT_VERSION >= 0x030100 + getCanvasView()->canvas()->drawArea(pageRect, &printpainter, false); +#else + + getCanvasView()->canvas()->drawArea(pageRect, &printpainter, true); +#endif + + } + + } + + NOTATION_DEBUG << "NotationView::print: QCanvas::drawArea done" << endl; + + for (size_t i = 0; i < m_staffs.size(); ++i) { + + LinedStaff *staff = m_staffs[i]; + + LinedStaff::LinedStaffCoords cc0 = staff->getLayoutCoordsForCanvasCoords + (pageRect.x(), pageRect.y()); + + LinedStaff::LinedStaffCoords cc1 = staff->getLayoutCoordsForCanvasCoords + (pageRect.x() + pageRect.width(), pageRect.y() + pageRect.height()); + + timeT t0 = m_hlayout->getTimeForX(cc0.first); + timeT t1 = m_hlayout->getTimeForX(cc1.first); + + m_staffs[i]->renderPrintable(t0, t1); + } + + printpainter.translate( -pageWidth, 0); + + if (pli != pages.end() && *pli - 1 < maxPageCount) + printer.newPage(); + + for (size_t i = 0; i < m_staffs.size(); ++i) { + m_staffs[i]->markChanged(); // recover any memory used for this page + PixmapArrayGC::deleteAll(); + } + } + + for (size_t i = 0; i < m_staffs.size(); ++i) { + for (Segment::iterator j = m_staffs[i]->getSegment().begin(); + j != m_staffs[i]->getSegment().end(); ++j) { + removeViewLocalProperties(*j); + } + delete m_staffs[i]; + } + m_staffs.clear(); + + printpainter.end(); + + Profiles::getInstance()->dump(); +} + +void +NotationView::updateThumbnails(bool complete) +{ + if (m_pageMode != LinedStaff::MultiPageMode) + return ; + + int pageWidth = getPageWidth(); + int pageHeight = getPageHeight(); + int leftMargin = 0, topMargin = 0; + getPageMargins(leftMargin, topMargin); + int maxPageCount = 1; + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + int pageCount = m_staffs[i]->getPageCount(); + if (pageCount > maxPageCount) + maxPageCount = pageCount; + } + + int thumbScale = 20; + QPixmap thumbnail(canvas()->width() / thumbScale, + canvas()->height() / thumbScale); + thumbnail.fill(Qt::white); + QPainter thumbPainter(&thumbnail); + + if (complete) { + + thumbPainter.scale(1.0 / double(thumbScale), 1.0 / double(thumbScale)); + thumbPainter.setPen(Qt::black); + thumbPainter.setBrush(Qt::white); + + /* + QCanvas *canvas = getCanvasView()->canvas(); + canvas->drawArea(QRect(0, 0, canvas->width(), canvas->height()), + &thumbPainter, false); + */ + // hide small texts, as we get a crash in Xft when trying to + // render them at this scale + if (m_title) + m_title->hide(); + if (m_subtitle) + m_subtitle->hide(); + if (m_composer) + m_composer->hide(); + if (m_copyright) + m_copyright->hide(); + + for (size_t page = 0; page < static_cast<size_t>(maxPageCount); ++page) { + + bool havePageNumber = ((m_pageNumbers.size() > page) && + (m_pageNumbers[page] != 0)); + if (havePageNumber) + m_pageNumbers[page]->hide(); + + QRect pageRect(m_leftGutter + leftMargin * 2 + pageWidth * page, + topMargin * 2, + pageWidth - leftMargin*3, + pageHeight - topMargin*3); + + QCanvas *canvas = getCanvasView()->canvas(); + canvas->drawArea(pageRect, &thumbPainter, false); + + if (havePageNumber) + m_pageNumbers[page]->show(); + } + + if (m_title) + m_title->show(); + if (m_subtitle) + m_subtitle->show(); + if (m_composer) + m_composer->show(); + if (m_copyright) + m_copyright->show(); + + } else { + + thumbPainter.setPen(Qt::black); + + for (int page = 0; page < maxPageCount; ++page) { + + int x = m_leftGutter + pageWidth * page + leftMargin / 4; + int y = 20; + int w = pageWidth - leftMargin / 2; + int h = pageHeight; + + QString str = QString("%1").arg(page + 1); + + thumbPainter.drawRect(x / thumbScale, y / thumbScale, + w / thumbScale, h / thumbScale); + + int tx = (x + w / 2) / thumbScale, ty = (y + h / 2) / thumbScale; + tx -= thumbPainter.fontMetrics().width(str) / 2; + thumbPainter.drawText(tx, ty, str); + } + } + + thumbPainter.end(); + if (m_pannerDialog) + m_pannerDialog->scrollbox()->setThumbnail(thumbnail); +} + +void NotationView::refreshSegment(Segment *segment, + timeT startTime, timeT endTime) +{ + NOTATION_DEBUG << "*** " << endl; + + if (m_inhibitRefresh) + return ; + NOTATION_DEBUG << "NotationView::refreshSegment(" << segment << "," << startTime << "," << endTime << ")" << endl; + Profiler foo("NotationView::refreshSegment"); + + emit usedSelection(); + + if (segment) { + LinedStaff *staff = getLinedStaff(*segment); + if (staff) + applyLayout(staff->getId(), startTime, endTime); + } else { + applyLayout( -1, startTime, endTime); + } + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + + Segment *ssegment = &m_staffs[i]->getSegment(); + bool thisStaff = (ssegment == segment || segment == 0); + m_staffs[i]->markChanged(startTime, endTime, !thisStaff); + } + + PixmapArrayGC::deleteAll(); + + statusBar()->changeItem(KTmpStatusMsg::getDefaultMsg(), + KTmpStatusMsg::getDefaultId()); + + Event::dumpStats(std::cerr); + if (m_deferredCursorMove == NoCursorMoveNeeded) { + slotSetInsertCursorPosition(getInsertionTime(), false, false); + } else { + doDeferredCursorMove(); + } + slotSetPointerPosition(getDocument()->getComposition().getPosition(), false); + + if (m_currentEventSelection && + m_currentEventSelection->getSegmentEvents().size() == 0) { + delete m_currentEventSelection; + m_currentEventSelection = 0; + //!!!??? was that the right thing to do? + } + + setMenuStates(); + slotSetOperationNameAndStatus(i18n(" Ready.")); + NOTATION_DEBUG << "*** " << endl; +} + +void NotationView::setMenuStates() +{ + // 1. set selection-related states + + // Clear states first, then enter only those ones that apply + // (so as to avoid ever clearing one after entering another, in + // case the two overlap at all) + stateChanged("have_selection", KXMLGUIClient::StateReverse); + stateChanged("have_notes_in_selection", KXMLGUIClient::StateReverse); + stateChanged("have_rests_in_selection", KXMLGUIClient::StateReverse); + + if (m_currentEventSelection) { + + NOTATION_DEBUG << "NotationView::setMenuStates: Have selection; it's " << m_currentEventSelection << " covering range from " << m_currentEventSelection->getStartTime() << " to " << m_currentEventSelection->getEndTime() << " (" << m_currentEventSelection->getSegmentEvents().size() << " events)" << endl; + + stateChanged("have_selection", KXMLGUIClient::StateNoReverse); + if (m_currentEventSelection->contains + (Note::EventType)) { + stateChanged("have_notes_in_selection", + KXMLGUIClient::StateNoReverse); + } + if (m_currentEventSelection->contains + (Note::EventRestType)) { + stateChanged("have_rests_in_selection", + KXMLGUIClient::StateNoReverse); + } + } + + // 2. set inserter-related states + + // #1372863 -- RestInserter is a subclass of NoteInserter, so we + // need to test dynamic_cast<RestInserter *> before + // dynamic_cast<NoteInserter *> (which will succeed for both) + + if (dynamic_cast<RestInserter *>(m_tool)) { + NOTATION_DEBUG << "Have rest inserter " << endl; + stateChanged("note_insert_tool_current", StateReverse); + stateChanged("rest_insert_tool_current", StateNoReverse); + } else if (dynamic_cast<NoteInserter *>(m_tool)) { + NOTATION_DEBUG << "Have note inserter " << endl; + stateChanged("note_insert_tool_current", StateNoReverse); + stateChanged("rest_insert_tool_current", StateReverse); + } else { + NOTATION_DEBUG << "Have neither inserter " << endl; + stateChanged("note_insert_tool_current", StateReverse); + stateChanged("rest_insert_tool_current", StateReverse); + } +} + +#define UPDATE_PROGRESS(n) \ + progressCount += (n); \ + if (progressTotal > 0) { \ + emit setProgress(progressCount * 100 / progressTotal); \ + ProgressDialog::processEvents(); \ + } + +void NotationView::readjustCanvasSize() +{ + Profiler profiler("NotationView::readjustCanvasSize"); + + double maxWidth = 0.0; + int maxHeight = 0; + + slotSetOperationNameAndStatus(i18n("Sizing and allocating canvas...")); + ProgressDialog::processEvents(); + + int progressTotal = m_staffs.size() + 2; + int progressCount = 0; + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + + LinedStaff &staff = *m_staffs[i]; + + staff.sizeStaff(*m_hlayout); + UPDATE_PROGRESS(1); + + if (staff.getTotalWidth() + staff.getX() > maxWidth) { + maxWidth = staff.getTotalWidth() + staff.getX() + 1; + } + + if (staff.getTotalHeight() + staff.getY() > maxHeight) { + maxHeight = staff.getTotalHeight() + staff.getY() + 1; + } + } + + int topMargin = 0, leftMargin = 0; + getPageMargins(leftMargin, topMargin); + + int pageWidth = getPageWidth(); + int pageHeight = getPageHeight(); + + NOTATION_DEBUG << "NotationView::readjustCanvasSize: maxHeight is " + << maxHeight << ", page height is " << pageHeight << endl + << " - maxWidth is " << maxWidth << ", page width is " << pageWidth << endl; + + + if (m_pageMode == LinedStaff::LinearMode) { + maxWidth = ((maxWidth / pageWidth) + 1) * pageWidth; + if (maxHeight < pageHeight) + maxHeight = pageHeight; + } else { + if (maxWidth < pageWidth) + maxWidth = pageWidth; + if (maxHeight < pageHeight + topMargin*2) + maxHeight = pageHeight + topMargin * 2; + } + + // now get the EditView to do the biz + readjustViewSize(QSize(int(maxWidth), maxHeight), true); + + UPDATE_PROGRESS(2); + + if (m_pannerDialog) { + + if (m_pageMode != LinedStaff::MultiPageMode) { + m_pannerDialog->hide(); + + } else { + + m_pannerDialog->show(); + + m_pannerDialog->setPageSize + (QSize(canvas()->width(), + canvas()->height())); + + m_pannerDialog->scrollbox()->setViewSize + (QSize(getCanvasView()->width(), + getCanvasView()->height())); + } + } + + // Give a correct vertical alignment to track headers + if ((m_pageMode == LinedStaff::LinearMode) && m_showHeadersGroup) { + m_headersGroupView->setContentsPos(0, getCanvasView()->contentsY()); + } +} + +void NotationView::slotNoteAction() +{ + const QObject* sigSender = sender(); + + NoteActionDataMap::Iterator noteAct = + m_noteActionDataMap->find(sigSender->name()); + + if (noteAct != m_noteActionDataMap->end()) { + m_lastNoteAction = sigSender->name(); + setCurrentSelectedNote(**noteAct); + setMenuStates(); + } else { + std::cerr << "NotationView::slotNoteAction() : couldn't find NoteActionData named '" + << sigSender->name() << "'\n"; + } +} + +void NotationView::slotLastNoteAction() +{ + KAction *action = actionCollection()->action(m_lastNoteAction); + if (!action) + action = actionCollection()->action("crotchet"); + + if (action) { + action->activate(); + } else { + std::cerr << "NotationView::slotNoteAction() : couldn't find action named '" + << m_lastNoteAction << "' or 'crotchet'\n"; + } +} + +void NotationView::slotAddMark() +{ + const QObject *s = sender(); + if (!m_currentEventSelection) + return ; + + MarkActionDataMap::Iterator i = m_markActionDataMap->find(s->name()); + + if (i != m_markActionDataMap->end()) { + addCommandToHistory(new AddMarkCommand + ((**i).mark, *m_currentEventSelection)); + } +} + +void NotationView::slotNoteChangeAction() +{ + const QObject* sigSender = sender(); + + NoteChangeActionDataMap::Iterator noteAct = + m_noteChangeActionDataMap->find(sigSender->name()); + + if (noteAct != m_noteChangeActionDataMap->end()) { + slotSetNoteDurations((**noteAct).noteType, (**noteAct).notationOnly); + } else { + std::cerr << "NotationView::slotNoteChangeAction() : couldn't find NoteChangeAction named '" + << sigSender->name() << "'\n"; + } +} + +void NotationView::initActionDataMaps() +{ + static bool called = false; + static int keys[] = + { Key_0, Key_3, Key_6, Key_8, Key_4, Key_2, Key_1, Key_5 }; + + if (called) + return ; + called = true; + + m_noteActionDataMap = new NoteActionDataMap; + + for (int rest = 0; rest < 2; ++rest) { + for (int dots = 0; dots < 2; ++dots) { + for (int type = Note::Longest; type >= Note::Shortest; --type) { + if (dots && (type == Note::Longest)) + continue; + + QString refName + (NotationStrings::getReferenceName(Note(type, dots), rest == 1)); + + QString shortName(refName); + shortName.replace(QRegExp("-"), "_"); + + QString titleName + (NotationStrings::getNoteName(Note(type, dots))); + + titleName = titleName.left(1).upper() + + titleName.right(titleName.length() - 1); + + if (rest) { + titleName.replace(QRegExp(i18n("note")), i18n("rest")); + } + + int keycode = keys[type - Note::Shortest]; + if (dots) // keycode += CTRL; -- used below for note change action + keycode = 0; + if (rest) // keycode += SHIFT; -- can't do shift+numbers + keycode = 0; + + m_noteActionDataMap->insert + (shortName, new NoteActionData + (titleName, shortName, refName, keycode, + rest > 0, type, dots)); + } + } + } + + m_noteChangeActionDataMap = new NoteChangeActionDataMap; + + for (int notationOnly = 0; notationOnly <= 1; ++notationOnly) { + for (int type = Note::Longest; type >= Note::Shortest; --type) { + + QString refName + (NotationStrings::getReferenceName(Note(type, 0), false)); + + QString shortName(QString("change_%1%2") + .arg(notationOnly ? "notation_" : "").arg(refName)); + shortName.replace(QRegExp("-"), "_"); + + QString titleName + (NotationStrings::getNoteName(Note(type, 0))); + + titleName = titleName.left(1).upper() + + titleName.right(titleName.length() - 1); + + int keycode = keys[type - Note::Shortest]; + keycode += CTRL; + if (notationOnly) + keycode += ALT; + + m_noteChangeActionDataMap->insert + (shortName, new NoteChangeActionData + (titleName, shortName, refName, keycode, + notationOnly ? true : false, type)); + } + } + + m_markActionDataMap = new MarkActionDataMap; + + std::vector<Mark> marks = Marks::getStandardMarks(); + for (unsigned int i = 0; i < marks.size(); ++i) { + + Mark mark = marks[i]; + QString markName(strtoqstr(mark)); + QString actionName = QString("add_%1").arg(markName); + + m_markActionDataMap->insert + (actionName, new MarkActionData + (AddMarkCommand::getGlobalName(mark), + actionName, 0, mark)); + } + +} + +void NotationView::setupProgress(KProgress* bar) +{ + if (bar) { + NOTATION_DEBUG << "NotationView::setupProgress(bar)\n"; + + connect(m_hlayout, SIGNAL(setProgress(int)), + bar, SLOT(setValue(int))); + + connect(m_hlayout, SIGNAL(incrementProgress(int)), + bar, SLOT(advance(int))); + + connect(this, SIGNAL(setProgress(int)), + bar, SLOT(setValue(int))); + + connect(this, SIGNAL(incrementProgress(int)), + bar, SLOT(advance(int))); + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + connect(m_staffs[i], SIGNAL(setProgress(int)), + bar, SLOT(setValue(int))); + + connect(m_staffs[i], SIGNAL(incrementProgress(int)), + bar, SLOT(advance(int))); + } + } + +} + +void NotationView::setupProgress(ProgressDialog* dialog) +{ + NOTATION_DEBUG << "NotationView::setupProgress(dialog)" << endl; + disconnectProgress(); + + if (dialog) { + setupProgress(dialog->progressBar()); + + connect(dialog, SIGNAL(cancelClicked()), + m_hlayout, SLOT(slotCancel())); + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + connect(m_staffs[i], SIGNAL(setOperationName(QString)), + this, SLOT(slotSetOperationNameAndStatus(QString))); + + connect(dialog, SIGNAL(cancelClicked()), + m_staffs[i], SLOT(slotCancel())); + } + + connect(this, SIGNAL(setOperationName(QString)), + dialog, SLOT(slotSetOperationName(QString))); + m_progressDisplayer = PROGRESS_DIALOG; + } + +} + +void NotationView::slotSetOperationNameAndStatus(QString name) +{ + emit setOperationName(name); + statusBar()->changeItem(QString(" %1").arg(name), + KTmpStatusMsg::getDefaultId()); +} + +void NotationView::disconnectProgress() +{ + NOTATION_DEBUG << "NotationView::disconnectProgress()" << endl; + + m_hlayout->disconnect(); + disconnect(SIGNAL(setProgress(int))); + disconnect(SIGNAL(incrementProgress(int))); + disconnect(SIGNAL(setOperationName(QString))); + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + m_staffs[i]->disconnect(); + } +} + +void NotationView::setupDefaultProgress() +{ + if (m_progressDisplayer != PROGRESS_BAR) { + NOTATION_DEBUG << "NotationView::setupDefaultProgress()" << endl; + disconnectProgress(); + setupProgress(m_progressBar); + m_progressDisplayer = PROGRESS_BAR; + } +} + +void NotationView::updateViewCaption() +{ + if (m_segments.size() == 1) { + + TrackId trackId = m_segments[0]->getTrack(); + Track *track = + m_segments[0]->getComposition()->getTrackById(trackId); + + int trackPosition = -1; + if (track) + trackPosition = track->getPosition(); + // std::cout << std::endl << std::endl << std::endl << "DEBUG TITLE BAR: " << getDocument()->getTitle() << std::endl << std::endl << std::endl; + setCaption(i18n("%1 - Segment Track #%2 - Notation") + .arg(getDocument()->getTitle()) + .arg(trackPosition + 1)); + + } else if (m_segments.size() == getDocument()->getComposition().getNbSegments()) { + + setCaption(i18n("%1 - All Segments - Notation") + .arg(getDocument()->getTitle())); + + } else { + + setCaption(i18n("%1 - Segment - Notation", "%1 - %n Segments - Notation", m_segments.size()) + .arg(getDocument()->getTitle())); + + } +} + +NotationView::NoteActionDataMap* NotationView::m_noteActionDataMap = 0; + +NotationView::NoteChangeActionDataMap* NotationView::m_noteChangeActionDataMap = 0; + +NotationView::MarkActionDataMap* NotationView::m_markActionDataMap = 0; + + +/// SLOTS + + +void +NotationView::slotUpdateInsertModeStatus() +{ + QString tripletMessage = i18n("Triplet"); + QString chordMessage = i18n("Chord"); + QString graceMessage = i18n("Grace"); + QString message; + + if (isInTripletMode()) { + message = i18n("%1 %2").arg(message).arg(tripletMessage); + } + + if (isInChordMode()) { + message = i18n("%1 %2").arg(message).arg(chordMessage); + } + + if (isInGraceMode()) { + message = i18n("%1 %2").arg(message).arg(graceMessage); + } + + m_insertModeLabel->setText(message); +} + +void +NotationView::slotUpdateAnnotationsStatus() +{ + if (!areAnnotationsVisible()) { + for (int i = 0; i < getStaffCount(); ++i) { + Segment &s = getStaff(i)->getSegment(); + for (Segment::iterator j = s.begin(); j != s.end(); ++j) { + if ((*j)->isa(Text::EventType) && + ((*j)->get<String>(Text::TextTypePropertyName) + == Text::Annotation)) { + m_annotationsLabel->setText(i18n("Hidden annotations")); + return ; + } + } + } + } + m_annotationsLabel->setText(""); + getToggleAction("show_annotations")->setChecked(areAnnotationsVisible()); +} + +void +NotationView::slotUpdateLilyPondDirectivesStatus() +{ + if (!areLilyPondDirectivesVisible()) { + for (int i = 0; i < getStaffCount(); ++i) { + Segment &s = getStaff(i)->getSegment(); + for (Segment::iterator j = s.begin(); j != s.end(); ++j) { + if ((*j)->isa(Text::EventType) && + ((*j)->get + <String> + (Text::TextTypePropertyName) + == Text::LilyPondDirective)) { + m_lilyPondDirectivesLabel->setText(i18n("Hidden LilyPond directives")); + return ; + } + } + } + } + m_lilyPondDirectivesLabel->setText(""); + getToggleAction("show_lilypond_directives")->setChecked(areLilyPondDirectivesVisible()); +} + +void +NotationView::slotChangeSpacingFromStringValue(const QString& spacingT) +{ + // spacingT has a '%' at the end + // + int spacing = spacingT.left(spacingT.length() - 1).toInt(); + slotChangeSpacing(spacing); +} + +void +NotationView::slotChangeSpacingFromAction() +{ + const QObject *s = sender(); + QString name = s->name(); + + if (name.left(8) == "spacing_") { + int spacing = name.right(name.length() - 8).toInt(); + + if (spacing > 0) + slotChangeSpacing(spacing); + + } else { + KMessageBox::sorry + (this, i18n("Unknown spacing action %1").arg(name)); + } +} + +void +NotationView::slotChangeSpacing(int spacing) +{ + if (m_hlayout->getSpacing() == spacing) + return ; + + m_hlayout->setSpacing(spacing); + + // m_spacingSlider->setSize(spacing); + + KToggleAction *action = dynamic_cast<KToggleAction *> + (actionCollection()->action(QString("spacing_%1").arg(spacing))); + if (action) + action->setChecked(true); + else { + std::cerr + << "WARNING: Expected action \"spacing_" << spacing + << "\" to be a KToggleAction, but it isn't (or doesn't exist)" + << std::endl; + } + + positionStaffs(); + applyLayout(); + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + m_staffs[i]->markChanged(); + } + + positionPages(); + updateControlRulers(true); + updateView(); +} + +void +NotationView::slotChangeProportionFromIndex(int n) +{ + std::vector<int> proportions = m_hlayout->getAvailableProportions(); + if (n >= (int)proportions.size()) + n = proportions.size() - 1; + slotChangeProportion(proportions[n]); +} + +void +NotationView::slotChangeProportionFromAction() +{ + const QObject *s = sender(); + QString name = s->name(); + + if (name.left(11) == "proportion_") { + int proportion = name.right(name.length() - 11).toInt(); + slotChangeProportion(proportion); + + } else { + KMessageBox::sorry + (this, i18n("Unknown proportion action %1").arg(name)); + } +} + +void +NotationView::slotChangeProportion(int proportion) +{ + if (m_hlayout->getProportion() == proportion) + return ; + + m_hlayout->setProportion(proportion); + + // m_proportionSlider->setSize(proportion); + + KToggleAction *action = dynamic_cast<KToggleAction *> + (actionCollection()->action(QString("proportion_%1").arg(proportion))); + if (action) + action->setChecked(true); + else { + std::cerr + << "WARNING: Expected action \"proportion_" << proportion + << "\" to be a KToggleAction, but it isn't (or doesn't exist)" + << std::endl; + } + + positionStaffs(); + applyLayout(); + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + m_staffs[i]->markChanged(); + } + + positionPages(); + updateControlRulers(true); + updateView(); +} + +void +NotationView::slotChangeFontFromAction() +{ + const QObject *s = sender(); + QString name = s->name(); + if (name.left(10) == "note_font_") { + name = name.right(name.length() - 10); + slotChangeFont(name); + } else { + KMessageBox::sorry + (this, i18n("Unknown font action %1").arg(name)); + } +} + +void +NotationView::slotChangeFontSizeFromAction() +{ + const QObject *s = sender(); + QString name = s->name(); + + if (name.left(15) == "note_font_size_") { + name = name.right(name.length() - 15); + bool ok = false; + int size = name.toInt(&ok); + if (ok) + slotChangeFont(m_fontName, size); + else { + KMessageBox::sorry + (this, i18n("Unknown font size %1").arg(name)); + } + } else { + KMessageBox::sorry + (this, i18n("Unknown font size action %1").arg(name)); + } +} + +void +NotationView::slotChangeFont(const QString &newName) +{ + NOTATION_DEBUG << "changeFont: " << newName << endl; + slotChangeFont(std::string(newName.utf8())); +} + +void +NotationView::slotChangeFont(std::string newName) +{ + int newSize = m_fontSize; + + if (!NoteFontFactory::isAvailableInSize(newName, newSize)) { + + int defaultSize = NoteFontFactory::getDefaultSize(newName); + newSize = m_config->readUnsignedNumEntry + ((getStaffCount() > 1 ? + "multistaffnotesize" : "singlestaffnotesize"), defaultSize); + + if (!NoteFontFactory::isAvailableInSize(newName, newSize)) { + newSize = defaultSize; + } + } + + slotChangeFont(newName, newSize); +} + +void +NotationView::slotChangeFontSize(int newSize) +{ + slotChangeFont(m_fontName, newSize); +} + +void +NotationView::slotChangeFontSizeFromStringValue(const QString& sizeT) +{ + int size = sizeT.toInt(); + slotChangeFont(m_fontName, size); +} + +void +NotationView::slotZoomIn() +{ + std::vector<int> sizes = NoteFontFactory::getScreenSizes(m_fontName); + for (int i = 0; i + 1 < sizes.size(); ++i) { + if (sizes[i] == m_fontSize) { + slotChangeFontSize(sizes[i + 1]); + return ; + } + } +} + +void +NotationView::slotZoomOut() +{ + std::vector<int> sizes = NoteFontFactory::getScreenSizes(m_fontName); + for (int i = 1; i < sizes.size(); ++i) { + if (sizes[i] == m_fontSize) { + slotChangeFontSize(sizes[i - 1]); + return ; + } + } +} + +void +NotationView::slotChangeFont(std::string newName, int newSize) +{ + if (newName == m_fontName && newSize == m_fontSize) + return ; + + NotePixmapFactory* npf = 0; + + try { + npf = new NotePixmapFactory(newName, newSize); + } catch (...) { + return ; + } + + bool changedFont = (newName != m_fontName || newSize != m_fontSize); + + std::string oldName = m_fontName; + m_fontName = newName; + m_fontSize = newSize; + setNotePixmapFactory(npf); + + // update the various GUI elements + + std::set<std::string> fs(NoteFontFactory::getFontNames()); + std::vector<std::string> f(fs.begin(), fs.end()); + std::sort(f.begin(), f.end()); + + for (unsigned int i = 0; i < f.size(); ++i) { + bool thisOne = (f[i] == m_fontName); + if (thisOne) + m_fontCombo->setCurrentItem(i); + KToggleAction *action = dynamic_cast<KToggleAction *> + (actionCollection()->action("note_font_" + strtoqstr(f[i]))); + NOTATION_DEBUG << "inspecting " << f[i] << (action ? ", have action" : ", no action") << endl; + if (action) + action->setChecked(thisOne); + else { + std::cerr + << "WARNING: Expected action \"note_font_" << f[i] + << "\" to be a KToggleAction, but it isn't (or doesn't exist)" + << std::endl; + } + } + + NOTATION_DEBUG << "about to reinitialise sizes" << endl; + + std::vector<int> sizes = NoteFontFactory::getScreenSizes(m_fontName); + m_fontSizeCombo->clear(); + QString value; + for (std::vector<int>::iterator i = sizes.begin(); i != sizes.end(); ++i) { + value.setNum(*i); + m_fontSizeCombo->insertItem(value); + } + value.setNum(m_fontSize); + m_fontSizeCombo->setCurrentText(value); + + setupFontSizeMenu(oldName); + + if (!changedFont) + return ; // might have been called to initialise menus etc + + NOTATION_DEBUG << "about to change font" << endl; + + if (m_pageMode == LinedStaff::MultiPageMode) { + + int pageWidth = getPageWidth(); + int topMargin = 0, leftMargin = 0; + getPageMargins(leftMargin, topMargin); + + m_hlayout->setPageWidth(pageWidth - leftMargin * 2); + } + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + m_staffs[i]->changeFont(m_fontName, m_fontSize); + } + + NOTATION_DEBUG << "about to position staffs" << endl; + + positionStaffs(); + + bool layoutApplied = applyLayout(); + if (!layoutApplied) + KMessageBox::sorry(0, "Couldn't apply layout"); + else { + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + m_staffs[i]->markChanged(); + } + } + + positionPages(); + updateControlRulers(true); + updateView(); +} + +void +NotationView::slotFilePrint() +{ + KTmpStatusMsg msg(i18n("Printing..."), this); + + SetWaitCursor waitCursor; + NotationView printingView(getDocument(), m_segments, + (QWidget *)parent(), this); + + if (!printingView.isOK()) { + NOTATION_DEBUG << "Print : operation cancelled\n"; + return ; + } + + printingView.print(); +} + +void +NotationView::slotFilePrintPreview() +{ + KTmpStatusMsg msg(i18n("Previewing..."), this); + + SetWaitCursor waitCursor; + NotationView printingView(getDocument(), m_segments, + (QWidget *)parent(), this); + + if (!printingView.isOK()) { + NOTATION_DEBUG << "Print preview : operation cancelled\n"; + return ; + } + + printingView.print(true); +} + +std::map<KProcess *, KTempFile *> NotationView::m_lilyTempFileMap; + +void NotationView::slotPrintLilyPond() +{ + KTmpStatusMsg msg(i18n("Printing LilyPond file..."), this); + KTempFile *file = new KTempFile(QString::null, ".ly"); + file->setAutoDelete(true); + if (!file->name()) { + // CurrentProgressDialog::freeze(); + KMessageBox::sorry(this, i18n("Failed to open a temporary file for LilyPond export.")); + delete file; + } + if (!exportLilyPondFile(file->name(), true)) { + return ; + } + KProcess *proc = new KProcess; + *proc << "rosegarden-lilypondview"; + *proc << "--graphical"; + *proc << "--print"; + *proc << file->name(); + connect(proc, SIGNAL(processExited(KProcess *)), + this, SLOT(slotLilyPondViewProcessExited(KProcess *))); + m_lilyTempFileMap[proc] = file; + proc->start(KProcess::NotifyOnExit); +} + +void NotationView::slotPreviewLilyPond() +{ + KTmpStatusMsg msg(i18n("Previewing LilyPond file..."), this); + KTempFile *file = new KTempFile(QString::null, ".ly"); + file->setAutoDelete(true); + if (!file->name()) { + // CurrentProgressDialog::freeze(); + KMessageBox::sorry(this, i18n("Failed to open a temporary file for LilyPond export.")); + delete file; + } + if (!exportLilyPondFile(file->name(), true)) { + return ; + } + KProcess *proc = new KProcess; + *proc << "rosegarden-lilypondview"; + *proc << "--graphical"; + *proc << "--pdf"; + *proc << file->name(); + connect(proc, SIGNAL(processExited(KProcess *)), + this, SLOT(slotLilyPondViewProcessExited(KProcess *))); + m_lilyTempFileMap[proc] = file; + proc->start(KProcess::NotifyOnExit); +} + +void NotationView::slotLilyPondViewProcessExited(KProcess *p) +{ + delete m_lilyTempFileMap[p]; + m_lilyTempFileMap.erase(p); + delete p; +} + +bool NotationView::exportLilyPondFile(QString file, bool forPreview) +{ + QString caption = "", heading = ""; + if (forPreview) { + caption = i18n("LilyPond Preview Options"); + heading = i18n("LilyPond preview options"); + } + + LilyPondOptionsDialog dialog(this, m_doc, caption, heading); + if (dialog.exec() != QDialog::Accepted) { + return false; + } + + ProgressDialog progressDlg(i18n("Exporting LilyPond file..."), + 100, + this); + + LilyPondExporter e(this, m_doc, std::string(QFile::encodeName(file))); + + connect(&e, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + + connect(&e, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + if (!e.write()) { + // CurrentProgressDialog::freeze(); + KMessageBox::sorry(this, i18n("Export failed. The file could not be opened for writing.")); + return false; + } + + return true; +} + +void NotationView::slotEditCut() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Cutting selection to clipboard..."), this); + + addCommandToHistory(new CutCommand(*m_currentEventSelection, + getDocument()->getClipboard())); +} + +void NotationView::slotEditDelete() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Deleting selection..."), this); + + addCommandToHistory(new EraseCommand(*m_currentEventSelection)); +} + +void NotationView::slotEditCopy() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Copying selection to clipboard..."), this); + + addCommandToHistory(new CopyCommand(*m_currentEventSelection, + getDocument()->getClipboard())); +} + +void NotationView::slotEditCutAndClose() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Cutting selection to clipboard..."), this); + + addCommandToHistory(new CutAndCloseCommand(*m_currentEventSelection, + getDocument()->getClipboard())); +} + +static const QString RESTRICTED_PASTE_FAILED_DESCRIPTION = i18n( + "The Restricted paste type requires enough empty " \ + "space (containing only rests) at the paste position " \ + "to hold all of the events to be pasted.\n" \ + "Not enough space was found.\n" \ + "If you want to paste anyway, consider using one of " \ + "the other paste types from the \"Paste...\" option " \ + "on the Edit menu. You can also change the default " \ + "paste type to something other than Restricted if " \ + "you wish." + ); + +void NotationView::slotEditPaste() +{ + Clipboard * clipboard = getDocument()->getClipboard(); + + if (clipboard->isEmpty()) { + slotStatusHelpMsg(i18n("Clipboard is empty")); + return ; + } + if (!clipboard->isSingleSegment()) { + slotStatusHelpMsg(i18n("Can't paste multiple Segments into one")); + return ; + } + + slotStatusHelpMsg(i18n("Inserting clipboard contents...")); + + LinedStaff *staff = getCurrentLinedStaff(); + Segment &segment = staff->getSegment(); + + // Paste at cursor position + // + timeT insertionTime = getInsertionTime(); + timeT endTime = insertionTime + + (clipboard->getSingleSegment()->getEndTime() - + clipboard->getSingleSegment()->getStartTime()); + + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + PasteEventsCommand::PasteType defaultType = (PasteEventsCommand::PasteType) + config->readUnsignedNumEntry("pastetype", + PasteEventsCommand::Restricted); + + PasteEventsCommand *command = new PasteEventsCommand + (segment, clipboard, insertionTime, defaultType); + + if (!command->isPossible()) { + KMessageBox::detailedError + (this, + i18n("Couldn't paste at this point."), RESTRICTED_PASTE_FAILED_DESCRIPTION); + } else { + addCommandToHistory(command); + setCurrentSelection(new EventSelection(command->getPastedEvents())); + slotSetInsertCursorPosition(endTime, true, false); + } +} + +void NotationView::slotEditGeneralPaste() +{ + Clipboard *clipboard = getDocument()->getClipboard(); + + if (clipboard->isEmpty()) { + slotStatusHelpMsg(i18n("Clipboard is empty")); + return ; + } + + slotStatusHelpMsg(i18n("Inserting clipboard contents...")); + + LinedStaff *staff = getCurrentLinedStaff(); + Segment &segment = staff->getSegment(); + + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + PasteEventsCommand::PasteType defaultType = (PasteEventsCommand::PasteType) + config->readUnsignedNumEntry("pastetype", + PasteEventsCommand::Restricted); + + PasteNotationDialog dialog(this, defaultType); + + if (dialog.exec() == QDialog::Accepted) { + + PasteEventsCommand::PasteType type = dialog.getPasteType(); + if (dialog.setAsDefault()) { + config->setGroup(NotationViewConfigGroup); + config->writeEntry("pastetype", type); + } + + timeT insertionTime = getInsertionTime(); + timeT endTime = insertionTime + + (clipboard->getSingleSegment()->getEndTime() - + clipboard->getSingleSegment()->getStartTime()); + + PasteEventsCommand *command = new PasteEventsCommand + (segment, clipboard, insertionTime, type); + + if (!command->isPossible()) { + KMessageBox::detailedError + (this, + i18n("Couldn't paste at this point."), + i18n(RESTRICTED_PASTE_FAILED_DESCRIPTION)); + } else { + addCommandToHistory(command); + setCurrentSelection(new EventSelection + (segment, insertionTime, endTime)); + slotSetInsertCursorPosition(endTime, true, false); + } + } +} + +void +NotationView::slotMoveEventsUpStaff() +{ + LinedStaff *targetStaff = getStaffAbove(); + if (!targetStaff) return; + if (!m_currentEventSelection) return; + Segment &targetSegment = targetStaff->getSegment(); + + KMacroCommand *command = new KMacroCommand(i18n("Move Events to Staff Above")); + + timeT insertionTime = m_currentEventSelection->getStartTime(); + + Clipboard *c = new Clipboard; + CopyCommand *cc = new CopyCommand(*m_currentEventSelection, c); + cc->execute(); + + command->addCommand(new EraseCommand(*m_currentEventSelection));; + + command->addCommand(new PasteEventsCommand + (targetSegment, c, + insertionTime, + PasteEventsCommand::NoteOverlay)); + + addCommandToHistory(command); + + delete c; +} + +void +NotationView::slotMoveEventsDownStaff() +{ + LinedStaff *targetStaff = getStaffBelow(); + if (!targetStaff) return; + if (!m_currentEventSelection) return; + Segment &targetSegment = targetStaff->getSegment(); + + KMacroCommand *command = new KMacroCommand(i18n("Move Events to Staff Below")); + + timeT insertionTime = m_currentEventSelection->getStartTime(); + + Clipboard *c = new Clipboard; + CopyCommand *cc = new CopyCommand(*m_currentEventSelection, c); + cc->execute(); + + command->addCommand(new EraseCommand(*m_currentEventSelection));; + + command->addCommand(new PasteEventsCommand + (targetSegment, c, + insertionTime, + PasteEventsCommand::NoteOverlay)); + + addCommandToHistory(command); + + delete c; +} + +void NotationView::slotPreviewSelection() +{ + if (!m_currentEventSelection) + return ; + + getDocument()->slotSetLoop(m_currentEventSelection->getStartTime(), + m_currentEventSelection->getEndTime()); +} + +void NotationView::slotClearLoop() +{ + getDocument()->slotSetLoop(0, 0); +} + +void NotationView::slotClearSelection() +{ + // Actually we don't clear the selection immediately: if we're + // using some tool other than the select tool, then the first + // press switches us back to the select tool. + + NotationSelector *selector = dynamic_cast<NotationSelector *>(m_tool); + + if (!selector) { + slotSelectSelected(); + } else { + setCurrentSelection(0); + } +} + +void NotationView::slotEditSelectFromStart() +{ + timeT t = getInsertionTime(); + Segment &segment = m_staffs[m_currentStaff]->getSegment(); + setCurrentSelection(new EventSelection(segment, + segment.getStartTime(), + t)); +} + +void NotationView::slotEditSelectToEnd() +{ + timeT t = getInsertionTime(); + Segment &segment = m_staffs[m_currentStaff]->getSegment(); + setCurrentSelection(new EventSelection(segment, + t, + segment.getEndMarkerTime())); +} + +void NotationView::slotEditSelectWholeStaff() +{ + Segment &segment = m_staffs[m_currentStaff]->getSegment(); + setCurrentSelection(new EventSelection(segment, + segment.getStartTime(), + segment.getEndMarkerTime())); +} + +void NotationView::slotFilterSelection() +{ + NOTATION_DEBUG << "NotationView::slotFilterSelection" << endl; + + Segment *segment = getCurrentSegment(); + EventSelection *existingSelection = m_currentEventSelection; + if (!segment || !existingSelection) + return ; + + EventFilterDialog dialog(this); + if (dialog.exec() == QDialog::Accepted) { + NOTATION_DEBUG << "slotFilterSelection- accepted" << endl; + + bool haveEvent = false; + + EventSelection *newSelection = new EventSelection(*segment); + EventSelection::eventcontainer &ec = + existingSelection->getSegmentEvents(); + for (EventSelection::eventcontainer::iterator i = + ec.begin(); i != ec.end(); ++i) { + if (dialog.keepEvent(*i)) { + haveEvent = true; + newSelection->addEvent(*i); + } + } + + if (haveEvent) + setCurrentSelection(newSelection); + else + setCurrentSelection(0); + } +} + +void NotationView::slotFinePositionLeft() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Pushing selection left..."), this); + + // half a note body width + addCommandToHistory(new IncrementDisplacementsCommand + (*m_currentEventSelection, -500, 0)); +} + +void NotationView::slotFinePositionRight() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Pushing selection right..."), this); + + // half a note body width + addCommandToHistory(new IncrementDisplacementsCommand + (*m_currentEventSelection, 500, 0)); +} + +void NotationView::slotFinePositionUp() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Pushing selection up..."), this); + + // half line height + addCommandToHistory(new IncrementDisplacementsCommand + (*m_currentEventSelection, 0, -500)); +} + +void NotationView::slotFinePositionDown() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Pushing selection down..."), this); + + // half line height + addCommandToHistory(new IncrementDisplacementsCommand + (*m_currentEventSelection, 0, 500)); +} + +void NotationView::slotFinePositionRestore() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Restoring computed positions..."), this); + + addCommandToHistory(new ResetDisplacementsCommand(*m_currentEventSelection)); +} + +void NotationView::slotMakeVisible() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Making visible..."), this); + + addCommandToHistory(new SetVisibilityCommand(*m_currentEventSelection, true)); +} + +void NotationView::slotMakeInvisible() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Making invisible..."), this); + + addCommandToHistory(new SetVisibilityCommand(*m_currentEventSelection, false)); +} + +void NotationView::slotToggleToolsToolBar() +{ + toggleNamedToolBar("Tools Toolbar"); +} + +void NotationView::slotToggleNotesToolBar() +{ + toggleNamedToolBar("Notes Toolbar"); +} + +void NotationView::slotToggleRestsToolBar() +{ + toggleNamedToolBar("Rests Toolbar"); +} + +void NotationView::slotToggleAccidentalsToolBar() +{ + toggleNamedToolBar("Accidentals Toolbar"); +} + +void NotationView::slotToggleClefsToolBar() +{ + toggleNamedToolBar("Clefs Toolbar"); +} + +void NotationView::slotToggleMetaToolBar() +{ + toggleNamedToolBar("Meta Toolbar"); +} + +void NotationView::slotToggleMarksToolBar() +{ + toggleNamedToolBar("Marks Toolbar"); +} + +void NotationView::slotToggleGroupToolBar() +{ + toggleNamedToolBar("Group Toolbar"); +} + +void NotationView::slotToggleLayoutToolBar() +{ + toggleNamedToolBar("Layout Toolbar"); +} + +void NotationView::slotToggleTransportToolBar() +{ + toggleNamedToolBar("Transport Toolbar"); +} + +void NotationView::toggleNamedToolBar(const QString& toolBarName, bool* force) +{ + KToolBar *namedToolBar = toolBar(toolBarName); + + if (!namedToolBar) { + NOTATION_DEBUG << "NotationView::toggleNamedToolBar() : toolBar " + << toolBarName << " not found" << endl; + return ; + } + + if (!force) { + + if (namedToolBar->isVisible()) + namedToolBar->hide(); + else + namedToolBar->show(); + } else { + + if (*force) + namedToolBar->show(); + else + namedToolBar->hide(); + } + + setSettingsDirty(); + +} + +void NotationView::slotGroupBeam() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Beaming group..."), this); + + addCommandToHistory(new BeamCommand + (*m_currentEventSelection)); +} + +void NotationView::slotGroupAutoBeam() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Auto-beaming selection..."), this); + + addCommandToHistory(new AutoBeamCommand + (*m_currentEventSelection)); +} + +void NotationView::slotGroupBreak() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Breaking groups..."), this); + + addCommandToHistory(new BreakCommand + (*m_currentEventSelection)); +} + +void NotationView::slotGroupSimpleTuplet() +{ + slotGroupTuplet(true); +} + +void NotationView::slotGroupGeneralTuplet() +{ + slotGroupTuplet(false); +} + +void NotationView::slotGroupTuplet(bool simple) +{ + timeT t = 0; + timeT unit = 0; + int tupled = 2; + int untupled = 3; + Segment *segment = 0; + bool hasTimingAlready = false; + + if (m_currentEventSelection) { + + t = m_currentEventSelection->getStartTime(); + + timeT duration = m_currentEventSelection->getTotalDuration(); + Note::Type unitType = + Note::getNearestNote(duration / 3, 0).getNoteType(); + unit = Note(unitType).getDuration(); + + if (!simple) { + TupletDialog dialog(this, unitType, duration); + if (dialog.exec() != QDialog::Accepted) + return ; + unit = Note(dialog.getUnitType()).getDuration(); + tupled = dialog.getTupledCount(); + untupled = dialog.getUntupledCount(); + hasTimingAlready = dialog.hasTimingAlready(); + } + + segment = &m_currentEventSelection->getSegment(); + + } else { + + t = getInsertionTime(); + + NoteInserter *currentInserter = dynamic_cast<NoteInserter *> + (m_toolBox->getTool(NoteInserter::ToolName)); + + Note::Type unitType; + + if (currentInserter) { + unitType = currentInserter->getCurrentNote().getNoteType(); + } else { + unitType = Note::Quaver; + } + + unit = Note(unitType).getDuration(); + + if (!simple) { + TupletDialog dialog(this, unitType); + if (dialog.exec() != QDialog::Accepted) + return ; + unit = Note(dialog.getUnitType()).getDuration(); + tupled = dialog.getTupledCount(); + untupled = dialog.getUntupledCount(); + hasTimingAlready = dialog.hasTimingAlready(); + } + + segment = &m_staffs[m_currentStaff]->getSegment(); + } + + addCommandToHistory(new TupletCommand + (*segment, t, unit, untupled, tupled, hasTimingAlready)); + + if (!hasTimingAlready) { + slotSetInsertCursorPosition(t + (unit * tupled), true, false); + } +} + +void NotationView::slotGroupUnTuplet() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Untupleting..."), this); + + addCommandToHistory(new UnTupletCommand + (*m_currentEventSelection)); +} + +void NotationView::slotGroupSlur() +{ + KTmpStatusMsg msg(i18n("Adding slur..."), this); + slotAddIndication(Indication::Slur, i18n("slur")); +} + +void NotationView::slotGroupPhrasingSlur() +{ + KTmpStatusMsg msg(i18n("Adding phrasing slur..."), this); + slotAddIndication(Indication::PhrasingSlur, i18n("phrasing slur")); +} + +void NotationView::slotGroupGlissando() +{ + KTmpStatusMsg msg(i18n("Adding glissando..."), this); + slotAddIndication(Indication::Glissando, i18n("glissando")); +} + +void NotationView::slotGroupCrescendo() +{ + KTmpStatusMsg msg(i18n("Adding crescendo..."), this); + slotAddIndication(Indication::Crescendo, i18n("dynamic")); +} + +void NotationView::slotGroupDecrescendo() +{ + KTmpStatusMsg msg(i18n("Adding decrescendo..."), this); + slotAddIndication(Indication::Decrescendo, i18n("dynamic")); +} + +void NotationView::slotGroupOctave2Up() +{ + KTmpStatusMsg msg(i18n("Adding octave..."), this); + slotAddIndication(Indication::QuindicesimaUp, i18n("ottava")); +} + +void NotationView::slotGroupOctaveUp() +{ + KTmpStatusMsg msg(i18n("Adding octave..."), this); + slotAddIndication(Indication::OttavaUp, i18n("ottava")); +} + +void NotationView::slotGroupOctaveDown() +{ + KTmpStatusMsg msg(i18n("Adding octave..."), this); + slotAddIndication(Indication::OttavaDown, i18n("ottava")); +} + +void NotationView::slotGroupOctave2Down() +{ + KTmpStatusMsg msg(i18n("Adding octave..."), this); + slotAddIndication(Indication::QuindicesimaDown, i18n("ottava")); +} + +void NotationView::slotAddIndication(std::string type, QString desc) +{ + if (!m_currentEventSelection) + return ; + + AddIndicationCommand *command = + new AddIndicationCommand(type, *m_currentEventSelection); + + if (command->canExecute()) { + addCommandToHistory(command); + setSingleSelectedEvent(m_currentEventSelection->getSegment(), + command->getLastInsertedEvent()); + } else { + KMessageBox::sorry(this, i18n("Can't add overlapping %1 indications").arg(desc)); // TODO PLURAL - how many 'indications' ? + delete command; + } +} + +void NotationView::slotGroupMakeChord() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Making chord..."), this); + + MakeChordCommand *command = + new MakeChordCommand(*m_currentEventSelection); + + addCommandToHistory(command); +} + +void NotationView::slotTransformsNormalizeRests() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Normalizing rests..."), this); + + addCommandToHistory(new NormalizeRestsCommand + (*m_currentEventSelection)); +} + +void NotationView::slotTransformsCollapseRests() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Collapsing rests..."), this); + + addCommandToHistory(new CollapseRestsCommand + (*m_currentEventSelection)); +} + +void NotationView::slotTransformsCollapseNotes() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Collapsing notes..."), this); + + addCommandToHistory(new CollapseNotesCommand + (*m_currentEventSelection)); +} + +void NotationView::slotTransformsTieNotes() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Tying notes..."), this); + + addCommandToHistory(new TieNotesCommand + (*m_currentEventSelection)); +} + +void NotationView::slotTransformsUntieNotes() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Untying notes..."), this); + + addCommandToHistory(new UntieNotesCommand + (*m_currentEventSelection)); +} + +void NotationView::slotTransformsMakeNotesViable() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Making notes viable..."), this); + + addCommandToHistory(new MakeNotesViableCommand + (*m_currentEventSelection)); +} + +void NotationView::slotTransformsDeCounterpoint() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Removing counterpoint..."), this); + + addCommandToHistory(new DeCounterpointCommand + (*m_currentEventSelection)); +} + +void NotationView::slotTransformsStemsUp() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Pointing stems up..."), this); + + addCommandToHistory(new ChangeStemsCommand + (true, *m_currentEventSelection)); +} + +void NotationView::slotTransformsStemsDown() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Pointing stems down..."), this); + + addCommandToHistory(new ChangeStemsCommand + (false, *m_currentEventSelection)); + +} + +void NotationView::slotTransformsRestoreStems() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Restoring computed stem directions..."), this); + + addCommandToHistory(new RestoreStemsCommand + (*m_currentEventSelection)); +} + +void NotationView::slotTransformsSlursAbove() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Positioning slurs..."), this); + + addCommandToHistory(new ChangeSlurPositionCommand + (true, *m_currentEventSelection)); +} + +void NotationView::slotTransformsSlursBelow() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Positioning slurs..."), this); + + addCommandToHistory(new ChangeSlurPositionCommand + (false, *m_currentEventSelection)); + +} + +void NotationView::slotTransformsRestoreSlurs() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Restoring slur positions..."), this); + + addCommandToHistory(new RestoreSlursCommand + (*m_currentEventSelection)); +} + +void NotationView::slotTransformsTiesAbove() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Positioning ties..."), this); + + addCommandToHistory(new ChangeTiePositionCommand + (true, *m_currentEventSelection)); +} + +void NotationView::slotTransformsTiesBelow() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Positioning ties..."), this); + + addCommandToHistory(new ChangeTiePositionCommand + (false, *m_currentEventSelection)); + +} + +void NotationView::slotTransformsRestoreTies() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Restoring tie positions..."), this); + + addCommandToHistory(new RestoreTiesCommand + (*m_currentEventSelection)); +} + +void NotationView::slotTransformsFixQuantization() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Fixing notation quantization..."), this); + + addCommandToHistory(new FixNotationQuantizeCommand + (*m_currentEventSelection)); +} + +void NotationView::slotTransformsRemoveQuantization() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Removing notation quantization..."), this); + + addCommandToHistory(new RemoveNotationQuantizeCommand + (*m_currentEventSelection)); +} + +void NotationView::slotSetStyleFromAction() +{ + const QObject *s = sender(); + QString name = s->name(); + + if (!m_currentEventSelection) + return ; + + if (name.left(6) == "style_") { + name = name.right(name.length() - 6); + + KTmpStatusMsg msg(i18n("Changing to %1 style...").arg(name), + this); + + addCommandToHistory(new ChangeStyleCommand + (NoteStyleName(qstrtostr(name)), + *m_currentEventSelection)); + } else { + KMessageBox::sorry + (this, i18n("Unknown style action %1").arg(name)); + } +} + +void NotationView::slotInsertNoteFromAction() +{ + const QObject *s = sender(); + QString name = s->name(); + + Segment &segment = m_staffs[m_currentStaff]->getSegment(); + + NoteInserter *noteInserter = dynamic_cast<NoteInserter *>(m_tool); + if (!noteInserter) { + KMessageBox::sorry(this, i18n("No note duration selected")); + return ; + } + + int pitch = 0; + Accidental accidental = + Accidentals::NoAccidental; + + timeT time(getInsertionTime()); + Rosegarden::Key key = segment.getKeyAtTime(time); + Clef clef = segment.getClefAtTime(time); + + try { + + pitch = getPitchFromNoteInsertAction(name, accidental, clef, key); + + } catch (...) { + + KMessageBox::sorry + (this, i18n("Unknown note insert action %1").arg(name)); + return ; + } + + KTmpStatusMsg msg(i18n("Inserting note"), this); + + NOTATION_DEBUG << "Inserting note at pitch " << pitch << endl; + + noteInserter->insertNote(segment, time, pitch, accidental); +} + +void NotationView::slotInsertRest() +{ + Segment &segment = m_staffs[m_currentStaff]->getSegment(); + timeT time = getInsertionTime(); + + RestInserter *restInserter = dynamic_cast<RestInserter *>(m_tool); + + if (!restInserter) { + + NoteInserter *noteInserter = dynamic_cast<NoteInserter *>(m_tool); + if (!noteInserter) { + KMessageBox::sorry(this, i18n("No note duration selected")); + return ; + } + + Note note(noteInserter->getCurrentNote()); + + restInserter = dynamic_cast<RestInserter*> + (m_toolBox->getTool(RestInserter::ToolName)); + + restInserter->slotSetNote(note.getNoteType()); + restInserter->slotSetDots(note.getDots()); + } + + restInserter->insertNote(segment, time, + 0, Accidentals::NoAccidental, true); +} + +void NotationView::slotSwitchFromRestToNote() +{ + RestInserter *restInserter = dynamic_cast<RestInserter *>(m_tool); + if (!restInserter) { + KMessageBox::sorry(this, i18n("No rest duration selected")); + return ; + } + + Note note(restInserter->getCurrentNote()); + + QString actionName = NotationStrings::getReferenceName(note, false); + actionName = actionName.replace("-", "_"); + + KRadioAction *action = dynamic_cast<KRadioAction *> + (actionCollection()->action(actionName)); + + if (!action) { + std::cerr << "WARNING: Failed to find note action \"" + << actionName << "\"" << std::endl; + } else { + action->activate(); + } + + NoteInserter *noteInserter = dynamic_cast<NoteInserter*> + (m_toolBox->getTool(NoteInserter::ToolName)); + + if (noteInserter) { + noteInserter->slotSetNote(note.getNoteType()); + noteInserter->slotSetDots(note.getDots()); + setTool(noteInserter); + } + + setMenuStates(); +} + +void NotationView::slotSwitchFromNoteToRest() +{ + NoteInserter *noteInserter = dynamic_cast<NoteInserter *>(m_tool); + if (!noteInserter) { + KMessageBox::sorry(this, i18n("No note duration selected")); + return ; + } + + Note note(noteInserter->getCurrentNote()); + + QString actionName = NotationStrings::getReferenceName(note, true); + actionName = actionName.replace("-", "_"); + + KRadioAction *action = dynamic_cast<KRadioAction *> + (actionCollection()->action(actionName)); + + if (!action) { + std::cerr << "WARNING: Failed to find rest action \"" + << actionName << "\"" << std::endl; + } else { + action->activate(); + } + + RestInserter *restInserter = dynamic_cast<RestInserter*> + (m_toolBox->getTool(RestInserter::ToolName)); + + if (restInserter) { + restInserter->slotSetNote(note.getNoteType()); + restInserter->slotSetDots(note.getDots()); + setTool(restInserter); + } + + setMenuStates(); +} + +void NotationView::slotToggleDot() +{ + NoteInserter *noteInserter = dynamic_cast<NoteInserter *>(m_tool); + if (noteInserter) { + Note note(noteInserter->getCurrentNote()); + if (note.getNoteType() == Note::Shortest || + note.getNoteType() == Note::Longest) + return ; + noteInserter->slotSetDots(note.getDots() ? 0 : 1); + setTool(noteInserter); + } else { + RestInserter *restInserter = dynamic_cast<RestInserter *>(m_tool); + if (restInserter) { + Note note(restInserter->getCurrentNote()); + if (note.getNoteType() == Note::Shortest || + note.getNoteType() == Note::Longest) + return ; + restInserter->slotSetDots(note.getDots() ? 0 : 1); + setTool(restInserter); + } else { + KMessageBox::sorry(this, i18n("No note or rest duration selected")); + } + } + + setMenuStates(); +} + +void NotationView::slotRespellDoubleFlat() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Forcing accidentals..."), this); + + addCommandToHistory(new RespellCommand(RespellCommand::Set, + Accidentals::DoubleFlat, + *m_currentEventSelection)); +} + +void NotationView::slotRespellFlat() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Forcing accidentals..."), this); + + addCommandToHistory(new RespellCommand(RespellCommand::Set, + Accidentals::Flat, + *m_currentEventSelection)); +} + +void NotationView::slotRespellNatural() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Forcing accidentals..."), this); + + addCommandToHistory(new RespellCommand(RespellCommand::Set, + Accidentals::Natural, + *m_currentEventSelection)); +} + +void NotationView::slotRespellSharp() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Forcing accidentals..."), this); + + addCommandToHistory(new RespellCommand(RespellCommand::Set, + Accidentals::Sharp, + *m_currentEventSelection)); +} + +void NotationView::slotRespellDoubleSharp() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Forcing accidentals..."), this); + + addCommandToHistory(new RespellCommand(RespellCommand::Set, + Accidentals::DoubleSharp, + *m_currentEventSelection)); +} + +void NotationView::slotRespellUp() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Forcing accidentals..."), this); + + addCommandToHistory(new RespellCommand(RespellCommand::Up, + Accidentals::NoAccidental, + *m_currentEventSelection)); +} + +void NotationView::slotRespellDown() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Forcing accidentals..."), this); + + addCommandToHistory(new RespellCommand(RespellCommand::Down, + Accidentals::NoAccidental, + *m_currentEventSelection)); +} + +void NotationView::slotRespellRestore() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Restoring accidentals..."), this); + + addCommandToHistory(new RespellCommand(RespellCommand::Restore, + Accidentals::NoAccidental, + *m_currentEventSelection)); +} + +void NotationView::slotShowCautionary() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Showing cautionary accidentals..."), this); + + addCommandToHistory(new MakeAccidentalsCautionaryCommand + (true, *m_currentEventSelection)); +} + +void NotationView::slotCancelCautionary() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Cancelling cautionary accidentals..."), this); + + addCommandToHistory(new MakeAccidentalsCautionaryCommand + (false, *m_currentEventSelection)); +} + +void NotationView::slotTransformsQuantize() +{ + if (!m_currentEventSelection) + return ; + + QuantizeDialog dialog(this, true); + + if (dialog.exec() == QDialog::Accepted) { + KTmpStatusMsg msg(i18n("Quantizing..."), this); + addCommandToHistory(new EventQuantizeCommand + (*m_currentEventSelection, + dialog.getQuantizer())); + } +} + +void NotationView::slotTransformsInterpret() +{ + if (!m_currentEventSelection) + return ; + + InterpretDialog dialog(this); + + if (dialog.exec() == QDialog::Accepted) { + KTmpStatusMsg msg(i18n("Interpreting selection..."), this); + addCommandToHistory(new InterpretCommand + (*m_currentEventSelection, + getDocument()->getComposition().getNotationQuantizer(), + dialog.getInterpretations())); + } +} + +void NotationView::slotSetNoteDurations(Note::Type type, bool notationOnly) +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Setting note durations..."), this); + addCommandToHistory(new SetNoteTypeCommand(*m_currentEventSelection, type, notationOnly)); +} + +void NotationView::slotAddDot() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Adding dot..."), this); + addCommandToHistory(new AddDotCommand(*m_currentEventSelection, false)); +} + +void NotationView::slotAddDotNotationOnly() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Adding dot..."), this); + addCommandToHistory(new AddDotCommand(*m_currentEventSelection, true)); +} + +void NotationView::slotAddSlashes() +{ + const QObject *s = sender(); + if (!m_currentEventSelection) + return ; + + QString name = s->name(); + int slashes = name.right(1).toInt(); + + addCommandToHistory(new AddSlashesCommand + (slashes, *m_currentEventSelection)); +} + +void NotationView::slotMarksAddTextMark() +{ + if (m_currentEventSelection) { + bool pressedOK = false; + + QString txt = KLineEditDlg::getText(i18n("Text: "), "", &pressedOK, this); + + if (pressedOK) { + addCommandToHistory(new AddTextMarkCommand + (qstrtostr(txt), *m_currentEventSelection)); + } + } +} + +void NotationView::slotMarksAddFingeringMark() +{ + if (m_currentEventSelection) { + bool pressedOK = false; + + QString txt = KLineEditDlg::getText(i18n("Fingering: "), "", &pressedOK, this); + + if (pressedOK) { + addCommandToHistory(new AddFingeringMarkCommand + (qstrtostr(txt), *m_currentEventSelection)); + } + } +} + +void NotationView::slotMarksAddFingeringMarkFromAction() +{ + const QObject *s = sender(); + QString name = s->name(); + + if (name.left(14) == "add_fingering_") { + + QString fingering = name.right(name.length() - 14); + + if (fingering == "plus") + fingering = "+"; + + if (m_currentEventSelection) { + addCommandToHistory(new AddFingeringMarkCommand + (qstrtostr(fingering), *m_currentEventSelection)); + } + } +} + +void NotationView::slotMarksRemoveMarks() +{ + if (m_currentEventSelection) + addCommandToHistory(new RemoveMarksCommand + (*m_currentEventSelection)); +} + +void NotationView::slotMarksRemoveFingeringMarks() +{ + if (m_currentEventSelection) + addCommandToHistory(new RemoveFingeringMarksCommand + (*m_currentEventSelection)); +} + +void +NotationView::slotMakeOrnament() +{ + if (!m_currentEventSelection) + return ; + + EventSelection::eventcontainer &ec = + m_currentEventSelection->getSegmentEvents(); + + int basePitch = -1; + int baseVelocity = -1; + NoteStyle *style = NoteStyleFactory::getStyle(NoteStyleFactory::DefaultStyle); + + for (EventSelection::eventcontainer::iterator i = + ec.begin(); i != ec.end(); ++i) { + if ((*i)->isa(Note::EventType)) { + if ((*i)->has(BaseProperties::PITCH)) { + basePitch = (*i)->get + <Int> + (BaseProperties::PITCH); + style = NoteStyleFactory::getStyleForEvent(*i); + if (baseVelocity != -1) + break; + } + if ((*i)->has(BaseProperties::VELOCITY)) { + baseVelocity = (*i)->get + <Int> + (BaseProperties::VELOCITY); + if (basePitch != -1) + break; + } + } + } + + Staff *staff = getCurrentStaff(); + Segment &segment = staff->getSegment(); + + timeT absTime = m_currentEventSelection->getStartTime(); + timeT duration = m_currentEventSelection->getTotalDuration(); + Note note(Note::getNearestNote(duration)); + + Track *track = + segment.getComposition()->getTrackById(segment.getTrack()); + QString name; + int barNo = segment.getComposition()->getBarNumber(absTime); + if (track) { + name = QString(i18n("Ornament track %1 bar %2").arg(track->getPosition() + 1).arg(barNo + 1)); + } else { + name = QString(i18n("Ornament bar %1").arg(barNo + 1)); + } + + MakeOrnamentDialog dialog(this, name, basePitch); + if (dialog.exec() != QDialog::Accepted) + return ; + + name = dialog.getName(); + basePitch = dialog.getBasePitch(); + + KMacroCommand *command = new KMacroCommand(i18n("Make Ornament")); + + command->addCommand(new CutCommand + (*m_currentEventSelection, + getDocument()->getClipboard())); + + command->addCommand(new PasteToTriggerSegmentCommand + (&getDocument()->getComposition(), + getDocument()->getClipboard(), + name, basePitch)); + + command->addCommand(new InsertTriggerNoteCommand + (segment, absTime, note, basePitch, baseVelocity, + style->getName(), + getDocument()->getComposition().getNextTriggerSegmentId(), + true, + BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH, + Marks::NoMark)); //!!! + + addCommandToHistory(command); +} + +void +NotationView::slotUseOrnament() +{ + // Take an existing note and match an ornament to it. + + if (!m_currentEventSelection) + return ; + + UseOrnamentDialog dialog(this, &getDocument()->getComposition()); + if (dialog.exec() != QDialog::Accepted) + return ; + + addCommandToHistory(new SetTriggerCommand(*m_currentEventSelection, + dialog.getId(), + true, + dialog.getRetune(), + dialog.getTimeAdjust(), + dialog.getMark(), + i18n("Use Ornament"))); +} + +void +NotationView::slotRemoveOrnament() +{ + if (!m_currentEventSelection) + return ; + + addCommandToHistory(new ClearTriggersCommand(*m_currentEventSelection, + i18n("Remove Ornaments"))); +} + +void NotationView::slotEditAddClef() +{ + Staff *staff = getCurrentStaff(); + Segment &segment = staff->getSegment(); + static Clef lastClef; + Clef clef; + Rosegarden::Key key; + timeT insertionTime = getInsertionTime(clef, key); + + ClefDialog dialog(this, m_notePixmapFactory, lastClef); + + if (dialog.exec() == QDialog::Accepted) { + + ClefDialog::ConversionType conversion = dialog.getConversionType(); + + bool shouldChangeOctave = (conversion != ClefDialog::NoConversion); + bool shouldTranspose = (conversion == ClefDialog::Transpose); + + addCommandToHistory + (new ClefInsertionCommand + (segment, insertionTime, dialog.getClef(), + shouldChangeOctave, shouldTranspose)); + + lastClef = dialog.getClef(); + } +} + +void NotationView::slotEditAddKeySignature() +{ + Staff *staff = getCurrentStaff(); + Segment &segment = staff->getSegment(); + Clef clef; + Rosegarden::Key key; + timeT insertionTime = getInsertionTime(clef, key); + + //!!! experimental: + CompositionTimeSliceAdapter adapter + (&getDocument()->getComposition(), insertionTime, + getDocument()->getComposition().getDuration()); + AnalysisHelper helper; + key = helper.guessKey(adapter); + + KeySignatureDialog dialog + (this, m_notePixmapFactory, clef, key, true, true, + i18n("Estimated key signature shown")); + + if (dialog.exec() == QDialog::Accepted && + dialog.isValid()) { + + KeySignatureDialog::ConversionType conversion = + dialog.getConversionType(); + + bool transposeKey = dialog.shouldBeTransposed(); + bool applyToAll = dialog.shouldApplyToAll(); + bool ignorePercussion = dialog.shouldIgnorePercussion(); + + if (applyToAll) { + addCommandToHistory + (new MultiKeyInsertionCommand + (getDocument(), + insertionTime, dialog.getKey(), + conversion == KeySignatureDialog::Convert, + conversion == KeySignatureDialog::Transpose, + transposeKey, + ignorePercussion)); + } else { + addCommandToHistory + (new KeyInsertionCommand + (segment, + insertionTime, dialog.getKey(), + conversion == KeySignatureDialog::Convert, + conversion == KeySignatureDialog::Transpose, + transposeKey, + false)); + } + } +} + +void NotationView::slotEditAddSustain(bool down) +{ + Staff *staff = getCurrentStaff(); + Segment &segment = staff->getSegment(); + timeT insertionTime = getInsertionTime(); + + Studio *studio = &getDocument()->getStudio(); + Track *track = segment.getComposition()->getTrackById(segment.getTrack()); + + if (track) { + + Instrument *instrument = studio->getInstrumentById + (track->getInstrument()); + if (instrument) { + MidiDevice *device = dynamic_cast<MidiDevice *> + (instrument->getDevice()); + if (device) { + for (ControlList::const_iterator i = + device->getControlParameters().begin(); + i != device->getControlParameters().end(); ++i) { + + if (i->getType() == Controller::EventType && + (i->getName() == "Sustain" || + strtoqstr(i->getName()) == i18n("Sustain"))) { + + addCommandToHistory + (new SustainInsertionCommand(segment, insertionTime, down, + i->getControllerValue())); + return ; + } + } + } else if (instrument->getDevice() && + instrument->getDevice()->getType() == Device::SoftSynth) { + addCommandToHistory + (new SustainInsertionCommand(segment, insertionTime, down, 64)); + } + } + } + + KMessageBox::sorry(this, i18n("There is no sustain controller defined for this device.\nPlease ensure the device is configured correctly in the Manage MIDI Devices dialog in the main window.")); +} + +void NotationView::slotEditAddSustainDown() +{ + slotEditAddSustain(true); +} + +void NotationView::slotEditAddSustainUp() +{ + slotEditAddSustain(false); +} + +void NotationView::slotEditTranspose() +{ + IntervalDialog intervalDialog(this, true, true); + int ok = intervalDialog.exec(); + + int semitones = intervalDialog.getChromaticDistance(); + int steps = intervalDialog.getDiatonicDistance(); + + if (!ok || (semitones == 0 && steps == 0)) return; + + // TODO combine commands into one + for (int i = 0; i < m_segments.size(); i++) + { + addCommandToHistory(new SegmentTransposeCommand(*(m_segments[i]), + intervalDialog.getChangeKey(), steps, semitones, + intervalDialog.getTransposeSegmentBack())); + } +} + +void NotationView::slotEditSwitchPreset() +{ + PresetHandlerDialog dialog(this, true); + + if (dialog.exec() != QDialog::Accepted) return; + + if (dialog.getConvertAllSegments()) { + // get all segments for this track and convert them. + Composition& comp = getDocument()->getComposition(); + TrackId selectedTrack = getCurrentSegment()->getTrack(); + + // satisfy #1885251 the way that seems most reasonble to me at the + // moment, only changing track parameters when acting on all segments on + // this track from the notation view + // + //!!! This won't be undoable, and I'm not sure if that's seriously + // wrong, or just mildly wrong, but I'm betting somebody will tell me + // about it if this was inappropriate + Track *track = comp.getTrackById(selectedTrack); + track->setPresetLabel(dialog.getName()); + track->setClef(dialog.getClef()); + track->setTranspose(dialog.getTranspose()); + track->setLowestPlayable(dialog.getLowRange()); + track->setHighestPlayable(dialog.getHighRange()); + + addCommandToHistory(new SegmentSyncCommand(comp.getSegments(), selectedTrack, + dialog.getTranspose(), + dialog.getLowRange(), + dialog.getHighRange(), + clefIndexToClef(dialog.getClef()))); + } else { + addCommandToHistory(new SegmentSyncCommand(m_segments, + dialog.getTranspose(), + dialog.getLowRange(), + dialog.getHighRange(), + clefIndexToClef(dialog.getClef()))); + } + + m_doc->slotDocumentModified(); + emit updateView(); +} + +void NotationView::slotEditElement(NotationStaff *staff, + NotationElement *element, bool advanced) +{ + if (advanced) { + + EventEditDialog dialog(this, *element->event(), true); + + if (dialog.exec() == QDialog::Accepted && + dialog.isModified()) { + + EventEditCommand *command = new EventEditCommand + (staff->getSegment(), + element->event(), + dialog.getEvent()); + + addCommandToHistory(command); + } + + } else if (element->event()->isa(Clef::EventType)) { + + try { + ClefDialog dialog(this, m_notePixmapFactory, + Clef(*element->event())); + + if (dialog.exec() == QDialog::Accepted) { + + ClefDialog::ConversionType conversion = dialog.getConversionType(); + bool shouldChangeOctave = (conversion != ClefDialog::NoConversion); + bool shouldTranspose = (conversion == ClefDialog::Transpose); + addCommandToHistory + (new ClefInsertionCommand + (staff->getSegment(), element->event()->getAbsoluteTime(), + dialog.getClef(), shouldChangeOctave, shouldTranspose)); + } + } catch (Exception e) { + std::cerr << e.getMessage() << std::endl; + } + + return ; + + } else if (element->event()->isa(Rosegarden::Key::EventType)) { + + try { + Clef clef(staff->getSegment().getClefAtTime + (element->event()->getAbsoluteTime())); + KeySignatureDialog dialog + (this, m_notePixmapFactory, clef, Rosegarden::Key(*element->event()), + false, true); + + if (dialog.exec() == QDialog::Accepted && + dialog.isValid()) { + + KeySignatureDialog::ConversionType conversion = + dialog.getConversionType(); + + addCommandToHistory + (new KeyInsertionCommand + (staff->getSegment(), + element->event()->getAbsoluteTime(), dialog.getKey(), + conversion == KeySignatureDialog::Convert, + conversion == KeySignatureDialog::Transpose, + dialog.shouldBeTransposed(), + dialog.shouldIgnorePercussion())); + } + + } catch (Exception e) { + std::cerr << e.getMessage() << std::endl; + } + + return ; + + } else if (element->event()->isa(Text::EventType)) { + + try { + TextEventDialog dialog + (this, m_notePixmapFactory, Text(*element->event())); + if (dialog.exec() == QDialog::Accepted) { + TextInsertionCommand *command = new TextInsertionCommand + (staff->getSegment(), + element->event()->getAbsoluteTime(), + dialog.getText()); + KMacroCommand *macroCommand = new KMacroCommand(command->name()); + macroCommand->addCommand(new EraseEventCommand(staff->getSegment(), + element->event(), false)); + macroCommand->addCommand(command); + addCommandToHistory(macroCommand); + } + } catch (Exception e) { + std::cerr << e.getMessage() << std::endl; + } + + return ; + + } else if (element->isNote() && + element->event()->has(BaseProperties::TRIGGER_SEGMENT_ID)) { + + int id = element->event()->get + <Int> + (BaseProperties::TRIGGER_SEGMENT_ID); + emit editTriggerSegment(id); + return ; + + } else { + + SimpleEventEditDialog dialog(this, getDocument(), *element->event(), false); + + if (dialog.exec() == QDialog::Accepted && + dialog.isModified()) { + + EventEditCommand *command = new EventEditCommand + (staff->getSegment(), + element->event(), + dialog.getEvent()); + + addCommandToHistory(command); + } + } +} + +void NotationView::slotBeginLilyPondRepeat() +{} + +void NotationView::slotDebugDump() +{ + if (m_currentEventSelection) { + EventSelection::eventcontainer &ec = + m_currentEventSelection->getSegmentEvents(); + int n = 0; + for (EventSelection::eventcontainer::iterator i = + ec.begin(); + i != ec.end(); ++i) { + std::cerr << "\n" << n++ << " [" << (*i) << "]" << std::endl; + (*i)->dump(std::cerr); + } + } +} + +void +NotationView::slotSetPointerPosition(timeT time) +{ + slotSetPointerPosition(time, m_playTracking); +} + +void +NotationView::slotSetPointerPosition(timeT time, bool scroll) +{ + Composition &comp = getDocument()->getComposition(); + int barNo = comp.getBarNumber(time); + + int minCy = 0; + double cx = 0; + bool haveMinCy = false; + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + + double layoutX = m_hlayout->getXForTimeByEvent(time); + Segment &seg = m_staffs[i]->getSegment(); + + bool good = true; + + if (barNo >= m_hlayout->getLastVisibleBarOnStaff(*m_staffs[i])) { + if (seg.isRepeating() && time < seg.getRepeatEndTime()) { + timeT mappedTime = + seg.getStartTime() + + ((time - seg.getStartTime()) % + (seg.getEndMarkerTime() - seg.getStartTime())); + layoutX = m_hlayout->getXForTimeByEvent(mappedTime); + } else { + good = false; + } + } else if (barNo < m_hlayout->getFirstVisibleBarOnStaff(*m_staffs[i])) { + good = false; + } + + if (!good) { + + m_staffs[i]->hidePointer(); + + } else { + + m_staffs[i]->setPointerPosition(layoutX); + + int cy; + m_staffs[i]->getPointerPosition(cx, cy); + + if (!haveMinCy || cy < minCy) { + minCy = cy; + haveMinCy = true; + } + } + } + + if (m_pageMode == LinedStaff::LinearMode) { + // be careful not to prevent user from scrolling up and down + haveMinCy = false; + } + + if (scroll) { + getCanvasView()->slotScrollHoriz(int(cx)); + if (haveMinCy) { + getCanvasView()->slotScrollVertToTop(minCy); + } + } + + updateView(); +} + +void +NotationView::slotUpdateRecordingSegment(Segment *segment, + timeT updateFrom) +{ + NOTATION_DEBUG << "NotationView::slotUpdateRecordingSegment: segment " << segment << ", updateFrom " << updateFrom << ", end time " << segment->getEndMarkerTime() << endl; + if (updateFrom >= segment->getEndMarkerTime()) + return ; + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + if (&m_staffs[i]->getSegment() == segment) { + refreshSegment(segment, 0, 0); + } + } + NOTATION_DEBUG << "NotationView::slotUpdateRecordingSegment: don't have segment " << segment << endl; +} + +void +NotationView::slotSetCurrentStaff(double x, int y) +{ + unsigned int staffNo; + for (staffNo = 0; staffNo < m_staffs.size(); ++staffNo) { + if (m_staffs[staffNo]->containsCanvasCoords(x, y)) + break; + } + + if (staffNo < m_staffs.size()) { + slotSetCurrentStaff(staffNo); + } +} + +void +NotationView::slotSetCurrentStaff(int staffNo) +{ + NOTATION_DEBUG << "NotationView::slotSetCurrentStaff(" << staffNo << ")" << endl; + + if (m_currentStaff != staffNo) { + + m_staffs[m_currentStaff]->setCurrent(false); + + m_currentStaff = staffNo; + + m_staffs[m_currentStaff]->setCurrent(true); + + Segment *segment = &m_staffs[m_currentStaff]->getSegment(); + + m_chordNameRuler->setCurrentSegment(segment); + m_rawNoteRuler->setCurrentSegment(segment); + m_rawNoteRuler->repaint(); + setControlRulersCurrentSegment(); + + updateView(); + + slotSetInsertCursorPosition(getInsertionTime(), false, false); + + m_headersGroup->setCurrent( + m_staffs[staffNo]->getSegment().getTrack()); + } +} + +void +NotationView::slotCurrentStaffUp() +{ + LinedStaff *staff = getStaffAbove(); + if (!staff) return; + slotSetCurrentStaff(staff->getId()); +} + +void +NotationView::slotCurrentStaffDown() +{ + LinedStaff *staff = getStaffBelow(); + if (!staff) return; + slotSetCurrentStaff(staff->getId()); +} + +void +NotationView::slotCurrentSegmentPrior() +{ + if (m_staffs.size() < 2) + return ; + + Composition *composition = + m_staffs[m_currentStaff]->getSegment().getComposition(); + + Track *track = composition-> + getTrackById(m_staffs[m_currentStaff]->getSegment().getTrack()); + if (!track) + return ; + + int lastStaffOnTrack = -1; + + // + // TODO: Cycle segments through rather in time order? + // Cycle only segments in the field of view? + // + for (int i = m_staffs.size()-1; i >= 0; --i) { + if (m_staffs[i]->getSegment().getTrack() == track->getId()) { + if (lastStaffOnTrack < 0) { + lastStaffOnTrack = i; + } + if (i < m_currentStaff) { + slotSetCurrentStaff(i); + slotEditSelectWholeStaff(); + return ; + } + } + } + if (lastStaffOnTrack >= 0) { + slotSetCurrentStaff(lastStaffOnTrack); + slotEditSelectWholeStaff(); + return ; + } +} + +void +NotationView::slotCurrentSegmentNext() +{ + if (m_staffs.size() < 2) + return ; + + Composition *composition = + m_staffs[m_currentStaff]->getSegment().getComposition(); + + Track *track = composition-> + getTrackById(m_staffs[m_currentStaff]->getSegment().getTrack()); + if (!track) + return ; + + int firstStaffOnTrack = -1; + + // + // TODO: Cycle segments through rather in time order? + // Cycle only segments in the field of view? + // + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + if (m_staffs[i]->getSegment().getTrack() == track->getId()) { + if (firstStaffOnTrack < 0) { + firstStaffOnTrack = i; + } + if (i > m_currentStaff) { + slotSetCurrentStaff(i); + slotEditSelectWholeStaff(); + return ; + } + } + } + if (firstStaffOnTrack >= 0) { + slotSetCurrentStaff(firstStaffOnTrack); + slotEditSelectWholeStaff(); + return ; + } +} + +void +NotationView::slotSetInsertCursorPosition(double x, int y, bool scroll, + bool updateNow) +{ + NOTATION_DEBUG << "NotationView::slotSetInsertCursorPosition: x " << x << ", y " << y << ", scroll " << scroll << ", now " << updateNow << endl; + + slotSetCurrentStaff(x, y); + + LinedStaff *staff = getLinedStaff(m_currentStaff); + Event *clefEvt, *keyEvt; + NotationElementList::iterator i = + staff->getElementUnderCanvasCoords(x, y, clefEvt, keyEvt); + + if (i == staff->getViewElementList()->end()) { + slotSetInsertCursorPosition(staff->getSegment().getEndTime(), scroll, + updateNow); + } else { + slotSetInsertCursorPosition((*i)->getViewAbsoluteTime(), scroll, + updateNow); + } +} + +void +NotationView::slotSetInsertCursorPosition(timeT t, bool scroll, bool updateNow) +{ + NOTATION_DEBUG << "NotationView::slotSetInsertCursorPosition: time " << t << ", scroll " << scroll << ", now " << updateNow << endl; + + m_insertionTime = t; + if (scroll) { + m_deferredCursorMove = CursorMoveAndMakeVisible; + } else { + m_deferredCursorMove = CursorMoveOnly; + } + if (updateNow) + doDeferredCursorMove(); +} + +void +NotationView::slotSetInsertCursorAndRecentre(timeT t, double cx, int, + bool updateNow) +{ + NOTATION_DEBUG << "NotationView::slotSetInsertCursorAndRecentre: time " << t << ", cx " << cx << ", now " << updateNow << ", contentsx" << getCanvasView()->contentsX() << ", w " << getCanvasView()->visibleWidth() << endl; + + m_insertionTime = t; + + // We only do the scroll bit if cx is in the right two-thirds of + // the window + + if (cx < (getCanvasView()->contentsX() + + getCanvasView()->visibleWidth() / 3)) { + + m_deferredCursorMove = CursorMoveOnly; + } else { + m_deferredCursorMove = CursorMoveAndScrollToPosition; + m_deferredCursorScrollToX = cx; + } + + if (updateNow) + doDeferredCursorMove(); +} + +void +NotationView::doDeferredCursorMove() +{ + NOTATION_DEBUG << "NotationView::doDeferredCursorMove: m_deferredCursorMove == " << m_deferredCursorMove << endl; + + if (m_deferredCursorMove == NoCursorMoveNeeded) { + return ; + } + + DeferredCursorMoveType type = m_deferredCursorMove; + m_deferredCursorMove = NoCursorMoveNeeded; + + timeT t = m_insertionTime; + + if (m_staffs.size() == 0) + return ; + LinedStaff *staff = getCurrentLinedStaff(); + Segment &segment = staff->getSegment(); + + if (t < segment.getStartTime()) { + t = segment.getStartTime(); + } + if (t > segment.getEndTime()) { + t = segment.getEndTime(); + } + + NotationElementList::iterator i = + staff->getViewElementList()->findNearestTime(t); + + while (i != staff->getViewElementList()->end() && + !static_cast<NotationElement*>(*i)->getCanvasItem()) + ++i; + + if (i == staff->getViewElementList()->end()) { + //!!! ??? + if (m_insertionTime >= staff->getSegment().getStartTime()) { + i = staff->getViewElementList()->begin(); + } + m_insertionTime = staff->getSegment().getStartTime(); + } else { + m_insertionTime = static_cast<NotationElement*>(*i)->getViewAbsoluteTime(); + } + + if (i == staff->getViewElementList()->end() || + t == segment.getEndTime() || + t == segment.getBarStartForTime(t)) { + + staff->setInsertCursorPosition(*m_hlayout, t); + + if (type == CursorMoveAndMakeVisible) { + double cx; + int cy; + staff->getInsertCursorPosition(cx, cy); + getCanvasView()->slotScrollHoriz(int(cx)); + getCanvasView()->slotScrollVertSmallSteps(cy); + } + + } else { + + // prefer a note or rest, if there is one, to a non-spacing event + if (!static_cast<NotationElement*>(*i)->isNote() && + !static_cast<NotationElement*>(*i)->isRest()) { + NotationElementList::iterator j = i; + while (j != staff->getViewElementList()->end()) { + if (static_cast<NotationElement*>(*j)->getViewAbsoluteTime() != + static_cast<NotationElement*>(*i)->getViewAbsoluteTime()) + break; + if (static_cast<NotationElement*>(*j)->getCanvasItem()) { + if (static_cast<NotationElement*>(*j)->isNote() || + static_cast<NotationElement*>(*j)->isRest()) { + i = j; + break; + } + } + ++j; + } + } + + if (static_cast<NotationElement*>(*i)->getCanvasItem()) { + + staff->setInsertCursorPosition + (static_cast<NotationElement*>(*i)->getCanvasX() - 2, + int(static_cast<NotationElement*>(*i)->getCanvasY())); + + if (type == CursorMoveAndMakeVisible) { + getCanvasView()->slotScrollHoriz + (int(static_cast<NotationElement*>(*i)->getCanvasX()) - 4); + } + } else { + std::cerr << "WARNING: No canvas item for this notation element:"; + (*i)->event()->dump(std::cerr); + } + } + + if (type == CursorMoveAndScrollToPosition) { + + // get current canvas x of insert cursor, which might not be + // what we just set + + double ccx = 0.0; + + NotationElementList::iterator i = + staff->getViewElementList()->findTime(t); + + if (i == staff->getViewElementList()->end()) { + if (i == staff->getViewElementList()->begin()) + return ; + double lx, lwidth; + --i; + if (static_cast<NotationElement*>(*i)->getCanvasItem()) { + ccx = static_cast<NotationElement*>(*i)->getCanvasX(); + static_cast<NotationElement*>(*i)->getLayoutAirspace(lx, lwidth); + } else { + std::cerr << "WARNING: No canvas item for this notation element*:"; + (*i)->event()->dump(std::cerr); + } + ccx += lwidth; + } else { + if (static_cast<NotationElement*>(*i)->getCanvasItem()) { + ccx = static_cast<NotationElement*>(*i)->getCanvasX(); + } else { + std::cerr << "WARNING: No canvas item for this notation element*:"; + (*i)->event()->dump(std::cerr); + } + } + + QScrollBar* hbar = getCanvasView()->horizontalScrollBar(); + hbar->setValue(int(hbar->value() - (m_deferredCursorScrollToX - ccx))); + } + + updateView(); +} + +void +NotationView::slotJumpCursorToPlayback() +{ + slotSetInsertCursorPosition(getDocument()->getComposition().getPosition()); +} + +void +NotationView::slotJumpPlaybackToCursor() +{ + emit jumpPlaybackTo(getInsertionTime()); +} + +void +NotationView::slotToggleTracking() +{ + m_playTracking = !m_playTracking; +} + +void NotationView::slotNoAccidental() +{ + emit changeAccidental(Accidentals::NoAccidental, false); +} + +void NotationView::slotFollowAccidental() +{ + emit changeAccidental(Accidentals::NoAccidental, true); +} + +void NotationView::slotSharp() +{ + emit changeAccidental(Accidentals::Sharp, false); +} + +void NotationView::slotFlat() +{ + emit changeAccidental(Accidentals::Flat, false); +} + +void NotationView::slotNatural() +{ + emit changeAccidental(Accidentals::Natural, false); +} + +void NotationView::slotDoubleSharp() +{ + emit changeAccidental(Accidentals::DoubleSharp, false); +} + +void NotationView::slotDoubleFlat() +{ + emit changeAccidental(Accidentals::DoubleFlat, false); +} + +void NotationView::slotTrebleClef() +{ + m_currentNotePixmap->setPixmap + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-treble"))); + setTool(m_toolBox->getTool(ClefInserter::ToolName)); + + dynamic_cast<ClefInserter*>(m_tool)->setClef(Clef::Treble); + setMenuStates(); +} + +void NotationView::slotAltoClef() +{ + m_currentNotePixmap->setPixmap + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-alto"))); + setTool(m_toolBox->getTool(ClefInserter::ToolName)); + + dynamic_cast<ClefInserter*>(m_tool)->setClef(Clef::Alto); + setMenuStates(); +} + +void NotationView::slotTenorClef() +{ + m_currentNotePixmap->setPixmap + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-tenor"))); + setTool(m_toolBox->getTool(ClefInserter::ToolName)); + + dynamic_cast<ClefInserter*>(m_tool)->setClef(Clef::Tenor); + setMenuStates(); +} + +void NotationView::slotBassClef() +{ + m_currentNotePixmap->setPixmap + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("clef-bass"))); + setTool(m_toolBox->getTool(ClefInserter::ToolName)); + + dynamic_cast<ClefInserter*>(m_tool)->setClef(Clef::Bass); + setMenuStates(); +} + +void NotationView::slotText() +{ + m_currentNotePixmap->setPixmap + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("text"))); + setTool(m_toolBox->getTool(TextInserter::ToolName)); + setMenuStates(); +} + +void NotationView::slotGuitarChord() +{ + m_currentNotePixmap->setPixmap + (NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("guitarchord"))); + setTool(m_toolBox->getTool(GuitarChordInserter::ToolName)); + setMenuStates(); +} + +void NotationView::slotEraseSelected() +{ + NOTATION_DEBUG << "NotationView::slotEraseSelected()" << endl; + setTool(m_toolBox->getTool(NotationEraser::ToolName)); + setMenuStates(); +} + +void NotationView::slotSelectSelected() +{ + NOTATION_DEBUG << "NotationView::slotSelectSelected()" << endl; + setTool(m_toolBox->getTool(NotationSelector::ToolName)); + setMenuStates(); +} + +void NotationView::slotLinearMode() +{ + setPageMode(LinedStaff::LinearMode); +} + +void NotationView::slotContinuousPageMode() +{ + setPageMode(LinedStaff::ContinuousPageMode); +} + +void NotationView::slotMultiPageMode() +{ + setPageMode(LinedStaff::MultiPageMode); +} + +void NotationView::slotToggleChordsRuler() +{ + if (m_hlayout->isPageMode()) + return ; + toggleWidget(m_chordNameRuler, "show_chords_ruler"); +} + +void NotationView::slotToggleRawNoteRuler() +{ + if (m_hlayout->isPageMode()) + return ; + toggleWidget(m_rawNoteRuler, "show_raw_note_ruler"); +} + +void NotationView::slotToggleTempoRuler() +{ + if (m_hlayout->isPageMode()) + return ; + toggleWidget(m_tempoRuler, "show_tempo_ruler"); +} + +void NotationView::slotToggleAnnotations() +{ + m_annotationsVisible = !m_annotationsVisible; + slotUpdateAnnotationsStatus(); + //!!! use refresh mechanism + refreshSegment(0, 0, 0); +} + +void NotationView::slotToggleLilyPondDirectives() +{ + m_lilyPondDirectivesVisible = !m_lilyPondDirectivesVisible; + slotUpdateLilyPondDirectivesStatus(); + //!!! use refresh mechanism + refreshSegment(0, 0, 0); +} + +void NotationView::slotEditLyrics() +{ + Staff *staff = getCurrentStaff(); + Segment &segment = staff->getSegment(); + + LyricEditDialog dialog(this, &segment); + + if (dialog.exec() == QDialog::Accepted) { + + KMacroCommand *macro = new KMacroCommand + (SetLyricsCommand::getGlobalName()); + + for (int i = 0; i < dialog.getVerseCount(); ++i) { + SetLyricsCommand *command = new SetLyricsCommand + (&segment, i, dialog.getLyricData(i)); + macro->addCommand(command); + } + + addCommandToHistory(macro); + } +} + +void NotationView::slotItemPressed(int height, int staffNo, + QMouseEvent* e, + NotationElement* el) +{ + NOTATION_DEBUG << "NotationView::slotItemPressed(height = " + << height << ", staffNo = " << staffNo + << ")" << endl; + + if (staffNo < 0 && el != 0) { + // We have an element but no staff -- that's because the + // element extended outside the staff region. But we need + // to handle it properly, so we rather laboriously need to + // find out which staff it was. + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + if (m_staffs[i]->getViewElementList()->findSingle(el) != + m_staffs[i]->getViewElementList()->end()) { + staffNo = m_staffs[i]->getId(); + break; + } + } + } + + ButtonState btnState = e->state(); + + if (btnState & ControlButton) { // on ctrl-click, set cursor position + + slotSetInsertCursorPosition(e->x(), (int)e->y()); + + } else { + + setActiveItem(0); + + timeT unknownTime = 0; + + if (e->type() == QEvent::MouseButtonDblClick) { + m_tool->handleMouseDoubleClick(unknownTime, height, + staffNo, e, el); + } else { + m_tool->handleMousePress(unknownTime, height, + staffNo, e, el); + } + } +} + +void NotationView::slotNonNotationItemPressed(QMouseEvent *e, QCanvasItem *it) +{ + if (e->type() != QEvent::MouseButtonDblClick) + return ; + + Staff *staff = getStaffForCanvasCoords(e->x(), e->y()); + if (!staff) + return ; + + NOTATION_DEBUG << "NotationView::slotNonNotationItemPressed(doubly)" << endl; + + if (dynamic_cast<QCanvasStaffNameSprite *>(it)) { + + std::string name = + staff->getSegment().getComposition()-> + getTrackById(staff->getSegment().getTrack())->getLabel(); + + bool ok = false; + QRegExpValidator validator(QRegExp(".*"), this); // empty is OK + + QString newText = KLineEditDlg::getText(QString("Change staff name"), + QString("Enter new staff name"), + strtoqstr(name), + &ok, + this, + &validator); + + if (ok) { + addCommandToHistory(new RenameTrackCommand + (staff->getSegment().getComposition(), + staff->getSegment().getTrack(), + qstrtostr(newText))); + + emit staffLabelChanged(staff->getSegment().getTrack(), newText); + } + + } else if (dynamic_cast<QCanvasTimeSigSprite *>(it)) { + + double layoutX = (dynamic_cast<QCanvasTimeSigSprite *>(it))->getLayoutX(); + emit editTimeSignature(m_hlayout->getTimeForX(layoutX)); + } +} + +void NotationView::slotTextItemPressed(QMouseEvent *e, QCanvasItem *it) +{ + if (e->type() != QEvent::MouseButtonDblClick) + return ; + + if (it == m_title) { + emit editMetadata(strtoqstr(CompositionMetadataKeys::Title.getName())); + } else if (it == m_subtitle) { + emit editMetadata(strtoqstr(CompositionMetadataKeys::Subtitle.getName())); + } else if (it == m_composer) { + emit editMetadata(strtoqstr(CompositionMetadataKeys::Composer.getName())); + } else if (it == m_copyright) { + emit editMetadata(strtoqstr(CompositionMetadataKeys::Copyright.getName())); + } else { + return ; + } + + positionStaffs(); +} + +void NotationView::slotMouseMoved(QMouseEvent *e) +{ + if (activeItem()) { + activeItem()->handleMouseMove(e); + updateView(); + } else { + int follow = m_tool->handleMouseMove(0, 0, // unknown time and height + e); + + if (getCanvasView()->isTimeForSmoothScroll()) { + + if (follow & RosegardenCanvasView::FollowHorizontal) { + getCanvasView()->slotScrollHorizSmallSteps(e->x()); + } + + if (follow & RosegardenCanvasView::FollowVertical) { + getCanvasView()->slotScrollVertSmallSteps(e->y()); + } + + } + } +} + +void NotationView::slotMouseReleased(QMouseEvent *e) +{ + if (activeItem()) { + activeItem()->handleMouseRelease(e); + setActiveItem(0); + updateView(); + } else + m_tool->handleMouseRelease(0, 0, // unknown time and height + e); +} + +void +NotationView::slotHoveredOverNoteChanged(const QString ¬eName) +{ + m_hoveredOverNoteName->setText(QString(" ") + noteName); +} + +void +NotationView::slotHoveredOverAbsoluteTimeChanged(unsigned int time) +{ + timeT t = time; + RealTime rt = + getDocument()->getComposition().getElapsedRealTime(t); + long ms = rt.msec(); + + int bar, beat, fraction, remainder; + getDocument()->getComposition().getMusicalTimeForAbsoluteTime + (t, bar, beat, fraction, remainder); + + // QString message; + // QString format("%ld (%ld.%03lds)"); + // format = i18n("Time: %1").arg(format); + // message.sprintf(format, t, rt.sec, ms); + + QString message = i18n("Time: %1 (%2.%3s)") + .arg(QString("%1-%2-%3-%4") + .arg(QString("%1").arg(bar + 1).rightJustify(3, '0')) + .arg(QString("%1").arg(beat).rightJustify(2, '0')) + .arg(QString("%1").arg(fraction).rightJustify(2, '0')) + .arg(QString("%1").arg(remainder).rightJustify(2, '0'))) + .arg(rt.sec) + .arg(QString("%1").arg(ms).rightJustify(3, '0')); + + m_hoveredOverAbsoluteTime->setText(message); +} + +void +NotationView::slotInsertableNoteEventReceived(int pitch, int velocity, bool noteOn) +{ + //!!! Problematic. Ideally we wouldn't insert events into windows + //that weren't actually visible, otherwise all hell could break + //loose (metaphorically speaking, I should probably add). I did + //think of checking isActiveWindow() and returning if the current + //window wasn't active, but that will prevent anyone from + //step-recording from e.g. vkeybd, which cannot be used without + //losing focus (and thus active-ness) from the Rosegarden window. + + //!!! I know -- we'll keep track of which edit view (or main view, + //or mixer, etc) is active, and we'll only allow insertion into + //the most recently activated. How about that? + + KToggleAction *action = dynamic_cast<KToggleAction *> + (actionCollection()->action("toggle_step_by_step")); + if (!action) { + NOTATION_DEBUG << "WARNING: No toggle_step_by_step action" << endl; + return ; + } + if (!action->isChecked()) + return ; + + Segment &segment = m_staffs[m_currentStaff]->getSegment(); + + NoteInserter *noteInserter = dynamic_cast<NoteInserter *>(m_tool); + if (!noteInserter) { + static bool showingError = false; + if (showingError) + return ; + showingError = true; + KMessageBox::sorry(this, i18n("Can't insert note: No note duration selected")); + showingError = false; + return ; + } + + if (m_inPaintEvent) { + NOTATION_DEBUG << "NotationView::slotInsertableNoteEventReceived: in paint event already" << endl; + if (noteOn) { + m_pendingInsertableNotes.push_back(std::pair<int, int>(pitch, velocity)); + } + return ; + } + + // If the segment is transposed, we want to take that into + // account. But the note has already been played back to the user + // at its untransposed pitch, because that's done by the MIDI THRU + // code in the sequencer which has no way to know whether a note + // was intended for step recording. So rather than adjust the + // pitch for playback according to the transpose setting, we have + // to adjust the stored pitch in the opposite direction. + + pitch -= segment.getTranspose(); + + // KTmpStatusMsg msg(i18n("Inserting note"), this); + + // We need to ensure that multiple notes hit at once come out as + // chords, without imposing the interpretation that overlapping + // notes are always chords and without getting too involved with + // the actual absolute times of the notes (this is still step + // editing, not proper recording). + + // First, if we're in chord mode, there's no problem. + + static int numberOfNotesOn = 0; + static timeT insertionTime = getInsertionTime(); + static time_t lastInsertionTime = 0; + + if (isInChordMode()) { + if (!noteOn) + return ; + NOTATION_DEBUG << "Inserting note in chord at pitch " << pitch << endl; + noteInserter->insertNote(segment, getInsertionTime(), pitch, + Accidentals::NoAccidental, + true); + + } else { + + if (!noteOn) { + numberOfNotesOn--; + } else if (noteOn) { + // Rules: + // + // * If no other note event has turned up within half a + // second, insert this note and advance. + // + // * Relatedly, if this note is within half a second of + // the previous one, they're chords. Insert the previous + // one, don't advance, and use the same rules for this. + // + // * If a note event turns up before that time has elapsed, + // we need to wait for the note-off events: if the second + // note happened less than half way through the first, + // it's a chord. + // + // We haven't implemented these yet... For now: + // + // Rules (hjj): + // + // * The overlapping notes are always included in to a chord. + // This is the most convenient for step inserting of chords. + // + // * The timer resets the numberOfNotesOn, if noteOff signals were + // drop out for some reason (which has not been encountered yet). + + time_t now; + time (&now); + double elapsed = difftime(now, lastInsertionTime); + time (&lastInsertionTime); + + if (numberOfNotesOn <= 0 || elapsed > 10.0 ) { + numberOfNotesOn = 0; + insertionTime = getInsertionTime(); + } + numberOfNotesOn++; + + noteInserter->insertNote(segment, insertionTime, pitch, + Accidentals::NoAccidental, + true); + } + } +} + +void +NotationView::slotInsertableNoteOnReceived(int pitch, int velocity) +{ + NOTATION_DEBUG << "NotationView::slotInsertableNoteOnReceived: " << pitch << endl; + slotInsertableNoteEventReceived(pitch, velocity, true); +} + +void +NotationView::slotInsertableNoteOffReceived(int pitch, int velocity) +{ + NOTATION_DEBUG << "NotationView::slotInsertableNoteOffReceived: " << pitch << endl; + slotInsertableNoteEventReceived(pitch, velocity, false); +} + +void +NotationView::slotInsertableTimerElapsed() +{} + +void +NotationView::slotToggleStepByStep() +{ + KToggleAction *action = dynamic_cast<KToggleAction *> + (actionCollection()->action("toggle_step_by_step")); + if (!action) { + NOTATION_DEBUG << "WARNING: No toggle_step_by_step action" << endl; + return ; + } + if (action->isChecked()) { // after toggling, that is + emit stepByStepTargetRequested(this); + } else { + emit stepByStepTargetRequested(0); + } +} + +void +NotationView::slotStepByStepTargetRequested(QObject *obj) +{ + KToggleAction *action = dynamic_cast<KToggleAction *> + (actionCollection()->action("toggle_step_by_step")); + if (!action) { + NOTATION_DEBUG << "WARNING: No toggle_step_by_step action" << endl; + return ; + } + action->setChecked(obj == this); +} + +void +NotationView::slotCheckRendered(double cx0, double cx1) +{ + // NOTATION_DEBUG << "slotCheckRendered(" << cx0 << "," << cx1 << ")" << endl; + + bool something = false; + + for (size_t i = 0; i < m_staffs.size(); ++i) { + + LinedStaff *staff = m_staffs[i]; + + LinedStaff::LinedStaffCoords cc0 = staff->getLayoutCoordsForCanvasCoords + (cx0, 0); + + LinedStaff::LinedStaffCoords cc1 = staff->getLayoutCoordsForCanvasCoords + (cx1, staff->getTotalHeight() + staff->getY()); + + timeT t0 = m_hlayout->getTimeForX(cc0.first); + timeT t1 = m_hlayout->getTimeForX(cc1.first); + + if (dynamic_cast<NotationStaff *>(staff)->checkRendered(t0, t1)) { + something = true; //!!! + } + } + + if (something) { + emit renderComplete(); + if (m_renderTimer) + delete m_renderTimer; + m_renderTimer = new QTimer(this); + connect(m_renderTimer, SIGNAL(timeout()), SLOT(slotRenderSomething())); + m_renderTimer->start(0, true); + } + + if (m_deferredCursorMove != NoCursorMoveNeeded) + doDeferredCursorMove(); +} + +void +NotationView::slotRenderSomething() +{ + delete m_renderTimer; + m_renderTimer = 0; + static clock_t lastWork = 0; + + clock_t now = clock(); + long elapsed = ((now - lastWork) * 1000 / CLOCKS_PER_SEC); + if (elapsed < 70) { + m_renderTimer = new QTimer(this); + connect(m_renderTimer, SIGNAL(timeout()), SLOT(slotRenderSomething())); + m_renderTimer->start(0, true); + return ; + } + lastWork = now; + + for (size_t i = 0; i < m_staffs.size(); ++i) { + + if (m_staffs[i]->doRenderWork(m_staffs[i]->getSegment().getStartTime(), + m_staffs[i]->getSegment().getEndTime())) { + m_renderTimer = new QTimer(this); + connect(m_renderTimer, SIGNAL(timeout()), SLOT(slotRenderSomething())); + m_renderTimer->start(0, true); + return ; + } + } + + PixmapArrayGC::deleteAll(); + NOTATION_DEBUG << "NotationView::slotRenderSomething: updating thumbnails" << endl; + updateThumbnails(true); + + // Update track headers when rendering is done + // (better late than never) + m_headersGroup->slotUpdateAllHeaders(getCanvasLeftX(), 0, true); + m_headersGroupView->setContentsPos(getCanvasView()->contentsX(), + getCanvasView()->contentsY()); +} + +NotationCanvasView* NotationView::getCanvasView() +{ + return dynamic_cast<NotationCanvasView *>(m_canvasView); +} + +void +NotationView::slotVerticalScrollHeadersGroup(int y) +{ + m_headersGroupView->setContentsPos(0, y); +} + +void +NotationView::slotShowHeadersGroup() +{ + m_showHeadersGroup = HeadersGroup::ShowAlways; + showHeadersGroup(); + + // Disable menu entry when headers are shown + m_showHeadersMenuEntry->setEnabled(false); +} + +void +NotationView::slotHideHeadersGroup() +{ + m_showHeadersGroup = HeadersGroup::ShowNever; + hideHeadersGroup(); + + // Enable menu entry when headers are hidden + m_showHeadersMenuEntry->setEnabled(true); +} + +void +NotationView::showHeadersGroup() +{ + if (m_headersGroupView && (m_pageMode == LinedStaff::LinearMode)) { + m_headersGroupView->show(); + m_headersTopFrame->show(); + m_rulerBoxFiller->show(); + } +} + +void +NotationView::hideHeadersGroup() +{ + if (m_headersGroupView) { + m_headersGroupView->hide(); + m_headersTopFrame->hide(); + m_rulerBoxFiller->hide(); + } +} + +void +NotationView::slotUpdateHeaders(int x, int y) +{ + m_headersGroup->slotUpdateAllHeaders(x, y); + m_headersGroupView->setContentsPos(x, y); +} + +void +NotationView::slotHeadersWidthChanged(int w) +{ + m_headersTopFrame->setFixedWidth(w); + m_rulerBoxFiller->setFixedWidth(w); + m_canvasView->updateLeftWidgetGeometry(); +} + + +int +NotationView::getCanvasVisibleWidth() +{ + if (getCanvasView()) { + return getCanvasView()->visibleWidth(); + } else { + return -1; + } +} + +int +NotationView::getHeadersTopFrameMinWidth() +{ + /// TODO : use a real button width got from a real button + + // 2 buttons (2 x 24) + 2 margins (2 x 4) + buttons spacing (4) + return 4 + 24 + 4 + 24 + 4; +} + +} +#include "NotationView.moc" diff --git a/src/gui/editors/notation/NotationView.h b/src/gui/editors/notation/NotationView.h new file mode 100644 index 0000000..7678f8a --- /dev/null +++ b/src/gui/editors/notation/NotationView.h @@ -0,0 +1,1131 @@ +/* -*- 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. +*/ + +#ifndef _RG_NOTATIONVIEW_H_ +#define _RG_NOTATIONVIEW_H_ + +#include "base/NotationTypes.h" +#include "base/Track.h" +#include "gui/general/EditView.h" +#include "gui/general/LinedStaff.h" +#include "gui/general/LinedStaffManager.h" +#include "NotationProperties.h" +#include "NotationCanvasView.h" +#include <string> +#include <kprocess.h> +#include <ktempfile.h> +#include <qmap.h> +#include <qsize.h> +#include <qstring.h> +#include <vector> +#include "base/Event.h" +#include "gui/general/ClefIndex.h" + + +class QWidget; +class QTimer; +class QPaintEvent; +class QObject; +class QMouseEvent; +class QLabel; +class QCursor; +class QCanvasItem; +class QCanvas; +class KProgress; +class KComboBox; +class KActionMenu; +class KAction; + + +namespace Rosegarden +{ + +class Staff; +class Segment; +class ScrollBoxDialog; +class RulerScale; +class RosegardenGUIDoc; +class RawNoteRuler; +class ProgressDialog; +class ProgressBar; +class NotePixmapFactory; +class NotationVLayout; +class NotationStaff; +class NotationHLayout; +class NotationElement; +class NoteActionData; +class NoteActionDataMap; +class MarkActionData; +class MarkActionDataMap; +class NoteChangeActionData; +class NoteChangeActionDataMap; +class Key; +class EventSelection; +class Event; +class Clef; +class ChordNameRuler; +class QDeferScrollView; +class HeadersGroup; + + +/** + * NotationView is a view for one or more Staff objects, each of + * which contains the notation data associated with a Segment. + * NotationView owns the Staff objects it displays. + * + * This class manages the relationship between NotationHLayout/ + * NotationVLayout and Staff data, as well as using rendering the + * actual notes (using NotePixmapFactory to generate the pixmaps). + */ + +class NotationView : public EditView, + public LinedStaffManager +{ + friend class NoteInserter; + friend class ClefInserter; + friend class NotationEraser; + friend class NotationSelectionPaster; + friend class LilyPondExporter; + + Q_OBJECT + +public: + explicit NotationView(RosegardenGUIDoc *doc, + std::vector<Segment *> segments, + QWidget *parent, + bool showProgressive); // update during initial render? + + /** + * Constructor for printing only. If parent is provided, a + * progress dialog will be shown -- otherwise not. If another + * NotationView is provided, the fonts and other settings used + * for printing will be taken from that view. + */ + explicit NotationView(RosegardenGUIDoc *doc, + std::vector<Segment *> segments, + QWidget *parent, + NotationView *referenceView); + + ~NotationView(); + +// void initialLayout(); + + /// constructed successfully? (main reason it might not is user hit Cancel) + bool isOK() const { return m_ok; } + + /** + * Return the view-local PropertyName definitions for this view + */ + const NotationProperties &getProperties() const; + + /// Return the number of staffs + int getStaffCount() { return m_staffs.size(); } + + /// Return a pointer to the staff at the specified index + Staff *getStaff(int i) { + return getLinedStaff(i); + } + + /// Return a pointer to the staff corresponding to the given segment + Staff *getStaff(const Segment &segment) { + return getLinedStaff(segment); + } + + /// Return a pointer to the staff at the specified index + LinedStaff *getLinedStaff(int i); + + /// Return a pointer to the staff corresponding to the given segment + LinedStaff *getLinedStaff(const Segment &segment); + + /// Return a pointer to the staff at the specified index + NotationStaff *getNotationStaff(int i) { + if (i >= 0 && unsigned(i) < m_staffs.size()) return m_staffs[i]; + else return 0; + } + + /// Return a pointer to the staff corresponding to the given segment + NotationStaff *getNotationStaff(const Segment &segment); + + /// Return true if the staff at the specified index is the current one + bool isCurrentStaff(int i); + + QCanvas* canvas() { return getCanvasView()->canvas(); } + + void setCanvasCursor(const QCursor &cursor) { + getCanvasView()->viewport()->setCursor(cursor); + } + + void setHeightTracking(bool t) { + getCanvasView()->setHeightTracking(t); + } + + /** + * Returns true if the view is actually for printing + */ + bool isInPrintMode() { return m_printMode; } + + /** + * Set the note or rest selected by the user from the toolbars + */ + void setCurrentSelectedNote(const char *pixmapName, + bool isRest, Note::Type, + int dots = 0); + + /** + * Set the note or rest selected by the user from the toolbars + */ + void setCurrentSelectedNote(const NoteActionData &); + + /** + * Discover whether chord-mode insertions are enabled (as opposed + * to the default melody-mode) + */ + bool isInChordMode(); + + /** + * Discover whether triplet-mode insertions are enabled + */ + bool isInTripletMode(); + + /** + * Discover whether grace-mode insertions are enabled + */ + bool isInGraceMode(); + + /** + * Discover whether annotations are being displayed or not + */ + bool areAnnotationsVisible() { return m_annotationsVisible; } + + /** + * Discover whether LilyPond directives are being displayed or not + */ + bool areLilyPondDirectivesVisible() { return m_lilyPondDirectivesVisible; } + + /** + * Set the current event selection. + * + * If preview is true, sound the selection as well. + * + * If redrawNow is true, recolour the elements on the canvas; + * otherwise just line up a refresh for the next paint event. + * + * (If the selection has changed as part of a modification to a + * segment, redrawNow should be unnecessary and undesirable, as a + * paint event will occur in the next event loop following the + * command invocation anyway.) + */ + virtual void setCurrentSelection(EventSelection*, + bool preview = false, + bool redrawNow = false); + + /** + * Set the current event selection to a single event + */ + void setSingleSelectedEvent(int staffNo, + Event *event, + bool preview = false, + bool redrawNow = false); + + /** + * Set the current event selection to a single event + */ + void setSingleSelectedEvent(Segment &segment, + Event *event, + bool preview = false, + bool redrawNow = false); + + /** + * Show and sound the given note. The height is used for display, + * the pitch for performance, so the two need not correspond (e.g. + * under ottava there may be octave differences). + */ + void showPreviewNote(int staffNo, double layoutX, + int pitch, int height, + const Note ¬e, + bool grace, + int velocity = -1); + + /// Remove any visible preview note + void clearPreviewNote(); + + /// Sound the given note + void playNote(Segment &segment, int pitch, int velocity = -1); + + /// Switches between page- and linear- layout modes + void setPageMode(LinedStaff::PageMode mode); + + /// Returns the page width according to the layout mode (page/linear) + int getPageWidth(); + + /// Returns the page height according to the layout mode (page/linear) + int getPageHeight(); + + /// Returns the margins within the page (zero if not in MultiPageMode) + void getPageMargins(int &left, int &top); + + /// Scrolls the view such that the given time is centered + void scrollToTime(timeT t); + + NotePixmapFactory *getNotePixmapFactory() const { + return m_notePixmapFactory; + } + + virtual void refreshSegment(Segment *segment, + timeT startTime = 0, + timeT endTime = 0); + + /** + * From LinedStaffManager + */ + virtual LinedStaff* getStaffForCanvasCoords(int x, int y) const; + + + /** + * Overridden from EditView + */ + virtual void updateView(); + + /** + * Render segments on printing painter. This uses the current + * font size and layout, rather than the optimal ones for the + * printer configuration (notation editing is not quite WYSIWYG, + * and we may be in a non-page mode). + * + * To print optimally use slotFilePrint, which will create + * another NotationView with the optimal settings and call print + * on that. + */ + virtual void print(bool previewOnly = false); + + /** + * Return X of the left of the canvas visible part. + */ + double getCanvasLeftX() { return getCanvasView()->contentsX(); } + + virtual RulerScale* getHLayout(); + + /** + * Return the notation window width + */ + int getCanvasVisibleWidth(); + + /** + * Return the minimal width which shall be allocated to + * the track headers top frame. + * (The width of the close button + the width of an info + * button still to come). + */ + int getHeadersTopFrameMinWidth(); + +public slots: + + /** + * Print the current set of segments, by creating another + * NotationView with the printing configuration but the same + * segments, font etc as this view and asking it to print. + */ + void slotFilePrint(); + + /** + * Preview the current set of segments, by creating another + * NotationView with the printing configuration but the same + * segments, font etc as this view and asking it to preview. + */ + void slotFilePrintPreview(); + + /** + * export a LilyPond file + */ + bool exportLilyPondFile(QString url, bool forPreview = false); + + /** + * Export to a temporary file and process + */ + void slotPrintLilyPond(); + void slotPreviewLilyPond(); + void slotLilyPondViewProcessExited(KProcess *); + + /** + * put the marked text/object into the clipboard and remove it + * from the document + */ + void slotEditCut(); + + /** + * put the marked text/object into the clipboard + */ + void slotEditCopy(); + + /** + * paste the clipboard into the document + */ + void slotEditPaste(); + + /** + * cut the selection and close the gap, moving subsequent events + * towards the start of the segment + */ + void slotEditCutAndClose(); + + /** + * paste the clipboard into the document, offering a choice for how + */ + void slotEditGeneralPaste(); + + /** + * delete the selection (cut without the copy) + */ + void slotEditDelete(); + + /** + * move the selection to the staff above + */ + void slotMoveEventsUpStaff(); + + /** + * move the selection to the staff below + */ + void slotMoveEventsDownStaff(); + + /** + * toggles the tools toolbar + */ + void slotToggleToolsToolBar(); + + /** + * toggles the notes toolbar + */ + void slotToggleNotesToolBar(); + + /** + * toggles the rests toolbar + */ + void slotToggleRestsToolBar(); + + /** + * toggles the accidentals toolbar + */ + void slotToggleAccidentalsToolBar(); + + /** + * toggles the clefs toolbar + */ + void slotToggleClefsToolBar(); + + /** + * toggles the marks toolbar + */ + void slotToggleMarksToolBar(); + + /** + * toggles the group toolbar + */ + void slotToggleGroupToolBar(); + + /** + * toggles the layout toolbar + */ + void slotToggleLayoutToolBar(); + + /** + * toggles the transport toolbar + */ + void slotToggleTransportToolBar(); + + /** + * toggles the meta toolbar + */ + void slotToggleMetaToolBar(); + + /// note switch slot + void slotNoteAction(); + + /// switch to last selected note + void slotLastNoteAction(); + + /// accidental switch slots + void slotNoAccidental(); + void slotFollowAccidental(); + void slotSharp(); + void slotFlat(); + void slotNatural(); + void slotDoubleSharp(); + void slotDoubleFlat(); + + /// clef switch slots + void slotTrebleClef(); + void slotAltoClef(); + void slotTenorClef(); + void slotBassClef(); + + /// text tool + void slotText(); + + /// guitar chord tool + void slotGuitarChord(); + + /// editing tools + void slotEraseSelected(); + void slotSelectSelected(); + + void slotToggleStepByStep(); + + /// status stuff + void slotUpdateInsertModeStatus(); + void slotUpdateAnnotationsStatus(); + void slotUpdateLilyPondDirectivesStatus(); + + /// edit menu + void slotPreviewSelection(); + void slotClearLoop(); + void slotClearSelection(); + void slotEditSelectFromStart(); + void slotEditSelectToEnd(); + void slotEditSelectWholeStaff(); + void slotFilterSelection(); + + /// view menu + void slotLinearMode(); + void slotContinuousPageMode(); + void slotMultiPageMode(); + void slotToggleChordsRuler(); + void slotToggleRawNoteRuler(); + void slotToggleTempoRuler(); + void slotToggleAnnotations(); + void slotToggleLilyPondDirectives(); + void slotEditLyrics(); + + /// Notation header slots + void slotShowHeadersGroup(); + void slotHideHeadersGroup(); + void slotVerticalScrollHeadersGroup(int); + void slotUpdateHeaders(int x, int y); + void slotHeadersWidthChanged(int w); + + /// Adjust notation header view when bottom ruler added or removed + void slotCanvasBottomWidgetHeightChanged(int); + + /// group slots + void slotGroupBeam(); + void slotGroupAutoBeam(); + void slotGroupBreak(); + void slotGroupSimpleTuplet(); + void slotGroupGeneralTuplet(); + void slotGroupTuplet(bool simple); + void slotGroupUnTuplet(); + void slotGroupSlur(); + void slotGroupPhrasingSlur(); + void slotGroupGlissando(); + void slotGroupCrescendo(); + void slotGroupDecrescendo(); + void slotGroupMakeChord(); + void slotGroupOctave2Up(); + void slotGroupOctaveUp(); + void slotGroupOctaveDown(); + void slotGroupOctave2Down(); + void slotAddIndication(std::string type, QString cat); + + /// transforms slots + void slotTransformsNormalizeRests(); + void slotTransformsCollapseRests(); + void slotTransformsCollapseNotes(); + void slotTransformsTieNotes(); + void slotTransformsUntieNotes(); + void slotTransformsMakeNotesViable(); + void slotTransformsDeCounterpoint(); + void slotTransformsStemsUp(); + void slotTransformsStemsDown(); + void slotTransformsRestoreStems(); + void slotTransformsSlursAbove(); + void slotTransformsSlursBelow(); + void slotTransformsRestoreSlurs(); + void slotTransformsTiesAbove(); + void slotTransformsTiesBelow(); + void slotTransformsRestoreTies(); + void slotTransformsQuantize(); + void slotTransformsFixQuantization(); + void slotTransformsRemoveQuantization(); + void slotTransformsInterpret(); + + void slotRespellDoubleFlat(); + void slotRespellFlat(); + void slotRespellNatural(); + void slotRespellSharp(); + void slotRespellDoubleSharp(); + void slotRespellUp(); + void slotRespellDown(); + void slotRespellRestore(); + void slotShowCautionary(); + void slotCancelCautionary(); + + void slotSetStyleFromAction(); + void slotInsertNoteFromAction(); + void slotInsertRest(); + void slotSwitchFromRestToNote(); + void slotSwitchFromNoteToRest(); + void slotToggleDot(); + + void slotAddMark(); + void slotMarksAddTextMark(); + void slotMarksAddFingeringMark(); + void slotMarksAddFingeringMarkFromAction(); + void slotMarksRemoveMarks(); + void slotMarksRemoveFingeringMarks(); + void slotMakeOrnament(); + void slotUseOrnament(); + void slotRemoveOrnament(); + + void slotNoteChangeAction(); + void slotSetNoteDurations(Note::Type, bool notationOnly); + void slotAddDot(); + void slotAddDotNotationOnly(); + + void slotAddSlashes(); + + void slotEditAddClef(); + void slotEditAddKeySignature(); + void slotEditAddSustainDown(); + void slotEditAddSustainUp(); + void slotEditAddSustain(bool down); + void slotEditTranspose(); + void slotEditSwitchPreset(); + void slotEditElement(NotationStaff *, NotationElement *, bool advanced); + + void slotFinePositionLeft(); + void slotFinePositionRight(); + void slotFinePositionUp(); + void slotFinePositionDown(); + void slotFinePositionRestore(); + + void slotMakeVisible(); + void slotMakeInvisible(); + + void slotDebugDump(); + + /// Canvas actions slots + + /** + * Called when a mouse press occurred on a notation element + * or somewhere on a staff + */ + void slotItemPressed(int height, int staffNo, QMouseEvent*, NotationElement*); + + /** + * Called when a mouse press occurred on a non-notation element + */ + void slotNonNotationItemPressed(QMouseEvent *e, QCanvasItem *i); + + /** + * Called when a mouse press occurred on a QCanvasText + */ + void slotTextItemPressed(QMouseEvent *e, QCanvasItem *i); + + void slotMouseMoved(QMouseEvent*); + void slotMouseReleased(QMouseEvent*); + + /** + * Called when the mouse cursor moves over a different height on + * the staff + * + * @see NotationCanvasView#hoveredOverNoteChange() + */ + void slotHoveredOverNoteChanged(const QString&); + + /** + * Called when the mouse cursor moves over a note which is at a + * different time on the staff + * + * @see NotationCanvasView#hoveredOverAbsoluteTimeChange() + */ + void slotHoveredOverAbsoluteTimeChanged(unsigned int); + + /** + * Set the time pointer position during playback (purely visual, + * doesn't affect playback). This is also at liberty to highlight + * some notes, if it so desires... + */ + void slotSetPointerPosition(timeT position); + + /** + * As above, but with the ability to specify whether to scroll or + * not to follow the pointer (above method uses the play tracking + * setting to determine that) + */ + void slotSetPointerPosition(timeT position, bool scroll); + + /** + * Update the recording segment if it's one of the ones in the + * view + */ + void slotUpdateRecordingSegment(Segment *recordingSegment, + timeT updatedFrom); + + /// Set the current staff to the one containing the given canvas Y coord + void slotSetCurrentStaff(double canvasX, int canvasY); + + /// Set the current staff to that with the given id + void slotSetCurrentStaff(int staffNo); + + /** + * Set the insert cursor position (from the top LoopRuler). + * If the segment has recently been changed and no refresh has + * occurred since, pass updateNow false; then the move will + * happen on the next update. + */ + void slotSetInsertCursorPosition(timeT position, + bool scroll, bool updateNow); + + virtual void slotSetInsertCursorPosition(timeT position) { + slotSetInsertCursorPosition(position, true, true); + } + + /// Set the insert cursor position from a mouse event location + void slotSetInsertCursorPosition(double canvasX, int canvasY, + bool scroll, bool updateNow); + + void slotSetInsertCursorPosition(double canvasX, int canvasY) { + slotSetInsertCursorPosition(canvasX, canvasY, true, true); + } + + /** + * Set the insert cursor position and scroll so it's at given point. + * If the segment has recently been changed and no refresh has + * occurred since, pass updateNow false; then the move will + * happen on the next update. + */ + void slotSetInsertCursorAndRecentre(timeT position, + double cx, int cy, + bool updateNow = true); + + void slotSetInsertCursorAndRecentre(timeT position, + double cx, double cy) { + slotSetInsertCursorAndRecentre(position, cx, static_cast<int>(cy), true); + } + + /// Set insert cursor to playback pointer position + void slotJumpCursorToPlayback(); + + /// Set playback pointer to insert cursor position (affects playback) + void slotJumpPlaybackToCursor(); + + /// Toggle tracking with the position pointer during playback + void slotToggleTracking(); + + /// Change the current staff to the one preceding the current one + void slotCurrentStaffUp(); + + /// Change the current staff to the one following the current one + void slotCurrentStaffDown(); + + /// Change the current segment to the one following the current one + void slotCurrentSegmentPrior(); + + /// Change the current segment to the one preceding the current one + void slotCurrentSegmentNext(); + + /// Changes the font of the staffs on the view, gets font name from sender + void slotChangeFontFromAction(); + + /// Changes the font of the staffs on the view + void slotChangeFont(std::string newFont); + + /// Changes the font and font size of the staffs on the view + void slotChangeFont(std::string newFont, int newSize); + + /// Changes the font of the staffs on the view + void slotChangeFont(const QString &newFont); + + /// Changes the font size of the staffs on the view + void slotChangeFontSize(int newSize); + + /// Changes the font size of the staffs on the view, gets size from sender + void slotChangeFontSizeFromAction(); + + /// Changes the font size of the staffs on the view to the nth size in the available size list + void slotChangeFontSizeFromStringValue(const QString&); + + /// Changes to the next font size up + void slotZoomIn(); + + /// Changes to the next font size down + void slotZoomOut(); + + /// Changes the hlayout spacing of the staffs on the view + void slotChangeSpacing(int newSpacing); + + /// Changes the hlayout spacing of the staffs on the view + void slotChangeSpacingFromStringValue(const QString&); + + /// Changes the hlayout spacing of the staffs on the view + void slotChangeSpacingFromAction(); + + /// Changes the hlayout proportion of the staffs on the view + void slotChangeProportion(int newProportion); + + /// Changes the hlayout proportion of the staffs on the view + void slotChangeProportionFromIndex(int newProportionIndex); + + /// Changes the hlayout proportion of the staffs on the view + void slotChangeProportionFromAction(); + + /// Note-on received asynchronously -- consider step-by-step editing + void slotInsertableNoteOnReceived(int pitch, int velocity); + + /// Note-off received asynchronously -- consider step-by-step editing + void slotInsertableNoteOffReceived(int pitch, int velocity); + + /// Note-on or note-off received asynchronously -- as above + void slotInsertableNoteEventReceived(int pitch, int velocity, bool noteOn); + + /// A timer set when a note-on event was received has elapsed + void slotInsertableTimerElapsed(); + + /// The given QObject has originated a step-by-step-editing request + void slotStepByStepTargetRequested(QObject *); + + /// Do on-demand rendering for a region. + void slotCheckRendered(double cx0, double cx1); + + /// Do some background rendering work. + void slotRenderSomething(); + + void slotSetOperationNameAndStatus(QString); + + // Update notation view based on track/staff name change + void slotUpdateStaffName(); + + // LilyPond Directive slots + void slotBeginLilyPondRepeat(); + +signals: + /** + * Emitted when the note selected in the palette changes + */ + void changeCurrentNote(bool isRest, Note::Type); + + /** + * Emitted when a new accidental has been choosen by the user + */ + void changeAccidental(Accidental, bool follow); + + /** + * Emitted when the selection has been cut or copied + * + * @see NotationSelector#hideSelection + */ + void usedSelection(); + + void play(); + void stop(); + void fastForwardPlayback(); + void rewindPlayback(); + void fastForwardPlaybackToEnd(); + void rewindPlaybackToBeginning(); + void jumpPlaybackTo(timeT); + void panic(); + + /// progress Report + void setProgress(int); + void incrementProgress(int); + void setOperationName(QString); + + void stepByStepTargetRequested(QObject *); + + void renderComplete(); + + void editTimeSignature(timeT); + + void editMetadata(QString); + + void editTriggerSegment(int); + + void staffLabelChanged(TrackId id, QString label); + +protected: + + virtual void paintEvent(QPaintEvent* e); + + /** + * init the action maps for notes, marks etc + */ + void initActionDataMaps(); + +protected slots: + /** + * save general Options like all bar positions and status as well + * as the geometry and the recent file list to the configuration + * file + */ + virtual void slotSaveOptions(); + +protected: + + /** + * read general Options again and initialize all variables like the recent file list + */ + virtual void readOptions(); + + void setOneToolbar(const char *actionName, + const char *toolbarName); + + /** + * create menus and toolbars + */ + virtual void setupActions(); + + /** + * create or re-initialise (after font change) the font size menu + */ + virtual void setupFontSizeMenu(std::string oldFontName = ""); + + /** + * Set KDE3+ menu states based on the current selection + */ + virtual void setMenuStates(); + + /** + * setup status bar + */ + virtual void initStatusBar(); + + /** + * Place the staffs at the correct x & y coordinates (before layout) + */ + void positionStaffs(); + + /** + * Place the page pixmaps (if any) at the correct x & y + * coordinates (after layout) + */ + void positionPages(); + + /** + * Update the panner thumbnail images. If complete is true, + * copy the entire mini-canvas. + */ + void updateThumbnails(bool complete); + + /** + * setup the layout/font toolbar + */ + void initLayoutToolbar(); + + /** + * Helper function to toggle a toolbar given its name + * If \a force point to a bool, then the bool's value + * is used to show/hide the toolbar. + */ + void toggleNamedToolBar(const QString& toolBarName, bool* force = 0); + + /// Calls all the relevant preparse and layout methods + virtual bool applyLayout(int staffNo = -1, + timeT startTime = 0, + timeT endTime = 0); + + /** + * Readjust the size of the canvas after a layout + * + * Checks the total width computed by the horizontal layout + * + * @see NotationHLayout#getTotalWidth() + */ + void readjustCanvasSize(); + + /** + * Override from EditView + * @see EditView#getViewSize + */ + virtual QSize getViewSize(); + + /** + * Override from EditView + * @see EditView#setViewSize + */ + virtual void setViewSize(QSize); + + /** + * Set the note pixmap factory + * + * The previous pixmap factory is deleted + */ + void setNotePixmapFactory(NotePixmapFactory*); + + virtual NotationCanvasView* getCanvasView(); + + virtual Segment *getCurrentSegment(); + virtual Staff *getCurrentStaff() { return getCurrentLinedStaff(); } + virtual LinedStaff *getCurrentLinedStaff(); + + virtual LinedStaff *getStaffAbove(); + virtual LinedStaff *getStaffBelow(); + + virtual bool hasSegment(Segment *segment); + + /** + * Return the time at which the insert cursor may be found. + */ + virtual timeT getInsertionTime(); + + /** + * Return the time at which the insert cursor may be found, + * and the time signature, clef and key at that time. + */ + virtual timeT getInsertionTime(Clef &clef, + Rosegarden::Key &key); + + void doDeferredCursorMove(); + + void removeViewLocalProperties(Event*); + + void setupProgress(KProgress*); + void setupProgress(ProgressDialog*); + void setupDefaultProgress(); + void disconnectProgress(); + + /** + * Test whether we've had too many preview notes recently + */ + bool canPreviewAnotherNote(); + + virtual void updateViewCaption(); + + void showHeadersGroup(); + void hideHeadersGroup(); + + + //--------------- Data members --------------------------------- + + NotationProperties m_properties; + + /// Displayed in the status bar, shows number of events selected + QLabel *m_selectionCounter; + + /// Displayed in the status bar, shows insertion mode + QLabel *m_insertModeLabel; + + /// Displayed in the status bar, shows when annotations are hidden + QLabel *m_annotationsLabel; + + /// Displayed in the status bar, shows when LilyPond directives are hidden + QLabel *m_lilyPondDirectivesLabel; + + /// Displayed in the status bar, shows progress of current operation + ProgressBar *m_progressBar; + + /// Displayed in the status bar, holds the pixmap of the current note + QLabel* m_currentNotePixmap; + + /// Displayed in the status bar, shows the pitch the cursor is at + QLabel* m_hoveredOverNoteName; + + /// Displayed in the status bar, shows the absolute time the cursor is at + QLabel* m_hoveredOverAbsoluteTime; + + std::vector<NotationStaff*> m_staffs; + int m_currentStaff; + int m_lastFinishingStaff; + + QCanvasItem *m_title; + QCanvasItem *m_subtitle; + QCanvasItem *m_composer; + QCanvasItem *m_copyright; + std::vector<QCanvasItem *> m_pages; + std::vector<QCanvasItem *> m_pageNumbers; + + timeT m_insertionTime; + enum DeferredCursorMoveType { + NoCursorMoveNeeded, + CursorMoveOnly, + CursorMoveAndMakeVisible, + CursorMoveAndScrollToPosition + }; + DeferredCursorMoveType m_deferredCursorMove; + double m_deferredCursorScrollToX; + + QString m_lastNoteAction; + + std::string m_fontName; + int m_fontSize; + LinedStaff::PageMode m_pageMode; + int m_leftGutter; + + NotePixmapFactory *m_notePixmapFactory; + + NotationHLayout* m_hlayout; + NotationVLayout* m_vlayout; + + ChordNameRuler *m_chordNameRuler; + QWidget *m_tempoRuler; + RawNoteRuler *m_rawNoteRuler; + bool m_annotationsVisible; + bool m_lilyPondDirectivesVisible; + + KAction* m_selectDefaultNote; + + typedef QMap<QString, NoteActionData *> NoteActionDataMap; + static NoteActionDataMap* m_noteActionDataMap; + + typedef QMap<QString, NoteChangeActionData *> NoteChangeActionDataMap; + static NoteChangeActionDataMap* m_noteChangeActionDataMap; + + typedef QMap<QString, MarkActionData *> MarkActionDataMap; + static MarkActionDataMap *m_markActionDataMap; + + KComboBox *m_fontCombo; + KComboBox *m_fontSizeCombo; + KComboBox *m_spacingCombo; + KActionMenu *m_fontSizeActionMenu; + ScrollBoxDialog *m_pannerDialog; + QTimer *m_renderTimer; + + bool m_playTracking; + + std::vector<std::pair<int, int> > m_pendingInsertableNotes; + + enum { PROGRESS_NONE, + PROGRESS_BAR, + PROGRESS_DIALOG } m_progressDisplayer; + + bool m_inhibitRefresh; + bool m_ok; + + bool m_printMode; + int m_printSize; + + static std::map<KProcess *, KTempFile *> m_lilyTempFileMap; + + int m_showHeadersGroup; + QDeferScrollView * m_headersGroupView; + HeadersGroup * m_headersGroup; + QFrame * m_headersTopFrame; + + KAction * m_showHeadersMenuEntry; + +}; + + +} + +#endif diff --git a/src/gui/editors/notation/NoteCharacter.cpp b/src/gui/editors/notation/NoteCharacter.cpp new file mode 100644 index 0000000..fdcb578 --- /dev/null +++ b/src/gui/editors/notation/NoteCharacter.cpp @@ -0,0 +1,133 @@ +/* -*- 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 "NoteCharacter.h" + +#include <qpainter.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qcanvas.h> +#include <qbitmap.h> + + +namespace Rosegarden +{ + +NoteCharacter::NoteCharacter() : + m_hotspot(0, 0), + m_pixmap(new QPixmap()), + m_rep(0) +{} + +NoteCharacter::NoteCharacter(QPixmap pixmap, + QPoint hotspot, NoteCharacterDrawRep *rep) : + m_hotspot(hotspot), + m_pixmap(new QPixmap(pixmap)), + m_rep(rep) +{} + +NoteCharacter::NoteCharacter(const NoteCharacter &c) : + m_hotspot(c.m_hotspot), + m_pixmap(new QPixmap(*c.m_pixmap)), + m_rep(c.m_rep) +{ + // nothing else +} + +NoteCharacter & +NoteCharacter::operator=(const NoteCharacter &c) +{ + if (&c == this) + return * this; + m_hotspot = c.m_hotspot; + m_pixmap = new QPixmap(*c.m_pixmap); + m_rep = c.m_rep; + return *this; +} + +NoteCharacter::~NoteCharacter() +{ + delete m_pixmap; +} + +int +NoteCharacter::getWidth() const +{ + return m_pixmap->width(); +} + +int +NoteCharacter::getHeight() const +{ + return m_pixmap->height(); +} + +QPoint +NoteCharacter::getHotspot() const +{ + return m_hotspot; +} + +QPixmap * +NoteCharacter::getPixmap() const +{ + return m_pixmap; +} + +QCanvasPixmap * +NoteCharacter::getCanvasPixmap() const +{ + return new QCanvasPixmap(*m_pixmap, m_hotspot); +} + +void +NoteCharacter::draw(QPainter *painter, int x, int y) const +{ + if (!m_rep) { + + painter->drawPixmap(x, y, *m_pixmap); + + } else { + + NoteCharacterDrawRep a(m_rep->size()); + + for (unsigned int i = 0; i < m_rep->size(); ++i) { + QPoint p(m_rep->point(i)); + a.setPoint(i, p.x() + x, p.y() + y); + } + + painter->drawLineSegments(a); + } +} + +void +NoteCharacter::drawMask(QPainter *painter, int x, int y) const +{ + if (!m_rep && m_pixmap->mask()) { + painter->drawPixmap(x, y, *(m_pixmap->mask())); + } +} + +} diff --git a/src/gui/editors/notation/NoteCharacter.h b/src/gui/editors/notation/NoteCharacter.h new file mode 100644 index 0000000..bc9359e --- /dev/null +++ b/src/gui/editors/notation/NoteCharacter.h @@ -0,0 +1,93 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTECHARACTER_H_ +#define _RG_NOTECHARACTER_H_ + +#include <qpixmap.h> +#include <qpoint.h> +#include <qpointarray.h> + + +class QPainter; +class QCanvasPixmap; + +namespace Rosegarden +{ + +class NoteCharacterDrawRep : public QPointArray +{ +public: + NoteCharacterDrawRep(int size = 0) : QPointArray(size) { } +}; + + +/** + * NoteCharacter knows how to draw a character from a font. It may be + * optimised for screen (using QPixmap underneath to produce + * low-resolution colour or greyscale glyphs) or printer (using some + * internal representation to draw in high-resolution monochrome on a + * print device). You can use screen characters on a printer and vice + * versa, but the performance and quality might not be as good. + * + * NoteCharacter objects are always constructed by the NoteFont, never + * directly. + */ + +class NoteCharacter +{ +public: + NoteCharacter(); + NoteCharacter(const NoteCharacter &); + NoteCharacter &operator=(const NoteCharacter &); + ~NoteCharacter(); + + int getWidth() const; + int getHeight() const; + + QPoint getHotspot() const; + + QPixmap *getPixmap() const; + QCanvasPixmap *getCanvasPixmap() const; + + void draw(QPainter *painter, int x, int y) const; + void drawMask(QPainter *painter, int x, int y) const; + +private: + friend class NoteFont; + NoteCharacter(QPixmap pixmap, QPoint hotspot, NoteCharacterDrawRep *rep); + + QPoint m_hotspot; + QPixmap *m_pixmap; // I own this + NoteCharacterDrawRep *m_rep; // I don't own this, it's a reference to a static in the NoteFont +}; + + +// Encapsulates NoteFontMap, and loads pixmaps etc on demand + + +} + +#endif diff --git a/src/gui/editors/notation/NoteCharacterNames.cpp b/src/gui/editors/notation/NoteCharacterNames.cpp new file mode 100644 index 0000000..bcd450c --- /dev/null +++ b/src/gui/editors/notation/NoteCharacterNames.cpp @@ -0,0 +1,123 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A 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 right of the authors to claim authorship of this work + has been asserted. + + 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 "NoteCharacterNames.h" + +namespace Rosegarden +{ + +namespace NoteCharacterNames +{ + +const CharName SHARP = "MUSIC SHARP SIGN"; +const CharName FLAT = "MUSIC FLAT SIGN"; +const CharName NATURAL = "MUSIC NATURAL SIGN"; +const CharName DOUBLE_SHARP = "MUSICAL SYMBOL DOUBLE SHARP"; +const CharName DOUBLE_FLAT = "MUSICAL SYMBOL DOUBLE FLAT"; + +const CharName BREVE = "MUSICAL SYMBOL BREVE"; +const CharName WHOLE_NOTE = "MUSICAL SYMBOL WHOLE NOTE"; +const CharName VOID_NOTEHEAD = "MUSICAL SYMBOL VOID NOTEHEAD"; +const CharName NOTEHEAD_BLACK = "MUSICAL SYMBOL NOTEHEAD BLACK"; + +const CharName X_NOTEHEAD = "MUSICAL SYMBOL X NOTEHEAD"; +const CharName CIRCLE_X_NOTEHEAD = "MUSICAL SYMBOL CIRCLE X NOTEHEAD"; +const CharName BREVIS = "MUSICAL SYMBOL BREVIS"; +const CharName SEMIBREVIS_WHITE = "MUSICAL SYMBOL SEMIBREVIS WHITE"; +const CharName SEMIBREVIS_BLACK = "MUSICAL SYMBOL SEMIBREVIS BLACK"; +const CharName TRIANGLE_NOTEHEAD_UP_WHITE = "MUSICAL SYMBOL TRIANGLE NOTEHEAD UP WHITE"; +const CharName TRIANGLE_NOTEHEAD_UP_BLACK = "MUSICAL SYMBOL TRIANGLE NOTEHEAD UP BLACK"; +const CharName SQUARE_NOTEHEAD_WHITE = "MUSICAL SYMBOL SQUARE NOTEHEAD WHITE"; +const CharName SQUARE_NOTEHEAD_BLACK = "MUSICAL SYMBOL SQUARE NOTEHEAD BLACK"; + +// These two names are not valid Unicode names. They describe flags +// that should be used to compose multi-flag notes, rather than used +// on their own. Unicode has no code point for these, but they're +// common in real fonts. COMBINING PARTIAL FLAG is a flag that may be +// drawn several times to make a multi-flag note; COMBINING PARTIAL +// FLAG FINAL may be used as the flag nearest the note head and may +// have an additional swash. (In many fonts, the FLAG 1 character may +// also be suitable for use as PARTIAL FLAG FINAL). +const CharName FLAG_PARTIAL = "MUSICAL SYMBOL COMBINING PARTIAL FLAG"; +const CharName FLAG_PARTIAL_FINAL = "MUSICAL SYMBOL COMBINING PARTIAL FLAG FINAL"; + +const CharName FLAG_1 = "MUSICAL SYMBOL COMBINING FLAG-1"; +const CharName FLAG_2 = "MUSICAL SYMBOL COMBINING FLAG-2"; +const CharName FLAG_3 = "MUSICAL SYMBOL COMBINING FLAG-3"; +const CharName FLAG_4 = "MUSICAL SYMBOL COMBINING FLAG-4"; + +const CharName MULTI_REST = "MUSICAL SYMBOL MULTI REST"; // Unicode-4 glyph 1D13A +const CharName MULTI_REST_ON_STAFF = "MUSICAL SYMBOL MULTI REST ON STAFF"; +const CharName WHOLE_REST = "MUSICAL SYMBOL WHOLE REST"; // Unicode-4 glyph 1D13B +const CharName WHOLE_REST_ON_STAFF = "MUSICAL SYMBOL WHOLE REST ON STAFF"; +const CharName HALF_REST = "MUSICAL SYMBOL HALF REST"; // Unicode-4 glyph 1D13C +const CharName HALF_REST_ON_STAFF = "MUSICAL SYMBOL HALF REST ON STAFF"; +const CharName QUARTER_REST = "MUSICAL SYMBOL QUARTER REST"; +const CharName EIGHTH_REST = "MUSICAL SYMBOL EIGHTH REST"; +const CharName SIXTEENTH_REST = "MUSICAL SYMBOL SIXTEENTH REST"; +const CharName THIRTY_SECOND_REST = "MUSICAL SYMBOL THIRTY-SECOND REST"; +const CharName SIXTY_FOURTH_REST = "MUSICAL SYMBOL SIXTY-FOURTH REST"; + +const CharName DOT = "MUSICAL SYMBOL COMBINING AUGMENTATION DOT"; + +const CharName ACCENT = "MUSICAL SYMBOL COMBINING ACCENT"; +const CharName TENUTO = "MUSICAL SYMBOL COMBINING TENUTO"; +const CharName STACCATO = "MUSICAL SYMBOL COMBINING STACCATO"; +const CharName STACCATISSIMO = "MUSICAL SYMBOL COMBINING STACCATISSIMO"; +const CharName MARCATO = "MUSICAL SYMBOL COMBINING MARCATO"; +const CharName FERMATA = "MUSICAL SYMBOL FERMATA"; +const CharName TRILL = "MUSICAL SYMBOL TR"; +const CharName TRILL_LINE = "MUSICAL SYMBOL COMBINING TRILL LINE"; +const CharName TURN = "MUSICAL SYMBOL TURN"; + +const CharName MORDENT = "MUSICAL SYMBOL MORDENT"; +const CharName MORDENT_INVERTED = "MUSICAL SYMBOL INVERTED MORDENT"; +const CharName MORDENT_LONG = "MUSICAL SYMBOL LONG MORDENT"; +const CharName MORDENT_LONG_INVERTED = "MUSICAL SYMBOL LONG INVERTED MORDENT"; + +const CharName PEDAL_MARK = "MUSICAL SYMBOL PEDAL MARK"; +const CharName PEDAL_UP_MARK = "MUSICAL SYMBOL PEDAL UP MARK"; + +const CharName UP_BOW = "MUSICAL SYMBOL COMBINING UP BOW"; +const CharName DOWN_BOW = "MUSICAL SYMBOL COMBINING DOWN BOW"; + +const CharName C_CLEF = "MUSICAL SYMBOL C CLEF"; +const CharName G_CLEF = "MUSICAL SYMBOL G CLEF"; +const CharName F_CLEF = "MUSICAL SYMBOL F CLEF"; + +const CharName COMMON_TIME = "MUSICAL SYMBOL COMMON TIME"; +const CharName CUT_TIME = "MUSICAL SYMBOL CUT TIME"; +const CharName DIGIT_ZERO = "DIGIT ZERO"; +const CharName DIGIT_ONE = "DIGIT ONE"; +const CharName DIGIT_TWO = "DIGIT TWO"; +const CharName DIGIT_THREE = "DIGIT THREE"; +const CharName DIGIT_FOUR = "DIGIT FOUR"; +const CharName DIGIT_FIVE = "DIGIT FIVE"; +const CharName DIGIT_SIX = "DIGIT SIX"; +const CharName DIGIT_SEVEN = "DIGIT SEVEN"; +const CharName DIGIT_EIGHT = "DIGIT EIGHT"; +const CharName DIGIT_NINE = "DIGIT NINE"; + +const CharName UNKNOWN = "__UNKNOWN__"; + +} + +} diff --git a/src/gui/editors/notation/NoteCharacterNames.h b/src/gui/editors/notation/NoteCharacterNames.h new file mode 100644 index 0000000..9022ecd --- /dev/null +++ b/src/gui/editors/notation/NoteCharacterNames.h @@ -0,0 +1,120 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A 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 right of the authors to claim authorship of this work + has been asserted. + + 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. +*/ + +#ifndef _NOTE_CHAR_NAME_H_ +#define _NOTE_CHAR_NAME_H_ + +#include "PropertyName.h" + +namespace Rosegarden { + +typedef PropertyName CharName; + +/// A selection of Unicode character names for symbols in a note font + +namespace NoteCharacterNames +{ +extern const CharName SHARP; +extern const CharName FLAT; +extern const CharName NATURAL; +extern const CharName DOUBLE_SHARP; +extern const CharName DOUBLE_FLAT; + +extern const CharName BREVE; +extern const CharName WHOLE_NOTE; +extern const CharName VOID_NOTEHEAD; +extern const CharName NOTEHEAD_BLACK; + +extern const CharName X_NOTEHEAD; +extern const CharName CIRCLE_X_NOTEHEAD; +extern const CharName SEMIBREVIS_WHITE; +extern const CharName SEMIBREVIS_BLACK; +extern const CharName TRIANGLE_NOTEHEAD_UP_WHITE; +extern const CharName TRIANGLE_NOTEHEAD_UP_BLACK; +extern const CharName SQUARE_NOTEHEAD_WHITE; +extern const CharName SQUARE_NOTEHEAD_BLACK; + +extern const CharName FLAG_PARTIAL; +extern const CharName FLAG_PARTIAL_FINAL; + +extern const CharName FLAG_1; +extern const CharName FLAG_2; +extern const CharName FLAG_3; +extern const CharName FLAG_4; + +extern const CharName MULTI_REST; +extern const CharName MULTI_REST_ON_STAFF; +extern const CharName WHOLE_REST; +extern const CharName WHOLE_REST_ON_STAFF; +extern const CharName HALF_REST; +extern const CharName HALF_REST_ON_STAFF; +extern const CharName QUARTER_REST; +extern const CharName EIGHTH_REST; +extern const CharName SIXTEENTH_REST; +extern const CharName THIRTY_SECOND_REST; +extern const CharName SIXTY_FOURTH_REST; + +extern const CharName DOT; + +extern const CharName ACCENT; +extern const CharName TENUTO; +extern const CharName STACCATO; +extern const CharName STACCATISSIMO; +extern const CharName MARCATO; +extern const CharName FERMATA; +extern const CharName TRILL; +extern const CharName TRILL_LINE; +extern const CharName TURN; +extern const CharName UP_BOW; +extern const CharName DOWN_BOW; + +extern const CharName MORDENT; +extern const CharName MORDENT_INVERTED; +extern const CharName MORDENT_LONG; +extern const CharName MORDENT_LONG_INVERTED; + +extern const CharName PEDAL_MARK; +extern const CharName PEDAL_UP_MARK; + +extern const CharName C_CLEF; +extern const CharName G_CLEF; +extern const CharName F_CLEF; + +extern const CharName COMMON_TIME; +extern const CharName CUT_TIME; +extern const CharName DIGIT_ZERO; +extern const CharName DIGIT_ONE; +extern const CharName DIGIT_TWO; +extern const CharName DIGIT_THREE; +extern const CharName DIGIT_FOUR; +extern const CharName DIGIT_FIVE; +extern const CharName DIGIT_SIX; +extern const CharName DIGIT_SEVEN; +extern const CharName DIGIT_EIGHT; +extern const CharName DIGIT_NINE; + +extern const CharName UNKNOWN; +} + +} + +#endif + diff --git a/src/gui/editors/notation/NoteFont.cpp b/src/gui/editors/notation/NoteFont.cpp new file mode 100644 index 0000000..95746c3 --- /dev/null +++ b/src/gui/editors/notation/NoteFont.cpp @@ -0,0 +1,650 @@ +/* -*- 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 "NoteFont.h" +#include "misc/Debug.h" + +#include "misc/Strings.h" +#include "base/Exception.h" +#include "gui/general/PixmapFunctions.h" +#include "NoteCharacter.h" +#include "NoteFontMap.h" +#include "SystemFont.h" +#include <qbitmap.h> +#include <qgarray.h> +#include <qimage.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qstring.h> +#include <qstringlist.h> + + +namespace Rosegarden +{ + +NoteFont::FontPixmapMap *NoteFont::m_fontPixmapMap = 0; + +NoteFont::DrawRepMap *NoteFont::m_drawRepMap = 0; +QPixmap *NoteFont::m_blankPixmap = 0; + + +NoteFont::NoteFont(std::string fontName, int size) : + m_fontMap(fontName) +{ + // Do the size checks first, to avoid doing the extra work if they fail + + std::set<int> sizes = m_fontMap.getSizes(); + + if (sizes.size() > 0) { + m_size = *sizes.begin(); + } else { + throw BadNoteFont(std::string("No sizes listed for font ") + fontName); + } + + if (size > 0) { + if (sizes.find(size) == sizes.end()) { + throw BadNoteFont(qstrtostr(QString("Font \"%1\" not available in size %2").arg(strtoqstr(fontName)).arg(size))); + } else { + m_size = size; + } + } + + // Create the global font map and blank pixmap if necessary + + if (m_fontPixmapMap == 0) { + m_fontPixmapMap = new FontPixmapMap(); + } + + if (m_blankPixmap == 0) { + m_blankPixmap = new QPixmap(10, 10); + m_blankPixmap->setMask(QBitmap(10, 10, TRUE)); + } + + // Locate our font's pixmap map in the font map, create if necessary + + std::string fontKey = qstrtostr(QString("__%1__%2__") + .arg(strtoqstr(m_fontMap.getName())) + .arg(m_size)); + + FontPixmapMap::iterator i = m_fontPixmapMap->find(fontKey); + if (i == m_fontPixmapMap->end()) { + (*m_fontPixmapMap)[fontKey] = new PixmapMap(); + } + + m_map = (*m_fontPixmapMap)[fontKey]; +} + +NoteFont::~NoteFont() +{ + // empty +} + +bool +NoteFont::getStemThickness(unsigned int &thickness) const +{ + thickness = m_size / 9 + 1; + return m_fontMap.getStemThickness(m_size, thickness); +} + +bool +NoteFont::getBeamThickness(unsigned int &thickness) const +{ + thickness = m_size / 2; + return m_fontMap.getBeamThickness(m_size, thickness); +} + +bool +NoteFont::getStemLength(unsigned int &length) const +{ + getStaffLineThickness(length); + length = (m_size + length) * 7 / 2; + return m_fontMap.getStemLength(m_size, length); +} + +bool +NoteFont::getFlagSpacing(unsigned int &spacing) const +{ + spacing = m_size; + return m_fontMap.getFlagSpacing(m_size, spacing); +} + +bool +NoteFont::getStaffLineThickness(unsigned int &thickness) const +{ + thickness = (m_size < 7 ? 1 : m_size / 7); + return m_fontMap.getStaffLineThickness(m_size, thickness); +} + +bool +NoteFont::getLegerLineThickness(unsigned int &thickness) const +{ + thickness = (m_size < 6 ? 1 : m_size / 6); + return m_fontMap.getLegerLineThickness(m_size, thickness); +} + +bool +NoteFont::lookup(CharName charName, bool inverted, QPixmap *&pixmap) const +{ + PixmapMap::iterator i = m_map->find(charName); + if (i != m_map->end()) { + if (inverted) { + pixmap = i->second.second; + if (!pixmap && i->second.first) + return false; + } else { + pixmap = i->second.first; + if (!pixmap && i->second.second) + return false; + } + return true; + } + pixmap = 0; + return false; +} + +void +NoteFont::add +(CharName charName, bool inverted, QPixmap *pixmap) const +{ + PixmapMap::iterator i = m_map->find(charName); + if (i != m_map->end()) { + if (inverted) { + delete i->second.second; + i->second.second = pixmap; + } else { + delete i->second.first; + i->second.first = pixmap; + } + } else { + if (inverted) { + (*m_map)[charName] = PixmapPair(0, pixmap); + } else { + (*m_map)[charName] = PixmapPair(pixmap, 0); + } + } +} + +NoteCharacterDrawRep * +NoteFont::lookupDrawRep(QPixmap *pixmap) const +{ + if (!m_drawRepMap) + m_drawRepMap = new DrawRepMap(); + + if (m_drawRepMap->find(pixmap) != m_drawRepMap->end()) { + + return (*m_drawRepMap)[pixmap]; + + } else { + + QImage image = pixmap->convertToImage(); + if (image.isNull()) + return 0; + + if (image.depth() > 1) { + image = image.convertDepth(1, Qt::MonoOnly | Qt::ThresholdDither); + } + + NoteCharacterDrawRep *a = new NoteCharacterDrawRep(); + + for (int yi = 0; yi < image.height(); ++yi) { + + unsigned char *line = image.scanLine(yi); + + int startx = 0; + + for (int xi = 0; xi <= image.width(); ++xi) { + + bool pixel = false; + + if (xi < image.width()) { + if (image.bitOrder() == QImage::LittleEndian) { + if (*(line + (xi >> 3)) & 1 << (xi & 7)) + pixel = true; + } else { + if (*(line + (xi >> 3)) & 1 << (7 - (xi & 7))) + pixel = true; + } + } + + if (!pixel) { + if (startx < xi) { + a->resize(a->size() + 2, QGArray::SpeedOptim); + a->setPoint(a->size() - 2, startx, yi); + a->setPoint(a->size() - 1, xi - 1, yi); + } + startx = xi + 1; + } + } + } + + (*m_drawRepMap)[pixmap] = a; + return a; + } +} + +bool +NoteFont::getPixmap(CharName charName, QPixmap &pixmap, bool inverted) const +{ + QPixmap *found = 0; + bool ok = lookup(charName, inverted, found); + if (ok) { + if (found) { + pixmap = *found; + return true; + } else { + pixmap = *m_blankPixmap; + return false; + } + } + + if (inverted && !m_fontMap.hasInversion(m_size, charName)) { + if (!getPixmap(charName, pixmap, !inverted)) + return false; + found = new QPixmap(PixmapFunctions::flipVertical(pixmap)); + add(charName, inverted, found); + pixmap = *found; + return true; + } + + std::string src; + ok = false; + + if (!inverted) + ok = m_fontMap.getSrc(m_size, charName, src); + else + ok = m_fontMap.getInversionSrc(m_size, charName, src); + + if (ok) { + NOTATION_DEBUG + << "NoteFont::getPixmap: Loading \"" << src << "\"" << endl; + + found = new QPixmap(strtoqstr(src)); + + if (!found->isNull()) { + + if (found->mask() == 0) { + std::cerr << "NoteFont::getPixmap: Warning: No automatic mask " + << "for character \"" << charName << "\"" + << (inverted ? " (inverted)" : "") << " in font \"" + << m_fontMap.getName() << "-" << m_size + << "\"; consider making xpm background transparent" + << std::endl; + found->setMask(PixmapFunctions::generateMask(*found)); + } + + add(charName, inverted, found); + pixmap = *found; + return true; + } + + std::cerr << "NoteFont::getPixmap: Warning: Unable to read pixmap file " << src << std::endl; + } else { + + int code = -1; + if (!inverted) + ok = m_fontMap.getCode(m_size, charName, code); + else + ok = m_fontMap.getInversionCode(m_size, charName, code); + + int glyph = -1; + if (!inverted) + ok = m_fontMap.getGlyph(m_size, charName, glyph); + else + ok = m_fontMap.getInversionGlyph(m_size, charName, glyph); + + if (code < 0 && glyph < 0) { + std::cerr << "NoteFont::getPixmap: Warning: No pixmap, code, or glyph for character \"" + << charName << "\"" << (inverted ? " (inverted)" : "") + << " in font \"" << m_fontMap.getName() << "\"" << std::endl; + add(charName, inverted, 0); + pixmap = *m_blankPixmap; + return false; + } + + int charBase = 0; + SystemFont *systemFont = + m_fontMap.getSystemFont(m_size, charName, charBase); + + if (!systemFont) { + if (!inverted && m_fontMap.hasInversion(m_size, charName)) { + if (!getPixmap(charName, pixmap, !inverted)) + return false; + found = new QPixmap(PixmapFunctions::flipVertical(pixmap)); + add(charName, inverted, found); + pixmap = *found; + return true; + } + + std::cerr << "NoteFont::getPixmap: Warning: No system font for character \"" + << charName << "\"" << (inverted ? " (inverted)" : "") + << " in font \"" << m_fontMap.getName() << "\"" << std::endl; + + add(charName, inverted, 0); + pixmap = *m_blankPixmap; + return false; + } + + SystemFont::Strategy strategy = + m_fontMap.getStrategy(m_size, charName); + + bool success; + found = new QPixmap(systemFont->renderChar(charName, + glyph, + code + charBase, + strategy, + success)); + + if (success) { + add(charName, inverted, found); + pixmap = *found; + return true; + } else { + add(charName, inverted, 0); + pixmap = *m_blankPixmap; + return false; + } + } + + add(charName, inverted, 0); + pixmap = *m_blankPixmap; + return false; +} + +bool +NoteFont::getColouredPixmap(CharName baseCharName, QPixmap &pixmap, + int hue, int minValue, bool inverted) const +{ + CharName charName(getNameWithColour(baseCharName, hue)); + + QPixmap *found = 0; + bool ok = lookup(charName, inverted, found); + if (ok) { + if (found) { + pixmap = *found; + return true; + } else { + pixmap = *m_blankPixmap; + return false; + } + } + + QPixmap basePixmap; + ok = getPixmap(baseCharName, basePixmap, inverted); + + if (!ok) { + add(charName, inverted, 0); + pixmap = *m_blankPixmap; + return false; + } + + found = new QPixmap + (PixmapFunctions::colourPixmap(basePixmap, hue, minValue)); + add(charName, inverted, found); + pixmap = *found; + return ok; +} + +bool +NoteFont::getShadedPixmap(CharName baseCharName, QPixmap &pixmap, + bool inverted) const +{ + CharName charName(getNameShaded(baseCharName)); + + QPixmap *found = 0; + bool ok = lookup(charName, inverted, found); + if (ok) { + if (found) { + pixmap = *found; + return true; + } else { + pixmap = *m_blankPixmap; + return false; + } + } + + QPixmap basePixmap; + ok = getPixmap(baseCharName, basePixmap, inverted); + + if (!ok) { + add(charName, inverted, 0); + pixmap = *m_blankPixmap; + return false; + } + + found = new QPixmap(PixmapFunctions::shadePixmap(basePixmap)); + add(charName, inverted, found); + pixmap = *found; + return ok; +} + +CharName +NoteFont::getNameWithColour(CharName base, int hue) const +{ + return qstrtostr(QString("%1__%2").arg(hue).arg(strtoqstr(base))); +} + +CharName +NoteFont::getNameShaded(CharName base) const +{ + return qstrtostr(QString("shaded__%1").arg(strtoqstr(base))); +} + +bool +NoteFont::getDimensions(CharName charName, int &x, int &y, bool inverted) const +{ + QPixmap pixmap; + bool ok = getPixmap(charName, pixmap, inverted); + x = pixmap.width(); + y = pixmap.height(); + return ok; +} + +int +NoteFont::getWidth(CharName charName) const +{ + int x, y; + getDimensions(charName, x, y); + return x; +} + +int +NoteFont::getHeight(CharName charName) const +{ + int x, y; + getDimensions(charName, x, y); + return y; +} + +bool +NoteFont::getHotspot(CharName charName, int &x, int &y, bool inverted) const +{ + int w, h; + getDimensions(charName, w, h, inverted); + bool ok = m_fontMap.getHotspot(m_size, charName, w, h, x, y); + + if (!ok) { + x = 0; + y = h / 2; + } + + if (inverted) { + y = h - y; + } + + return ok; +} + +QPoint +NoteFont::getHotspot(CharName charName, bool inverted) const +{ + int x, y; + (void)getHotspot(charName, x, y, inverted); + return QPoint(x, y); +} + +bool +NoteFont::getCharacter(CharName charName, + NoteCharacter &character, + CharacterType type, + bool inverted) +{ + QPixmap pixmap; + if (!getPixmap(charName, pixmap, inverted)) + return false; + + if (type == Screen) { + character = NoteCharacter(pixmap, + getHotspot(charName, inverted), + 0); + } else { + + // Get the pointer direct from cache (depends on earlier call + // to getPixmap to put it in the cache if available) + + QPixmap *pmapptr = 0; + bool found = lookup(charName, inverted, pmapptr); + + NoteCharacterDrawRep *rep = 0; + if (found && pmapptr) + rep = lookupDrawRep(pmapptr); + + character = NoteCharacter(pixmap, + getHotspot(charName, inverted), + rep); + } + + return true; +} + +NoteCharacter +NoteFont::getCharacter(CharName charName, + CharacterType type, + bool inverted) +{ + NoteCharacter character; + getCharacter(charName, character, type, inverted); + return character; +} + +bool +NoteFont::getCharacterColoured(CharName charName, + int hue, int minValue, + NoteCharacter &character, + CharacterType type, + bool inverted) +{ + QPixmap pixmap; + if (!getColouredPixmap(charName, pixmap, hue, minValue, inverted)) { + return false; + } + + if (type == Screen) { + + character = NoteCharacter(pixmap, + getHotspot(charName, inverted), + 0); + + } else { + + // Get the pointer direct from cache (depends on earlier call + // to getPixmap to put it in the cache if available) + + QPixmap *pmapptr = 0; + CharName cCharName(getNameWithColour(charName, hue)); + bool found = lookup(cCharName, inverted, pmapptr); + + NoteCharacterDrawRep *rep = 0; + if (found && pmapptr) + rep = lookupDrawRep(pmapptr); + + character = NoteCharacter(pixmap, + getHotspot(charName, inverted), + rep); + } + + return true; +} + +NoteCharacter +NoteFont::getCharacterColoured(CharName charName, + int hue, int minValue, + CharacterType type, + bool inverted) +{ + NoteCharacter character; + getCharacterColoured(charName, hue, minValue, character, type, inverted); + return character; +} + +bool +NoteFont::getCharacterShaded(CharName charName, + NoteCharacter &character, + CharacterType type, + bool inverted) +{ + QPixmap pixmap; + if (!getShadedPixmap(charName, pixmap, inverted)) { + return false; + } + + if (type == Screen) { + + character = NoteCharacter(pixmap, + getHotspot(charName, inverted), + 0); + + } else { + + // Get the pointer direct from cache (depends on earlier call + // to getPixmap to put it in the cache if available) + + QPixmap *pmapptr = 0; + CharName cCharName(getNameShaded(charName)); + bool found = lookup(cCharName, inverted, pmapptr); + + NoteCharacterDrawRep *rep = 0; + if (found && pmapptr) + rep = lookupDrawRep(pmapptr); + + character = NoteCharacter(pixmap, + getHotspot(charName, inverted), + rep); + } + + return true; +} + +NoteCharacter +NoteFont::getCharacterShaded(CharName charName, + CharacterType type, + bool inverted) +{ + NoteCharacter character; + getCharacterShaded(charName, character, type, inverted); + return character; +} + +} diff --git a/src/gui/editors/notation/NoteFont.h b/src/gui/editors/notation/NoteFont.h new file mode 100644 index 0000000..81a3b19 --- /dev/null +++ b/src/gui/editors/notation/NoteFont.h @@ -0,0 +1,184 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTEFONT_H_ +#define _RG_NOTEFONT_H_ + +#include "base/Exception.h" +#include <map> +#include "NoteCharacter.h" +#include "NoteFontMap.h" +#include <set> +#include <string> +#include <qpoint.h> +#include <utility> +#include "gui/editors/notation/NoteCharacterNames.h" + + +class QPixmap; +class PixmapMap; +class NoteCharacterDrawRep; +class FontPixmapMap; +class DrawRepMap; + + +namespace Rosegarden +{ + + + +class NoteFont +{ +public: + enum CharacterType { Screen, Printer }; + + typedef Exception BadNoteFont; + ~NoteFont(); + + std::string getName() const { return m_fontMap.getName(); } + int getSize() const { return m_size; } + bool isSmooth() const { return m_fontMap.isSmooth(); } + const NoteFontMap &getNoteFontMap() const { return m_fontMap; } + + /// Returns false + thickness=1 if not specified + bool getStemThickness(unsigned int &thickness) const; + + /// Returns false + a guess at suitable thickness if not specified + bool getBeamThickness(unsigned int &thickness) const; + + /// Returns false + a guess at suitable length if not specified + bool getStemLength(unsigned int &length) const; + + /// Returns false + a guess at suitable spacing if not specified + bool getFlagSpacing(unsigned int &spacing) const; + + /// Returns false + thickness=1 if not specified + bool getStaffLineThickness(unsigned int &thickness) const; + + /// Returns false + thickness=1 if not specified + bool getLegerLineThickness(unsigned int &thickness) const; + + /// Returns false if not available + bool getCharacter(CharName charName, + NoteCharacter &character, + CharacterType type = Screen, + bool inverted = false); + + /// Returns an empty character if not available + NoteCharacter getCharacter(CharName charName, + CharacterType type = Screen, + bool inverted = false); + + /// Returns false if not available + bool getCharacterColoured(CharName charName, + int hue, int minValue, + NoteCharacter &character, + CharacterType type = Screen, + bool inverted = false); + + /// Returns an empty character if not available + NoteCharacter getCharacterColoured(CharName charName, + int hue, int minValue, + CharacterType type = Screen, + bool inverted = false); + + /// Returns false if not available + bool getCharacterShaded(CharName charName, + NoteCharacter &character, + CharacterType type = Screen, + bool inverted = false); + + /// Returns an empty character if not available + NoteCharacter getCharacterShaded(CharName charName, + CharacterType type = Screen, + bool inverted = false); + + /// Returns false + dimensions of blank pixmap if none found + bool getDimensions(CharName charName, int &x, int &y, + bool inverted = false) const; + + /// Ignores problems, returning dimension of blank pixmap if necessary + int getWidth(CharName charName) const; + + /// Ignores problems, returning dimension of blank pixmap if necessary + int getHeight(CharName charName) const; + + /// Returns false + centre-left of pixmap if no hotspot specified + bool getHotspot(CharName charName, int &x, int &y, + bool inverted = false) const; + + /// Ignores problems, returns centre-left of pixmap if necessary + QPoint getHotspot(CharName charName, bool inverted = false) const; + +private: + /// Returns false + blank pixmap if it can't find the right one + bool getPixmap(CharName charName, QPixmap &pixmap, + bool inverted = false) const; + + /// Returns false + blank pixmap if it can't find the right one + bool getColouredPixmap(CharName charName, QPixmap &pixmap, + int hue, int minValue, + bool inverted = false) const; + + /// Returns false + blank pixmap if it can't find the right one + bool getShadedPixmap(CharName charName, QPixmap &pixmap, + bool inverted = false) const; + + friend class NoteFontFactory; + NoteFont(std::string fontName, int size = 0); + std::set<int> getSizes() const { return m_fontMap.getSizes(); } + + bool lookup(CharName charName, bool inverted, QPixmap *&pixmap) const; + void add(CharName charName, bool inverted, QPixmap *pixmap) const; + + NoteCharacterDrawRep *lookupDrawRep(QPixmap *pixmap) const; + + CharName getNameWithColour(CharName origName, int hue) const; + CharName getNameShaded(CharName origName) const; + + typedef std::pair<QPixmap *, QPixmap *> PixmapPair; + typedef std::map<CharName, PixmapPair> PixmapMap; + typedef std::map<std::string, PixmapMap *> FontPixmapMap; + + typedef std::map<QPixmap *, NoteCharacterDrawRep *> DrawRepMap; + + //--------------- Data members --------------------------------- + + int m_size; + NoteFontMap m_fontMap; + + mutable PixmapMap *m_map; // pointer at a member of m_fontPixmapMap + + static FontPixmapMap *m_fontPixmapMap; + static DrawRepMap *m_drawRepMap; + + static QPixmap *m_blankPixmap; +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/NoteFontFactory.cpp b/src/gui/editors/notation/NoteFontFactory.cpp new file mode 100644 index 0000000..2decce4 --- /dev/null +++ b/src/gui/editors/notation/NoteFontFactory.cpp @@ -0,0 +1,236 @@ +/* -*- 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 "NoteFontFactory.h" +#include "misc/Debug.h" +#include <kapplication.h> + +#include <klocale.h> +#include <kstddirs.h> +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/Exception.h" +#include "gui/kdeext/KStartupLogo.h" +#include "NoteFont.h" +#include "NoteFontMap.h" +#include <kconfig.h> +#include <kglobal.h> +#include <kmessagebox.h> +#include <qdir.h> +#include <qstring.h> +#include <qstringlist.h> +#include <algorithm> + + +namespace Rosegarden +{ + +std::set<std::string> +NoteFontFactory::getFontNames(bool forceRescan) +{ + NOTATION_DEBUG << "NoteFontFactory::getFontNames: forceRescan = " << forceRescan << endl; + + if (forceRescan) + m_fontNames.clear(); + if (!m_fontNames.empty()) + return m_fontNames; + + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + + QString fontNameList = ""; + if (!forceRescan) { + fontNameList = config->readEntry("notefontlist"); + } + + NOTATION_DEBUG << "NoteFontFactory::getFontNames: read from cache: " << fontNameList << endl; + + QStringList names = QStringList::split(",", fontNameList); + + if (names.empty()) { + + NOTATION_DEBUG << "NoteFontFactory::getFontNames: No names available, rescanning..." << endl; + + QString mappingDir = + KGlobal::dirs()->findResource("appdata", "fonts/mappings/"); + QDir dir(mappingDir); + if (!dir.exists()) { + std::cerr << "NoteFontFactory::getFontNames: mapping directory \"" + << mappingDir << "\" not found" << std::endl; + return m_fontNames; + } + + dir.setFilter(QDir::Files | QDir::Readable); + QStringList files = dir.entryList(); + for (QStringList::Iterator i = files.begin(); i != files.end(); ++i) { + + if ((*i).length() > 4 && (*i).right(4).lower() == ".xml") { + + std::string name(qstrtostr((*i).left((*i).length() - 4))); + + try { + NoteFontMap map(name); + if (map.ok()) + names.append(strtoqstr(map.getName())); + } catch (Exception e) { + KStartupLogo::hideIfStillThere(); + KMessageBox::error(0, strtoqstr(e.getMessage())); + throw; + } + } + } + } + + QString savedNames = ""; + + for (QStringList::Iterator i = names.begin(); i != names.end(); ++i) { + m_fontNames.insert(qstrtostr(*i)); + if (i != names.begin()) + savedNames += ","; + savedNames += *i; + } + + config->writeEntry("notefontlist", savedNames); + + return m_fontNames; +} + +std::vector<int> +NoteFontFactory::getAllSizes(std::string fontName) +{ + NoteFont *font = getFont(fontName, 0); + if (!font) + return std::vector<int>(); + + std::set + <int> s(font->getSizes()); + std::vector<int> v; + for (std::set + <int>::iterator i = s.begin(); i != s.end(); ++i) { + v.push_back(*i); + } + + std::sort(v.begin(), v.end()); + return v; +} + +std::vector<int> +NoteFontFactory::getScreenSizes(std::string fontName) +{ + NoteFont *font = getFont(fontName, 0); + if (!font) + return std::vector<int>(); + + std::set + <int> s(font->getSizes()); + std::vector<int> v; + for (std::set + <int>::iterator i = s.begin(); i != s.end(); ++i) { + if (*i <= 16) + v.push_back(*i); + } + std::sort(v.begin(), v.end()); + return v; +} + +NoteFont * +NoteFontFactory::getFont(std::string fontName, int size) +{ + std::map<std::pair<std::string, int>, NoteFont *>::iterator i = + m_fonts.find(std::pair<std::string, int>(fontName, size)); + + if (i == m_fonts.end()) { + try { + NoteFont *font = new NoteFont(fontName, size); + m_fonts[std::pair<std::string, int>(fontName, size)] = font; + return font; + } catch (Exception e) { + KStartupLogo::hideIfStillThere(); + KMessageBox::error(0, strtoqstr(e.getMessage())); + throw; + } + } else { + return i->second; + } +} + +std::string +NoteFontFactory::getDefaultFontName() +{ + static std::string defaultFont = ""; + if (defaultFont != "") + return defaultFont; + + std::set + <std::string> fontNames = getFontNames(); + + if (fontNames.find("Feta") != fontNames.end()) + defaultFont = "Feta"; + else { + fontNames = getFontNames(true); + if (fontNames.find("Feta") != fontNames.end()) + defaultFont = "Feta"; + else if (fontNames.find("Feta Pixmaps") != fontNames.end()) + defaultFont = "Feta Pixmaps"; + else if (fontNames.size() > 0) + defaultFont = *fontNames.begin(); + else { + QString message = i18n("Can't obtain a default font -- no fonts found"); + KStartupLogo::hideIfStillThere(); + KMessageBox::error(0, message); + throw NoFontsAvailable(qstrtostr(message)); + } + } + + return defaultFont; +} + +int +NoteFontFactory::getDefaultSize(std::string fontName) +{ + // always return 8 if it's supported! + std::vector<int> sizes(getScreenSizes(fontName)); + for (unsigned int i = 0; i < sizes.size(); ++i) { + if (sizes[i] == 8) + return sizes[i]; + } + return sizes[sizes.size() / 2]; +} + +bool +NoteFontFactory::isAvailableInSize(std::string fontName, int size) +{ + std::vector<int> sizes(getAllSizes(fontName)); + for (unsigned int i = 0; i < sizes.size(); ++i) { + if (sizes[i] == size) + return true; + } + return false; +} + +std::set<std::string> NoteFontFactory::m_fontNames; +std::map<std::pair<std::string, int>, NoteFont *> NoteFontFactory::m_fonts; + +} diff --git a/src/gui/editors/notation/NoteFontFactory.h b/src/gui/editors/notation/NoteFontFactory.h new file mode 100644 index 0000000..33e6e80 --- /dev/null +++ b/src/gui/editors/notation/NoteFontFactory.h @@ -0,0 +1,71 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTEFONTFACTORY_H_ +#define _RG_NOTEFONTFACTORY_H_ + +#include "base/Exception.h" +#include <map> +#include <set> +#include <string> +#include <vector> + + + + +namespace Rosegarden +{ + +class NoteFont; + + +class NoteFontFactory +{ +public: + typedef Exception NoFontsAvailable; + + // Any method passed a fontName argument may throw BadFont or + // MappingFileReadFailed; any other method may throw NoFontsAvailable + + static NoteFont *getFont(std::string fontName, int size); + + static std::set<std::string> getFontNames(bool forceRescan = false); + static std::vector<int> getAllSizes(std::string fontName); // sorted + static std::vector<int> getScreenSizes(std::string fontName); // sorted + + static std::string getDefaultFontName(); + static int getDefaultSize(std::string fontName); + static bool isAvailableInSize(std::string fontName, int size); + +private: + static std::set<std::string> m_fontNames; + static std::map<std::pair<std::string, int>, NoteFont *> m_fonts; +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/NoteFontMap.cpp b/src/gui/editors/notation/NoteFontMap.cpp new file mode 100644 index 0000000..e11c126 --- /dev/null +++ b/src/gui/editors/notation/NoteFontMap.cpp @@ -0,0 +1,1088 @@ +/* -*- 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 "NoteFontMap.h" +#include "misc/Debug.h" + +#include <klocale.h> +#include <kstddirs.h> +#include "misc/Strings.h" +#include "base/Exception.h" +#include "SystemFont.h" +#include <kglobal.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qpixmap.h> +#include <qregexp.h> +#include <qstring.h> +#include <qstringlist.h> + + +namespace Rosegarden +{ + +NoteFontMap::NoteFontMap(std::string name) : + m_name(name), + m_smooth(false), + m_srcDirectory(name), + m_characterDestination(0), + m_hotspotCharName(""), + m_errorString(i18n("unknown error")), + m_ok(true) +{ + m_fontDirectory = KGlobal::dirs()->findResource("appdata", "fonts/"); + + QString mapFileName; + + QString mapFileMixedName = QString("%1/mappings/%2.xml") + .arg(m_fontDirectory) + .arg(strtoqstr(name)); + + QFileInfo mapFileMixedInfo(mapFileMixedName); + + if (!mapFileMixedInfo.isReadable()) { + + QString lowerName = strtoqstr(name).lower(); + lowerName.replace(QRegExp(" "), "_"); + QString mapFileLowerName = QString("%1/mappings/%2.xml") + .arg(m_fontDirectory) + .arg(lowerName); + + QFileInfo mapFileLowerInfo(mapFileLowerName); + + if (!mapFileLowerInfo.isReadable()) { + if (mapFileLowerName != mapFileMixedName) { + throw MappingFileReadFailed + (qstrtostr(i18n("Can't open font mapping file %1 or %2"). + arg(mapFileMixedName).arg(mapFileLowerName))); + } else { + throw MappingFileReadFailed + (qstrtostr(i18n("Can't open font mapping file %1"). + arg(mapFileMixedName))); + } + } else { + mapFileName = mapFileLowerName; + } + } else { + mapFileName = mapFileMixedName; + } + + QFile mapFile(mapFileName); + + QXmlInputSource source(mapFile); + QXmlSimpleReader reader; + reader.setContentHandler(this); + reader.setErrorHandler(this); + bool ok = reader.parse(source); + mapFile.close(); + + if (!ok) { + throw MappingFileReadFailed(qstrtostr(m_errorString)); + } +} + +NoteFontMap::~NoteFontMap() +{ + for (SystemFontMap::iterator i = m_systemFontCache.begin(); + i != m_systemFontCache.end(); ++i) { + delete i->second; + } +} + +bool +NoteFontMap::characters(QString &chars) +{ + if (!m_characterDestination) + return true; + *m_characterDestination += qstrtostr(chars); + return true; +} + +int +NoteFontMap::toSize(int baseSize, double factor, bool limitAtOne) +{ + double dsize = factor * baseSize; + dsize += 0.5; + if (limitAtOne && dsize < 1.0) + dsize = 1.0; + return int(dsize); +} + +bool +NoteFontMap::startElement(const QString &, const QString &, + const QString &qName, + const QXmlAttributes &attributes) +{ + QString lcName = qName.lower(); + m_characterDestination = 0; + + // The element names are actually unique within the whole file; + // we don't bother checking we're in the right context. Leave that + // to the DTD, when we have one. + + if (lcName == "rosegarden-font-encoding") { + + QString s; + + s = attributes.value("name"); + if (s) { + m_name = qstrtostr(s); + m_srcDirectory = m_name; + } + + } else if (lcName == "font-information") { + + QString s; + + s = attributes.value("origin"); + if (s) + m_origin = qstrtostr(s); + + s = attributes.value("copyright"); + if (s) + m_copyright = qstrtostr(s); + + s = attributes.value("mapped-by"); + if (s) + m_mappedBy = qstrtostr(s); + + s = attributes.value("type"); + if (s) + m_type = qstrtostr(s); + + s = attributes.value("autocrop"); + if (s) { + std::cerr << "Warning: autocrop attribute in note font mapping file is no longer supported\n(all fonts are now always autocropped)" << std::endl; + } + + s = attributes.value("smooth"); + if (s) + m_smooth = (s.lower() == "true"); + + } else if (lcName == "font-sizes") { + } + else if (lcName == "font-size") { + + QString s = attributes.value("note-height"); + if (!s) { + m_errorString = "note-height is a required attribute of font-size"; + return false; + } + int noteHeight = s.toInt(); + + SizeData &sizeData = m_sizes[noteHeight]; + + s = attributes.value("staff-line-thickness"); + if (s) + sizeData.setStaffLineThickness(s.toInt()); + + s = attributes.value("leger-line-thickness"); + if (s) + sizeData.setLegerLineThickness(s.toInt()); + + s = attributes.value("stem-thickness"); + if (s) + sizeData.setStemThickness(s.toInt()); + + s = attributes.value("beam-thickness"); + if (s) + sizeData.setBeamThickness(s.toInt()); + + s = attributes.value("stem-length"); + if (s) + sizeData.setStemLength(s.toInt()); + + s = attributes.value("flag-spacing"); + if (s) + sizeData.setFlagSpacing(s.toInt()); + + s = attributes.value("border-x"); + if (s) { + std::cerr << "Warning: border-x attribute in note font mapping file is no longer supported\n(use hotspot-x for note head or flag)" << std::endl; + } + + s = attributes.value("border-y"); + if (s) { + std::cerr << "Warning: border-y attribute in note font mapping file is no longer supported" << std::endl; + } + + int fontId = 0; + s = attributes.value("font-id"); + if (s) + fontId = s.toInt(); + + s = attributes.value("font-height"); + if (s) + sizeData.setFontHeight(fontId, s.toInt()); + + } else if (lcName == "font-scale") { + + double fontHeight = -1.0; + double beamThickness = -1.0; + double stemLength = -1.0; + double flagSpacing = -1.0; + double staffLineThickness = -1.0; + double legerLineThickness = -1.0; + double stemThickness = -1.0; + + QString s; + + s = attributes.value("font-height"); + if (s) + fontHeight = qstrtodouble(s); + else { + m_errorString = "font-height is a required attribute of font-scale"; + return false; + } + + s = attributes.value("staff-line-thickness"); + if (s) + staffLineThickness = qstrtodouble(s); + + s = attributes.value("leger-line-thickness"); + if (s) + legerLineThickness = qstrtodouble(s); + + s = attributes.value("stem-thickness"); + if (s) + stemThickness = qstrtodouble(s); + + s = attributes.value("beam-thickness"); + if (s) + beamThickness = qstrtodouble(s); + + s = attributes.value("stem-length"); + if (s) + stemLength = qstrtodouble(s); + + s = attributes.value("flag-spacing"); + if (s) + flagSpacing = qstrtodouble(s); + + int fontId = 0; + s = attributes.value("font-id"); + if (s) + fontId = s.toInt(); + + //!!! need to be able to calculate max size -- checkFont needs + //to take a size argument; unfortunately Qt doesn't seem to be + //able to report to us when a scalable font was loaded in the + //wrong size, so large sizes might be significantly inaccurate + //as it just stops scaling up any further at somewhere around + //120px. We could test whether the metric for the black + //notehead is noticeably smaller than the notehead should be, + //and reject if so? [update -- no, that doesn't work either, + //Qt just returns the correct metric even if drawing the + //incorrect size] + + for (int sz = 1; sz <= 30; sz += (sz == 1 ? 1 : 2)) { + + SizeData & sizeData = m_sizes[sz]; + unsigned int temp; + + if (sizeData.getStaffLineThickness(temp) == false && + staffLineThickness >= 0.0) + sizeData.setStaffLineThickness(toSize(sz, staffLineThickness, true)); + + if (sizeData.getLegerLineThickness(temp) == false && + legerLineThickness >= 0.0) + sizeData.setLegerLineThickness(toSize(sz, legerLineThickness, true)); + + if (sizeData.getStemThickness(temp) == false && + stemThickness >= 0.0) + sizeData.setStemThickness(toSize(sz, stemThickness, true)); + + if (sizeData.getBeamThickness(temp) == false && + beamThickness >= 0.0) + sizeData.setBeamThickness(toSize(sz, beamThickness, true)); + + if (sizeData.getStemLength(temp) == false && + stemLength >= 0.0) + sizeData.setStemLength(toSize(sz, stemLength, true)); + + if (sizeData.getFlagSpacing(temp) == false && + flagSpacing >= 0.0) + sizeData.setFlagSpacing(toSize(sz, flagSpacing, true)); + + if (sizeData.getFontHeight(fontId, temp) == false) + sizeData.setFontHeight(fontId, toSize(sz, fontHeight, true)); + } + + } else if (lcName == "font-symbol-map") { + } + else if (lcName == "src-directory") { + + QString d = attributes.value("name"); + if (!d) { + m_errorString = "name is a required attribute of src-directory"; + return false; + } + + m_srcDirectory = qstrtostr(d); + + } else if (lcName == "codebase") { + + int bn = 0, fn = 0; + bool ok; + QString base = attributes.value("base"); + if (!base) { + m_errorString = "base is a required attribute of codebase"; + return false; + } + bn = base.toInt(&ok); + if (!ok || bn < 0) { + m_errorString = + QString("invalid base attribute \"%1\" (must be integer >= 0)"). + arg(base); + return false; + } + + QString fontId = attributes.value("font-id"); + if (!fontId) { + m_errorString = "font-id is a required attribute of codebase"; + return false; + } + fn = fontId.stripWhiteSpace().toInt(&ok); + if (!ok || fn < 0) { + m_errorString = + QString("invalid font-id attribute \"%1\" (must be integer >= 0)"). + arg(fontId); + return false; + } + + m_bases[fn] = bn; + + } else if (lcName == "symbol") { + + QString symbolName = attributes.value("name"); + if (!symbolName) { + m_errorString = "name is a required attribute of symbol"; + return false; + } + SymbolData symbolData; + + QString src = attributes.value("src"); + QString code = attributes.value("code"); + QString glyph = attributes.value("glyph"); + + int icode = -1; + bool ok = false; + if (code) { + icode = code.stripWhiteSpace().toInt(&ok); + if (!ok || icode < 0) { + m_errorString = + QString("invalid code attribute \"%1\" (must be integer >= 0)"). + arg(code); + return false; + } + symbolData.setCode(icode); + } + + int iglyph = -1; + ok = false; + if (glyph) { + iglyph = glyph.stripWhiteSpace().toInt(&ok); + if (!ok || iglyph < 0) { + m_errorString = + QString("invalid glyph attribute \"%1\" (must be integer >= 0)"). + arg(glyph); + return false; + } + symbolData.setGlyph(iglyph); + } + + if (!src && icode < 0 && iglyph < 0) { + m_errorString = "symbol must have either src, code, or glyph attribute"; + return false; + } + if (src) + symbolData.setSrc(qstrtostr(src)); + + QString inversionSrc = attributes.value("inversion-src"); + if (inversionSrc) + symbolData.setInversionSrc(qstrtostr(inversionSrc)); + + QString inversionCode = attributes.value("inversion-code"); + if (inversionCode) { + icode = inversionCode.stripWhiteSpace().toInt(&ok); + if (!ok || icode < 0) { + m_errorString = + QString("invalid inversion code attribute \"%1\" (must be integer >= 0)"). + arg(inversionCode); + return false; + } + symbolData.setInversionCode(icode); + } + + QString inversionGlyph = attributes.value("inversion-glyph"); + if (inversionGlyph) { + iglyph = inversionGlyph.stripWhiteSpace().toInt(&ok); + if (!ok || iglyph < 0) { + m_errorString = + QString("invalid inversion glyph attribute \"%1\" (must be integer >= 0)"). + arg(inversionGlyph); + return false; + } + symbolData.setInversionGlyph(iglyph); + } + + QString fontId = attributes.value("font-id"); + if (fontId) { + int n = fontId.stripWhiteSpace().toInt(&ok); + if (!ok || n < 0) { + m_errorString = + QString("invalid font-id attribute \"%1\" (must be integer >= 0)"). + arg(fontId); + return false; + } + symbolData.setFontId(n); + } + + m_data[qstrtostr(symbolName.upper())] = symbolData; + + } else if (lcName == "font-hotspots") { + } + else if (lcName == "hotspot") { + + QString s = attributes.value("name"); + if (!s) { + m_errorString = "name is a required attribute of hotspot"; + return false; + } + m_hotspotCharName = qstrtostr(s.upper()); + + } else if (lcName == "scaled") { + + if (m_hotspotCharName == "") { + m_errorString = "scaled-element must be in hotspot-element"; + return false; + } + + QString s = attributes.value("x"); + double x = -1.0; + if (s) + x = qstrtodouble(s); + + s = attributes.value("y"); + if (!s) { + m_errorString = "y is a required attribute of scaled"; + return false; + } + double y = qstrtodouble(s); + + HotspotDataMap::iterator i = m_hotspots.find(m_hotspotCharName); + if (i == m_hotspots.end()) { + m_hotspots[m_hotspotCharName] = HotspotData(); + i = m_hotspots.find(m_hotspotCharName); + } + + i->second.setScaledHotspot(x, y); + + } else if (lcName == "fixed") { + + if (m_hotspotCharName == "") { + m_errorString = "fixed-element must be in hotspot-element"; + return false; + } + + QString s = attributes.value("x"); + int x = 0; + if (s) + x = s.toInt(); + + s = attributes.value("y"); + int y = 0; + if (s) + y = s.toInt(); + + HotspotDataMap::iterator i = m_hotspots.find(m_hotspotCharName); + if (i == m_hotspots.end()) { + m_hotspots[m_hotspotCharName] = HotspotData(); + i = m_hotspots.find(m_hotspotCharName); + } + + i->second.addHotspot(0, x, y); + + } else if (lcName == "when") { + + if (m_hotspotCharName == "") { + m_errorString = "when-element must be in hotspot-element"; + return false; + } + + QString s = attributes.value("note-height"); + if (!s) { + m_errorString = "note-height is a required attribute of when"; + return false; + } + int noteHeight = s.toInt(); + + s = attributes.value("x"); + int x = 0; + if (s) + x = s.toInt(); + + s = attributes.value("y"); + if (!s) { + m_errorString = "y is a required attribute of when"; + return false; + } + int y = s.toInt(); + + HotspotDataMap::iterator i = m_hotspots.find(m_hotspotCharName); + if (i == m_hotspots.end()) { + m_hotspots[m_hotspotCharName] = HotspotData(); + i = m_hotspots.find(m_hotspotCharName); + } + + i->second.addHotspot(noteHeight, x, y); + + } else if (lcName == "font-requirements") { + } + else if (lcName == "font-requirement") { + + QString id = attributes.value("font-id"); + int n = -1; + bool ok = false; + if (id) { + n = id.stripWhiteSpace().toInt(&ok); + if (!ok) { + m_errorString = + QString("invalid font-id attribute \"%1\" (must be integer >= 0)"). + arg(id); + return false; + } + } else { + m_errorString = "font-id is a required attribute of font-requirement"; + return false; + } + + QString name = attributes.value("name"); + QString names = attributes.value("names"); + + if (name) { + if (names) { + m_errorString = "font-requirement may have name or names attribute, but not both"; + return false; + } + + SystemFont *font = SystemFont::loadSystemFont + (SystemFontSpec(name, 12)); + + if (font) { + m_systemFontNames[n] = name; + delete font; + } else { + std::cerr << QString("Warning: Unable to load font \"%1\"").arg(name) << std::endl; + m_ok = false; + } + + } else if (names) { + + bool have = false; + QStringList list = QStringList::split(",", names, false); + for (QStringList::Iterator i = list.begin(); i != list.end(); ++i) { + SystemFont *font = SystemFont::loadSystemFont + (SystemFontSpec(*i, 12)); + if (font) { + m_systemFontNames[n] = *i; + have = true; + delete font; + break; + } + } + if (!have) { + std::cerr << QString("Warning: Unable to load any of the fonts in \"%1\""). + arg(names) << std::endl; + m_ok = false; + } + + } else { + m_errorString = "font-requirement must have either name or names attribute"; + return false; + } + + QString s = attributes.value("strategy").lower(); + SystemFont::Strategy strategy = SystemFont::PreferGlyphs; + + if (s) { + if (s == "prefer-glyphs") + strategy = SystemFont::PreferGlyphs; + else if (s == "prefer-codes") + strategy = SystemFont::PreferCodes; + else if (s == "only-glyphs") + strategy = SystemFont::OnlyGlyphs; + else if (s == "only-codes") + strategy = SystemFont::OnlyCodes; + else { + std::cerr << "Warning: Unknown strategy value " << s + << " (known values are prefer-glyphs, prefer-codes," + << " only-glyphs, only-codes)" << std::endl; + } + } + + m_systemFontStrategies[n] = strategy; + + } else { + } + + if (m_characterDestination) + *m_characterDestination = ""; + return true; +} + +bool +NoteFontMap::error(const QXmlParseException& exception) +{ + m_errorString = QString("%1 at line %2, column %3: %4") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()) + .arg(m_errorString); + return QXmlDefaultHandler::error(exception); +} + +bool +NoteFontMap::fatalError(const QXmlParseException& exception) +{ + m_errorString = QString("%1 at line %2, column %3: %4") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()) + .arg(m_errorString); + return QXmlDefaultHandler::fatalError(exception); +} + +std::set<int> +NoteFontMap::getSizes() const +{ + std::set<int> sizes; + + for (SizeDataMap::const_iterator i = m_sizes.begin(); + i != m_sizes.end(); ++i) { + sizes.insert(i->first); + } + + return sizes; +} + +std::set<CharName> +NoteFontMap::getCharNames() const +{ + std::set<CharName> names; + + for (SymbolDataMap::const_iterator i = m_data.begin(); + i != m_data.end(); ++i) { + names.insert(i->first); + } + + return names; +} + +bool +NoteFontMap::checkFile(int size, std::string &src) const +{ + QString pixmapFileMixedName = QString("%1/%2/%3/%4.xpm") + .arg(m_fontDirectory) + .arg(strtoqstr(m_srcDirectory)) + .arg(size) + .arg(strtoqstr(src)); + + QFileInfo pixmapFileMixedInfo(pixmapFileMixedName); + + if (!pixmapFileMixedInfo.isReadable()) { + + QString pixmapFileLowerName = QString("%1/%2/%3/%4.xpm") + .arg(m_fontDirectory) + .arg(strtoqstr(m_srcDirectory).lower()) + .arg(size) + .arg(strtoqstr(src)); + + QFileInfo pixmapFileLowerInfo(pixmapFileLowerName); + + if (!pixmapFileLowerInfo.isReadable()) { + if (pixmapFileMixedName != pixmapFileLowerName) { + std::cerr << "Warning: Unable to open pixmap file " + << pixmapFileMixedName << " or " << pixmapFileLowerName + << std::endl; + } else { + std::cerr << "Warning: Unable to open pixmap file " + << pixmapFileMixedName << std::endl; + } + return false; + } else { + src = qstrtostr(pixmapFileLowerName); + } + } else { + src = qstrtostr(pixmapFileMixedName); + } + + return true; +} + +bool +NoteFontMap::hasInversion(int, CharName charName) const +{ + SymbolDataMap::const_iterator i = m_data.find(charName); + if (i == m_data.end()) + return false; + return i->second.hasInversion(); +} + +bool +NoteFontMap::getSrc(int size, CharName charName, std::string &src) const +{ + SymbolDataMap::const_iterator i = m_data.find(charName); + if (i == m_data.end()) + return false; + + src = i->second.getSrc(); + if (src == "") + return false; + return checkFile(size, src); +} + +bool +NoteFontMap::getInversionSrc(int size, CharName charName, std::string &src) const +{ + SymbolDataMap::const_iterator i = m_data.find(charName); + if (i == m_data.end()) + return false; + + if (!i->second.hasInversion()) + return false; + src = i->second.getInversionSrc(); + if (src == "") + return false; + return checkFile(size, src); +} + +SystemFont * +NoteFontMap::getSystemFont(int size, CharName charName, int &charBase) +const +{ + SymbolDataMap::const_iterator i = m_data.find(charName); + if (i == m_data.end()) + return false; + + SizeDataMap::const_iterator si = m_sizes.find(size); + if (si == m_sizes.end()) + return false; + + int fontId = i->second.getFontId(); + + unsigned int fontHeight = 0; + if (!si->second.getFontHeight(fontId, fontHeight)) { + if (fontId == 0 || !si->second.getFontHeight(0, fontHeight)) { + fontHeight = size; + } + } + + SystemFontNameMap::const_iterator fni = m_systemFontNames.find(fontId); + if (fontId < 0 || fni == m_systemFontNames.end()) + return false; + QString fontName = fni->second; + + CharBaseMap::const_iterator bi = m_bases.find(fontId); + if (bi == m_bases.end()) + charBase = 0; + else + charBase = bi->second; + + SystemFontSpec spec(fontName, fontHeight); + SystemFontMap::const_iterator fi = m_systemFontCache.find(spec); + if (fi != m_systemFontCache.end()) { + return fi->second; + } + + SystemFont *font = SystemFont::loadSystemFont(spec); + if (!font) + return 0; + m_systemFontCache[spec] = font; + + NOTATION_DEBUG << "NoteFontMap::getFont: loaded font " << fontName + << " at pixel size " << fontHeight << endl; + + return font; +} + +SystemFont::Strategy +NoteFontMap::getStrategy(int, CharName charName) const +{ + SymbolDataMap::const_iterator i = m_data.find(charName); + if (i == m_data.end()) + return SystemFont::PreferGlyphs; + + int fontId = i->second.getFontId(); + SystemFontStrategyMap::const_iterator si = + m_systemFontStrategies.find(fontId); + + if (si != m_systemFontStrategies.end()) { + return si->second; + } + + return SystemFont::PreferGlyphs; +} + +bool +NoteFontMap::getCode(int, CharName charName, int &code) const +{ + SymbolDataMap::const_iterator i = m_data.find(charName); + if (i == m_data.end()) + return false; + + code = i->second.getCode(); + return (code >= 0); +} + +bool +NoteFontMap::getInversionCode(int, CharName charName, int &code) const +{ + SymbolDataMap::const_iterator i = m_data.find(charName); + if (i == m_data.end()) + return false; + + code = i->second.getInversionCode(); + return (code >= 0); +} + +bool +NoteFontMap::getGlyph(int, CharName charName, int &glyph) const +{ + SymbolDataMap::const_iterator i = m_data.find(charName); + if (i == m_data.end()) + return false; + + glyph = i->second.getGlyph(); + return (glyph >= 0); +} + +bool +NoteFontMap::getInversionGlyph(int, CharName charName, int &glyph) const +{ + SymbolDataMap::const_iterator i = m_data.find(charName); + if (i == m_data.end()) + return false; + + glyph = i->second.getInversionGlyph(); + return (glyph >= 0); +} + +bool +NoteFontMap::getStaffLineThickness(int size, unsigned int &thickness) const +{ + SizeDataMap::const_iterator i = m_sizes.find(size); + if (i == m_sizes.end()) + return false; + + return i->second.getStaffLineThickness(thickness); +} + +bool +NoteFontMap::getLegerLineThickness(int size, unsigned int &thickness) const +{ + SizeDataMap::const_iterator i = m_sizes.find(size); + if (i == m_sizes.end()) + return false; + + return i->second.getLegerLineThickness(thickness); +} + +bool +NoteFontMap::getStemThickness(int size, unsigned int &thickness) const +{ + SizeDataMap::const_iterator i = m_sizes.find(size); + if (i == m_sizes.end()) + return false; + + return i->second.getStemThickness(thickness); +} + +bool +NoteFontMap::getBeamThickness(int size, unsigned int &thickness) const +{ + SizeDataMap::const_iterator i = m_sizes.find(size); + if (i == m_sizes.end()) + return false; + + return i->second.getBeamThickness(thickness); +} + +bool +NoteFontMap::getStemLength(int size, unsigned int &length) const +{ + SizeDataMap::const_iterator i = m_sizes.find(size); + if (i == m_sizes.end()) + return false; + + return i->second.getStemLength(length); +} + +bool +NoteFontMap::getFlagSpacing(int size, unsigned int &spacing) const +{ + SizeDataMap::const_iterator i = m_sizes.find(size); + if (i == m_sizes.end()) + return false; + + return i->second.getFlagSpacing(spacing); +} + +bool +NoteFontMap::getHotspot(int size, CharName charName, int width, int height, + int &x, int &y) const +{ + HotspotDataMap::const_iterator i = m_hotspots.find(charName); + if (i == m_hotspots.end()) + return false; + return i->second.getHotspot(size, width, height, x, y); +} + +bool +NoteFontMap::HotspotData::getHotspot(int size, int width, int height, + int &x, int &y) const +{ + DataMap::const_iterator i = m_data.find(size); + if (i == m_data.end()) { + i = m_data.find(0); // fixed-pixel hotspot + x = 0; + if (m_scaled.first >= 0) { + x = toSize(width, m_scaled.first, false); + } else { + if (i != m_data.end()) { + x = i->second.first; + } + } + if (m_scaled.second >= 0) { + y = toSize(height, m_scaled.second, false); + return true; + } else { + if (i != m_data.end()) { + y = i->second.second; + return true; + } + return false; + } + } + x = i->second.first; + y = i->second.second; + return true; +} + +QStringList +NoteFontMap::getSystemFontNames() const +{ + QStringList names; + for (SystemFontNameMap::const_iterator i = m_systemFontNames.begin(); + i != m_systemFontNames.end(); ++i) { + names.append(i->second); + } + return names; +} + +void +NoteFontMap::dump() const +{ + // debug code + + std::cout << "Font data:\nName: " << getName() << "\nOrigin: " << getOrigin() + << "\nCopyright: " << getCopyright() << "\nMapped by: " + << getMappedBy() << "\nType: " << getType() + << "\nSmooth: " << isSmooth() << std::endl; + + std::set<int> sizes = getSizes(); + std::set<CharName> names = getCharNames(); + + for (std::set<int>::iterator sizei = sizes.begin(); sizei != sizes.end(); + ++sizei) { + + std::cout << "\nSize: " << *sizei << "\n" << std::endl; + + unsigned int t = 0; + + if (getStaffLineThickness(*sizei, t)) { + std::cout << "Staff line thickness: " << t << std::endl; + } + + if (getLegerLineThickness(*sizei, t)) { + std::cout << "Leger line thickness: " << t << std::endl; + } + + if (getStemThickness(*sizei, t)) { + std::cout << "Stem thickness: " << t << std::endl; + } + + if (getBeamThickness(*sizei, t)) { + std::cout << "Beam thickness: " << t << std::endl; + } + + if (getStemLength(*sizei, t)) { + std::cout << "Stem length: " << t << std::endl; + } + + if (getFlagSpacing(*sizei, t)) { + std::cout << "Flag spacing: " << t << std::endl; + } + + for (std::set<CharName>::iterator namei = names.begin(); + namei != names.end(); ++namei) { + + std::cout << "\nCharacter: " << namei->c_str() << std::endl; + + std::string s; + int x, y, c; + + if (getSrc(*sizei, *namei, s)) { + std::cout << "Src: " << s << std::endl; + } + + if (getInversionSrc(*sizei, *namei, s)) { + std::cout << "Inversion src: " << s << std::endl; + } + + if (getCode(*sizei, *namei, c)) { + std::cout << "Code: " << c << std::endl; + } + + if (getInversionCode(*sizei, *namei, c)) { + std::cout << "Inversion code: " << c << std::endl; + } + + if (getGlyph(*sizei, *namei, c)) { + std::cout << "Glyph: " << c << std::endl; + } + + if (getInversionGlyph(*sizei, *namei, c)) { + std::cout << "Inversion glyph: " << c << std::endl; + } + + if (getHotspot(*sizei, *namei, 1, 1, x, y)) { + std::cout << "Hot spot: (" << x << "," << y << ")" << std::endl; + } + } + } +} + +} diff --git a/src/gui/editors/notation/NoteFontMap.h b/src/gui/editors/notation/NoteFontMap.h new file mode 100644 index 0000000..119db76 --- /dev/null +++ b/src/gui/editors/notation/NoteFontMap.h @@ -0,0 +1,333 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTEFONTMAP_H_ +#define _RG_NOTEFONTMAP_H_ + +#include "base/Exception.h" +#include <map> +#include <set> +#include <string> +#include "SystemFont.h" +#include <qstring.h> +#include <qstringlist.h> +#include <utility> +#include <qxml.h> +#include "gui/editors/notation/NoteCharacterNames.h" + + +class QXmlParseException; +class QXmlAttributes; + + +namespace Rosegarden +{ + + + +class NoteFontMap : public QXmlDefaultHandler +{ +public: + typedef Exception MappingFileReadFailed; + + NoteFontMap(std::string name); // load and parse the XML mapping file + ~NoteFontMap(); + + /** + * ok() returns false if the file read succeeded but the font + * relies on system fonts that are not available. (If the file + * read fails, the constructor throws MappingFileReadFailed.) + */ + bool ok() const { return m_ok; } + + std::string getName() const { return m_name; } + std::string getOrigin() const { return m_origin; } + std::string getCopyright() const { return m_copyright; } + std::string getMappedBy() const { return m_mappedBy; } + std::string getType() const { return m_type; } + bool isSmooth() const { return m_smooth; } + + std::set<int> getSizes() const; + std::set<CharName> getCharNames() const; + + bool getStaffLineThickness(int size, unsigned int &thickness) const; + bool getLegerLineThickness(int size, unsigned int &thickness) const; + bool getStemThickness(int size, unsigned int &thickness) const; + bool getBeamThickness(int size, unsigned int &thickness) const; + bool getStemLength(int size, unsigned int &length) const; + bool getFlagSpacing(int size, unsigned int &spacing) const; + + bool hasInversion(int size, CharName charName) const; + + bool getSrc(int size, CharName charName, std::string &src) const; + bool getInversionSrc(int size, CharName charName, std::string &src) const; + + SystemFont *getSystemFont(int size, CharName charName, int &charBase) const; + SystemFont::Strategy getStrategy(int size, CharName charName) const; + bool getCode(int size, CharName charName, int &code) const; + bool getInversionCode(int size, CharName charName, int &code) const; + bool getGlyph(int size, CharName charName, int &glyph) const; + bool getInversionGlyph(int size, CharName charName, int &glyph) const; + + bool getHotspot(int size, CharName charName, int width, int height, + int &x, int &y) const; + + // Xml handler methods: + + virtual bool startElement + (const QString& namespaceURI, const QString& localName, + const QString& qName, const QXmlAttributes& atts); + + virtual bool characters(QString &); + + bool error(const QXmlParseException& exception); + bool fatalError(const QXmlParseException& exception); + + void dump() const; + + // Not for general use, but very handy for diagnostic display + QStringList getSystemFontNames() const; + + // want this to be private, but need access from HotspotData + static int toSize(int baseSize, double factor, bool limitAtOne); + +private: + class SymbolData + { + public: + SymbolData() : m_fontId(0), + m_src(""), m_inversionSrc(""), + m_code(-1), m_inversionCode(-1), + m_glyph(-1), m_inversionGlyph(-1) { } + ~SymbolData() { } + + void setFontId(int id) { m_fontId = id; } + int getFontId() const { return m_fontId; } + + void setSrc(std::string src) { m_src = src; } + std::string getSrc() const { return m_src; } + + void setCode(int code) { m_code = code; } + int getCode() const { return m_code; } + + void setGlyph(int glyph) { m_glyph = glyph; } + int getGlyph() const { return m_glyph; } + + void setInversionSrc(std::string inversion) { m_inversionSrc = inversion; } + std::string getInversionSrc() const { return m_inversionSrc; } + + void setInversionCode(int code) { m_inversionCode = code; } + int getInversionCode() const { return m_inversionCode; } + + void setInversionGlyph(int glyph) { m_inversionGlyph = glyph; } + int getInversionGlyph() const { return m_inversionGlyph; } + + bool hasInversion() const { + return m_inversionGlyph >= 0 || + m_inversionCode >= 0 || + m_inversionSrc != ""; + } + + private: + int m_fontId; + std::string m_src; + std::string m_inversionSrc; + int m_code; + int m_inversionCode; + int m_glyph; + int m_inversionGlyph; + }; + + class HotspotData + { + private: + typedef std::pair<int, int> Point; + typedef std::pair<double, double> ScaledPoint; + typedef std::map<int, Point> DataMap; + + public: + HotspotData() : m_scaled(-1.0, -1.0) { } + ~HotspotData() { } + + void addHotspot(int size, int x, int y) { + m_data[size] = Point(x, y); + } + + void setScaledHotspot(double x, double y) { + m_scaled = ScaledPoint(x, y); + } + + bool getHotspot(int size, int width, int height, int &x, int &y) const; + + private: + DataMap m_data; + ScaledPoint m_scaled; + }; + + class SizeData + { + public: + SizeData() : m_stemThickness(-1), + m_beamThickness(-1), + m_stemLength(-1), + m_flagSpacing(-1), + m_staffLineThickness(-1), + m_legerLineThickness(-1) { } + ~SizeData() { } + + void setStemThickness(unsigned int i) { + m_stemThickness = (int)i; + } + void setBeamThickness(unsigned int i) { + m_beamThickness = (int)i; + } + void setStemLength(unsigned int i) { + m_stemLength = (int)i; + } + void setFlagSpacing(unsigned int i) { + m_flagSpacing = (int)i; + } + void setStaffLineThickness(unsigned int i) { + m_staffLineThickness = (int)i; + } + void setLegerLineThickness(unsigned int i) { + m_legerLineThickness = (int)i; + } + void setFontHeight(int fontId, unsigned int h) { + m_fontHeights[fontId] = (int)h; + } + + bool getStemThickness(unsigned int &i) const { + if (m_stemThickness >= 0) { + i = (unsigned int)m_stemThickness; + return true; + } else return false; + } + + bool getBeamThickness(unsigned int &i) const { + if (m_beamThickness >= 0) { + i = (unsigned int)m_beamThickness; + return true; + } else return false; + } + + bool getStemLength(unsigned int &i) const { + if (m_stemLength >= 0) { + i = (unsigned int)m_stemLength; + return true; + } else return false; + } + + bool getFlagSpacing(unsigned int &i) const { + if (m_flagSpacing >= 0) { + i = (unsigned int)m_flagSpacing; + return true; + } else return false; + } + + bool getStaffLineThickness(unsigned int &i) const { + if (m_staffLineThickness >= 0) { + i = (unsigned int)m_staffLineThickness; + return true; + } else return false; + } + + bool getLegerLineThickness(unsigned int &i) const { + if (m_legerLineThickness >= 0) { + i = (unsigned int)m_legerLineThickness; + return true; + } else return false; + } + + bool getFontHeight(int fontId, unsigned int &h) const { + std::map<int, int>::const_iterator fhi = m_fontHeights.find(fontId); + if (fhi != m_fontHeights.end()) { + h = (unsigned int)fhi->second; + return true; + } + return false; + } + + private: + int m_stemThickness; + int m_beamThickness; + int m_stemLength; + int m_flagSpacing; + int m_staffLineThickness; + int m_legerLineThickness; + std::map<int, int> m_fontHeights; // per-font-id + }; + + //--------------- Data members --------------------------------- + + std::string m_name; + std::string m_origin; + std::string m_copyright; + std::string m_mappedBy; + std::string m_type; + bool m_smooth; + + std::string m_srcDirectory; + + typedef std::map<CharName, SymbolData> SymbolDataMap; + SymbolDataMap m_data; + + typedef std::map<CharName, HotspotData> HotspotDataMap; + HotspotDataMap m_hotspots; + + typedef std::map<int, SizeData> SizeDataMap; + SizeDataMap m_sizes; + + typedef std::map<int, QString> SystemFontNameMap; + SystemFontNameMap m_systemFontNames; + + typedef std::map<int, SystemFont::Strategy> SystemFontStrategyMap; + SystemFontStrategyMap m_systemFontStrategies; + + typedef std::map<SystemFontSpec, SystemFont *> SystemFontMap; + mutable SystemFontMap m_systemFontCache; + + typedef std::map<int, int> CharBaseMap; + CharBaseMap m_bases; + + // For use when reading the XML file: + bool m_expectingCharacters; + std::string *m_characterDestination; + std::string m_hotspotCharName; + QString m_errorString; + + bool checkFile(int size, std::string &src) const; + QString m_fontDirectory; + + bool m_ok; +}; + + +class NoteCharacterDrawRep; + + +} + +#endif diff --git a/src/gui/editors/notation/NoteFontViewer.cpp b/src/gui/editors/notation/NoteFontViewer.cpp new file mode 100644 index 0000000..81f07e9 --- /dev/null +++ b/src/gui/editors/notation/NoteFontViewer.cpp @@ -0,0 +1,125 @@ +/* -*- 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 "NoteFontViewer.h" + +#include <klocale.h> +#include "FontViewFrame.h" +#include <kcombobox.h> +#include <kdialogbase.h> +#include <ktoolbar.h> +#include <qlabel.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qvbox.h> +#include <qwidget.h> + + +namespace Rosegarden +{ + +void +NoteFontViewer::slotViewChanged(int i) +{ + m_frame->setGlyphs(i == 0); + + m_rows->clear(); + int firstRow = -1; + + for (int r = 0; r < 256; ++r) { + if (m_frame->hasRow(r)) { + m_rows->insertItem(QString("%1").arg(r)); + if (firstRow < 0) + firstRow = r; + } + } + + if (firstRow < 0) { + m_rows->setEnabled(false); + m_frame->setRow(0); + } else { + m_rows->setEnabled(true); + m_frame->setRow(firstRow); + } +} + +void +NoteFontViewer::slotRowChanged(const QString &s) +{ + bool ok; + int i = s.toInt(&ok); + if (ok) + m_frame->setRow(i); +} + +void +NoteFontViewer::slotFontChanged(const QString &s) +{ + m_frame->setFont(s); + slotViewChanged(m_view->currentItem()); +} + +NoteFontViewer::NoteFontViewer(QWidget *parent, QString noteFontName, + QStringList fontNames, int pixelSize) : + KDialogBase(parent, 0, true, + i18n("Note Font Viewer: %1").arg(noteFontName), Close) +{ + QVBox *box = makeVBoxMainWidget(); + KToolBar* controls = new KToolBar(box); + controls->setMargin(3); + + (void) new QLabel(i18n(" Component: "), controls); + m_font = new KComboBox(controls); + + for (QStringList::iterator i = fontNames.begin(); i != fontNames.end(); + ++i) { + m_font->insertItem(*i); + } + + (void) new QLabel(i18n(" View: "), controls); + m_view = new KComboBox(controls); + + m_view->insertItem(i18n("Glyphs")); + m_view->insertItem(i18n("Codes")); + + (void) new QLabel(i18n(" Page: "), controls); + m_rows = new KComboBox(controls); + + m_frame = new FontViewFrame(pixelSize, box); + + connect(m_font, SIGNAL(activated(const QString &)), + this, SLOT(slotFontChanged(const QString &))); + + connect(m_view, SIGNAL(activated(int)), + this, SLOT(slotViewChanged(int))); + + connect(m_rows, SIGNAL(activated(const QString &)), + this, SLOT(slotRowChanged(const QString &))); + + slotFontChanged(m_font->currentText()); +} + +} +#include "NoteFontViewer.moc" diff --git a/src/gui/editors/notation/NoteFontViewer.h b/src/gui/editors/notation/NoteFontViewer.h new file mode 100644 index 0000000..31c8613 --- /dev/null +++ b/src/gui/editors/notation/NoteFontViewer.h @@ -0,0 +1,68 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTEFONTVIEWER_H_ +#define _RG_NOTEFONTVIEWER_H_ + +#include <kdialogbase.h> +#include <qstring.h> +#include <qstringlist.h> + + +class QWidget; +class KComboBox; + + +namespace Rosegarden +{ + +class FontViewFrame; + + +class NoteFontViewer : public KDialogBase +{ + Q_OBJECT + +public: + NoteFontViewer(QWidget *parent, QString noteFontName, + QStringList systemFontNames, int pixelSize); + +protected slots: + void slotFontChanged(const QString &); + void slotViewChanged(int); + void slotRowChanged(const QString &); + +private: + KComboBox *m_font; + KComboBox *m_view; + KComboBox *m_rows; + FontViewFrame *m_frame; +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/NoteInserter.cpp b/src/gui/editors/notation/NoteInserter.cpp new file mode 100644 index 0000000..66adafe --- /dev/null +++ b/src/gui/editors/notation/NoteInserter.cpp @@ -0,0 +1,722 @@ +/* -*- 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 "NoteInserter.h" +#include "misc/Debug.h" +#include <kapplication.h> + +#include "base/BaseProperties.h" +#include <klocale.h> +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Staff.h" +#include "base/ViewElement.h" +#include "commands/notation/NoteInsertionCommand.h" +#include "commands/notation/RestInsertionCommand.h" +#include "commands/notation/TupletCommand.h" +#include "gui/general/EditTool.h" +#include "gui/general/LinedStaff.h" +#include "gui/general/RosegardenCanvasView.h" +#include "NotationProperties.h" +#include "NotationStrings.h" +#include "NotationTool.h" +#include "NotationView.h" +#include "NotationStaff.h" +#include "NotePixmapFactory.h" +#include "NoteStyleFactory.h" +#include <kaction.h> +#include <kcommand.h> +#include <kconfig.h> +#include <qiconset.h> +#include <qregexp.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +NoteInserter::NoteInserter(NotationView* view) + : NotationTool("NoteInserter", view), + m_noteType(Note::Quaver), + m_noteDots(0), + m_autoBeam(true), + m_accidental(Accidentals::NoAccidental), + m_lastAccidental(Accidentals::NoAccidental), + m_followAccidental(false) +{ + QIconSet icon; + + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + m_autoBeam = config->readBoolEntry("autobeam", true); + m_matrixInsertType = (config->readNumEntry("inserttype", 0) > 0); + m_defaultStyle = qstrtostr(config->readEntry + ("style", strtoqstr(NoteStyleFactory::DefaultStyle))); + + KToggleAction *autoBeamAction = + new KToggleAction(i18n("Auto-Beam when appropriate"), 0, this, + SLOT(slotToggleAutoBeam()), actionCollection(), + "toggle_auto_beam"); + autoBeamAction->setChecked(m_autoBeam); + + for (unsigned int i = 0; i < 6; ++i) { + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap(m_actionsAccidental[i][3]))); + KRadioAction* noteAction = new KRadioAction(i18n(m_actionsAccidental[i][0]), + icon, 0, this, + m_actionsAccidental[i][1], + actionCollection(), + m_actionsAccidental[i][2]); + noteAction->setExclusiveGroup("accidentals"); + } + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("dotted-crotchet"))); + new KToggleAction(i18n("Dotted note"), icon, 0, this, + SLOT(slotToggleDot()), actionCollection(), + "toggle_dot"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("select"))); + new KAction(i18n("Switch to Select Tool"), icon, 0, this, + SLOT(slotSelectSelected()), actionCollection(), + "select"); + + new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this, + SLOT(slotEraseSelected()), actionCollection(), + "erase"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("rest-crotchet"))); + new KAction(i18n("Switch to Inserting Rests"), icon, 0, this, + SLOT(slotRestsSelected()), actionCollection(), + "rests"); + + createMenu("noteinserter.rc"); + + connect(m_parentView, SIGNAL(changeAccidental(Accidental, bool)), + this, SLOT(slotSetAccidental(Accidental, bool))); +} + +NoteInserter::NoteInserter(const QString& menuName, NotationView* view) + : NotationTool(menuName, view), + m_noteType(Note::Quaver), + m_noteDots(0), + m_autoBeam(false), + m_clickHappened(false), + m_accidental(Accidentals::NoAccidental), + m_lastAccidental(Accidentals::NoAccidental), + m_followAccidental(false) +{ + connect(m_parentView, SIGNAL(changeAccidental(Accidental, bool)), + this, SLOT(slotSetAccidental(Accidental, bool))); +} + +NoteInserter::~NoteInserter() +{} + +void NoteInserter::ready() +{ + m_clickHappened = false; + m_nParentView->setCanvasCursor(Qt::crossCursor); + m_nParentView->setHeightTracking(true); +} + +void +NoteInserter::handleLeftButtonPress(timeT, + int, + int staffNo, + QMouseEvent* e, + ViewElement*) +{ + if (staffNo < 0) + return ; + computeLocationAndPreview(e); +} + +int +NoteInserter::handleMouseMove(timeT, + int, + QMouseEvent *e) +{ + if (m_clickHappened) { + computeLocationAndPreview(e); + } + + return RosegardenCanvasView::NoFollow; +} + +void +NoteInserter::handleMouseRelease(timeT, + int, + QMouseEvent *e) +{ + if (!m_clickHappened) + return ; + bool okay = computeLocationAndPreview(e); + m_clickHappened = false; + if (!okay) + return ; + clearPreview(); + + Note note(m_noteType, m_noteDots); + timeT endTime = m_clickTime + note.getDuration(); + Segment &segment = m_nParentView->getStaff(m_clickStaffNo)->getSegment(); + + Segment::iterator realEnd = segment.findTime(endTime); + if (!segment.isBeforeEndMarker( realEnd) || + !segment.isBeforeEndMarker(++realEnd)) { + endTime = segment.getEndMarkerTime(); + } else { + endTime = std::max(endTime, (*realEnd)->getNotationAbsoluteTime()); + } + + Event *lastInsertedEvent = doAddCommand + (segment, m_clickTime, endTime, note, m_clickPitch, + (m_accidental == Accidentals::NoAccidental && + m_followAccidental) ? + m_lastAccidental : m_accidental); + + if (lastInsertedEvent) { + + m_nParentView->setSingleSelectedEvent + (m_clickStaffNo, lastInsertedEvent); + + if (m_nParentView->isInChordMode()) { + m_nParentView->slotSetInsertCursorAndRecentre + (lastInsertedEvent->getAbsoluteTime(), e->x(), (int)e->y(), + false); + } else { + m_nParentView->slotSetInsertCursorAndRecentre + (lastInsertedEvent->getAbsoluteTime() + + lastInsertedEvent->getDuration(), e->x(), (int)e->y(), + false); + } + } +} + +void +NoteInserter::insertNote(Segment &segment, timeT insertionTime, + int pitch, Accidental accidental, + bool suppressPreview) +{ + Note note(m_noteType, m_noteDots); + timeT endTime = insertionTime + note.getDuration(); + + Segment::iterator realEnd = segment.findTime(endTime); + if (!segment.isBeforeEndMarker( realEnd) || + !segment.isBeforeEndMarker(++realEnd)) { + endTime = segment.getEndMarkerTime(); + } else { + endTime = std::max(endTime, (*realEnd)->getNotationAbsoluteTime()); + } + + Event *lastInsertedEvent = doAddCommand + (segment, insertionTime, endTime, note, pitch, accidental); + + if (lastInsertedEvent) { + + m_nParentView->setSingleSelectedEvent(segment, lastInsertedEvent); + + if (m_nParentView->isInChordMode()) { + m_nParentView->slotSetInsertCursorPosition + (lastInsertedEvent->getAbsoluteTime(), true, false); + } else { + m_nParentView->slotSetInsertCursorPosition + (lastInsertedEvent->getAbsoluteTime() + + lastInsertedEvent->getDuration(), true, false); + } + } + + if (!suppressPreview) + m_nParentView->playNote(segment, pitch); +} + +bool +NoteInserter::computeLocationAndPreview(QMouseEvent *e) +{ + double x = e->x(); + int y = (int)e->y(); + + LinedStaff *staff = m_nParentView->getStaffForCanvasCoords(e->x(), y); + if (!staff) { + clearPreview(); + return false; + } + + int staffNo = staff->getId(); + if (m_clickHappened && staffNo != m_clickStaffNo) { + // abandon + clearPreview(); + return false; + } + + // If we're inserting grace notes, then we need to "dress to the + // right", as it were + bool grace = m_nParentView->isInGraceMode(); + + int height = staff->getHeightAtCanvasCoords(x, y); + + Event *clefEvt = 0, *keyEvt = 0; + Clef clef; + Rosegarden::Key key; + + NotationElementList::iterator itr = + staff->getElementUnderCanvasCoords(x, y, clefEvt, keyEvt); + if (itr == staff->getViewElementList()->end()) { + clearPreview(); + return false; + } + + NotationElement* el = static_cast<NotationElement*>(*itr); + + timeT time = el->event()->getAbsoluteTime(); // not getViewAbsoluteTime() + m_clickInsertX = el->getLayoutX(); + if (clefEvt) + clef = Clef(*clefEvt); + if (keyEvt) + key = Rosegarden::Key(*keyEvt); + + int subordering = el->event()->getSubOrdering(); + float targetSubordering = subordering; + + if (grace && el->getCanvasItem()) { + + NotationStaff *ns = dynamic_cast<NotationStaff *>(staff); + if (!ns) { + std::cerr << "WARNING: NoteInserter: Staff is not a NotationStaff" + << std::endl; + } else { + std::cerr << "x=" << x << ", el->getCanvasX()=" << el->getCanvasX() << std::endl; + if (el->isRest()) std::cerr << "elt is a rest" << std::endl; + if (x - el->getCanvasX() > + ns->getNotePixmapFactory(false).getNoteBodyWidth()) { + NotationElementList::iterator j(itr); + while (++j != staff->getViewElementList()->end()) { + NotationElement *candidate = static_cast<NotationElement *>(*j); + if ((candidate->isNote() || candidate->isRest()) && + (candidate->getViewAbsoluteTime() + > el->getViewAbsoluteTime() || + candidate->event()->getSubOrdering() + > el->event()->getSubOrdering())) { + itr = j; + el = candidate; + m_clickInsertX = el->getLayoutX(); + time = el->event()->getAbsoluteTime(); + subordering = el->event()->getSubOrdering(); + targetSubordering = subordering; + break; + } + } + } + } + + if (x - el->getCanvasX() < 1) { + targetSubordering -= 0.5; + } + } + + if (el->isRest() && el->getCanvasItem()) { + time += getOffsetWithinRest(staffNo, itr, x); + m_clickInsertX += (x - el->getCanvasX()); + } + + Pitch p(height, clef, key, m_accidental); + int pitch = p.getPerformancePitch(); + + // [RFE 987960] When inserting via mouse, if no accidental is + // selected, we use the same accidental (and thus the same pitch) + // as of the previous note found at this height -- iff such a note + // is found more recently than the last key signature. + + if (m_accidental == Accidentals::NoAccidental && + m_followAccidental) { + Segment &segment = staff->getSegment(); + m_lastAccidental = m_accidental; + Segment::iterator i = segment.findNearestTime(time); + while (i != segment.end()) { + if ((*i)->isa(Rosegarden::Key::EventType)) break; + if ((*i)->isa(Note::EventType)) { + if ((*i)->has(NotationProperties::HEIGHT_ON_STAFF) && + (*i)->has(BaseProperties::PITCH)) { + int h = (*i)->get<Int>(NotationProperties::HEIGHT_ON_STAFF); + if (h == height) { + pitch = (*i)->get<Int>(BaseProperties::PITCH); + (*i)->get<String>(BaseProperties::ACCIDENTAL, + m_lastAccidental); + break; + } + } + } + if (i == segment.begin()) break; + --i; + } + } + + bool changed = false; + + if (m_clickHappened) { + if (time != m_clickTime || + subordering != m_clickSubordering || + pitch != m_clickPitch || + height != m_clickHeight || + staffNo != m_clickStaffNo) { + changed = true; + } + } else { + m_clickHappened = true; + changed = true; + } + + if (changed) { + m_clickTime = time; + m_clickSubordering = subordering; + m_clickPitch = pitch; + m_clickHeight = height; + m_clickStaffNo = staffNo; + m_targetSubordering = targetSubordering; + + showPreview(); + } + + return true; +} + +void NoteInserter::showPreview() +{ + Segment &segment = m_nParentView->getStaff(m_clickStaffNo)->getSegment(); + + int pitch = m_clickPitch; + pitch += getOttavaShift(segment, m_clickTime) * 12; + + m_nParentView->showPreviewNote(m_clickStaffNo, m_clickInsertX, + pitch, m_clickHeight, + Note(m_noteType, m_noteDots), + m_nParentView->isInGraceMode()); +} + +void NoteInserter::clearPreview() +{ + m_nParentView->clearPreviewNote(); +} + +timeT +NoteInserter::getOffsetWithinRest(int staffNo, + const NotationElementList::iterator &i, + double &canvasX) // will be snapped +{ + //!!! To make this work correctly in tuplet mode, our divisor would + // have to be the tupletified duration of the tuplet unit -- we can + // do that, we just haven't yet + if (m_nParentView->isInTripletMode()) + return 0; + + Staff *staff = m_nParentView->getStaff(staffNo); + NotationElement* el = static_cast<NotationElement*>(*i); + if (!el->getCanvasItem()) + return 0; + double offset = canvasX - el->getCanvasX(); + + if (offset < 0) + return 0; + + double airX, airWidth; + el->getLayoutAirspace(airX, airWidth); + double origin = ((*i)->getLayoutX() - airX) / 2; + double width = airWidth - origin; + + timeT duration = (*i)->getViewDuration(); + + TimeSignature timeSig = + staff->getSegment().getComposition()->getTimeSignatureAt + ((*i)->event()->getAbsoluteTime()); + timeT unit = timeSig.getUnitDuration(); + + int unitCount = duration / unit; + if (unitCount > 1) { + + timeT result = (int)((offset / width) * unitCount); + if (result > unitCount - 1) + result = unitCount - 1; + + double visibleWidth(airWidth); + NotationElementList::iterator j(i); + if (++j != staff->getViewElementList()->end()) { + visibleWidth = (*j)->getLayoutX() - (*i)->getLayoutX(); + } + offset = (visibleWidth * result) / unitCount; + canvasX = el->getCanvasX() + offset; + + result *= unit; + return result; + } + + return 0; +} + +int +NoteInserter::getOttavaShift(Segment &segment, timeT time) +{ + // Find out whether we're in an ottava section. + + int ottavaShift = 0; + + for (Segment::iterator i = segment.findTime(time); ; --i) { + + if (!segment.isBeforeEndMarker(i)) { + break; + } + + if ((*i)->isa(Indication::EventType)) { + try { + Indication ind(**i); + if (ind.isOttavaType()) { + timeT endTime = + (*i)->getNotationAbsoluteTime() + + (*i)->getNotationDuration(); + if (time < endTime) { + ottavaShift = ind.getOttavaShift(); + } + break; + } + } catch (...) { } + } + + if (i == segment.begin()) { + break; + } + } + + return ottavaShift; +} + +Event * +NoteInserter::doAddCommand(Segment &segment, timeT time, timeT endTime, + const Note ¬e, int pitch, Accidental accidental) +{ + timeT noteEnd = time + note.getDuration(); + + // #1046934: make it possible to insert triplet at end of segment! + if (m_nParentView->isInTripletMode()) { + noteEnd = time + (note.getDuration() * 2 / 3); + } + + if (time < segment.getStartTime() || + endTime > segment.getEndMarkerTime() || + noteEnd > segment.getEndMarkerTime()) { + return 0; + } + + pitch += getOttavaShift(segment, time) * 12; + + float targetSubordering = 0; + if (m_nParentView->isInGraceMode()) { + targetSubordering = m_targetSubordering; + } + + NoteInsertionCommand *insertionCommand = + new NoteInsertionCommand + (segment, time, endTime, note, pitch, accidental, + (m_autoBeam && !m_nParentView->isInTripletMode() && !m_nParentView->isInGraceMode()) ? + NoteInsertionCommand::AutoBeamOn : NoteInsertionCommand::AutoBeamOff, + m_matrixInsertType && !m_nParentView->isInGraceMode() ? + NoteInsertionCommand::MatrixModeOn : NoteInsertionCommand::MatrixModeOff, + m_nParentView->isInGraceMode() ? + (m_nParentView->isInTripletMode() ? + NoteInsertionCommand::GraceAndTripletModesOn : + NoteInsertionCommand::GraceModeOn) + : NoteInsertionCommand::GraceModeOff, + targetSubordering, + m_defaultStyle); + + KCommand *activeCommand = insertionCommand; + + if (m_nParentView->isInTripletMode() && !m_nParentView->isInGraceMode()) { + Segment::iterator i(segment.findTime(time)); + if (i != segment.end() && + !(*i)->has(BaseProperties::BEAMED_GROUP_TUPLET_BASE)) { + + KMacroCommand *command = new KMacroCommand(insertionCommand->name()); + + //## Attempted fix to bug reported on rg-user by SlowPic + //## <[email protected]> 28/02/2005 22:32:56 UTC: Triplet input error + //# HJJ: Comment out this attempt. It breaks the splitting of + //# the first bars into rests. + //## if ((*i)->isa(Note::EventRestType) && + //## (*i)->getNotationDuration() > (note.getDuration() * 3)) { + // split the rest + command->addCommand(new RestInsertionCommand + (segment, time, + time + note.getDuration() * 2, + Note::getNearestNote(note.getDuration() * 2))); + //## } + //# These comments should probably be deleted. + + command->addCommand(new TupletCommand + (segment, time, note.getDuration(), + 3, 2, true)); // #1046934: "has timing already" + command->addCommand(insertionCommand); + activeCommand = command; + } + } + + m_nParentView->addCommandToHistory(activeCommand); + + NOTATION_DEBUG << "NoteInserter::doAddCommand: accidental is " + << accidental << endl; + + return insertionCommand->getLastInsertedEvent(); +} + +void NoteInserter::slotSetNote(Note::Type nt) +{ + m_noteType = nt; +} + +void NoteInserter::slotSetDots(unsigned int dots) +{ + m_noteDots = dots; + + KToggleAction *dotsAction = dynamic_cast<KToggleAction *> + (actionCollection()->action("toggle_dot")); + if (dotsAction) + dotsAction->setChecked(dots > 0); +} + +void NoteInserter::slotSetAccidental(Accidental accidental, + bool follow) +{ + NOTATION_DEBUG << "NoteInserter::setAccidental: accidental is " + << accidental << endl; + m_accidental = accidental; + m_followAccidental = follow; +} + +void NoteInserter::slotNoAccidental() +{ + m_parentView->actionCollection()->action("no_accidental")->activate(); +} + +void NoteInserter::slotFollowAccidental() +{ + m_parentView->actionCollection()->action("follow_accidental")->activate(); +} + +void NoteInserter::slotSharp() +{ + m_parentView->actionCollection()->action("sharp_accidental")->activate(); +} + +void NoteInserter::slotFlat() +{ + m_parentView->actionCollection()->action("flat_accidental")->activate(); +} + +void NoteInserter::slotNatural() +{ + m_parentView->actionCollection()->action("natural_accidental")->activate(); +} + +void NoteInserter::slotDoubleSharp() +{ + m_parentView->actionCollection()->action("double_sharp_accidental")->activate(); +} + +void NoteInserter::slotDoubleFlat() +{ + m_parentView->actionCollection()->action("double_flat_accidental")->activate(); +} + +void NoteInserter::slotToggleDot() +{ + m_noteDots = (m_noteDots) ? 0 : 1; + Note note(m_noteType, m_noteDots); + QString actionName(NotationStrings::getReferenceName(note)); + actionName.replace(QRegExp("-"), "_"); + KAction *action = m_parentView->actionCollection()->action(actionName); + if (!action) { + std::cerr << "WARNING: No such action as " << actionName << std::endl; + } else { + action->activate(); + } +} + +void NoteInserter::slotToggleAutoBeam() +{ + m_autoBeam = !m_autoBeam; +} + +void NoteInserter::slotEraseSelected() +{ + m_parentView->actionCollection()->action("erase")->activate(); +} + +void NoteInserter::slotSelectSelected() +{ + m_parentView->actionCollection()->action("select")->activate(); +} + +void NoteInserter::slotRestsSelected() +{ + Note note(m_noteType, m_noteDots); + QString actionName(NotationStrings::getReferenceName(note, true)); + actionName.replace(QRegExp("-"), "_"); + KAction *action = m_parentView->actionCollection()->action(actionName); + if (!action) { + std::cerr << "WARNING: No such action as " << actionName << std::endl; + } else { + action->activate(); + } +} + +const char* NoteInserter::m_actionsAccidental[][4] = +{ + { "No accidental", "1slotNoAccidental()", "no_accidental", + "accidental-none" }, + { "Follow accidental", "1slotFollowAccidental()", "follow_accidental", + "accidental-follow" }, + { "Sharp", "1slotSharp()", "sharp_accidental", + "accidental-sharp" }, + { "Flat", "1slotFlat()", "flat_accidental", + "accidental-flat" }, + { "Natural", "1slotNatural()", "natural_accidental", + "accidental-natural" }, + { "Double sharp", "1slotDoubleSharp()", "double_sharp_accidental", + "accidental-doublesharp" }, + { "Double flat", "1slotDoubleFlat()", "double_flat_accidental", + "accidental-doubleflat" } +}; + +const QString NoteInserter::ToolName = "noteinserter"; + +} +#include "NoteInserter.moc" diff --git a/src/gui/editors/notation/NoteInserter.h b/src/gui/editors/notation/NoteInserter.h new file mode 100644 index 0000000..cb46b38 --- /dev/null +++ b/src/gui/editors/notation/NoteInserter.h @@ -0,0 +1,166 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTEINSERTER_H_ +#define _RG_NOTEINSERTER_H_ + +#include "base/NotationTypes.h" +#include "NotationTool.h" +#include "NotationElement.h" +#include "NoteStyle.h" +#include <qstring.h> +#include "base/Event.h" + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class ViewElement; +class Segment; +class NotationView; +class Event; + + +/** + * This tool will insert notes on mouse click events + */ +class NoteInserter : public NotationTool +{ + Q_OBJECT + + friend class NotationToolBox; + +public: + ~NoteInserter(); + + virtual void handleLeftButtonPress(timeT, + int height, + int staffNo, + QMouseEvent*, + ViewElement* el); + + virtual int handleMouseMove(timeT time, + int height, + QMouseEvent*); + + virtual void handleMouseRelease(timeT time, + int height, + QMouseEvent*); + + virtual void ready(); + + Note getCurrentNote() { + return Note(m_noteType, m_noteDots); + } + + /// Insert a note as if the user has clicked at the given time & pitch + void insertNote(Segment &segment, + timeT insertionTime, + int pitch, + Accidental accidental, + bool suppressPreview = false); + + static const QString ToolName; + +public slots: + /// Set the type of note (quaver, breve...) which will be inserted + void slotSetNote(Note::Type); + + /// Set the nb of dots the inserted note will have + void slotSetDots(unsigned int dots); + + /// Set the accidental for the notes which will be inserted + void slotSetAccidental(Accidental, bool follow); + +protected: + NoteInserter(NotationView*); + + /// this ctor is used by RestInserter + NoteInserter(const QString& menuName, NotationView*); + + timeT getOffsetWithinRest(int staffNo, + const NotationElementList::iterator&, + double &canvasX); + + int getOttavaShift(Segment &segment, timeT time); + + virtual Event *doAddCommand(Segment &, + timeT time, + timeT endTime, + const Note &, + int pitch, Accidental); + + virtual bool computeLocationAndPreview(QMouseEvent *e); + virtual void showPreview(); + virtual void clearPreview(); + +protected slots: + // RMB menu slots + void slotNoAccidental(); + void slotFollowAccidental(); + void slotSharp(); + void slotFlat(); + void slotNatural(); + void slotDoubleSharp(); + void slotDoubleFlat(); + void slotToggleDot(); + void slotToggleAutoBeam(); + + void slotEraseSelected(); + void slotSelectSelected(); + void slotRestsSelected(); + +protected: + //--------------- Data members --------------------------------- + + Note::Type m_noteType; + unsigned int m_noteDots; + bool m_autoBeam; + bool m_matrixInsertType; + NoteStyleName m_defaultStyle; + + bool m_clickHappened; + timeT m_clickTime; + int m_clickSubordering; + int m_clickPitch; + int m_clickHeight; + int m_clickStaffNo; + double m_clickInsertX; + float m_targetSubordering; + + Accidental m_accidental; + Accidental m_lastAccidental; + bool m_followAccidental; + + static const char* m_actionsAccidental[][4]; +}; + + +} + +#endif 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; +} + +} diff --git a/src/gui/editors/notation/NotePixmapFactory.h b/src/gui/editors/notation/NotePixmapFactory.h new file mode 100644 index 0000000..14b4773 --- /dev/null +++ b/src/gui/editors/notation/NotePixmapFactory.h @@ -0,0 +1,358 @@ +/* -*- 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. +*/ + +#ifndef _RG_NOTEPIXMAPFACTORY_H_ +#define _RG_NOTEPIXMAPFACTORY_H_ + +#include "base/NotationTypes.h" +#include <map> +#include "NoteCharacter.h" +#include <string> +#include <qfont.h> +#include <qfontmetrics.h> +#include <qpixmap.h> +#include <qpoint.h> +#include "base/Event.h" +#include "gui/editors/notation/NoteCharacterNames.h" + + +class QPainter; +class QCanvasPixmap; +class QBitmap; + + +namespace Rosegarden +{ + +namespace Guitar { class Fingering; } + +class TimeSignature; +class Text; +class NoteStyle; +class NotePixmapParameters; +class NoteFont; +class NotePixmapPainter; +class NotePixmapCache; +class Clef; +class TrackHeader; + +/** + * Generates QCanvasPixmaps for various notation items. + */ + +class NotePixmapFactory +{ +public: + NotePixmapFactory(std::string fontName = "", int size = -1); + NotePixmapFactory(const NotePixmapFactory &); + NotePixmapFactory &operator=(const NotePixmapFactory &); + ~NotePixmapFactory(); + + std::string getFontName() const; + int getSize() const; + + void setSelected(bool selected) { m_selected = selected; } + bool isSelected() const { return m_selected; } + + void setShaded(bool shaded) { m_shaded = shaded; } + bool isShaded() const { return m_shaded; } + + void setNoteStyle(NoteStyle *style) { m_style = style; } + const NoteStyle *getNoteStyle() const { return m_style; } + + // Display methods -- create canvas pixmaps: + + QCanvasPixmap* makeNotePixmap(const NotePixmapParameters ¶meters); + QCanvasPixmap* makeRestPixmap(const NotePixmapParameters ¶meters); + QCanvasPixmap* makeClefPixmap(const Clef &clef); + QCanvasPixmap* makeKeyPixmap(const Key &key, + const Clef &clef, + Key previousKey = + Key::DefaultKey); + QCanvasPixmap* makeTimeSigPixmap(const TimeSignature& sig); + QCanvasPixmap* makeHairpinPixmap(int length, bool isCrescendo); + QCanvasPixmap* makeSlurPixmap(int length, int dy, bool above, bool phrasing); + QCanvasPixmap* makeOttavaPixmap(int length, int octavesUp); + QCanvasPixmap* makePedalDownPixmap(); + QCanvasPixmap* makePedalUpPixmap(); + QCanvasPixmap* makeUnknownPixmap(); + QCanvasPixmap* makeTextPixmap(const Text &text); + QCanvasPixmap* makeGuitarChordPixmap(const Guitar::Fingering &fingering, + int x, int y); + + QCanvasPixmap* makeNoteHaloPixmap(const NotePixmapParameters ¶meters); + + // Printing methods -- draw direct to a paint device: + + void drawNote(const NotePixmapParameters ¶meters, + QPainter &painter, int x, int y); + void drawRest(const NotePixmapParameters ¶meters, + QPainter &painter, int x, int y); + void drawHairpin(int length, bool isCrescendo, + QPainter &painter, int x, int y); + void drawSlur(int length, int dy, bool above, bool phrasing, + QPainter &painter, int x, int y); + void drawOttava(int length, int octavesUp, + QPainter &painter, int x, int y); + void drawText(const Text &text, + QPainter &painter, int x, int y); + + // Other support methods for producing pixmaps for other contexts: + + static QCanvasPixmap *makeToolbarPixmap(const char *name, + bool menuSize = false); + static QCanvasPixmap *makeNoteMenuPixmap(timeT duration, + timeT &errorReturn); + static QCanvasPixmap *makeMarkMenuPixmap(Mark); + + QCanvasPixmap* makePitchDisplayPixmap(int pitch, + const Clef &clef, + bool useSharps); + QCanvasPixmap* makePitchDisplayPixmap(int pitch, + const Clef &clef, + int octave, + int step); + QCanvasPixmap* makeClefDisplayPixmap(const Clef &clef); + QCanvasPixmap* makeKeyDisplayPixmap(const Key &key, + const Clef &clef); + + QCanvasPixmap* makeTrackHeaderPixmap(int width, int height, + TrackHeader *header); + + // Bounding box and other geometry methods: + + int getNoteBodyWidth (Note::Type = + Note::Crotchet) const; + + int getNoteBodyHeight(Note::Type = + Note::Crotchet) const; + + int getAccidentalWidth (const Accidental &, + int shift = 0, bool extra = false) const; + int getAccidentalHeight(const Accidental &) const; + + int getLineSpacing() const; + int getStemLength() const; + int getStemThickness() const; + int getStaffLineThickness() const; + int getLegerLineThickness() const; + int getDotWidth() const; + int getBarMargin() const; + + int getClefWidth(const Clef &clef) const; + int getTimeSigWidth(const TimeSignature ×ig) const; + int getRestWidth(const Note &restType) const; + int getKeyWidth(const Key &key, + Key previousKey = Key::DefaultKey) const; + int getTextWidth(const Text &text) const; + + /** + * Returns the width of clef and key signature drawn in a track header. + */ + int getClefAndKeyWidth(const Key &key, const Clef &clef); + + /** + * Returns the Number of Text Lines that can be written at top and bottom + * of a track header. + * The parameter is the track header height. + * Always returns a value >= 1. + */ + int getTrackHeaderNTL(int height); + + /** + * Returns the width of a text string written in a track header. + */ + int getTrackHeaderTextWidth(QString str); + + /** + * Returns the spacing of a text lines written in a track header. + */ + int getTrackHeaderTextLineSpacing(); + + /** + * Returns from the beginning of "text" a string of horizontal size + * "width" (when written with m_trackHeaderFont) and removes it + * from "text". + */ + QString getOneLine(QString &text, int width); + + + /** + * We need this function because as of Qt 3.1, QCanvasPixmap + * is no longer copyable by value, while QPixmap still is. + * + * So all the makeXXPixmap are now returning QCanvasPixmap* + * instead of QCanvasPixmap, but we need an easy way to + * convert them to QPixmap, since we use them that + * way quite often (to generate toolbar button icons for instance). + */ + static QPixmap toQPixmap(QCanvasPixmap*); + static void dumpStats(std::ostream &); + + + static const char* const defaultSerifFontFamily; + static const char* const defaultSansSerifFontFamily; + static const char* const defaultTimeSigFontFamily; + + +protected: + void init(std::string fontName, int size); + void initMaybe() { if (!m_font) init("", -1); } + + void drawNoteAux(const NotePixmapParameters ¶meters, + QPainter *painter, int x, int y); + void drawRestAux(const NotePixmapParameters ¶meters, QPoint &hotspot, + QPainter *painter, int x, int y); + void drawHairpinAux(int length, bool isCrescendo, + QPainter *painter, int x, int y); + void drawSlurAux(int length, int dy, bool above, bool smooth, bool tie, bool phrasing, + QPoint &hotspot, + QPainter *painter, int x, int y); + void drawOttavaAux(int length, int octavesUp, + QPainter *painter, int x, int y); + void drawTextAux(const Text &text, + QPainter *painter, int x, int y); + + int getStemLength(const NotePixmapParameters &) const; + + void makeRoomForAccidental(Accidental, bool cautionary, int shift, bool extra); + void drawAccidental(Accidental, bool cautionary); + + void makeRoomForMarks(bool isStemmed, const NotePixmapParameters ¶ms, int stemLength); + void drawMarks(bool isStemmed, const NotePixmapParameters ¶ms, int stemLength); + + void makeRoomForLegerLines(const NotePixmapParameters ¶ms); + void drawLegerLines(const NotePixmapParameters ¶ms); + + void makeRoomForStemAndFlags(int flagCount, int stemLength, + const NotePixmapParameters ¶ms, + QPoint &startPoint, QPoint &endPoint); + void drawFlags(int flagCount, const NotePixmapParameters ¶ms, + const QPoint &startPoint, const QPoint &endPoint); + void drawStem(const NotePixmapParameters ¶ms, + const QPoint &startPoint, const QPoint &endPoint, + int shortening); + + void makeRoomForBeams(const NotePixmapParameters ¶ms); + void drawBeams(const QPoint &, const NotePixmapParameters ¶ms, + int beamCount); + + void drawSlashes(const QPoint &, const NotePixmapParameters ¶ms, + int slashCount); + + void makeRoomForTuplingLine(const NotePixmapParameters ¶ms); + void drawTuplingLine(const NotePixmapParameters ¶ms); + + void drawShallowLine(int x0, int y0, int x1, int y1, int thickness, + bool smooth); + void drawTie(bool above, int length, int shift); + + void drawBracket(int length, bool left, bool curly, int x, int y); + + QFont getTextFont(const Text &text) const; + + QCanvasPixmap* makeAnnotationPixmap(const Text &text); + QCanvasPixmap* makeAnnotationPixmap(const Text &text, const bool isLilyPondDirective); + + void createPixmapAndMask(int width, int height, + int maskWidth = -1, + int maskHeight = -1); + QCanvasPixmap* makeCanvasPixmap(QPoint hotspot, bool generateMask = false); + + enum ColourType { + PlainColour, + QuantizedColour, + HighlightedColour, + TriggerColour, + OutRangeColour + }; + + /// draws selected/shaded status from m_selected/m_shaded: + NoteCharacter getCharacter(CharName name, ColourType type, bool inverted); + + /// draws selected/shaded status from m_selected/m_shaded: + bool getCharacter(CharName name, NoteCharacter &ch, ColourType type, bool inverted); + + void drawNoteHalo(int x, int y, int w, int h); + + //--------------- Data members --------------------------------- + + NoteFont *m_font; + NoteStyle *m_style; + bool m_selected; + bool m_shaded; + + int m_noteBodyWidth, m_noteBodyHeight; + int m_left, m_right, m_above, m_below; + int m_borderX, m_borderY; + + QFont m_tupletCountFont; + QFontMetrics m_tupletCountFontMetrics; + + QFont m_textMarkFont; + QFontMetrics m_textMarkFontMetrics; + + QFont m_fingeringFont; + QFontMetrics m_fingeringFontMetrics; + + QFont m_timeSigFont; + QFontMetrics m_timeSigFontMetrics; + + QFont m_bigTimeSigFont; + QFontMetrics m_bigTimeSigFontMetrics; + + QFont m_ottavaFont; + QFontMetrics m_ottavaFontMetrics; + + QFont m_clefOttavaFont; + QFontMetrics m_clefOttavaFontMetrics; + + QFont m_trackHeaderFont; + QFontMetrics m_trackHeaderFontMetrics; + + QFont m_trackHeaderBoldFont; + QFontMetrics m_trackHeaderBoldFontMetrics; + + QPixmap *m_generatedPixmap; + QBitmap *m_generatedMask; + + int m_generatedWidth; + int m_generatedHeight; + bool m_inPrinterMethod; + + NotePixmapPainter *m_p; + + mutable NotePixmapCache *m_dottedRestCache; + + typedef std::map<const char *, QFont> TextFontCache; + mutable TextFontCache m_textFontCache; + + static QPoint m_pointZero; +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/NotePixmapPainter.h b/src/gui/editors/notation/NotePixmapPainter.h new file mode 100644 index 0000000..ed9d541 --- /dev/null +++ b/src/gui/editors/notation/NotePixmapPainter.h @@ -0,0 +1,148 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTEPIXMAPPAINTER_H_ +#define _RG_NOTEPIXMAPPAINTER_H_ + +#include <qpainter.h> + +namespace Rosegarden { + +class NotePixmapPainter +{ + // Just a trivial class that instructs two painters to do the + // same thing (one for the pixmap, one for the mask). We only + // duplicate those methods we actually use in NotePixmapFactory + +public: + NotePixmapPainter() : + m_painter(&m_myPainter) { } + + void beginExternal(QPainter *painter) { + + m_externalPainter = painter; + m_useMask = false; + + painter->setPen(QPen(Qt::black, 1, Qt::SolidLine, + Qt::RoundCap, Qt::RoundJoin)); + + if (m_externalPainter) { + m_painter = m_externalPainter; + } else { + m_painter = &m_myPainter; + } + } + + bool begin(QPaintDevice *device, QPaintDevice *mask = 0, bool unclipped = false) { + + m_externalPainter = 0; + + if (mask) { + m_useMask = true; + m_maskPainter.begin(mask, unclipped); + } else { + m_useMask = false; + } + + m_painter = &m_myPainter; + return m_painter->begin(device, unclipped); + } + + bool end() { + if (m_useMask) m_maskPainter.end(); + return m_painter->end(); + } + + QPainter &painter() { + return *m_painter; + } + + QPainter &maskPainter() { + return m_maskPainter; + } + + void drawPoint(int x, int y) { + m_painter->drawPoint(x, y); + if (m_useMask) m_maskPainter.drawPoint(x, y); + } + + void drawLine(int x1, int y1, int x2, int y2) { + m_painter->drawLine(x1, y1, x2, y2); + if (m_useMask) m_maskPainter.drawLine(x1, y1, x2, y2); + } + + void drawRect(int x, int y, int w, int h) { + m_painter->drawRect(x, y, w, h); + if (m_useMask) m_maskPainter.drawRect(x, y, w, h); + } + + void drawArc(int x, int y, int w, int h, int a, int alen) { + m_painter->drawArc(x, y, w, h, a, alen); + if (m_useMask) m_maskPainter.drawArc(x, y, w, h, a, alen); + } + + void drawPolygon(const QPointArray &a, bool winding = false, + int index = 0, int n = -1) { + m_painter->drawPolygon(a, winding, index, n); + if (m_useMask) m_maskPainter.drawPolygon(a, winding, index, n); + } + + void drawPolyline(const QPointArray &a, int index = 0, int n = -1) { + m_painter->drawPolyline(a, index, n); + if (m_useMask) m_maskPainter.drawPolyline(a, index, n); + } + + void drawPixmap(int x, int y, const QPixmap &pm, + int sx = 0, int sy = 0, int sw = -1, int sh = -1) { + m_painter->drawPixmap(x, y, pm, sx, sy, sw, sh); + if (m_useMask) m_maskPainter.drawPixmap(x, y, *(pm.mask()), sx, sy, sw, sh); + } + + void drawText(int x, int y, const QString &string) { + m_painter->drawText(x, y, string); + if (m_useMask) m_maskPainter.drawText(x, y, string); + } + + void drawNoteCharacter(int x, int y, const NoteCharacter &character) { + character.draw(m_painter, x, y); + if (m_useMask) character.drawMask(&m_maskPainter, x, y); + } + + void drawEllipse(int x, int y, int w, int h) { + m_painter->drawEllipse(x, y, w, h); + if (m_useMask) m_maskPainter.drawEllipse(x, y, w, h); + } + +private: + bool m_useMask; + QPainter m_myPainter; + QPainter m_maskPainter; + QPainter *m_externalPainter; + QPainter *m_painter; +}; + +} + +#endif diff --git a/src/gui/editors/notation/NotePixmapParameters.cpp b/src/gui/editors/notation/NotePixmapParameters.cpp new file mode 100644 index 0000000..b6dd7fb --- /dev/null +++ b/src/gui/editors/notation/NotePixmapParameters.cpp @@ -0,0 +1,151 @@ +/* -*- 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 "NotePixmapParameters.h" + +#include "base/NotationTypes.h" + + +namespace Rosegarden +{ + +NotePixmapParameters::NotePixmapParameters(Note::Type noteType, + int dots, + Accidental accidental) : + m_noteType(noteType), + m_dots(dots), + m_accidental(accidental), + m_cautionary(false), + m_shifted(false), + m_dotShifted(false), + m_accidentalShift(0), + m_drawFlag(true), + m_drawStem(true), + m_stemGoesUp(true), + m_stemLength( -1), + m_legerLines(0), + m_slashes(0), + m_selected(false), + m_highlighted(false), + m_quantized(false), + m_trigger(false), + m_onLine(false), + m_safeVertDistance(0), + m_restOutsideStave(false), + m_beamed(false), + m_nextBeamCount(0), + m_thisPartialBeams(false), + m_nextPartialBeams(false), + m_width(1), + m_gradient(0.0), + m_tupletCount(0), + m_tuplingLineY(0), + m_tuplingLineWidth(0), + m_tuplingLineGradient(0.0), + m_tied(false), + m_tieLength(0), + m_tiePositionExplicit(false), + m_tieAbove(false), + m_inRange(true) +{ + // nothing else +} + +NotePixmapParameters::~NotePixmapParameters() +{ + // nothing to see here +} + +void +NotePixmapParameters::setMarks(const std::vector<Mark> &marks) +{ + m_marks.clear(); + for (unsigned int i = 0; i < marks.size(); ++i) + m_marks.push_back(marks[i]); +} + +void +NotePixmapParameters::removeMarks() +{ + m_marks.clear(); +} + +std::vector<Rosegarden::Mark> +NotePixmapParameters::getNormalMarks() const +{ + std::vector<Mark> marks; + + for (std::vector<Mark>::const_iterator mi = m_marks.begin(); + mi != m_marks.end(); ++mi) { + + if (*mi == Marks::Pause || + *mi == Marks::UpBow || + *mi == Marks::DownBow || + *mi == Marks::Trill || + *mi == Marks::LongTrill || + *mi == Marks::TrillLine || + *mi == Marks::Turn || + Marks::isFingeringMark(*mi)) + continue; + + marks.push_back(*mi); + } + + return marks; +} + +std::vector<Rosegarden::Mark> +NotePixmapParameters::getAboveMarks() const +{ + std::vector<Mark> marks; + + // fingerings before other marks + + for (std::vector<Mark>::const_iterator mi = m_marks.begin(); + mi != m_marks.end(); ++mi) { + + if (Marks::isFingeringMark(*mi)) { + marks.push_back(*mi); + } + } + + for (std::vector<Mark>::const_iterator mi = m_marks.begin(); + mi != m_marks.end(); ++mi) { + + if (*mi == Marks::Pause || + *mi == Marks::UpBow || + *mi == Marks::DownBow || + *mi == Marks::Trill || + *mi == Marks::LongTrill || + *mi == Marks::TrillLine || + *mi == Marks::Turn) { + marks.push_back(*mi); + } + } + + return marks; +} + +} diff --git a/src/gui/editors/notation/NotePixmapParameters.h b/src/gui/editors/notation/NotePixmapParameters.h new file mode 100644 index 0000000..f7bfee7 --- /dev/null +++ b/src/gui/editors/notation/NotePixmapParameters.h @@ -0,0 +1,161 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTEPIXMAPPARAMETERS_H_ +#define _RG_NOTEPIXMAPPARAMETERS_H_ + +#include "base/NotationTypes.h" +#include <vector> + + + + +namespace Rosegarden +{ + + + +class NotePixmapParameters +{ +public: + NotePixmapParameters(Note::Type noteType, + int dots, + Accidental accidental = + Accidentals::NoAccidental); + ~NotePixmapParameters(); + + void setNoteType(Note::Type type) { m_noteType = type; } + void setDots(int dots) { m_dots = dots; } + void setAccidental(Accidental acc) { m_accidental = acc; } + + void setAccidentalCautionary(bool cautionary) { m_cautionary = cautionary; } + void setNoteHeadShifted(bool shifted) { m_shifted = shifted; } + void setNoteDotShifted(bool shifted) { m_dotShifted = shifted; } + void setAccidentalShift(int shift) { m_accidentalShift = shift; } + void setAccExtraShift(bool extra) { m_accidentalExtra = extra; } + + void setDrawFlag(bool df) { m_drawFlag = df; } + void setDrawStem(bool ds) { m_drawStem = ds; } + void setStemGoesUp(bool up) { m_stemGoesUp = up; } + void setStemLength(int length) { m_stemLength = length; } + void setLegerLines(int lines) { m_legerLines = lines; } + void setSlashes(int slashes) { m_slashes = slashes; } + void setRestOutside(bool os) { m_restOutsideStave = os; } + + void setSelected(bool selected) { m_selected = selected; } + void setHighlighted(bool highlighted) { m_highlighted = highlighted;} + void setQuantized(bool quantized) { m_quantized = quantized; } + void setTrigger(bool trigger) { m_trigger = trigger; } + void setIsOnLine(bool isOnLine) { m_onLine = isOnLine; } + void setSafeVertDistance(int safe) { m_safeVertDistance = safe; } + + void setBeamed(bool beamed) { m_beamed = beamed; } + void setNextBeamCount(int tc) { m_nextBeamCount = tc; } + void setThisPartialBeams(bool pt) { m_thisPartialBeams = pt; } + void setNextPartialBeams(bool pt) { m_nextPartialBeams = pt; } + void setWidth(int width) { m_width = width; } + void setGradient(double gradient) { m_gradient = gradient; } + + void setTupletCount(int count) { m_tupletCount = count; } + void setTuplingLineY(int y) { m_tuplingLineY = y; } + void setTuplingLineWidth(int width) { m_tuplingLineWidth = width; } + void setTuplingLineGradient(double g) { m_tuplingLineGradient = g; } + void setTuplingLineFollowsBeam(bool b){ m_tuplingLineFollowsBeam = b; } + + void setTied(bool tied) { m_tied = tied; } + void setTieLength(int tieLength) { m_tieLength = tieLength; } + + void setTiePosition(bool expl, bool above) { + m_tiePositionExplicit = expl; + m_tieAbove = above; + } + + void setMarks(const std::vector<Mark> &marks); + void removeMarks(); + + void setInRange(bool inRange) { m_inRange = inRange; } + + std::vector<Mark> getNormalMarks() const; + std::vector<Mark> getAboveMarks() const; // bowings, pause etc + + +private: + friend class NotePixmapFactory; + + //--------------- Data members --------------------------------- + + Note::Type m_noteType; + int m_dots; + Accidental m_accidental; + + bool m_cautionary; + bool m_shifted; + bool m_dotShifted; + int m_accidentalShift; + bool m_accidentalExtra; + bool m_drawFlag; + bool m_drawStem; + bool m_stemGoesUp; + int m_stemLength; + int m_legerLines; + int m_slashes; + bool m_selected; + bool m_highlighted; + bool m_quantized; + bool m_trigger; + bool m_onLine; + int m_safeVertDistance; + bool m_restOutsideStave; + + bool m_beamed; + int m_nextBeamCount; + bool m_thisPartialBeams; + bool m_nextPartialBeams; + int m_width; + double m_gradient; + + int m_tupletCount; + int m_tuplingLineY; + int m_tuplingLineWidth; + double m_tuplingLineGradient; + bool m_tuplingLineFollowsBeam; + + bool m_tied; + int m_tieLength; + bool m_tiePositionExplicit; + bool m_tieAbove; + + bool m_inRange; + + std::vector<Mark> m_marks; +}; + + +class NotePixmapPainter; + + +} + +#endif diff --git a/src/gui/editors/notation/NoteStyle.cpp b/src/gui/editors/notation/NoteStyle.cpp new file mode 100644 index 0000000..0b3332d --- /dev/null +++ b/src/gui/editors/notation/NoteStyle.cpp @@ -0,0 +1,485 @@ +/* -*- 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 "NoteStyle.h" + +#include "base/NotationTypes.h" +#include "base/PropertyName.h" +#include "NoteCharacterNames.h" +#include "NoteStyleFactory.h" + + +namespace Rosegarden +{ + +NoteStyle::~NoteStyle() +{ + // nothing +} + +const NoteStyle::NoteHeadShape NoteStyle::AngledOval = "angled oval"; + +const NoteStyle::NoteHeadShape NoteStyle::LevelOval = "level oval"; + +const NoteStyle::NoteHeadShape NoteStyle::Breve = "breve"; + +const NoteStyle::NoteHeadShape NoteStyle::Cross = "cross"; + +const NoteStyle::NoteHeadShape NoteStyle::TriangleUp = "triangle up"; + +const NoteStyle::NoteHeadShape NoteStyle::TriangleDown = "triangle down"; + +const NoteStyle::NoteHeadShape NoteStyle::Diamond = "diamond"; + +const NoteStyle::NoteHeadShape NoteStyle::Rectangle = "rectangle"; + +const NoteStyle::NoteHeadShape NoteStyle::Number = "number"; + +const NoteStyle::NoteHeadShape NoteStyle::CustomCharName = "custom character"; + + + +NoteStyle::NoteHeadShape + +NoteStyle::getShape(Note::Type type) +{ + NoteDescriptionMap::iterator i = m_notes.find(type); + if (i == m_notes.end()) { + if (m_baseStyle) + return m_baseStyle->getShape(type); + std::cerr + << "WARNING: NoteStyle::getShape: No shape defined for note type " + << type << ", defaulting to AngledOval" << std::endl; + return AngledOval; + } + + return i->second.shape; +} + +bool +NoteStyle::isFilled(Note::Type type) +{ + NoteDescriptionMap::iterator i = m_notes.find(type); + if (i == m_notes.end()) { + if (m_baseStyle) + return m_baseStyle->isFilled(type); + std::cerr + << "WARNING: NoteStyle::isFilled: No definition for note type " + << type << ", defaulting to true" << std::endl; + return true; + } + + return i->second.filled; +} + +bool +NoteStyle::hasStem(Note::Type type) +{ + NoteDescriptionMap::iterator i = m_notes.find(type); + if (i == m_notes.end()) { + if (m_baseStyle) + return m_baseStyle->hasStem(type); + std::cerr + << "WARNING: NoteStyle::hasStem: No definition for note type " + << type << ", defaulting to true" << std::endl; + return true; + } + + return i->second.stem; +} + +int +NoteStyle::getFlagCount(Note::Type type) +{ + NoteDescriptionMap::iterator i = m_notes.find(type); + if (i == m_notes.end()) { + if (m_baseStyle) + return m_baseStyle->getFlagCount(type); + std::cerr + << "WARNING: NoteStyle::getFlagCount: No definition for note type " + << type << ", defaulting to 0" << std::endl; + return 0; + } + + return i->second.flags; +} + +int +NoteStyle::getSlashCount(Note::Type type) +{ + NoteDescriptionMap::iterator i = m_notes.find(type); + if (i == m_notes.end()) { + if (m_baseStyle) + return m_baseStyle->getSlashCount(type); + std::cerr + << "WARNING: NoteStyle::getSlashCount: No definition for note type " + << type << ", defaulting to 0" << std::endl; + return 0; + } + + return i->second.slashes; +} + +void +NoteStyle::getStemFixPoints(Note::Type type, + HFixPoint &hfix, VFixPoint &vfix) +{ + NoteDescriptionMap::iterator i = m_notes.find(type); + if (i == m_notes.end()) { + if (m_baseStyle) { + m_baseStyle->getStemFixPoints(type, hfix, vfix); + return ; + } + std::cerr + << "WARNING: NoteStyle::getStemFixPoints: " + << "No definition for note type " << type + << ", defaulting to (Normal,Middle)" << std::endl; + hfix = Normal; + vfix = Middle; + return ; + } + + hfix = i->second.hfix; + vfix = i->second.vfix; +} + +NoteStyle::CharNameRec + +NoteStyle::getNoteHeadCharName(Note::Type type) +{ + NoteDescriptionMap::iterator i = m_notes.find(type); + if (i == m_notes.end()) { + if (m_baseStyle) + return m_baseStyle->getNoteHeadCharName(type); + std::cerr + << "WARNING: NoteStyle::getNoteHeadCharName: No definition for note type " + << type << ", defaulting to NOTEHEAD_BLACK" << std::endl; + return CharNameRec(NoteCharacterNames::NOTEHEAD_BLACK, false); + } + + const NoteDescription &desc(i->second); + CharName name = NoteCharacterNames::UNKNOWN; + bool inverted = false; + + if (desc.shape == AngledOval) { + + name = desc.filled ? NoteCharacterNames::NOTEHEAD_BLACK + : NoteCharacterNames::VOID_NOTEHEAD; + + } else if (desc.shape == LevelOval) { + + if (desc.filled) { + std::cerr << "WARNING: NoteStyle::getNoteHeadCharName: No filled level oval head" << std::endl; + } + name = NoteCharacterNames::WHOLE_NOTE; + + } else if (desc.shape == Breve) { + + if (desc.filled) { + std::cerr << "WARNING: NoteStyle::getNoteHeadCharName: No filled breve head" << std::endl; + } + name = NoteCharacterNames::BREVE; + + } else if (desc.shape == Cross) { + + name = desc.filled ? NoteCharacterNames::X_NOTEHEAD + : NoteCharacterNames::CIRCLE_X_NOTEHEAD; + + } else if (desc.shape == TriangleUp) { + + name = desc.filled ? NoteCharacterNames::TRIANGLE_NOTEHEAD_UP_BLACK + : NoteCharacterNames::TRIANGLE_NOTEHEAD_UP_WHITE; + + } else if (desc.shape == TriangleDown) { + + name = desc.filled ? NoteCharacterNames::TRIANGLE_NOTEHEAD_UP_BLACK + : NoteCharacterNames::TRIANGLE_NOTEHEAD_UP_WHITE; + inverted = true; + + } else if (desc.shape == Diamond) { + + name = desc.filled ? NoteCharacterNames::SEMIBREVIS_BLACK + : NoteCharacterNames::SEMIBREVIS_WHITE; + + } else if (desc.shape == Rectangle) { + + name = desc.filled ? NoteCharacterNames::SQUARE_NOTEHEAD_BLACK + : NoteCharacterNames::SQUARE_NOTEHEAD_WHITE; + + } else if (desc.shape == Number) { + + std::cerr << "WARNING: NoteStyle::getNoteHeadCharName: Number not yet implemented" << std::endl; + name = NoteCharacterNames::UNKNOWN; //!!! + + } else if (desc.shape == CustomCharName) { + + name = desc.charName; + + } else { + + name = NoteCharacterNames::UNKNOWN; + } + + return CharNameRec(name, inverted); +} + +CharName +NoteStyle::getAccidentalCharName(const Accidental &a) +{ + if (a == Accidentals::Sharp) + return NoteCharacterNames::SHARP; + else if (a == Accidentals::Flat) + return NoteCharacterNames::FLAT; + else if (a == Accidentals::Natural) + return NoteCharacterNames::NATURAL; + else if (a == Accidentals::DoubleSharp) + return NoteCharacterNames::DOUBLE_SHARP; + else if (a == Accidentals::DoubleFlat) + return NoteCharacterNames::DOUBLE_FLAT; + return NoteCharacterNames::UNKNOWN; +} + +CharName +NoteStyle::getMarkCharName(const Mark &mark) +{ + if (mark == Marks::Accent) + return NoteCharacterNames::ACCENT; + else if (mark == Marks::Tenuto) + return NoteCharacterNames::TENUTO; + else if (mark == Marks::Staccato) + return NoteCharacterNames::STACCATO; + else if (mark == Marks::Staccatissimo) + return NoteCharacterNames::STACCATISSIMO; + else if (mark == Marks::Marcato) + return NoteCharacterNames::MARCATO; + else if (mark == Marks::Trill) + return NoteCharacterNames::TRILL; + else if (mark == Marks::LongTrill) + return NoteCharacterNames::TRILL; + else if (mark == Marks::TrillLine) + return NoteCharacterNames::TRILL_LINE; + else if (mark == Marks::Turn) + return NoteCharacterNames::TURN; + else if (mark == Marks::Pause) + return NoteCharacterNames::FERMATA; + else if (mark == Marks::UpBow) + return NoteCharacterNames::UP_BOW; + else if (mark == Marks::DownBow) + return NoteCharacterNames::DOWN_BOW; + else if (mark == Marks::Mordent) + return NoteCharacterNames::MORDENT; + else if (mark == Marks::MordentInverted) + return NoteCharacterNames::MORDENT_INVERTED; + else if (mark == Marks::MordentLong) + return NoteCharacterNames::MORDENT_LONG; + else if (mark == Marks::MordentLongInverted) + return NoteCharacterNames::MORDENT_LONG_INVERTED; + // Things like "sf" and "rf" are generated from text fonts + return NoteCharacterNames::UNKNOWN; +} + +CharName +NoteStyle::getClefCharName(const Clef &clef) +{ + std::string clefType(clef.getClefType()); + + if (clefType == Clef::Bass || clefType == Clef::Varbaritone || clefType == Clef::Subbass) { + return NoteCharacterNames::F_CLEF; + } else if (clefType == Clef::Treble || clefType == Clef::French) { + return NoteCharacterNames::G_CLEF; + } else { + return NoteCharacterNames::C_CLEF; + } +} + +CharName +NoteStyle::getRestCharName(Note::Type type, bool restOutsideStave) +{ + switch (type) { + case Note::Hemidemisemiquaver: + return NoteCharacterNames::SIXTY_FOURTH_REST; + case Note::Demisemiquaver: + return NoteCharacterNames::THIRTY_SECOND_REST; + case Note::Semiquaver: + return NoteCharacterNames::SIXTEENTH_REST; + case Note::Quaver: + return NoteCharacterNames::EIGHTH_REST; + case Note::Crotchet: + return NoteCharacterNames::QUARTER_REST; + case Note::Minim: + return restOutsideStave ? + NoteCharacterNames::HALF_REST + : NoteCharacterNames::HALF_REST_ON_STAFF; + case Note::Semibreve: + return restOutsideStave ? + NoteCharacterNames::WHOLE_REST + : NoteCharacterNames::WHOLE_REST_ON_STAFF; + case Note::Breve: + return restOutsideStave ? + NoteCharacterNames::MULTI_REST + : NoteCharacterNames::MULTI_REST_ON_STAFF; + default: + return NoteCharacterNames::UNKNOWN; + } +} + +CharName +NoteStyle::getPartialFlagCharName(bool final) +{ + if (final) + return NoteCharacterNames::FLAG_PARTIAL_FINAL; + else + return NoteCharacterNames::FLAG_PARTIAL; +} + +CharName +NoteStyle::getFlagCharName(int flagCount) +{ + switch (flagCount) { + case 1: + return NoteCharacterNames::FLAG_1; + case 2: + return NoteCharacterNames::FLAG_2; + case 3: + return NoteCharacterNames::FLAG_3; + case 4: + return NoteCharacterNames::FLAG_4; + default: + return NoteCharacterNames::UNKNOWN; + } +} + +CharName +NoteStyle::getTimeSignatureDigitName(int digit) +{ + switch (digit) { + case 0: + return NoteCharacterNames::DIGIT_ZERO; + case 1: + return NoteCharacterNames::DIGIT_ONE; + case 2: + return NoteCharacterNames::DIGIT_TWO; + case 3: + return NoteCharacterNames::DIGIT_THREE; + case 4: + return NoteCharacterNames::DIGIT_FOUR; + case 5: + return NoteCharacterNames::DIGIT_FIVE; + case 6: + return NoteCharacterNames::DIGIT_SIX; + case 7: + return NoteCharacterNames::DIGIT_SEVEN; + case 8: + return NoteCharacterNames::DIGIT_EIGHT; + case 9: + return NoteCharacterNames::DIGIT_NINE; + default: + return NoteCharacterNames::UNKNOWN; + } +} + +void +NoteStyle::setBaseStyle(NoteStyleName name) +{ + try { + m_baseStyle = NoteStyleFactory::getStyle(name); + if (m_baseStyle == this) + m_baseStyle = 0; + } catch (NoteStyleFactory::StyleUnavailable u) { + if (name != NoteStyleFactory::DefaultStyle) { + std::cerr + << "NoteStyle::setBaseStyle: Base style " + << name << " not available, defaulting to " + << NoteStyleFactory::DefaultStyle << std::endl; + setBaseStyle(NoteStyleFactory::DefaultStyle); + } else { + std::cerr + << "NoteStyle::setBaseStyle: Base style " + << name << " not available" << std::endl; + m_baseStyle = 0; + } + } +} + +void +NoteStyle::checkDescription(Note::Type note) +{ + if (m_baseStyle && (m_notes.find(note) == m_notes.end())) { + m_baseStyle->checkDescription(note); + m_notes[note] = m_baseStyle->m_notes[note]; + } +} + +void +NoteStyle::setShape(Note::Type note, NoteHeadShape shape) +{ + checkDescription(note); + m_notes[note].shape = shape; +} + +void +NoteStyle::setCharName(Note::Type note, CharName charName) +{ + checkDescription(note); + m_notes[note].charName = charName; +} + +void +NoteStyle::setFilled(Note::Type note, bool filled) +{ + checkDescription(note); + m_notes[note].filled = filled; +} + +void +NoteStyle::setStem(Note::Type note, bool stem) +{ + checkDescription(note); + m_notes[note].stem = stem; +} + +void +NoteStyle::setFlagCount(Note::Type note, int flags) +{ + checkDescription(note); + m_notes[note].flags = flags; +} + +void +NoteStyle::setSlashCount(Note::Type note, int slashes) +{ + checkDescription(note); + m_notes[note].slashes = slashes; +} + +void +NoteStyle::setStemFixPoints(Note::Type note, HFixPoint hfix, VFixPoint vfix) +{ + checkDescription(note); + m_notes[note].hfix = hfix; + m_notes[note].vfix = vfix; +} + +} diff --git a/src/gui/editors/notation/NoteStyle.h b/src/gui/editors/notation/NoteStyle.h new file mode 100644 index 0000000..3959e01 --- /dev/null +++ b/src/gui/editors/notation/NoteStyle.h @@ -0,0 +1,142 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTESTYLE_H_ +#define _RG_NOTESTYLE_H_ + +#include "base/NotationTypes.h" +#include <map> +#include "NoteCharacterNames.h" +#include <string> +#include <utility> +#include "gui/editors/notation/NoteCharacterNames.h" + + +class Mark; +class Accidental; + + +namespace Rosegarden +{ + +class Clef; + +typedef std::string NoteStyleName; + + +class NoteStyle +{ +public: + virtual ~NoteStyle(); + + typedef std::string NoteHeadShape; + + static const NoteHeadShape AngledOval; + static const NoteHeadShape LevelOval; + static const NoteHeadShape Breve; + static const NoteHeadShape Cross; + static const NoteHeadShape TriangleUp; + static const NoteHeadShape TriangleDown; + static const NoteHeadShape Diamond; + static const NoteHeadShape Rectangle; + static const NoteHeadShape CustomCharName; + static const NoteHeadShape Number; + + enum HFixPoint { Normal, Central, Reversed }; + enum VFixPoint { Near, Middle, Far }; + + NoteStyleName getName() const { return m_name; } + + NoteHeadShape getShape (Note::Type); + bool isFilled (Note::Type); + bool hasStem (Note::Type); + int getFlagCount (Note::Type); + int getSlashCount(Note::Type); + + typedef std::pair<CharName, bool> CharNameRec; // bool is "inverted" + CharNameRec getNoteHeadCharName(Note::Type); + + CharName getRestCharName(Note::Type, bool restOutsideStave); + CharName getPartialFlagCharName(bool final); + CharName getFlagCharName(int flagCount); + CharName getAccidentalCharName(const Accidental &); + CharName getMarkCharName(const Mark &); + CharName getClefCharName(const Clef &); + CharName getTimeSignatureDigitName(int digit); + + void setBaseStyle (NoteStyleName name); + void setShape (Note::Type, NoteHeadShape); + void setCharName (Note::Type, CharName); + void setFilled (Note::Type, bool); + void setStem (Note::Type, bool); + void setFlagCount (Note::Type, int); + void setSlashCount(Note::Type, int); + + void getStemFixPoints(Note::Type, HFixPoint &, VFixPoint &); + void setStemFixPoints(Note::Type, HFixPoint, VFixPoint); + +protected: + struct NoteDescription { + NoteHeadShape shape; // if CustomCharName, use charName + CharName charName; // only used if shape == CustomCharName + bool filled; + bool stem; + int flags; + int slashes; + HFixPoint hfix; + VFixPoint vfix; + + NoteDescription() : + shape(AngledOval), charName(NoteCharacterNames::UNKNOWN), + filled(true), stem(true), flags(0), slashes(0), + hfix(Normal), vfix(Middle) { } + + NoteDescription(NoteHeadShape _shape, CharName _charName, + bool _filled, bool _stem, int _flags, int _slashes, + HFixPoint _hfix, VFixPoint _vfix) : + shape(_shape), charName(_charName), + filled(_filled), stem(_stem), flags(_flags), slashes(_slashes), + hfix(_hfix), vfix(_vfix) { } + }; + + typedef std::map<Note::Type, NoteDescription> NoteDescriptionMap; + + NoteDescriptionMap m_notes; + NoteStyle *m_baseStyle; + NoteStyleName m_name; + + void checkDescription(Note::Type type); + +protected: // for use by NoteStyleFileReader + NoteStyle(NoteStyleName name) : m_baseStyle(0), m_name(name) { } + friend class NoteStyleFileReader; +}; + + + + +} + +#endif diff --git a/src/gui/editors/notation/NoteStyleFactory.cpp b/src/gui/editors/notation/NoteStyleFactory.cpp new file mode 100644 index 0000000..d4a8be8 --- /dev/null +++ b/src/gui/editors/notation/NoteStyleFactory.cpp @@ -0,0 +1,124 @@ +/* -*- 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 "NoteStyleFactory.h" + +#include <kstddirs.h> +#include "misc/Strings.h" +#include "base/Event.h" +#include "base/Exception.h" +#include "NotationProperties.h" +#include "NoteStyle.h" +#include "NoteStyleFileReader.h" +#include <kglobal.h> +#include <qdir.h> +#include <qfileinfo.h> +#include <qstring.h> +#include <qstringlist.h> + + +namespace Rosegarden +{ + +const NoteStyleName NoteStyleFactory::DefaultStyle = "Classical"; + +std::vector<NoteStyleName> +NoteStyleFactory::getAvailableStyleNames() +{ + std::vector<NoteStyleName> names; + + QString styleDir = KGlobal::dirs()->findResource("appdata", "styles/"); + QDir dir(styleDir); + if (!dir.exists()) { + std::cerr << "NoteStyle::getAvailableStyleNames: directory \"" << styleDir + << "\" not found" << std::endl; + return names; + } + + dir.setFilter(QDir::Files | QDir::Readable); + QStringList files = dir.entryList(); + bool foundDefault = false; + + for (QStringList::Iterator i = files.begin(); i != files.end(); ++i) { + if ((*i).length() > 4 && (*i).right(4) == ".xml") { + QFileInfo fileInfo(QString("%1/%2").arg(styleDir).arg(*i)); + if (fileInfo.exists() && fileInfo.isReadable()) { + std::string styleName = qstrtostr((*i).left((*i).length() - 4)); + if (styleName == DefaultStyle) + foundDefault = true; + names.push_back(styleName); + } + } + } + + if (!foundDefault) { + std::cerr << "NoteStyleFactory::getAvailableStyleNames: WARNING: Default style name \"" << DefaultStyle << "\" not found" << std::endl; + } + + return names; +} + +NoteStyle * +NoteStyleFactory::getStyle(NoteStyleName name) +{ + StyleMap::iterator i = m_styles.find(name); + + if (i == m_styles.end()) { + + try { + NoteStyle *newStyle = NoteStyleFileReader(name).getStyle(); + m_styles[name] = newStyle; + return newStyle; + + } catch (NoteStyleFileReader::StyleFileReadFailed f) { + std::cerr + << "NoteStyleFactory::getStyle: Style file read failed: " + << f.getMessage() << std::endl; + throw StyleUnavailable("Style file read failed: " + + f.getMessage()); + } + + } else { + return i->second; + } +} + +NoteStyle * +NoteStyleFactory::getStyleForEvent(Event *event) +{ + NoteStyleName styleName; + if (event->get + <String>(NotationProperties::NOTE_STYLE, styleName)) { + return getStyle(styleName); + } + else { + return getStyle(DefaultStyle); + } +} + +NoteStyleFactory::StyleMap NoteStyleFactory::m_styles; + + +} diff --git a/src/gui/editors/notation/NoteStyleFactory.h b/src/gui/editors/notation/NoteStyleFactory.h new file mode 100644 index 0000000..795537d --- /dev/null +++ b/src/gui/editors/notation/NoteStyleFactory.h @@ -0,0 +1,61 @@ + +/* -*- 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. +*/ + +#ifndef _RG_NOTESTYLEFACTORY_H_ +#define _RG_NOTESTYLEFACTORY_H_ + +#include "base/Exception.h" +#include <map> +#include <vector> +#include "NoteStyle.h" + + +namespace Rosegarden +{ + +class NoteStyle; +class Event; + +class NoteStyleFactory +{ +public: + static std::vector<NoteStyleName> getAvailableStyleNames(); + static const NoteStyleName DefaultStyle; + + static NoteStyle *getStyle(NoteStyleName name); + static NoteStyle *getStyleForEvent(Event *event); + + typedef Exception StyleUnavailable; + +private: + typedef std::map<std::string, NoteStyle *> StyleMap; + static StyleMap m_styles; +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/NoteStyleFileReader.cpp b/src/gui/editors/notation/NoteStyleFileReader.cpp new file mode 100644 index 0000000..b3f3464 --- /dev/null +++ b/src/gui/editors/notation/NoteStyleFileReader.cpp @@ -0,0 +1,193 @@ +/* -*- 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 "NoteStyleFileReader.h" + +#include <string> +#include "NoteStyle.h" +#include <qfileinfo.h> +#include <qdir.h> + +#include <kglobal.h> +#include <kstddirs.h> +#include <klocale.h> + +#include "misc/Strings.h" +#include "NotationStrings.h" +#include "misc/Debug.h" + +namespace Rosegarden { + + +NoteStyleFileReader::NoteStyleFileReader(std::string name) : + m_style(new NoteStyle(name)), + m_haveNote(false) +{ + QString styleDirectory = + KGlobal::dirs()->findResource("appdata", "styles/"); + + QString styleFileName = + QString("%1/%2.xml").arg(styleDirectory).arg(strtoqstr(name)); + + QFileInfo fileInfo(styleFileName); + + if (!fileInfo.isReadable()) { + throw StyleFileReadFailed + (qstrtostr(i18n("Can't open style file %1").arg(styleFileName))); + } + + QFile styleFile(styleFileName); + + QXmlInputSource source(styleFile); + QXmlSimpleReader reader; + reader.setContentHandler(this); + reader.setErrorHandler(this); + bool ok = reader.parse(source); + styleFile.close(); + + if (!ok) { + throw StyleFileReadFailed(qstrtostr(m_errorString)); + } +} + +bool +NoteStyleFileReader::startElement(const QString &, const QString &, + const QString &qName, + const QXmlAttributes &attributes) +{ + QString lcName = qName.lower(); + + if (lcName == "rosegarden-note-style") { + + QString s = attributes.value("base-style"); + if (s) m_style->setBaseStyle(qstrtostr(s)); + + } else if (lcName == "note") { + + m_haveNote = true; + + QString s = attributes.value("type"); + if (!s) { + m_errorString = i18n("type is a required attribute of note"); + return false; + } + + try { + Note::Type type = NotationStrings::getNoteForName(s).getNoteType(); + if (!setFromAttributes(type, attributes)) return false; + + } catch (NotationStrings::MalformedNoteName n) { + m_errorString = i18n("Unrecognised note name %1").arg(s); + return false; + } + + } else if (lcName == "global") { + + if (m_haveNote) { + m_errorString = i18n("global element must precede note elements"); + return false; + } + + for (Note::Type type = Note::Shortest; type <= Note::Longest; ++type) { + if (!setFromAttributes(type, attributes)) return false; + } + } + + return true; +} + + +bool +NoteStyleFileReader::setFromAttributes(Note::Type type, + const QXmlAttributes &attributes) +{ + QString s; + bool haveShape = false; + + s = attributes.value("shape"); + if (s) { + m_style->setShape(type, qstrtostr(s.lower())); + haveShape = true; + } + + s = attributes.value("charname"); + if (s) { + if (haveShape) { + m_errorString = i18n("global and note elements may have shape " + "or charname attribute, but not both"); + return false; + } + m_style->setShape(type, NoteStyle::CustomCharName); + m_style->setCharName(type, qstrtostr(s)); + } + + s = attributes.value("filled"); + if (s) m_style->setFilled(type, s.lower() == "true"); + + s = attributes.value("stem"); + if (s) m_style->setStem(type, s.lower() == "true"); + + s = attributes.value("flags"); + if (s) m_style->setFlagCount(type, s.toInt()); + + s = attributes.value("slashes"); + if (s) m_style->setSlashCount(type, s.toInt()); + + NoteStyle::HFixPoint hfix; + NoteStyle::VFixPoint vfix; + m_style->getStemFixPoints(type, hfix, vfix); + bool haveHFix = false; + bool haveVFix = false; + + s = attributes.value("hfixpoint"); + if (s) { + s = s.lower(); + haveHFix = true; + if (s == "normal") hfix = NoteStyle::Normal; + else if (s == "central") hfix = NoteStyle::Central; + else if (s == "reversed") hfix = NoteStyle::Reversed; + else haveHFix = false; + } + + s = attributes.value("vfixpoint"); + if (s) { + s = s.lower(); + haveVFix = true; + if (s == "near") vfix = NoteStyle::Near; + else if (s == "middle") vfix = NoteStyle::Middle; + else if (s == "far") vfix = NoteStyle::Far; + else haveVFix = false; + } + + if (haveHFix || haveVFix) { + m_style->setStemFixPoints(type, hfix, vfix); + // otherwise they inherit from base style, so avoid setting here + } + + return true; +} + + +} + diff --git a/src/gui/editors/notation/NoteStyleFileReader.h b/src/gui/editors/notation/NoteStyleFileReader.h new file mode 100644 index 0000000..d3dfbbe --- /dev/null +++ b/src/gui/editors/notation/NoteStyleFileReader.h @@ -0,0 +1,59 @@ +/* -*- 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. +*/ + +#ifndef _RG_NOTESTYLEFILEREADER_H_ +#define _RG_NOTESTYLEFILEREADER_H_ + +#include <qxml.h> + +#include "NoteStyle.h" + +namespace Rosegarden { + +class NoteStyleFileReader : public QXmlDefaultHandler +{ +public: + NoteStyleFileReader(NoteStyleName name); + + typedef Rosegarden::Exception StyleFileReadFailed; + + NoteStyle *getStyle() { return m_style; } + + // Xml handler methods: + + virtual bool startElement + (const QString& namespaceURI, const QString& localName, + const QString& qName, const QXmlAttributes& atts); + +private: + bool setFromAttributes(Note::Type type, const QXmlAttributes &attributes); + + QString m_errorString; + NoteStyle *m_style; + bool m_haveNote; +}; + +} + +#endif diff --git a/src/gui/editors/notation/RestInserter.cpp b/src/gui/editors/notation/RestInserter.cpp new file mode 100644 index 0000000..399cf2d --- /dev/null +++ b/src/gui/editors/notation/RestInserter.cpp @@ -0,0 +1,150 @@ +/* -*- 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 "RestInserter.h" + +#include <klocale.h> +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/BaseProperties.h" +#include "base/Segment.h" +#include "commands/notation/NoteInsertionCommand.h" +#include "commands/notation/RestInsertionCommand.h" +#include "commands/notation/TupletCommand.h" +#include "gui/general/EditTool.h" +#include "NotationStrings.h" +#include "NotationTool.h" +#include "NotationView.h" +#include "NoteInserter.h" +#include "NotePixmapFactory.h" +#include <kaction.h> +#include <kcommand.h> +#include <qiconset.h> +#include <qregexp.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +RestInserter::RestInserter(NotationView* view) + : NoteInserter("RestInserter", view) +{ + QIconSet icon; + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("dotted-rest-crotchet"))); + new KToggleAction(i18n("Dotted rest"), icon, 0, this, + SLOT(slotToggleDot()), actionCollection(), + "toggle_dot"); + + icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("select"))); + new KAction(i18n("Switch to Select Tool"), icon, 0, this, + SLOT(slotSelectSelected()), actionCollection(), + "select"); + + new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this, + SLOT(slotEraseSelected()), actionCollection(), + "erase"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("crotchet"))); + new KAction(i18n("Switch to Inserting Notes"), icon, 0, this, + SLOT(slotNotesSelected()), actionCollection(), + "notes"); + + createMenu("restinserter.rc"); +} + +void +RestInserter::showPreview() +{ + // no preview available for now +} + +Event * +RestInserter::doAddCommand(Segment &segment, timeT time, timeT endTime, + const Note ¬e, int, Accidental) +{ + if (time < segment.getStartTime() || + endTime > segment.getEndMarkerTime() || + time + note.getDuration() > segment.getEndMarkerTime()) { + return 0; + } + + NoteInsertionCommand *insertionCommand = + new RestInsertionCommand(segment, time, endTime, note); + + KCommand *activeCommand = insertionCommand; + + if (m_nParentView->isInTripletMode()) { + Segment::iterator i(segment.findTime(time)); + if (i != segment.end() && + !(*i)->has(BEAMED_GROUP_TUPLET_BASE)) { + + KMacroCommand *command = new KMacroCommand(insertionCommand->name()); + command->addCommand(new TupletCommand + (segment, time, note.getDuration())); + command->addCommand(insertionCommand); + activeCommand = command; + } + } + + m_nParentView->addCommandToHistory(activeCommand); + + return insertionCommand->getLastInsertedEvent(); +} + +void RestInserter::slotToggleDot() +{ + m_noteDots = (m_noteDots) ? 0 : 1; + Note note(m_noteType, m_noteDots); + QString actionName(NotationStrings::getReferenceName(note, true)); + actionName.replace(QRegExp("-"), "_"); + KAction *action = m_parentView->actionCollection()->action(actionName); + if (!action) { + std::cerr << "WARNING: No such action as " << actionName << std::endl; + } else { + action->activate(); + } +} + +void RestInserter::slotNotesSelected() +{ + Note note(m_noteType, m_noteDots); + QString actionName(NotationStrings::getReferenceName(note)); + actionName.replace(QRegExp(" "), "_"); + m_parentView->actionCollection()->action(actionName)->activate(); +} + +const QString RestInserter::ToolName = "restinserter"; + +} +#include "RestInserter.moc" diff --git a/src/gui/editors/notation/RestInserter.h b/src/gui/editors/notation/RestInserter.h new file mode 100644 index 0000000..90239fb --- /dev/null +++ b/src/gui/editors/notation/RestInserter.h @@ -0,0 +1,76 @@ + +/* -*- 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. +*/ + +#ifndef _RG_RESTINSERTER_H_ +#define _RG_RESTINSERTER_H_ + +#include "NoteInserter.h" +#include <qstring.h> +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Note; +class NotationView; +class Event; + + +/** + * This tool will insert rests on mouse click events + */ +class RestInserter : public NoteInserter +{ + Q_OBJECT + + friend class NotationToolBox; + +public: + + static const QString ToolName; + +protected: + RestInserter(NotationView*); + + virtual Event *doAddCommand(Segment &, + timeT time, + timeT endTime, + const Note &, + int pitch, Accidental); + virtual void showPreview(); + +protected slots: + void slotToggleDot(); + void slotNotesSelected(); +}; + + +} + +#endif diff --git a/src/gui/editors/notation/SystemFont.cpp b/src/gui/editors/notation/SystemFont.cpp new file mode 100644 index 0000000..71f0ce7 --- /dev/null +++ b/src/gui/editors/notation/SystemFont.cpp @@ -0,0 +1,165 @@ +/* -*- 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 "SystemFont.h" +#include "SystemFontQt.h" +#include "SystemFontXft.h" + +#include "misc/Debug.h" + +#include <kstddirs.h> +#include "NoteFontMap.h" +#include <qfont.h> +#include <qfontinfo.h> +#include <qpixmap.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +SystemFont * +SystemFont::loadSystemFont(const SystemFontSpec &spec) +{ + QString name = spec.first; + int size = spec.second; + + NOTATION_DEBUG << "SystemFont::loadSystemFont: name is " << name << ", size " << size << endl; + + if (name == "DEFAULT") { + QFont font; + font.setPixelSize(size); + return new SystemFontQt(font); + } + +#ifdef HAVE_XFT + + FcPattern *pattern, *match; + FcResult result; + FcChar8 *matchFamily; + XftFont *xfont = 0; + + Display *dpy = QPaintDevice::x11AppDisplay(); + static bool haveFcDirectory = false; + + if (!dpy) { + std::cerr << "SystemFont::loadSystemFont[Xft]: Xft support requested but no X11 display available!" << std::endl; + goto qfont; + } + + if (!haveFcDirectory) { + QString fontDir = KGlobal::dirs()->findResource("appdata", "fonts/"); + if (!FcConfigAppFontAddDir(FcConfigGetCurrent(), + (const FcChar8 *)fontDir.latin1())) { + NOTATION_DEBUG << "SystemFont::loadSystemFont[Xft]: Failed to add font directory " << fontDir << " to fontconfig, continuing without it" << endl; + } + haveFcDirectory = true; + } + + pattern = FcPatternCreate(); + FcPatternAddString(pattern, FC_FAMILY, (FcChar8 *)name.latin1()); + FcPatternAddInteger(pattern, FC_PIXEL_SIZE, size); + FcConfigSubstitute(FcConfigGetCurrent(), pattern, FcMatchPattern); + + result = FcResultMatch; + match = FcFontMatch(FcConfigGetCurrent(), pattern, &result); + FcPatternDestroy(pattern); + + if (!match || result != FcResultMatch) { + NOTATION_DEBUG << "SystemFont::loadSystemFont[Xft]: No match for font " + << name << " (result is " << result + << "), falling back on QFont" << endl; + if (match) + FcPatternDestroy(match); + goto qfont; + } + + FcPatternGetString(match, FC_FAMILY, 0, &matchFamily); + NOTATION_DEBUG << "SystemFont::loadSystemFont[Xft]: match family is " + << (char *)matchFamily << endl; + + if (QString((char *)matchFamily).lower() != name.lower()) { + NOTATION_DEBUG << "SystemFont::loadSystemFont[Xft]: Wrong family returned, falling back on QFont" << endl; + FcPatternDestroy(match); + goto qfont; + } + + xfont = XftFontOpenPattern(dpy, match); + if (!xfont) { + FcPatternDestroy(match); + NOTATION_DEBUG << "SystemFont::loadSystemFont[Xft]: Unable to load font " + << name << " via Xft, falling back on QFont" << endl; + goto qfont; + } + + NOTATION_DEBUG << "SystemFont::loadSystemFont[Xft]: successfully loaded font " + << name << " through Xft" << endl; + + return new SystemFontXft(dpy, xfont); + + +qfont: + +#endif + + QFont qfont(name, size, QFont::Normal); + qfont.setPixelSize(size); + + QFontInfo info(qfont); + + NOTATION_DEBUG << "SystemFont::loadSystemFont[Qt]: have family " << info.family() << " (exactMatch " << info.exactMatch() << ")" << endl; + + // return info.exactMatch(); + + // The Qt documentation says: + // + // bool QFontInfo::exactMatch() const + // Returns TRUE if the matched window system font is exactly the + // same as the one specified by the font; otherwise returns FALSE. + // + // My arse. I specify "feta", I get "Verdana", and exactMatch + // returns true. Uh huh. + // + // UPDATE: in newer versions of Qt, I specify "fughetta", I get + // "Fughetta [macromedia]", and exactMatch returns false. Just as + // useless, but in a different way. + + QString family = info.family().lower(); + + if (family == name.lower()) + return new SystemFontQt(qfont); + else { + int bracket = family.find(" ["); + if (bracket > 1) + family = family.left(bracket); + if (family == name.lower()) + return new SystemFontQt(qfont); + } + + NOTATION_DEBUG << "SystemFont::loadSystemFont[Qt]: Wrong family returned, failing" << endl; + return 0; +} + +} diff --git a/src/gui/editors/notation/SystemFont.h b/src/gui/editors/notation/SystemFont.h new file mode 100644 index 0000000..0acc2dd --- /dev/null +++ b/src/gui/editors/notation/SystemFont.h @@ -0,0 +1,63 @@ + +/* -*- 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. +*/ + +#ifndef _RG_SYSTEMFONT_H_ +#define _RG_SYSTEMFONT_H_ + +#include <qpixmap.h> +#include "gui/editors/notation/NoteCharacterNames.h" + + +class SystemFontSpec; + + +namespace Rosegarden +{ + +typedef std::pair<QString, int> SystemFontSpec; + + +class SystemFont +{ +public: + enum Strategy { + PreferGlyphs, PreferCodes, OnlyGlyphs, OnlyCodes + }; + + virtual QPixmap renderChar(CharName charName, + int glyph, int code, + Strategy strategy, + bool &success) = 0; + + static SystemFont *loadSystemFont(const SystemFontSpec &spec); +}; + + +// Helper class for looking up information about a font + + +} + +#endif diff --git a/src/gui/editors/notation/SystemFontQt.cpp b/src/gui/editors/notation/SystemFontQt.cpp new file mode 100644 index 0000000..f9c99b1 --- /dev/null +++ b/src/gui/editors/notation/SystemFontQt.cpp @@ -0,0 +1,78 @@ +/* -*- 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 "SystemFontQt.h" + +#include "misc/Debug.h" +#include "gui/general/PixmapFunctions.h" + +#include <qfont.h> +#include <qfontmetrics.h> +#include <qpainter.h> +#include <qpixmap.h> + +namespace Rosegarden { + +QPixmap +SystemFontQt::renderChar(CharName charName, int glyph, int code, + Strategy strategy, bool &success) +{ + success = false; + + if (strategy == OnlyGlyphs) { + NOTATION_DEBUG << "SystemFontQt::renderChar: OnlyGlyphs strategy not supported by Qt renderer, can't render character " << charName.getName() << " (glyph " << glyph << ")" << endl; + return QPixmap(); + } + + if (code < 0) { + NOTATION_DEBUG << "SystemFontQt::renderChar: Can't render using Qt with only glyph value (" << glyph << ") for character " << charName.getName() << ", need a code point" << endl; + return QPixmap(); + } + + QFontMetrics metrics(m_font); + QChar qc(code); + + QPixmap map; + map = QPixmap(metrics.width(qc), metrics.height()); + + map.fill(); + QPainter painter; + painter.begin(&map); + painter.setFont(m_font); + painter.setPen(Qt::black); + + NOTATION_DEBUG << "NoteFont: Drawing character code " + << code << " for " << charName.getName() + << " using QFont" << endl; + + painter.drawText(0, metrics.ascent(), qc); + + painter.end(); + map.setMask(PixmapFunctions::generateMask(map, Qt::white.rgb())); + + success = true; + return map; +} + +} diff --git a/src/gui/editors/notation/SystemFontQt.h b/src/gui/editors/notation/SystemFontQt.h new file mode 100644 index 0000000..ea8f5f2 --- /dev/null +++ b/src/gui/editors/notation/SystemFontQt.h @@ -0,0 +1,49 @@ +/* -*- 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. +*/ + +#ifndef _RG_SYSTEMFONTQT_H_ +#define _RG_SYSTEMFONTQT_H_ + +#include "SystemFont.h" + +#include <qfont.h> + +namespace Rosegarden { + +class SystemFontQt : public SystemFont +{ +public: + SystemFontQt(QFont &font) : m_font(font) { } + virtual ~SystemFontQt() { } + + virtual QPixmap renderChar(CharName charName, int glyph, int code, + Strategy strategy, bool &success); + +private: + QFont m_font; +}; + +} + +#endif diff --git a/src/gui/editors/notation/SystemFontXft.cpp b/src/gui/editors/notation/SystemFontXft.cpp new file mode 100644 index 0000000..ce42f61 --- /dev/null +++ b/src/gui/editors/notation/SystemFontXft.cpp @@ -0,0 +1,193 @@ +/* -*- 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 "SystemFontXft.h" + +#ifdef HAVE_XFT + +#include "misc/Debug.h" +#include "gui/general/PixmapFunctions.h" + +namespace Rosegarden { + +/*!!! Just test code. + +int +staticMoveTo(FT_Vector *to, void *) +{ + NOTATION_DEBUG << "moveTo: (" << to->x << "," << to->y << ")" << endl; + return 0; +} + +int +staticLineTo(FT_Vector *to, void *) +{ + NOTATION_DEBUG << "lineTo: (" << to->x << "," << to->y << ")" << endl; + return 0; +} + +int +staticConicTo(FT_Vector *control, FT_Vector *to, void *) +{ + NOTATION_DEBUG << "conicTo: (" << to->x << "," << to->y << ") control (" << control->x << "," << control->y << ")" << endl; + return 0; +} + +int +staticCubicTo(FT_Vector *control1, FT_Vector *control2, FT_Vector *to, void *) +{ + NOTATION_DEBUG << "cubicTo: (" << to->x << "," << to->y << ") control1 (" << control1->x << "," << control1->y << ") control2 (" << control2->x << "," << control2->y << ")" << endl; + return 0; +} + +*/ + +QPixmap +SystemFontXft::renderChar(CharName charName, int glyph, int code, + Strategy strategy, bool &success) +{ + success = false; + + if (glyph < 0 && code < 0) { + NOTATION_DEBUG << "SystemFontXft::renderChar: Have neither glyph nor code point for character " << charName.getName() << ", can't render" << endl; + return QPixmap(); + } + + if (code < 0 && strategy == OnlyCodes) { + NOTATION_DEBUG << "SystemFontXft::renderChar: strategy is OnlyCodes but no code point provided for character " << charName.getName() << " (glyph is " << glyph << ")" << endl; + return QPixmap(); + } + + if (glyph < 0 && strategy == OnlyGlyphs) { + NOTATION_DEBUG << "SystemFontXft::renderChar: strategy is OnlyGlyphs but no glyph index provided for character " << charName.getName() << " (code is " << code << ")" << endl; + return QPixmap(); + } + + XGlyphInfo extents; + + bool useGlyph = true; + if (glyph < 0 || (strategy == PreferCodes && code >= 0)) useGlyph = false; + if (glyph >= 0 && useGlyph == false && !XftCharExists(m_dpy, m_font, code)) { + NOTATION_DEBUG << "SystemFontXft::renderChar: code " << code << " is preferred for character " << charName.getName() << ", but it doesn't exist in font! Falling back to glyph " << glyph << endl; + useGlyph = true; + } + + if (useGlyph) { + FT_UInt ui(glyph); + XftGlyphExtents(m_dpy, m_font, &ui, 1, &extents); + if (extents.width == 0 || extents.height == 0) { + NOTATION_DEBUG + << "SystemFontXft::renderChar: zero extents for character " + << charName.getName() << " (glyph " << glyph << ")" << endl; + return QPixmap(); + } + } else { + FcChar32 char32(code); + XftTextExtents32(m_dpy, m_font, &char32, 1, &extents); + if (extents.width == 0 || extents.height == 0) { + NOTATION_DEBUG + << "SystemFontXft::renderChar: zero extents for character " + << charName.getName() << " (code " << code << ")" << endl; + return QPixmap(); + } + } + + QPixmap map(extents.width, extents.height); + map.fill(); + + Drawable drawable = (Drawable)map.handle(); + if (!drawable) { + std::cerr << "ERROR: SystemFontXft::renderChar: No drawable in QPixmap!" << std::endl; + return map; + } + + XftDraw *draw = XftDrawCreate(m_dpy, + drawable, + (Visual *)map.x11Visual(), + map.x11Colormap()); + + QColor pen(Qt::black); + XftColor col; + col.color.red = pen.red () | pen.red() << 8; + col.color.green = pen.green () | pen.green() << 8; + col.color.blue = pen.blue () | pen.blue() << 8; + col.color.alpha = 0xffff; + col.pixel = pen.pixel(); + + if (useGlyph) { + NOTATION_DEBUG << "NoteFont: drawing raw character glyph " + << glyph << " for " << charName.getName() + << " using Xft" << endl; + FT_UInt ui(glyph); + XftDrawGlyphs(draw, &col, m_font, extents.x, extents.y, &ui, 1); + } else { + NOTATION_DEBUG << "NoteFont: drawing character code " + << code << " for " << charName.getName() + << " using Xft" << endl; + FcChar32 char32(code); + XftDrawString32(draw, &col, m_font, extents.x, extents.y, &char32, 1); + } + + XftDrawDestroy(draw); + + map.setMask(PixmapFunctions::generateMask(map, Qt::white.rgb())); + success = true; + + + //!!! experimental stuff +/*!!! + FT_Face face = XftLockFace(m_font); + if (!face) { + NOTATION_DEBUG << "Couldn't lock face" << endl; + return map; + } + // not checking return value here + FT_Load_Glyph(face, glyph, 0); + if (face->glyph->format != FT_GLYPH_FORMAT_OUTLINE) { + NOTATION_DEBUG << "Glyph " << glyph << " isn't an outline" << endl; + XftUnlockFace(m_font); + return map; + } + FT_Glyph ftglyph; + FT_Get_Glyph(face->glyph, &ftglyph); + FT_Outline &outline = ((FT_OutlineGlyph)ftglyph)->outline; + NOTATION_DEBUG << "Outline: " << outline.n_contours << " contours, " + << outline.n_points << " points" << endl; + + + FT_Outline_Funcs funcs = { + staticMoveTo, staticLineTo, staticConicTo, staticCubicTo, 0, 0 + }; + FT_Outline_Decompose(&outline, &funcs, 0); + FT_Done_Glyph(ftglyph); + XftUnlockFace(m_font); +*/ + + return map; +} + +} + +#endif /* HAVE_XFT */ + diff --git a/src/gui/editors/notation/SystemFontXft.h b/src/gui/editors/notation/SystemFontXft.h new file mode 100644 index 0000000..b1487c4 --- /dev/null +++ b/src/gui/editors/notation/SystemFontXft.h @@ -0,0 +1,58 @@ +/* -*- 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. +*/ + +#ifndef _RG_SYSTEMFONTXFT_H_ +#define _RG_SYSTEMFONTXFT_H_ + +#ifdef HAVE_XFT + +#include "SystemFont.h" + +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_OUTLINE_H +#include FT_GLYPH_H +#include <X11/Xft/Xft.h> + +namespace Rosegarden { + +class SystemFontXft : public SystemFont +{ +public: + SystemFontXft(Display *dpy, XftFont *font) : m_dpy(dpy), m_font(font) { } + virtual ~SystemFontXft() { if (m_font) XftFontClose(m_dpy, m_font); } + + virtual QPixmap renderChar(CharName charName, int glyph, int code, + Strategy strategy, bool &success); + +private: + Display *m_dpy; + XftFont *m_font; +}; + +} + +#endif + +#endif diff --git a/src/gui/editors/notation/TextInserter.cpp b/src/gui/editors/notation/TextInserter.cpp new file mode 100644 index 0000000..aa8e1ff --- /dev/null +++ b/src/gui/editors/notation/TextInserter.cpp @@ -0,0 +1,169 @@ +/* -*- 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 "TextInserter.h" + +#include <klocale.h> +#include "base/Event.h" +#include "base/Exception.h" +#include "base/NotationTypes.h" +#include "base/ViewElement.h" +#include "commands/notation/EraseEventCommand.h" +#include "commands/notation/TextInsertionCommand.h" +#include "gui/dialogs/TextEventDialog.h" +#include "gui/general/EditTool.h" +#include "gui/general/LinedStaff.h" +#include "NotationTool.h" +#include "NotationView.h" +#include "NotePixmapFactory.h" +#include "NotationElement.h" +#include <kaction.h> +#include <qdialog.h> +#include <qiconset.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +TextInserter::TextInserter(NotationView* view) + : NotationTool("TextInserter", view), + m_text("", Text::Dynamic) +{ + QIconSet icon = QIconSet(NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("select"))); + new KAction(i18n("Switch to Select Tool"), icon, 0, this, + SLOT(slotSelectSelected()), actionCollection(), + "select"); + + new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this, + SLOT(slotEraseSelected()), actionCollection(), + "erase"); + + icon = QIconSet + (NotePixmapFactory::toQPixmap(NotePixmapFactory:: + makeToolbarPixmap("crotchet"))); + new KAction(i18n("Switch to Inserting Notes"), icon, 0, this, + SLOT(slotNotesSelected()), actionCollection(), + "notes"); + + createMenu("textinserter.rc"); +} + +void TextInserter::slotNotesSelected() +{ + m_nParentView->slotLastNoteAction(); +} + +void TextInserter::slotEraseSelected() +{ + m_parentView->actionCollection()->action("erase")->activate(); +} + +void TextInserter::slotSelectSelected() +{ + m_parentView->actionCollection()->action("select")->activate(); +} + +void TextInserter::ready() +{ + m_nParentView->setCanvasCursor(Qt::crossCursor); + m_nParentView->setHeightTracking(false); +} + +void TextInserter::handleLeftButtonPress(timeT, + int, + int staffNo, + QMouseEvent* e, + ViewElement *element) +{ + if (staffNo < 0) + return ; + LinedStaff *staff = m_nParentView->getLinedStaff(staffNo); + + Text defaultText(m_text); + timeT insertionTime; + Event *eraseEvent = 0; + + if (element && element->event()->isa(Text::EventType)) { + + // edit an existing text, if that's what we clicked on + + try { + defaultText = Text(*element->event()); + } catch (Exception e) {} + + insertionTime = element->event()->getAbsoluteTime(); // not getViewAbsoluteTime() + + eraseEvent = element->event(); + + } else { + + Event *clef = 0, *key = 0; + + NotationElementList::iterator closestElement = + staff->getClosestElementToCanvasCoords(e->x(), (int)e->y(), + clef, key, false, -1); + + if (closestElement == staff->getViewElementList()->end()) + return ; + + insertionTime = (*closestElement)->event()->getAbsoluteTime(); // not getViewAbsoluteTime() + + } + + TextEventDialog *dialog = new TextEventDialog + (m_nParentView, m_nParentView->getNotePixmapFactory(), defaultText); + + if (dialog->exec() == QDialog::Accepted) { + + m_text = dialog->getText(); + + TextInsertionCommand *command = + new TextInsertionCommand + (staff->getSegment(), insertionTime, m_text); + + if (eraseEvent) { + KMacroCommand *macroCommand = new KMacroCommand(command->name()); + macroCommand->addCommand(new EraseEventCommand(staff->getSegment(), + eraseEvent, false)); + macroCommand->addCommand(command); + m_nParentView->addCommandToHistory(macroCommand); + } else { + m_nParentView->addCommandToHistory(command); + } + + Event *event = command->getLastInsertedEvent(); + if (event) + m_nParentView->setSingleSelectedEvent(staffNo, event); + } + + delete dialog; +} + +const QString TextInserter::ToolName = "textinserter"; + +} +#include "TextInserter.moc" diff --git a/src/gui/editors/notation/TextInserter.h b/src/gui/editors/notation/TextInserter.h new file mode 100644 index 0000000..3b4821b --- /dev/null +++ b/src/gui/editors/notation/TextInserter.h @@ -0,0 +1,78 @@ + +/* -*- 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. +*/ + +#ifndef _RG_TEXTINSERTER_H_ +#define _RG_TEXTINSERTER_H_ + +#include "base/NotationTypes.h" +#include "NotationTool.h" +#include <qstring.h> +#include "base/Event.h" + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class ViewElement; +class NotationView; + + +/** + * This tool will request and insert text on mouse click events + */ +class TextInserter : public NotationTool +{ + Q_OBJECT + + friend class NotationToolBox; + +public: + virtual void ready(); + + virtual void handleLeftButtonPress(timeT, + int height, + int staffNo, + QMouseEvent*, + ViewElement* el); + static const QString ToolName; + +protected slots: + void slotNotesSelected(); + void slotEraseSelected(); + void slotSelectSelected(); + +protected: + TextInserter(NotationView*); + Text m_text; +}; + + + +} + +#endif diff --git a/src/gui/editors/notation/TrackHeader.cpp b/src/gui/editors/notation/TrackHeader.cpp new file mode 100644 index 0000000..32fab2f --- /dev/null +++ b/src/gui/editors/notation/TrackHeader.cpp @@ -0,0 +1,450 @@ +/* -*- 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]> + + This file is Copyright 2007-2008 + Yves Guillemot <[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 "TrackHeader.h" +#include "HeadersGroup.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/StaffExportTypes.h" +#include "base/Colour.h" +#include "base/ColourMap.h" +#include "base/Track.h" +#include "gui/general/GUIPalette.h" +#include "gui/general/LinedStaff.h" +#include "document/RosegardenGUIDoc.h" +#include "misc/Strings.h" +#include "NotePixmapFactory.h" +#include "NotationView.h" +#include "NotationStaff.h" + +#include <map> +#include <set> +#include <string> +#include <utility> + +#include <kapplication.h> +#include <klocale.h> +#include <qsize.h> +#include <qwidget.h> +#include <qhbox.h> +#include <qvbox.h> +#include <qpushbutton.h> +#include <qlabel.h> +#include <qframe.h> +#include <qstring.h> +#include <qtooltip.h> + + +namespace Rosegarden +{ + + +// Status bits +const int TrackHeader::SEGMENT_HERE = 1 << 0; +const int TrackHeader::SUPERIMPOSED_SEGMENTS = 1 << 1; +const int TrackHeader::INCONSISTENT_CLEFS = 1 << 2; +const int TrackHeader::INCONSISTENT_KEYS = 1 << 3; +const int TrackHeader::INCONSISTENT_LABELS = 1 << 4; +const int TrackHeader::INCONSISTENT_TRANSPOSITIONS = 1 << 5; +const int TrackHeader::BEFORE_FIRST_SEGMENT = 1 << 6; + + +TrackHeader::TrackHeader(QWidget *parent, TrackId trackId, int height, int ypos) : + QLabel(parent), + m_track(trackId), + m_height(height), + m_ypos(ypos), + m_lastClef(Clef()), + m_lastKey(Rosegarden::Key()), + m_lastLabel(QString("")), + m_lastTranspose(0), + m_lastUpperText(QString("")), + m_neverUpdated(true), + m_isCurrent(true), + m_lastStatusPart(0), + m_lastWidth(0), + m_key(Rosegarden::Key()), + m_label(QString("")), + m_transpose(0), + m_status(0), + m_current(false) +{ + + m_notationView = static_cast<HeadersGroup *>(parent)->getNotationView(); + + setFrameStyle(QFrame::Box | QFrame::Plain); + setCurrent(false); + + + // + // Tooltip text creation + + Composition *comp = + static_cast<HeadersGroup *>(parent)->getComposition(); + Track *track = comp->getTrackById(m_track); + int trackPos = comp->getTrackPositionById(m_track); + + QString toolTipText = QString(i18n("Track %1 : \"%2\"") + .arg(trackPos + 1) + .arg(strtoqstr(track->getLabel()))); + + QString preset = track->getPresetLabel(); + if (preset != QString("")) + toolTipText += QString(i18n("\nNotate for: %1").arg(preset)); + + QString notationSize = i18n("normal"); + switch (track->getStaffSize()) { + case StaffTypes::Small: + notationSize = i18n("small"); + break; + case StaffTypes::Tiny: + notationSize = i18n("tiny"); + break; + } + + QString bracketText = i18n("--"); + switch (track->getStaffBracket()) { + case Brackets::SquareOn: + bracketText = "[-"; + break; + case Brackets::SquareOff: + bracketText = "-]"; + break; + case Brackets::SquareOnOff: + bracketText = "[-]"; + break; + case Brackets::CurlyOn: + bracketText = "{-"; + break; + case Brackets::CurlyOff: + bracketText = "-}"; + break; + case Brackets::CurlySquareOn: + bracketText = "{[-"; + break; + case Brackets::CurlySquareOff: + bracketText = "-]}"; + break; + } + + toolTipText += QString(i18n("\nSize: %1, Bracket: %2 ")) + .arg(notationSize) + .arg(bracketText); + + // Sort segments by position on the track + SortedSegments segments; + for (int i=0; i<m_notationView->getStaffCount(); i++) { + + NotationStaff * notationStaff = m_notationView->getNotationStaff(i); + Segment &segment = notationStaff->getSegment(); + TrackId trackId = segment.getTrack(); + + if (trackId == m_track) { + segments.insert(&segment); + } + } + + for (SortedSegments::iterator i=segments.begin(); i!=segments.end(); ++i) { + timeT segStart = (*i)->getStartTime(); + timeT segEnd = (*i)->getEndMarkerTime(); + int barStart = comp->getBarNumber(segStart) + 1; + int barEnd = comp->getBarNumber(segEnd) + 1; + + int transpose = (*i)->getTranspose(); + if (transpose) { + QString transposeName; + transposeValueToName(transpose, transposeName); + toolTipText += QString(i18n("\nbars [%1-%2] in %3 (tr=%4) : \"%5\"")) + .arg(barStart) + .arg(barEnd) + .arg(transposeName) + .arg(transpose) + .arg(strtoqstr((*i)->getLabel())); + } else { + toolTipText += QString(i18n("\nbars [%1-%2] (tr=%3) : \"%4\"")) + .arg(barStart) + .arg(barEnd) + .arg(transpose) + .arg(strtoqstr((*i)->getLabel())); + } + } + + QToolTip::add(this, toolTipText); + + m_firstSeg = *segments.begin(); + m_firstSegStartTime = m_firstSeg->getStartTime(); + + /// This may not work if two segments are superimposed + /// at the beginning of the track (inconsistencies are + /// not detected). + /// TODO : Look for the first segment(s) in + /// lookAtStaff() and not here. +} + +void +TrackHeader::setCurrent(bool current) +{ + /// TODO : use colours from GUIPalette + + if (current != m_isCurrent) { + m_isCurrent = current; + if (current) { + setLineWidth(2); + setMargin(0); + setPaletteForegroundColor(QColor(0, 0, 255)); + } else { + setLineWidth(1); + setMargin(1); + setPaletteForegroundColor(QColor(0, 0, 0)); + } + } +} + +void +TrackHeader::transposeValueToName(int transpose, QString &transposeName) +{ + + /// TODO : Should be rewrited using methods from Pitch class + + int noteIndex = transpose % 12; + if (noteIndex < 0) noteIndex += 12; + + switch(noteIndex) { + case 0 : transposeName = i18n("C"); break; + case 1 : transposeName = i18n("C#"); break; + case 2 : transposeName = i18n("D"); break; + case 3 : transposeName = i18n("Eb"); break; + case 4 : transposeName = i18n("E"); break; + case 5 : transposeName = i18n("F"); break; + case 6 : transposeName = i18n("F#"); break; + case 7 : transposeName = i18n("G"); break; + case 8 : transposeName = i18n("G#"); break; + case 9 : transposeName = i18n("A"); break; + case 10 : transposeName = i18n("Bb"); break; + case 11 : transposeName = i18n("B"); break; + } +} + +int +TrackHeader::lookAtStaff(double x, int maxWidth) +{ + // Read Clef and Key on canvas at (x, m_ypos + m_height / 2) + // then guess the header needed width and return it + + // When walking through the segments : + // clef, key, label and transpose are current values + // clef0, key0, label0 and transpose0 are preceding values used to look + // for inconsistencies + // key1, label1 and transpose1 are "visible" (opposed at invisible as are + // key=<C major>, label="" or transpose=0) + // preceding or current values which may be + // displayed with a red colour if some + // inconsistency occurs. + Clef clef, clef0; + Rosegarden::Key key, key0, key1 = Rosegarden::Key("C major"); + QString label = QString(""), label0, label1 = QString(""); + int transpose = 0, transpose0, transpose1 = 0; + + int staff; + + Composition *comp = + static_cast<HeadersGroup *>(parent())->getComposition(); + Track *track = comp->getTrackById(m_track); + int trackPos = comp->getTrackPositionById(m_track); + + int status = 0; + bool current = false; + for (int i=0; i<m_notationView->getStaffCount(); i++) { + NotationStaff * notationStaff = m_notationView->getNotationStaff(i); + Segment &segment = notationStaff->getSegment(); + TrackId trackId = segment.getTrack(); + if (trackId == m_track) { + + /// TODO : What if a segment has been moved ??? + timeT xTime = notationStaff->getTimeAtCanvasCoords(x, m_ypos); + if (xTime < m_firstSegStartTime) { + status |= BEFORE_FIRST_SEGMENT; + /// TODO : What if superimposed segments ??? + m_firstSeg->getFirstClefAndKey(clef, key); + label = strtoqstr(m_firstSeg->getLabel()); + transpose = m_firstSeg->getTranspose(); + current = current || m_notationView->isCurrentStaff(i); + break; + } + timeT segStart = segment.getStartTime(); + timeT segEnd = segment.getEndMarkerTime(); + current = current || m_notationView->isCurrentStaff(i); + + if ((xTime >= segStart) && (xTime < segEnd)) { + + notationStaff->getClefAndKeyAtCanvasCoords(x, + m_ypos + m_height / 2, clef, key); + label = strtoqstr(segment.getLabel()); + transpose = segment.getTranspose(); + + if (status & SEGMENT_HERE) { + status |= SUPERIMPOSED_SEGMENTS; + if (clef != clef0) + status |= INCONSISTENT_CLEFS; + if (key != key0) + status |= INCONSISTENT_KEYS; + if (label != label0) + status |= INCONSISTENT_LABELS; + if (transpose != transpose0) + status |= INCONSISTENT_TRANSPOSITIONS; + } else { + status |= SEGMENT_HERE; + } + + staff = i; + + // If current value is visible, remember it + if (key.getAccidentalCount()) key1 = key; + if (label.stripWhiteSpace().length()) label1 = label; + if (transpose) transpose1 = transpose; + + // Current values become last values + clef0 = clef; + key0 = key; + label0 = label; + transpose0 = transpose; + } // if(xTime...) + } // if(trackId...) + } + + // Remember current data (but only visible data if inconsistency) + m_clef = clef; + m_key = (status & INCONSISTENT_KEYS) ? key1 : key; + m_label = (status & INCONSISTENT_LABELS) ? label1 : label; + m_transpose = (status & INCONSISTENT_TRANSPOSITIONS) ? transpose1 : transpose; + m_current = current; + m_status = status; + + QString noteName; + transposeValueToName(m_transpose, noteName); + + m_upperText = QString(i18n("%1: %2") + .arg(trackPos + 1) + .arg(strtoqstr(track->getLabel()))); + if (m_transpose) m_transposeText = i18n(" in %1").arg(noteName); + else m_transposeText = QString(""); + + NotePixmapFactory * npf = m_notationView->getNotePixmapFactory(); + int clefAndKeyWidth = npf->getClefAndKeyWidth(m_key, m_clef); + + // How many text lines may be written above or under the clef + // in track header ? + m_numberOfTextLines = npf->getTrackHeaderNTL(m_height); + + int trackLabelWidth = + npf->getTrackHeaderTextWidth(m_upperText + m_transposeText) + / m_numberOfTextLines; + int segmentNameWidth = + npf->getTrackHeaderTextWidth(m_label) / m_numberOfTextLines; + + // Get the max. width from upper text and lower text + int width = (segmentNameWidth > trackLabelWidth) + ? segmentNameWidth : trackLabelWidth; + + // Text width is limited by max header Width + if (width > maxWidth) width = maxWidth; + + // But clef and key width may override max header width + if (width < clefAndKeyWidth) width = clefAndKeyWidth; + + return width; +} + + + +void +TrackHeader::updateHeader(int width) +{ + + // Update the header (using given width) if necessary + + // Filter out bits whose display doesn't depend from + int statusPart = m_status & ~(SUPERIMPOSED_SEGMENTS); + + // Header should be updated only if necessary + if ( m_neverUpdated + || (width != m_lastWidth) + || (statusPart != m_lastStatusPart) + || (m_key != m_lastKey) + || (m_clef != m_lastClef) + || (m_label != m_lastLabel) + || (m_upperText != m_lastUpperText) + || (m_transpose != m_lastTranspose)) { + + m_neverUpdated = false; + m_lastStatusPart = statusPart; + m_lastKey = m_key; + m_lastClef = m_clef; + m_lastLabel = m_label; + m_lastTranspose = m_transpose; + m_lastUpperText = m_upperText; + + bool drawClef = true; + QColor clefColour; + if (m_status & (SEGMENT_HERE | BEFORE_FIRST_SEGMENT)) { + if (m_status & (INCONSISTENT_CLEFS | INCONSISTENT_KEYS)) + clefColour = Qt::red; + else + clefColour = Qt::black; + } else { + drawClef = false; + } + + NotePixmapFactory * npf = m_notationView->getNotePixmapFactory(); + QPixmap pmap = NotePixmapFactory::toQPixmap( + npf->makeTrackHeaderPixmap(width, m_height, this)); + + setPixmap(pmap); + setFixedWidth(width); + + // Forced width may differ from localy computed width + m_lastWidth = width; + } + + // Highlight header if track is the current one + setCurrent(m_current); +} + +bool +TrackHeader::SegmentCmp::operator()(const Segment * s1, const Segment * s2) const +{ + // Sort segments by start time, then by end time + if (s1->getStartTime() < s2->getStartTime()) return true; + if (s1->getStartTime() > s2->getStartTime()) return false; + if (s1->getEndMarkerTime() < s2->getEndMarkerTime()) return true; + return false; +} + +} +#include "TrackHeader.moc" diff --git a/src/gui/editors/notation/TrackHeader.h b/src/gui/editors/notation/TrackHeader.h new file mode 100644 index 0000000..0104430 --- /dev/null +++ b/src/gui/editors/notation/TrackHeader.h @@ -0,0 +1,219 @@ + +/* -*- 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]> + + This file is Copyright 2007-2008 + Yves Guillemot <[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. +*/ + + +#ifndef _RG_TRACKHEADER_H_ +#define _RG_TRACKHEADER_H_ + +#include "base/NotationTypes.h" +#include "base/Track.h" + +#include <qsize.h> +#include <qwidget.h> +#include <qlabel.h> + +#include <set> + +class QLabel; + + +namespace Rosegarden +{ + +class NotePixmapFactory; +class NotationView; +class ColourMap; +class Segment; + +class TrackHeader : public QLabel +{ + Q_OBJECT +public: + /** + * Create a new track header for track of id trackId. + * *parent is the parent widget, height the height of staff and + * ypos is the staff y position on canvas. + */ + TrackHeader(QWidget *parent, TrackId trackId, int height, int ypos); + + /** + * Draw a blue line around header when current is true + * (intended to highlight the "current" track). + */ + void setCurrent(bool current); + + /** + * Examine staff at x position and gather data needed to draw + * the track header. + * Return the minimum width required to display the track header. + * maxWidth is the maximum width allowed to show text. Return width + * may be greater than maxWidth if needed to show clef and key signature. + * (Header have always to show complete clef and key signature). + */ + int lookAtStaff(double x, int maxWidth); + + /** + * (Re)draw the header on the notation view using the data gathered + * by lookAtStaff() last call and the specified width. + */ + void updateHeader(int width); + + /** + * Return the Id of the associated track. + */ + TrackId getId() + { return m_track; + } + + /** + * Return how many text lines may be written in the header (above + * the clef and under the clef). + * This data is coming from the last call of lookAtStaff(). + */ + int getNumberOfTextLines() { return m_numberOfTextLines; } + + /** + * Return the Clef to draw in the header + */ + Clef & getClef() { return m_clef; } + + /** + * Get which key signature should be drawn in the header + * from the last call of lookAtStaff(). + */ + Rosegarden::Key & getKey() { return m_key; } + + /** + * Return true if a Clef (and a key signature) have to be drawn in the header + */ + bool isAClefToDraw() + { + return (m_status & SEGMENT_HERE) || (m_status & BEFORE_FIRST_SEGMENT); + } + + /** + * Return the text to write in the header top + */ + QString getUpperText() { return m_upperText; } + + /** + * Return the transposition text + * (to be written at the end of the upper text) + */ + QString getTransposeText() { return m_transposeText; } + + /** + * Return the text to write in the header bottom + */ + QString getLowerText() { return m_label; } + + /** + * Return true if two segments or more are superimposed and are + * not using the same clef + */ + bool isClefInconsistent() { return m_status & INCONSISTENT_CLEFS; } + + /** + * Return true if two segments or more are superimposed and are + * not using the same key signature + */ + bool isKeyInconsistent() { return m_status & INCONSISTENT_KEYS; } + + /** + * Return true if two segments or more are superimposed and are + * not using the same label + */ + bool isLabelInconsistent() { return m_status & INCONSISTENT_LABELS; } + + /** + * Return true if two segments or more are superimposed and are + * not using the same transposition + */ + bool isTransposeInconsistent() + { + return m_status & INCONSISTENT_TRANSPOSITIONS; + } + + +private : + /** + * Convert the transpose value to the instrument tune and + * return it in a printable string. + */ + void transposeValueToName(int transpose, QString &transposeName); + + + // Status bits + static const int SEGMENT_HERE; + static const int SUPERIMPOSED_SEGMENTS; + static const int INCONSISTENT_CLEFS; + static const int INCONSISTENT_KEYS; + static const int INCONSISTENT_LABELS; + static const int INCONSISTENT_TRANSPOSITIONS; + static const int BEFORE_FIRST_SEGMENT; + + TrackId m_track; + int m_height; + int m_ypos; + NotationView * m_notationView; + + Clef m_lastClef; + Rosegarden::Key m_lastKey; + QString m_lastLabel; + int m_lastTranspose; + QString m_lastUpperText; + bool m_neverUpdated; + bool m_isCurrent; + int m_lastStatusPart; + int m_lastWidth; + + Clef m_clef; + Rosegarden::Key m_key; + QString m_label; + int m_transpose; + int m_status; + bool m_current; + + QString m_upperText; + QString m_transposeText; + int m_numberOfTextLines; + + // Used to sort the segments listed in the header toolTipText + struct SegmentCmp { + bool operator()(const Segment *s1, const Segment *s2) const; + }; + typedef std::multiset<Segment *, SegmentCmp> SortedSegments; + + // First segment on the track. + Segment * m_firstSeg; + timeT m_firstSegStartTime; +}; + +} + +#endif |