diff options
Diffstat (limited to 'src/gui/editors')
233 files changed, 68456 insertions, 0 deletions
diff --git a/src/gui/editors/eventlist/EventView.cpp b/src/gui/editors/eventlist/EventView.cpp new file mode 100644 index 0000000..13bd294 --- /dev/null +++ b/src/gui/editors/eventlist/EventView.cpp @@ -0,0 +1,1606 @@ +/* -*- 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 "EventView.h" +#include "EventViewItem.h" +#include "TrivialVelocityDialog.h" +#include "base/BaseProperties.h" +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Clipboard.h" +#include "base/Event.h" +#include "base/MidiTypes.h" +#include "base/NotationTypes.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/SegmentPerformanceHelper.h" +#include "base/Selection.h" +#include "base/Track.h" +#include "base/TriggerSegment.h" +#include "commands/edit/CopyCommand.h" +#include "commands/edit/CutCommand.h" +#include "commands/edit/EraseCommand.h" +#include "commands/edit/EventEditCommand.h" +#include "commands/edit/PasteEventsCommand.h" +#include "commands/edit/EventInsertionCommand.h" +#include "commands/segment/SegmentLabelCommand.h" +#include "commands/segment/SetTriggerSegmentBasePitchCommand.h" +#include "commands/segment/SetTriggerSegmentBaseVelocityCommand.h" +#include "commands/segment/SetTriggerSegmentDefaultRetuneCommand.h" +#include "commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "gui/dialogs/EventEditDialog.h" +#include "gui/dialogs/PitchDialog.h" +#include "gui/dialogs/SimpleEventEditDialog.h" +#include "gui/general/EditViewBase.h" +#include "gui/general/MidiPitchLabel.h" +#include "gui/kdeext/KTmpStatusMsg.h" +#include "gui/dialogs/EventFilterDialog.h" +#include <kaction.h> +#include <kconfig.h> +#include <klocale.h> +#include <kstatusbar.h> +#include <kstddirs.h> +#include <kglobal.h> +#include <klineeditdlg.h> +#include <klistview.h> +#include <kxmlguiclient.h> +#include <qbuttongroup.h> +#include <qcanvas.h> +#include <qcheckbox.h> +#include <qdialog.h> +#include <qframe.h> +#include <qgroupbox.h> +#include <qiconset.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qlistview.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qpopupmenu.h> +#include <qpushbutton.h> +#include <qsize.h> +#include <qstring.h> +#include <qwidget.h> +#include <algorithm> + + +namespace Rosegarden +{ + +int +EventView::m_lastSetEventFilter = -1; + + +EventView::EventView(RosegardenGUIDoc *doc, + std::vector<Segment *> segments, + QWidget *parent): + EditViewBase(doc, segments, 2, parent, "eventview"), + m_eventFilter(Note | Text | SystemExclusive | Controller | + ProgramChange | PitchBend | Indication | Other), + m_menu(0) +{ + m_isTriggerSegment = false; + m_triggerName = m_triggerPitch = m_triggerVelocity = 0; + + if (!segments.empty()) { + Segment *s = *segments.begin(); + if (s->getComposition()) { + int id = s->getComposition()->getTriggerSegmentId(s); + if (id >= 0) + m_isTriggerSegment = true; + } + } + + if (m_lastSetEventFilter < 0) + m_lastSetEventFilter = m_eventFilter; + else + m_eventFilter = m_lastSetEventFilter; + + initStatusBar(); + setupActions(); + + // define some note filtering buttons in a group + // + m_filterGroup = + new QButtonGroup(1, Horizontal, i18n("Event filters"), getCentralWidget()); + + m_noteCheckBox = new QCheckBox(i18n("Note"), m_filterGroup); + m_programCheckBox = new QCheckBox(i18n("Program Change"), m_filterGroup); + m_controllerCheckBox = new QCheckBox(i18n("Controller"), m_filterGroup); + m_pitchBendCheckBox = new QCheckBox(i18n("Pitch Bend"), m_filterGroup); + m_sysExCheckBox = new QCheckBox(i18n("System Exclusive"), m_filterGroup); + m_keyPressureCheckBox = new QCheckBox(i18n("Key Pressure"), m_filterGroup); + m_channelPressureCheckBox = new QCheckBox(i18n("Channel Pressure"), m_filterGroup); + m_restCheckBox = new QCheckBox(i18n("Rest"), m_filterGroup); + m_indicationCheckBox = new QCheckBox(i18n("Indication"), m_filterGroup); + m_textCheckBox = new QCheckBox(i18n("Text"), m_filterGroup); + m_otherCheckBox = new QCheckBox(i18n("Other"), m_filterGroup); + m_grid->addWidget(m_filterGroup, 2, 0); + + // Connect up + // + connect(m_filterGroup, SIGNAL(released(int)), + SLOT(slotModifyFilter(int))); + + m_eventList = new KListView(getCentralWidget()); + m_eventList->setItemsRenameable(true); + + m_grid->addWidget(m_eventList, 2, 1); + + if (m_isTriggerSegment) { + + int id = segments[0]->getComposition()->getTriggerSegmentId(segments[0]); + TriggerSegmentRec *rec = + segments[0]->getComposition()->getTriggerSegmentRec(id); + + QGroupBox *groupBox = new QGroupBox + (1, Horizontal, i18n("Triggered Segment Properties"), getCentralWidget()); + + QFrame *frame = new QFrame(groupBox); + QGridLayout *layout = new QGridLayout(frame, 5, 3, 5, 5); + + layout->addWidget(new QLabel(i18n("Label: "), frame), 0, 0); + QString label = strtoqstr(segments[0]->getLabel()); + if (label == "") + label = i18n("<no label>"); + m_triggerName = new QLabel(label, frame); + layout->addWidget(m_triggerName, 0, 1); + QPushButton *editButton = new QPushButton(i18n("edit"), frame); + layout->addWidget(editButton, 0, 2); + connect(editButton, SIGNAL(clicked()), this, SLOT(slotEditTriggerName())); + + layout->addWidget(new QLabel(i18n("Base pitch: "), frame), 1, 0); + m_triggerPitch = new QLabel(QString("%1").arg(rec->getBasePitch()), frame); + layout->addWidget(m_triggerPitch, 1, 1); + editButton = new QPushButton(i18n("edit"), frame); + layout->addWidget(editButton, 1, 2); + connect(editButton, SIGNAL(clicked()), this, SLOT(slotEditTriggerPitch())); + + layout->addWidget(new QLabel(i18n("Base velocity: "), frame), 2, 0); + m_triggerVelocity = new QLabel(QString("%1").arg(rec->getBaseVelocity()), frame); + layout->addWidget(m_triggerVelocity, 2, 1); + editButton = new QPushButton(i18n("edit"), frame); + layout->addWidget(editButton, 2, 2); + connect(editButton, SIGNAL(clicked()), this, SLOT(slotEditTriggerVelocity())); + + /*!!! Comment out these two options, which are not yet used + anywhere else -- intended for use with library ornaments, not + yet implemented + + layout->addWidget(new QLabel(i18n("Default timing: "), frame), 3, 0); + + KComboBox *adjust = new KComboBox(frame); + layout->addMultiCellWidget(adjust, 3, 3, 1, 2); + adjust->insertItem(i18n("As stored")); + adjust->insertItem(i18n("Truncate if longer than note")); + adjust->insertItem(i18n("End at same time as note")); + adjust->insertItem(i18n("Stretch or squash segment to note duration")); + + std::string timing = rec->getDefaultTimeAdjust(); + if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_NONE) { + adjust->setCurrentItem(0); + } else if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH) { + adjust->setCurrentItem(3); + } else if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_START) { + adjust->setCurrentItem(1); + } else if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_END) { + adjust->setCurrentItem(2); + } + + connect(adjust, SIGNAL(activated(int)), this, SLOT(slotTriggerTimeAdjustChanged(int))); + + QCheckBox *retune = new QCheckBox(i18n("Adjust pitch to trigger note by default"), frame); + retune->setChecked(rec->getDefaultRetune()); + connect(retune, SIGNAL(clicked()), this, SLOT(slotTriggerRetuneChanged())); + layout->addMultiCellWidget(retune, 4, 4, 1, 2); + + */ + + m_grid->addWidget(groupBox, 2, 2); + + } + + updateViewCaption(); + + for (unsigned int i = 0; i < m_segments.size(); ++i) { + m_segments[i]->addObserver(this); + } + + // Connect double clicker + // + connect(m_eventList, SIGNAL(doubleClicked(QListViewItem*)), + SLOT(slotPopupEventEditor(QListViewItem*))); + + connect(m_eventList, + SIGNAL(rightButtonPressed(QListViewItem*, const QPoint&, int)), + SLOT(slotPopupMenu(QListViewItem*, const QPoint&, int))); + + m_eventList->setAllColumnsShowFocus(true); + m_eventList->setSelectionMode(QListView::Extended); + + m_eventList->addColumn(i18n("Time ")); + m_eventList->addColumn(i18n("Duration ")); + m_eventList->addColumn(i18n("Event Type ")); + m_eventList->addColumn(i18n("Pitch ")); + m_eventList->addColumn(i18n("Velocity ")); + m_eventList->addColumn(i18n("Type (Data1) ")); + m_eventList->addColumn(i18n("Value (Data2) ")); + + for (int col = 0; col < m_eventList->columns(); ++col) + m_eventList->setRenameable(col, true); + + readOptions(); + setButtonsToFilter(); + applyLayout(); + + makeInitialSelection(doc->getComposition().getPosition()); + + slotCompositionStateUpdate(); + + setOutOfCtor(); +} + +EventView::~EventView() +{ + for (unsigned int i = 0; i < m_segments.size(); ++i) { + RG_DEBUG << "~EventView - removing this observer from " << m_segments[i] << endl; + m_segments[i]->removeObserver(this); + } +} + +void +EventView::eventRemoved(const Segment *, Event *e) +{ + m_deletedEvents.insert(e); +} + +void +EventView::segmentDeleted(const Segment *s) +{ + std::vector<Segment *>::iterator i = std::find(m_segments.begin(), m_segments.end(), s); + + if (i != m_segments.end()) { + m_segments.erase(i); + } else { + RG_DEBUG << "%%% WARNING - EventView::segmentDeleted() called on non-registered segment - should not happen\n"; + } + +} + +bool +EventView::applyLayout(int /*staffNo*/) +{ + // If no selection has already been set then we copy what's + // already set and try to replicate this after the rebuild + // of the view. + // + if (m_listSelection.size() == 0) { + QPtrList<QListViewItem> selection = m_eventList->selectedItems(); + + if (selection.count()) { + QPtrListIterator<QListViewItem> it(selection); + QListViewItem *listItem; + + while ((listItem = it.current()) != 0) { + m_listSelection.push_back(m_eventList->itemIndex(*it)); + ++it; + } + } + } + + // Ok, recreate list + // + m_eventList->clear(); + + m_config->setGroup(EventViewConfigGroup); + int timeMode = m_config->readNumEntry("timemode", 0); + + for (unsigned int i = 0; i < m_segments.size(); i++) { + SegmentPerformanceHelper helper(*m_segments[i]); + + for (Segment::iterator it = m_segments[i]->begin(); + m_segments[i]->isBeforeEndMarker(it); it++) { + timeT eventTime = + helper.getSoundingAbsoluteTime(it); + + QString velyStr; + QString pitchStr; + QString data1Str = ""; + QString data2Str = ""; + QString durationStr; + + // Event filters + // + + if ((*it)->isa(Note::EventRestType)) { + if (!(m_eventFilter & Rest)) + continue; + + } else if ((*it)->isa(Note::EventType)) { + if (!(m_eventFilter & Note)) + continue; + + } else if ((*it)->isa(Indication::EventType)) { + if (!(m_eventFilter & Indication)) + continue; + + } else if ((*it)->isa(PitchBend::EventType)) { + if (!(m_eventFilter & PitchBend)) + continue; + + } else if ((*it)->isa(SystemExclusive::EventType)) { + if (!(m_eventFilter & SystemExclusive)) + continue; + + } else if ((*it)->isa(ProgramChange::EventType)) { + if (!(m_eventFilter & ProgramChange)) + continue; + + } else if ((*it)->isa(ChannelPressure::EventType)) { + if (!(m_eventFilter & ChannelPressure)) + continue; + + } else if ((*it)->isa(KeyPressure::EventType)) { + if (!(m_eventFilter & KeyPressure)) + continue; + + } else if ((*it)->isa(Controller::EventType)) { + if (!(m_eventFilter & Controller)) + continue; + + } else if ((*it)->isa(Text::EventType)) { + if (!(m_eventFilter & Text)) + continue; + + } else { + if (!(m_eventFilter & Other)) + continue; + } + + // avoid debug stuff going to stderr if no properties found + + if ((*it)->has(BaseProperties::PITCH)) { + int p = (*it)->get + <Int>(BaseProperties::PITCH); + pitchStr = QString("%1 %2 ") + .arg(p).arg(MidiPitchLabel(p).getQString()); + } else if ((*it)->isa(Note::EventType)) { + pitchStr = "<not set>"; + } + + if ((*it)->has(BaseProperties::VELOCITY)) { + velyStr = QString("%1 "). + arg((*it)->get + <Int>(BaseProperties::VELOCITY)); + } else if ((*it)->isa(Note::EventType)) { + velyStr = "<not set>"; + } + + if ((*it)->has(Controller::NUMBER)) { + data1Str = QString("%1 "). + arg((*it)->get + <Int>(Controller::NUMBER)); + } else if ((*it)->has(Text::TextTypePropertyName)) { + data1Str = QString("%1 "). + arg(strtoqstr((*it)->get + <String> + (Text::TextTypePropertyName))); + } else if ((*it)->has(Indication:: + IndicationTypePropertyName)) { + data1Str = QString("%1 "). + arg(strtoqstr((*it)->get + <String> + (Indication:: + IndicationTypePropertyName))); + } else if ((*it)->has(::Rosegarden::Key::KeyPropertyName)) { + data1Str = QString("%1 "). + arg(strtoqstr((*it)->get + <String> + (::Rosegarden::Key::KeyPropertyName))); + } else if ((*it)->has(Clef::ClefPropertyName)) { + data1Str = QString("%1 "). + arg(strtoqstr((*it)->get + <String> + (Clef::ClefPropertyName))); + } else if ((*it)->has(PitchBend::MSB)) { + data1Str = QString("%1 "). + arg((*it)->get + <Int>(PitchBend::MSB)); + } else if ((*it)->has(BaseProperties::BEAMED_GROUP_TYPE)) { + data1Str = QString("%1 "). + arg(strtoqstr((*it)->get + <String> + (BaseProperties::BEAMED_GROUP_TYPE))); + } + + if ((*it)->has(Controller::VALUE)) { + data2Str = QString("%1 "). + arg((*it)->get + <Int>(Controller::VALUE)); + } else if ((*it)->has(Text::TextPropertyName)) { + data2Str = QString("%1 "). + arg(strtoqstr((*it)->get + <String> + (Text::TextPropertyName))); + /*!!! + } else if ((*it)->has(Indication:: + IndicationTypePropertyName)) { + data2Str = QString("%1 "). + arg((*it)->get<Int>(Indication:: + IndicationDurationPropertyName)); + */ + } else if ((*it)->has(PitchBend::LSB)) { + data2Str = QString("%1 "). + arg((*it)->get + <Int>(PitchBend::LSB)); + } else if ((*it)->has(BaseProperties::BEAMED_GROUP_ID)) { + data2Str = i18n("(group %1) "). + arg((*it)->get + <Int>(BaseProperties::BEAMED_GROUP_ID)); + } + + if ((*it)->has(ProgramChange::PROGRAM)) { + data1Str = QString("%1 "). + arg((*it)->get + <Int>(ProgramChange::PROGRAM) + 1); + } + + if ((*it)->has(ChannelPressure::PRESSURE)) { + data1Str = QString("%1 "). + arg((*it)->get + <Int>(ChannelPressure::PRESSURE)); + } + + if ((*it)->isa(KeyPressure::EventType) && + (*it)->has(KeyPressure::PITCH)) { + data1Str = QString("%1 "). + arg((*it)->get + <Int>(KeyPressure::PITCH)); + } + + if ((*it)->has(KeyPressure::PRESSURE)) { + data2Str = QString("%1 "). + arg((*it)->get + <Int>(KeyPressure::PRESSURE)); + } + + + if ((*it)->getDuration() > 0 || + (*it)->isa(Note::EventType) || + (*it)->isa(Note::EventRestType)) { + durationStr = makeDurationString(eventTime, + (*it)->getDuration(), + timeMode); + } + + QString timeStr = makeTimeString(eventTime, timeMode); + + new EventViewItem(m_segments[i], + *it, + m_eventList, + timeStr, + durationStr, + strtoqstr((*it)->getType()), + pitchStr, + velyStr, + data1Str, + data2Str); + } + } + + + if (m_eventList->childCount() == 0) { + if (m_segments.size()) + new QListViewItem(m_eventList, + i18n("<no events at this filter level>")); + else + new QListViewItem(m_eventList, i18n("<no events>")); + + m_eventList->setSelectionMode(QListView::NoSelection); + stateChanged("have_selection", KXMLGUIClient::StateReverse); + } else { + m_eventList->setSelectionMode(QListView::Extended); + + // If no selection then select the first event + if (m_listSelection.size() == 0) + m_listSelection.push_back(0); + + stateChanged("have_selection", KXMLGUIClient::StateNoReverse); + } + + // Set a selection from a range of indexes + // + std::vector<int>::iterator sIt = m_listSelection.begin(); + int index = 0; + + for (; sIt != m_listSelection.end(); ++sIt) { + index = *sIt; + + while (index > 0 && !m_eventList->itemAtIndex(index)) + index--; + + m_eventList->setSelected(m_eventList->itemAtIndex(index), true); + m_eventList->setCurrentItem(m_eventList->itemAtIndex(index)); + + // ensure visible + m_eventList->ensureItemVisible(m_eventList->itemAtIndex(index)); + } + + m_listSelection.clear(); + m_deletedEvents.clear(); + + return true; +} + +void +EventView::makeInitialSelection(timeT time) +{ + m_listSelection.clear(); + + EventViewItem *goodItem = 0; + int goodItemNo = 0; + + int i = 0; + + for (QListViewItem *child = m_eventList->firstChild(); + child; + child = child->nextSibling()) { + + EventViewItem * item = dynamic_cast<EventViewItem *>(child); + + if (item) { + if (item->getEvent()->getAbsoluteTime() > time) + break; + goodItem = item; + goodItemNo = i; + } + + ++i; + } + /*!!! + for (int i = 0; m_eventList->itemAtIndex(i); ++i) { + + EventViewItem *item = dynamic_cast<EventViewItem *> + (m_eventList->itemAtIndex(i)); + + if (item) { + if (item->getEvent()->getAbsoluteTime() > time) break; + goodItem = item; + goodItemNo = i; + } + } + */ + if (goodItem) { + m_listSelection.push_back(goodItemNo); + m_eventList->setSelected(goodItem, true); + m_eventList->ensureItemVisible(goodItem); + } +} + +QString +EventView::makeTimeString(timeT time, int timeMode) +{ + switch (timeMode) { + + case 0: // musical time + { + int bar, beat, fraction, remainder; + getDocument()->getComposition().getMusicalTimeForAbsoluteTime + (time, bar, beat, fraction, remainder); + ++bar; + return QString("%1%2%3-%4%5-%6%7-%8%9 ") + .arg(bar / 100) + .arg((bar % 100) / 10) + .arg(bar % 10) + .arg(beat / 10) + .arg(beat % 10) + .arg(fraction / 10) + .arg(fraction % 10) + .arg(remainder / 10) + .arg(remainder % 10); + } + + case 1: // real time + { + RealTime rt = + getDocument()->getComposition().getElapsedRealTime(time); + // return QString("%1 ").arg(rt.toString().c_str()); + return QString("%1 ").arg(rt.toText().c_str()); + } + + default: + return QString("%1 ").arg(time); + } +} + +QString +EventView::makeDurationString(timeT time, + timeT duration, int timeMode) +{ + switch (timeMode) { + + case 0: // musical time + { + int bar, beat, fraction, remainder; + getDocument()->getComposition().getMusicalTimeForDuration + (time, duration, bar, beat, fraction, remainder); + return QString("%1%2%3-%4%5-%6%7-%8%9 ") + .arg(bar / 100) + .arg((bar % 100) / 10) + .arg(bar % 10) + .arg(beat / 10) + .arg(beat % 10) + .arg(fraction / 10) + .arg(fraction % 10) + .arg(remainder / 10) + .arg(remainder % 10); + } + + case 1: // real time + { + RealTime rt = + getDocument()->getComposition().getRealTimeDifference + (time, time + duration); + // return QString("%1 ").arg(rt.toString().c_str()); + return QString("%1 ").arg(rt.toText().c_str()); + } + + default: + return QString("%1 ").arg(duration); + } +} + +void +EventView::refreshSegment(Segment * /*segment*/, + timeT /*startTime*/, + timeT /*endTime*/) +{ + RG_DEBUG << "EventView::refreshSegment" << endl; + applyLayout(0); +} + +void +EventView::updateView() +{ + m_eventList->update(); +} + +void +EventView::slotEditTriggerName() +{ + bool ok = false; + QString newLabel = KLineEditDlg::getText(i18n("Segment label"), i18n("Label:"), + strtoqstr(m_segments[0]->getLabel()), + &ok, this); + + if (ok) { + SegmentSelection selection; + selection.insert(m_segments[0]); + SegmentLabelCommand *cmd = new SegmentLabelCommand(selection, newLabel); + addCommandToHistory(cmd); + m_triggerName->setText(newLabel); + } +} + +void +EventView::slotEditTriggerPitch() +{ + int id = m_segments[0]->getComposition()->getTriggerSegmentId(m_segments[0]); + + TriggerSegmentRec *rec = + m_segments[0]->getComposition()->getTriggerSegmentRec(id); + + PitchDialog *dlg = new PitchDialog(this, i18n("Base pitch"), rec->getBasePitch()); + + if (dlg->exec() == QDialog::Accepted) { + addCommandToHistory(new SetTriggerSegmentBasePitchCommand + (&getDocument()->getComposition(), id, dlg->getPitch())); + m_triggerPitch->setText(QString("%1").arg(dlg->getPitch())); + } +} + +void +EventView::slotEditTriggerVelocity() +{ + int id = m_segments[0]->getComposition()->getTriggerSegmentId(m_segments[0]); + + TriggerSegmentRec *rec = + m_segments[0]->getComposition()->getTriggerSegmentRec(id); + + TrivialVelocityDialog *dlg = new TrivialVelocityDialog + (this, i18n("Base velocity"), rec->getBaseVelocity()); + + if (dlg->exec() == QDialog::Accepted) { + addCommandToHistory(new SetTriggerSegmentBaseVelocityCommand + (&getDocument()->getComposition(), id, dlg->getVelocity())); + m_triggerVelocity->setText(QString("%1").arg(dlg->getVelocity())); + } +} + +void +EventView::slotTriggerTimeAdjustChanged(int option) +{ + std::string adjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH; + + switch (option) { + + case 0: + adjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_NONE; + break; + case 1: + adjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_START; + break; + case 2: + adjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_END; + break; + case 3: + adjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH; + break; + + default: + break; + } + + int id = m_segments[0]->getComposition()->getTriggerSegmentId(m_segments[0]); + + TriggerSegmentRec *rec = + m_segments[0]->getComposition()->getTriggerSegmentRec(id); + + addCommandToHistory(new SetTriggerSegmentDefaultTimeAdjustCommand + (&getDocument()->getComposition(), id, adjust)); +} + +void +EventView::slotTriggerRetuneChanged() +{ + int id = m_segments[0]->getComposition()->getTriggerSegmentId(m_segments[0]); + + TriggerSegmentRec *rec = + m_segments[0]->getComposition()->getTriggerSegmentRec(id); + + addCommandToHistory(new SetTriggerSegmentDefaultRetuneCommand + (&getDocument()->getComposition(), id, !rec->getDefaultRetune())); +} + +void +EventView::slotEditCut() +{ + QPtrList<QListViewItem> selection = m_eventList->selectedItems(); + + if (selection.count() == 0) + return ; + + RG_DEBUG << "EventView::slotEditCut - cutting " + << selection.count() << " items" << endl; + + QPtrListIterator<QListViewItem> it(selection); + QListViewItem *listItem; + EventViewItem *item; + EventSelection *cutSelection = 0; + int itemIndex = -1; + + while ((listItem = it.current()) != 0) { + item = dynamic_cast<EventViewItem*>((*it)); + + if (itemIndex == -1) + itemIndex = m_eventList->itemIndex(*it); + + if (item) { + if (cutSelection == 0) + cutSelection = + new EventSelection(*(item->getSegment())); + + cutSelection->addEvent(item->getEvent()); + } + ++it; + } + + if (cutSelection) { + if (itemIndex >= 0) { + m_listSelection.clear(); + m_listSelection.push_back(itemIndex); + } + + addCommandToHistory(new CutCommand(*cutSelection, + getDocument()->getClipboard())); + } +} + +void +EventView::slotEditCopy() +{ + QPtrList<QListViewItem> selection = m_eventList->selectedItems(); + + if (selection.count() == 0) + return ; + + RG_DEBUG << "EventView::slotEditCopy - copying " + << selection.count() << " items" << endl; + + QPtrListIterator<QListViewItem> it(selection); + QListViewItem *listItem; + EventViewItem *item; + EventSelection *copySelection = 0; + + // clear the selection for post modification updating + // + m_listSelection.clear(); + + while ((listItem = it.current()) != 0) { + item = dynamic_cast<EventViewItem*>((*it)); + + m_listSelection.push_back(m_eventList->itemIndex(*it)); + + if (item) { + if (copySelection == 0) + copySelection = + new EventSelection(*(item->getSegment())); + + copySelection->addEvent(item->getEvent()); + } + ++it; + } + + if (copySelection) { + addCommandToHistory(new CopyCommand(*copySelection, + getDocument()->getClipboard())); + } +} + +void +EventView::slotEditPaste() +{ + if (getDocument()->getClipboard()->isEmpty()) { + slotStatusHelpMsg(i18n("Clipboard is empty")); + return ; + } + + KTmpStatusMsg msg(i18n("Inserting clipboard contents..."), this); + + timeT insertionTime = 0; + + QPtrList<QListViewItem> selection = m_eventList->selectedItems(); + if (selection.count()) { + EventViewItem *item = dynamic_cast<EventViewItem*>(selection.at(0)); + + if (item) + insertionTime = item->getEvent()->getAbsoluteTime(); + + // remember the selection + // + m_listSelection.clear(); + + QPtrListIterator<QListViewItem> it(selection); + QListViewItem *listItem; + + while ((listItem = it.current()) != 0) { + m_listSelection.push_back(m_eventList->itemIndex(*it)); + ++it; + } + } + + + PasteEventsCommand *command = new PasteEventsCommand + (*m_segments[0], getDocument()->getClipboard(), + insertionTime, PasteEventsCommand::MatrixOverlay); + + if (!command->isPossible()) { + slotStatusHelpMsg(i18n("Couldn't paste at this point")); + } else + addCommandToHistory(command); + + RG_DEBUG << "EventView::slotEditPaste - pasting " + << selection.count() << " items" << endl; +} + +void +EventView::slotEditDelete() +{ + QPtrList<QListViewItem> selection = m_eventList->selectedItems(); + if (selection.count() == 0) + return ; + + RG_DEBUG << "EventView::slotEditDelete - deleting " + << selection.count() << " items" << endl; + + QPtrListIterator<QListViewItem> it(selection); + QListViewItem *listItem; + EventViewItem *item; + EventSelection *deleteSelection = 0; + int itemIndex = -1; + + while ((listItem = it.current()) != 0) { + item = dynamic_cast<EventViewItem*>((*it)); + + if (itemIndex == -1) + itemIndex = m_eventList->itemIndex(*it); + + if (item) { + if (m_deletedEvents.find(item->getEvent()) != m_deletedEvents.end()) { + ++it; + continue; + } + + if (deleteSelection == 0) + deleteSelection = + new EventSelection(*m_segments[0]); + + deleteSelection->addEvent(item->getEvent()); + } + ++it; + } + + if (deleteSelection) { + + if (itemIndex >= 0) { + m_listSelection.clear(); + m_listSelection.push_back(itemIndex); + } + + addCommandToHistory(new EraseCommand(*deleteSelection)); + + } +} + +void +EventView::slotEditInsert() +{ + RG_DEBUG << "EventView::slotEditInsert" << endl; + + timeT insertTime = m_segments[0]->getStartTime(); + timeT insertDuration = 960; + + QPtrList<QListViewItem> selection = m_eventList->selectedItems(); + + if (selection.count() > 0) { + EventViewItem *item = + dynamic_cast<EventViewItem*>(selection.getFirst()); + + if (item) { + insertTime = item->getEvent()->getAbsoluteTime(); + insertDuration = item->getEvent()->getDuration(); + } + } + + // Create default event + // + Event *event = + new Event(Note::EventType, + insertTime, + insertDuration); + event->set + <Int>(BaseProperties::PITCH, 70); + event->set + <Int>(BaseProperties::VELOCITY, 100); + + SimpleEventEditDialog dialog(this, getDocument(), *event, true); + + if (dialog.exec() == QDialog::Accepted) { + EventInsertionCommand *command = + new EventInsertionCommand(*m_segments[0], + new Event(dialog.getEvent())); + addCommandToHistory(command); + } +} + +void +EventView::slotEditEvent() +{ + RG_DEBUG << "EventView::slotEditEvent" << endl; + + QPtrList<QListViewItem> selection = m_eventList->selectedItems(); + + if (selection.count() > 0) { + EventViewItem *item = + dynamic_cast<EventViewItem*>(selection.getFirst()); + + if (item) { + Event *event = item->getEvent(); + SimpleEventEditDialog dialog(this, getDocument(), *event, false); + + if (dialog.exec() == QDialog::Accepted && dialog.isModified()) { + EventEditCommand *command = + new EventEditCommand(*(item->getSegment()), + event, + dialog.getEvent()); + + addCommandToHistory(command); + } + } + } +} + +void +EventView::slotEditEventAdvanced() +{ + RG_DEBUG << "EventView::slotEditEventAdvanced" << endl; + + QPtrList<QListViewItem> selection = m_eventList->selectedItems(); + + if (selection.count() > 0) { + EventViewItem *item = + dynamic_cast<EventViewItem*>(selection.getFirst()); + + if (item) { + Event *event = item->getEvent(); + EventEditDialog dialog(this, *event); + + if (dialog.exec() == QDialog::Accepted && dialog.isModified()) { + EventEditCommand *command = + new EventEditCommand(*(item->getSegment()), + event, + dialog.getEvent()); + + addCommandToHistory(command); + } + } + } +} + +void +EventView::slotSelectAll() +{ + m_listSelection.clear(); + for (int i = 0; m_eventList->itemAtIndex(i); ++i) { + m_listSelection.push_back(i); + m_eventList->setSelected(m_eventList->itemAtIndex(i), true); + } +} + +void +EventView::slotClearSelection() +{ + m_listSelection.clear(); + for (int i = 0; m_eventList->itemAtIndex(i); ++i) { + m_eventList->setSelected(m_eventList->itemAtIndex(i), false); + } +} + +void +EventView::slotFilterSelection() +{ + m_listSelection.clear(); + QPtrList<QListViewItem> selection = m_eventList->selectedItems(); + if (selection.count() == 0) + return ; + + EventFilterDialog dialog(this); + if (dialog.exec() == QDialog::Accepted) { + + QPtrListIterator<QListViewItem> it(selection); + QListViewItem *listItem; + + while ((listItem = it.current()) != 0) { + + EventViewItem * item = dynamic_cast<EventViewItem*>(*it); + if (!item) { + ++it; + continue; + } + + if (!dialog.keepEvent(item->getEvent())) { + m_listSelection.push_back(m_eventList->itemIndex(*it)); + m_eventList->setSelected(item, false); + } + + ++it; + } + } +} + +void +EventView::setupActions() +{ + EditViewBase::setupActions("eventlist.rc"); + + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QIconSet icon(QPixmap(pixmapDir + "/toolbar/event-insert.png")); + + new KAction(i18n("&Insert Event"), icon, Key_I, this, + SLOT(slotEditInsert()), actionCollection(), + "insert"); + + QCanvasPixmap pixmap(pixmapDir + "/toolbar/event-delete.png"); + icon = QIconSet(pixmap); + + new KAction(i18n("&Delete Event"), icon, Key_Delete, this, + SLOT(slotEditDelete()), actionCollection(), + "delete"); + + pixmap.load(pixmapDir + "/toolbar/event-edit.png"); + icon = QIconSet(pixmap); + + new KAction(i18n("&Edit Event"), icon, Key_E, this, + SLOT(slotEditEvent()), actionCollection(), + "edit_simple"); + + pixmap.load(pixmapDir + "/toolbar/event-edit-advanced.png"); + icon = QIconSet(pixmap); + + new KAction(i18n("&Advanced Event Editor"), icon, Key_A, this, + SLOT(slotEditEventAdvanced()), actionCollection(), + "edit_advanced"); + + // icon = QIconSet(QCanvasPixmap(pixmapDir + "/toolbar/eventfilter.xpm")); + new KAction(i18n("&Filter Selection"), "filter", Key_F, this, + SLOT(slotFilterSelection()), actionCollection(), + "filter_selection"); + + new KAction(i18n("Select &All"), Key_A + CTRL, this, + SLOT(slotSelectAll()), actionCollection(), + "select_all"); + + new KAction(i18n("Clear Selection"), Key_Escape, this, + SLOT(slotClearSelection()), actionCollection(), + "clear_selection"); + + m_config->setGroup(EventViewConfigGroup); + int timeMode = m_config->readNumEntry("timemode", 0); + + KRadioAction *action; + + pixmap.load(pixmapDir + "/toolbar/time-musical.png"); + icon = QIconSet(pixmap); + + action = new KRadioAction(i18n("&Musical Times"), icon, 0, this, + SLOT(slotMusicalTime()), + actionCollection(), "time_musical"); + action->setExclusiveGroup("timeMode"); + if (timeMode == 0) + action->setChecked(true); + + pixmap.load(pixmapDir + "/toolbar/time-real.png"); + icon = QIconSet(pixmap); + + action = new KRadioAction(i18n("&Real Times"), icon, 0, this, + SLOT(slotRealTime()), + actionCollection(), "time_real"); + action->setExclusiveGroup("timeMode"); + if (timeMode == 1) + action->setChecked(true); + + pixmap.load(pixmapDir + "/toolbar/time-raw.png"); + icon = QIconSet(pixmap); + + action = new KRadioAction(i18n("Ra&w Times"), icon, 0, this, + SLOT(slotRawTime()), + actionCollection(), "time_raw"); + action->setExclusiveGroup("timeMode"); + if (timeMode == 2) + action->setChecked(true); + + if (m_isTriggerSegment) { + KAction *action = actionCollection()->action("open_in_matrix"); + if (action) + delete action; + action = actionCollection()->action("open_in_notation"); + if (action) + delete action; + } + + createGUI(getRCFileName()); +} + +void +EventView::initStatusBar() +{ + KStatusBar* sb = statusBar(); + + /* + m_hoveredOverNoteName = new QLabel(sb); + m_hoveredOverAbsoluteTime = new QLabel(sb); + + m_hoveredOverNoteName->setMinimumWidth(32); + m_hoveredOverAbsoluteTime->setMinimumWidth(160); + + sb->addWidget(m_hoveredOverAbsoluteTime); + sb->addWidget(m_hoveredOverNoteName); + */ + + sb->insertItem(KTmpStatusMsg::getDefaultMsg(), + KTmpStatusMsg::getDefaultId(), 1); + sb->setItemAlignment(KTmpStatusMsg::getDefaultId(), + AlignLeft | AlignVCenter); + + //m_selectionCounter = new QLabel(sb); + //sb->addWidget(m_selectionCounter); +} + +QSize +EventView::getViewSize() +{ + return m_eventList->size(); +} + +void +EventView::setViewSize(QSize s) +{ + m_eventList->setFixedSize(s); +} + +void +EventView::readOptions() +{ + m_config->setGroup(EventViewConfigGroup); + EditViewBase::readOptions(); + m_eventFilter = m_config->readNumEntry("eventfilter", m_eventFilter); + m_eventList->restoreLayout(m_config, EventViewLayoutConfigGroupName); +} + +void +EventView::slotSaveOptions() +{ + m_config->setGroup(EventViewConfigGroup); + m_config->writeEntry("eventfilter", m_eventFilter); + m_eventList->saveLayout(m_config, EventViewLayoutConfigGroupName); +} + +Segment * +EventView::getCurrentSegment() +{ + if (m_segments.empty()) + return 0; + else + return *m_segments.begin(); +} + +void +EventView::slotModifyFilter(int button) +{ + QCheckBox *checkBox = dynamic_cast<QCheckBox*>(m_filterGroup->find(button)); + + if (checkBox == 0) + return ; + + if (checkBox->isChecked()) { + switch (button) { + case 0: + m_eventFilter |= EventView::Note; + break; + + case 1: + m_eventFilter |= EventView::ProgramChange; + break; + + case 2: + m_eventFilter |= EventView::Controller; + break; + + case 3: + m_eventFilter |= EventView::PitchBend; + break; + + case 4: + m_eventFilter |= EventView::SystemExclusive; + break; + + case 5: + m_eventFilter |= EventView::KeyPressure; + break; + + case 6: + m_eventFilter |= EventView::ChannelPressure; + break; + + case 7: + m_eventFilter |= EventView::Rest; + break; + + case 8: + m_eventFilter |= EventView::Indication; + break; + + case 9: + m_eventFilter |= EventView::Text; + break; + + case 10: + m_eventFilter |= EventView::Other; + break; + + default: + break; + } + + } else { + switch (button) { + case 0: + m_eventFilter ^= EventView::Note; + break; + + case 1: + m_eventFilter ^= EventView::ProgramChange; + break; + + case 2: + m_eventFilter ^= EventView::Controller; + break; + + case 3: + m_eventFilter ^= EventView::PitchBend; + break; + + case 4: + m_eventFilter ^= EventView::SystemExclusive; + break; + + case 5: + m_eventFilter ^= EventView::KeyPressure; + break; + + case 6: + m_eventFilter ^= EventView::ChannelPressure; + break; + + case 7: + m_eventFilter ^= EventView::Rest; + break; + + case 8: + m_eventFilter ^= EventView::Indication; + break; + + case 9: + m_eventFilter ^= EventView::Text; + break; + + case 10: + m_eventFilter ^= EventView::Other; + break; + + default: + break; + } + } + + m_lastSetEventFilter = m_eventFilter; + + applyLayout(0); +} + +void +EventView::setButtonsToFilter() +{ + if (m_eventFilter & Note) + m_noteCheckBox->setChecked(true); + else + m_noteCheckBox->setChecked(false); + + if (m_eventFilter & ProgramChange) + m_programCheckBox->setChecked(true); + else + m_programCheckBox->setChecked(false); + + if (m_eventFilter & Controller) + m_controllerCheckBox->setChecked(true); + else + m_controllerCheckBox->setChecked(false); + + if (m_eventFilter & SystemExclusive) + m_sysExCheckBox->setChecked(true); + else + m_sysExCheckBox->setChecked(false); + + if (m_eventFilter & Text) + m_textCheckBox->setChecked(true); + else + m_textCheckBox->setChecked(false); + + if (m_eventFilter & Rest) + m_restCheckBox->setChecked(true); + else + m_restCheckBox->setChecked(false); + + if (m_eventFilter & PitchBend) + m_pitchBendCheckBox->setChecked(true); + else + m_pitchBendCheckBox->setChecked(false); + + if (m_eventFilter & ChannelPressure) + m_channelPressureCheckBox->setChecked(true); + else + m_channelPressureCheckBox->setChecked(false); + + if (m_eventFilter & KeyPressure) + m_keyPressureCheckBox->setChecked(true); + else + m_keyPressureCheckBox->setChecked(false); + + if (m_eventFilter & Indication) { + m_indicationCheckBox->setChecked(true); + } else { + m_indicationCheckBox->setChecked(false); + } + + if (m_eventFilter & Other) { + m_otherCheckBox->setChecked(true); + } else { + m_otherCheckBox->setChecked(false); + } +} + +void +EventView::slotMusicalTime() +{ + m_config->setGroup(EventViewConfigGroup); + m_config->writeEntry("timemode", 0); + applyLayout(); +} + +void +EventView::slotRealTime() +{ + m_config->setGroup(EventViewConfigGroup); + m_config->writeEntry("timemode", 1); + applyLayout(); +} + +void +EventView::slotRawTime() +{ + m_config->setGroup(EventViewConfigGroup); + m_config->writeEntry("timemode", 2); + applyLayout(); +} + +void +EventView::slotPopupEventEditor(QListViewItem *item) +{ + EventViewItem *eItem = dynamic_cast<EventViewItem*>(item); + + //!!! trigger events + + if (eItem) { + Event *event = eItem->getEvent(); + SimpleEventEditDialog *dialog = + new SimpleEventEditDialog(this, getDocument(), *event, false); + + if (dialog->exec() == QDialog::Accepted && dialog->isModified()) { + EventEditCommand *command = + new EventEditCommand(*(eItem->getSegment()), + event, + dialog->getEvent()); + + addCommandToHistory(command); + } + + } +} + +void +EventView::slotPopupMenu(QListViewItem *item, const QPoint &pos, int) +{ + if (!item) + return ; + + EventViewItem *eItem = dynamic_cast<EventViewItem*>(item); + if (!eItem || !eItem->getEvent()) + return ; + + if (!m_menu) + createMenu(); + + if (m_menu) + //m_menu->exec(QCursor::pos()); + m_menu->exec(pos); + else + RG_DEBUG << "EventView::showMenu() : no menu to show\n"; +} + +void +EventView::createMenu() +{ + m_menu = new QPopupMenu(this); + m_menu->insertItem(i18n("Open in Event Editor"), 0); + m_menu->insertItem(i18n("Open in Expert Event Editor"), 1); + + connect(m_menu, SIGNAL(activated(int)), + SLOT(slotMenuActivated(int))); +} + +void +EventView::slotMenuActivated(int value) +{ + RG_DEBUG << "EventView::slotMenuActivated - value = " << value << endl; + + if (value == 0) { + EventViewItem *eItem = dynamic_cast<EventViewItem*> + (m_eventList->currentItem()); + + if (eItem) { + Event *event = eItem->getEvent(); + SimpleEventEditDialog *dialog = + new SimpleEventEditDialog(this, getDocument(), *event, false); + + if (dialog->exec() == QDialog::Accepted && dialog->isModified()) { + EventEditCommand *command = + new EventEditCommand(*(eItem->getSegment()), + event, + dialog->getEvent()); + + addCommandToHistory(command); + } + + } + } else if (value == 1) { + EventViewItem *eItem = dynamic_cast<EventViewItem*> + (m_eventList->currentItem()); + + if (eItem) { + Event *event = eItem->getEvent(); + EventEditDialog *dialog = new EventEditDialog(this, *event); + + if (dialog->exec() == QDialog::Accepted && dialog->isModified()) { + EventEditCommand *command = + new EventEditCommand(*(eItem->getSegment()), + event, + dialog->getEvent()); + + addCommandToHistory(command); + } + + } + } + + return ; +} + +void +EventView::updateViewCaption() +{ + if (m_isTriggerSegment) { + + setCaption(i18n("%1 - Triggered Segment: %2") + .arg(getDocument()->getTitle()) + .arg(strtoqstr(m_segments[0]->getLabel()))); + + + } else 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(); + + setCaption(i18n("%1 - Segment Track #%2 - Event List") + .arg(getDocument()->getTitle()) + .arg(trackPosition + 1)); + + } else { + + setCaption(i18n("%1 - %2 Segments - Event List") + .arg(getDocument()->getTitle()) + .arg(m_segments.size())); + } + +} + +} +#include "EventView.moc" diff --git a/src/gui/editors/eventlist/EventView.h b/src/gui/editors/eventlist/EventView.h new file mode 100644 index 0000000..4c540e6 --- /dev/null +++ b/src/gui/editors/eventlist/EventView.h @@ -0,0 +1,205 @@ + +/* -*- 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_EVENTVIEW_H_ +#define _RG_EVENTVIEW_H_ + +#include "base/MidiTypes.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "gui/general/EditViewBase.h" +#include <set> +#include <qsize.h> +#include <qstring.h> +#include <vector> +#include "base/Event.h" + + +class QWidget; +class QPopupMenu; +class QPoint; +class QListViewItem; +class QLabel; +class QCheckBox; +class QButtonGroup; +class KListView; + + +namespace Rosegarden +{ + +class Segment; +class RosegardenGUIDoc; +class Event; + + +class EventView : public EditViewBase, public SegmentObserver +{ + Q_OBJECT + + // Event filters + // + enum EventFilter + { + None = 0x0000, + Note = 0x0001, + Rest = 0x0002, + Text = 0x0004, + SystemExclusive = 0x0008, + Controller = 0x0010, + ProgramChange = 0x0020, + PitchBend = 0x0040, + ChannelPressure = 0x0080, + KeyPressure = 0x0100, + Indication = 0x0200, + Other = 0x0400 + }; + +public: + EventView(RosegardenGUIDoc *doc, + std::vector<Segment *> segments, + QWidget *parent); + + virtual ~EventView(); + + virtual bool applyLayout(int staffNo = -1); + + virtual void refreshSegment(Segment *segment, + timeT startTime = 0, + timeT endTime = 0); + + virtual void updateView(); + + virtual void setupActions(); + virtual void initStatusBar(); + virtual QSize getViewSize(); + virtual void setViewSize(QSize); + + // Set the button states to the current filter positions + // + void setButtonsToFilter(); + + // Menu creation and show + // + void createMenu(); + +public slots: + + // standard slots + virtual void slotEditCut(); + virtual void slotEditCopy(); + virtual void slotEditPaste(); + + // other edit slots + void slotEditDelete(); + void slotEditInsert(); + void slotEditEvent(); + void slotEditEventAdvanced(); + + void slotFilterSelection(); + void slotSelectAll(); + void slotClearSelection(); + + void slotMusicalTime(); + void slotRealTime(); + void slotRawTime(); + + // Show RMB menu + // + void slotPopupMenu(QListViewItem*, const QPoint&, int); + void slotMenuActivated(int); + + // on double click on the event list + // + void slotPopupEventEditor(QListViewItem*); + + // Change filter parameters + // + void slotModifyFilter(int); + + virtual void eventAdded(const Segment *, Event *) { } + virtual void eventRemoved(const Segment *, Event *); + virtual void endMarkerTimeChanged(const Segment *, bool) { } + virtual void segmentDeleted(const Segment *); + +signals: + void editTriggerSegment(int); + +protected slots: + virtual void slotSaveOptions(); + + void slotEditTriggerName(); + void slotEditTriggerPitch(); + void slotEditTriggerVelocity(); + void slotTriggerTimeAdjustChanged(int); + void slotTriggerRetuneChanged(); + +protected: + + virtual void readOptions(); + void makeInitialSelection(timeT); + QString makeTimeString(timeT time, int timeMode); + QString makeDurationString(timeT time, + timeT duration, int timeMode); + virtual Segment *getCurrentSegment(); + + virtual void updateViewCaption(); + + //--------------- Data members --------------------------------- + + bool m_isTriggerSegment; + QLabel *m_triggerName; + QLabel *m_triggerPitch; + QLabel *m_triggerVelocity; + + KListView *m_eventList; + int m_eventFilter; + + static int m_lastSetEventFilter; + + QButtonGroup *m_filterGroup; + QCheckBox *m_noteCheckBox; + QCheckBox *m_textCheckBox; + QCheckBox *m_sysExCheckBox; + QCheckBox *m_programCheckBox; + QCheckBox *m_controllerCheckBox; + QCheckBox *m_restCheckBox; + QCheckBox *m_pitchBendCheckBox; + QCheckBox *m_keyPressureCheckBox; + QCheckBox *m_channelPressureCheckBox; + QCheckBox *m_indicationCheckBox; + QCheckBox *m_otherCheckBox; + + std::vector<int> m_listSelection; + std::set<Event *> m_deletedEvents; // deleted since last refresh + + QPopupMenu *m_menu; + +}; + + +} + +#endif diff --git a/src/gui/editors/eventlist/EventViewItem.cpp b/src/gui/editors/eventlist/EventViewItem.cpp new file mode 100644 index 0000000..4435a2b --- /dev/null +++ b/src/gui/editors/eventlist/EventViewItem.cpp @@ -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. +*/ + +#include "EventViewItem.h" +#include "base/Event.h" + +namespace Rosegarden +{ + +// Reimplementation of sort for numeric columns - taking the +// right hand argument from the left is equivalent to the +// the QString compare(). +// +int +EventViewItem::compare(QListViewItem *i, int col, bool ascending) const +{ + EventViewItem *ei = dynamic_cast<EventViewItem *>(i); + if (!ei) return QListViewItem::compare(i, col, ascending); + + if (col == 0) { // time + Rosegarden::Event &e1 = *m_event; + Rosegarden::Event &e2 = *ei->m_event; + if (e2 < e1) return 1; + else if (e1 < e2) return -1; + else return 0; + } else if (col == 2 || col == 5 || col == 6) { // event type, data1, data2 + // we have to do string compares even for data1/data2 which are + // often numeric, just because they aren't _always_ numeric and + // we don't want to prevent the user being able to separate + // e.g. crescendo from decrescendo + if (key(col, ascending).compare(i->key(col, ascending)) == 0) { + return compare(i, 0, ascending); + } else { + return key(col, ascending).compare(i->key(col, ascending)); + } + } else if (col == 3) { // pitch + // numeric comparison for pitch used to work when we only + // showed the numeric pitch number, but then we added the MIDI + // pitch name as well and that broke plain numeric comparison + return key(col, ascending).section(' ', 0, 0).toInt() - + i->key(col, ascending).section(' ', 0, 0).toInt(); + } else { // numeric comparison + return key(col, ascending).toInt() - i->key(col, ascending).toInt(); + } +} + +} diff --git a/src/gui/editors/eventlist/EventViewItem.h b/src/gui/editors/eventlist/EventViewItem.h new file mode 100644 index 0000000..832e652 --- /dev/null +++ b/src/gui/editors/eventlist/EventViewItem.h @@ -0,0 +1,101 @@ +/* -*- 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_EVENTVIEWITEM_H_ +#define _RG_EVENTVIEWITEM_H_ + +#include <klistview.h> + +namespace Rosegarden +{ + +class Segment; +class Event; + +// EventView specialisation of a QListViewItem with the +// addition of a segment pointer +// +class EventViewItem : public KListViewItem +{ +public: + EventViewItem(Rosegarden::Segment *segment, + Rosegarden::Event *event, + KListView *parent) : + KListViewItem(parent), + m_segment(segment), + m_event(event) {;} + + EventViewItem(Rosegarden::Segment *segment, + Rosegarden::Event *event, + KListViewItem *parent) : + KListViewItem(parent), + m_segment(segment), + m_event(event) {;} + + EventViewItem(Rosegarden::Segment *segment, + Rosegarden::Event *event, + QListView *parent, QString label1, + QString label2 = QString::null, + QString label3 = QString::null, + QString label4 = QString::null, + QString label5 = QString::null, + QString label6 = QString::null, + QString label7 = QString::null, + QString label8 = QString::null) : + KListViewItem(parent, label1, label2, label3, label4, + label5, label6, label7, label8), + m_segment(segment), + m_event(event) {;} + + EventViewItem(Rosegarden::Segment *segment, + Rosegarden::Event *event, + KListViewItem *parent, QString label1, + QString label2 = QString::null, + QString label3 = QString::null, + QString label4 = QString::null, + QString label5 = QString::null, + QString label6 = QString::null, + QString label7 = QString::null, + QString label8 = QString::null) : + KListViewItem(parent, label1, label2, label3, label4, + label5, label6, label7, label8), + m_segment(segment), + m_event(event) {;} + + Rosegarden::Segment* getSegment() { return m_segment; } + Rosegarden::Event* getEvent() { return m_event; } + + // Reimplement so that we can sort numerically + // + virtual int compare(QListViewItem *i, int col, bool ascending) const; + +protected: + + Rosegarden::Segment *m_segment; + Rosegarden::Event *m_event; +}; + +} + +#endif /*EVENTVIEWITEM_H_*/ diff --git a/src/gui/editors/eventlist/TrivialVelocityDialog.cpp b/src/gui/editors/eventlist/TrivialVelocityDialog.cpp new file mode 100644 index 0000000..4e609d4 --- /dev/null +++ b/src/gui/editors/eventlist/TrivialVelocityDialog.cpp @@ -0,0 +1,48 @@ +/* -*- 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 "TrivialVelocityDialog.h" + +#include <qspinbox.h> +#include <qlabel.h> +#include <qhbox.h> + +namespace Rosegarden { + +TrivialVelocityDialog::TrivialVelocityDialog(QWidget *parent, QString label, int deft) : + KDialogBase(parent, 0, true, label, Ok | Cancel) + { + QHBox *hbox = makeHBoxMainWidget(); + new QLabel(label, hbox); + m_spin = new QSpinBox(0, 127, 1, hbox); + m_spin->setValue(deft); + } + +int +TrivialVelocityDialog::getVelocity() +{ + return m_spin->value(); +} + +} diff --git a/src/gui/editors/eventlist/TrivialVelocityDialog.h b/src/gui/editors/eventlist/TrivialVelocityDialog.h new file mode 100644 index 0000000..ca19de9 --- /dev/null +++ b/src/gui/editors/eventlist/TrivialVelocityDialog.h @@ -0,0 +1,48 @@ +/* -*- 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_TRIVIALVELOCITYDIALOG_H_ +#define _RG_TRIVIALVELOCITYDIALOG_H_ + +#include <kdialogbase.h> + +class QHBox; +class QSpinBox; + +namespace Rosegarden { + +class TrivialVelocityDialog : public KDialogBase +{ +public: + TrivialVelocityDialog(QWidget *parent, QString label, int deft); + + int getVelocity(); + +protected: + QSpinBox *m_spin; +}; + +} + +#endif /*TRIVIALVELOCITYDIALOG_H_*/ diff --git a/src/gui/editors/guitar/Chord.cpp b/src/gui/editors/guitar/Chord.cpp new file mode 100644 index 0000000..23efe7d --- /dev/null +++ b/src/gui/editors/guitar/Chord.cpp @@ -0,0 +1,113 @@ +/* -*- 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 "Chord.h" +#include "base/Event.h" + +#include <qstring.h> + +namespace Rosegarden +{ + +namespace Guitar +{ +const std::string Chord::EventType = "guitarchord"; +const short Chord::EventSubOrdering = -60; +const PropertyName Chord::RootPropertyName = "root"; +const PropertyName Chord::ExtPropertyName = "ext"; +const PropertyName Chord::FingeringPropertyName = "fingering"; + + +Chord::Chord() + : m_isUserChord(false) +{ +} + +Chord::Chord(const QString& root, const QString& ext) + : m_root(root), + m_ext(ext), + m_isUserChord(false) +{ + if (m_ext.isEmpty()) + m_ext = QString::null; +} + +Chord::Chord(const Event& e) + : m_isUserChord(false) +{ + std::string f; + bool ok; + + ok = e.get<String>(RootPropertyName, f); + if (ok) + m_root = f; + + ok = e.get<String>(ExtPropertyName, f); + if (ok) { + if (f.length() == 0) + m_ext = QString::null; + else + m_ext = f; + } + + ok = e.get<String>(FingeringPropertyName, f); + if (ok) { + QString qf(f); + QString errString; + + Fingering fingering = Fingering::parseFingering(qf, errString); + setFingering(fingering); + } +} + +Event* Chord::getAsEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering); + e->set<String>(RootPropertyName, m_root); + e->set<String>(ExtPropertyName, m_ext); + e->set<String>(FingeringPropertyName, getFingering().toString()); + return e; +} + +const QRegExp Chord::ALT_BASS_REGEXP("/[A-G]"); + +bool operator<(const Chord& a, const Chord& b) +{ + if (a.m_root != b.m_root) { + return a.m_root < b.m_root; + } else if (a.m_ext != b.m_ext) { + if (a.m_ext.isEmpty()) // chords with no ext need to be stored first + return true; + if (b.m_ext.isEmpty()) + return false; + return a.m_ext < b.m_ext; + } else { + return a.m_fingering < b.m_fingering; + } + +} + +} + +} diff --git a/src/gui/editors/guitar/Chord.h b/src/gui/editors/guitar/Chord.h new file mode 100644 index 0000000..9e84cc3 --- /dev/null +++ b/src/gui/editors/guitar/Chord.h @@ -0,0 +1,106 @@ +/* -*- 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_CHORD_H_ +#define _RG_CHORD_H_ + +#include "Fingering.h" +#include "base/Event.h" +#include "misc/Debug.h" + +#include <vector> +#include <qstring.h> +#include <qregexp.h> + +namespace Rosegarden +{ + +class Event; + +namespace Guitar +{ + +class Chord +{ + friend bool operator<(const Chord&, const Chord&); + +public: + static const std::string EventType; + static const short EventSubOrdering; + static const PropertyName RootPropertyName; + static const PropertyName ExtPropertyName; + static const PropertyName FingeringPropertyName; + + Chord(); + Chord(const QString& root, const QString& ext = QString::null); + Chord(const Event&); + + Event* getAsEvent(timeT absoluteTime) const; + + bool isEmpty() const { return m_root.isEmpty(); } + bool operator!() const { return !m_root; } + + bool isUserChord() const { return m_isUserChord; } + void setUserChord(bool c) { m_isUserChord = c; } + + QString getRoot() const { return m_root; } + void setRoot(QString r) { m_root = r; } + + QString getExt() const { return m_ext; } + void setExt(QString r) { m_ext = r.isEmpty() ? QString::null : r; } + + bool hasAltBass() const { return m_ext.contains(ALT_BASS_REGEXP); } + + Fingering getFingering() const { return m_fingering; } + void setFingering(Fingering f) { m_fingering = f; } + + struct ChordCmp + { + bool operator()(const Chord &e1, const Chord &e2) const { + return e1 < e2; + } + bool operator()(const Chord *e1, const Chord *e2) const { + return *e1 < *e2; + } + }; + +protected: + + static const QRegExp ALT_BASS_REGEXP; + + QString m_root; + QString m_ext; + + Fingering m_fingering; + + bool m_isUserChord; +}; + +bool operator<(const Chord&, const Chord&); + +} + +} + +#endif /*_RG_CHORD2_H_*/ diff --git a/src/gui/editors/guitar/ChordMap.cpp b/src/gui/editors/guitar/ChordMap.cpp new file mode 100644 index 0000000..06662d9 --- /dev/null +++ b/src/gui/editors/guitar/ChordMap.cpp @@ -0,0 +1,223 @@ +/* -*- 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 "misc/Debug.h" +#include "ChordMap.h" + +#include <qfile.h> +#include <qtextstream.h> + +namespace Rosegarden +{ + +namespace Guitar +{ + +ChordMap::ChordMap() + : m_needSave(false) +{ +} + +void ChordMap::insert(const Chord& c) +{ + m_map.insert(c); + m_needSave = true; +} + + +ChordMap::chordarray +ChordMap::getChords(const QString& root, const QString& ext) const +{ + chordarray res; + + Chord tmp(root, ext); + NOTATION_DEBUG << "ChordMap::getChords : chord = " << tmp << " - ext is empty : " << ext.isEmpty() << endl; + + for (chordset::const_iterator i = m_map.lower_bound(tmp); i != m_map.end(); ++i) { + NOTATION_DEBUG << "ChordMap::getChords : checking chord " << *i << endl; + + if (i->getRoot() != root) + break; + + if (/* ext.isNull() || */ i->getExt() == ext) { + NOTATION_DEBUG << "ChordMap::getChords : adding chord " << *i << endl; + res.push_back(*i); + } else { + break; + } + } + + return res; +} + +QStringList +ChordMap::getRootList() const +{ + static QStringList rootNotes; + + if (rootNotes.count() == 0) { + rootNotes = QStringList::split(QString(","), "A,A#/Bb,B,C,C#/Db,D,D#/Eb,E,F,F#/Gb,G,G#/Ab"); + } + + // extract roots from map itself - not a very good idea + // +// QString currentRoot; +// +// for(chordset::const_iterator i = m_map.begin(); i != m_map.end(); ++i) { +// const Chord& chord = *i; +// if (chord.getRoot() != currentRoot) { +// rootNotes.push_back(chord.getRoot()); +// currentRoot = chord.getRoot(); +// } +// } + + return rootNotes; +} + +QStringList +ChordMap::getExtList(const QString& root) const +{ + QStringList extList; + QString currentExt = "ZZ"; + + Chord tmp(root); + + for(chordset::const_iterator i = m_map.lower_bound(tmp); i != m_map.end(); ++i) { + const Chord& chord = *i; +// NOTATION_DEBUG << "ChordMap::getExtList : chord = " << chord << endl; + + if (chord.getRoot() != root) + break; + + if (chord.getExt() != currentExt) { +// NOTATION_DEBUG << "ChordMap::getExtList : adding ext " << chord.getExt() << " for root " << root << endl; + extList.push_back(chord.getExt()); + currentExt = chord.getExt(); + } + } + + return extList; +} + +void +ChordMap::substitute(const Chord& oldChord, const Chord& newChord) +{ + remove(oldChord); + insert(newChord); +} + +void +ChordMap::remove(const Chord& c) +{ + m_map.erase(c); + m_needSave = true; +} + +bool ChordMap::saveDocument(const QString& filename, bool userChordsOnly, QString& errMsg) +{ + QFile file(filename); + file.open(IO_WriteOnly); + + QTextStream outStream(&file); + + outStream.setEncoding(QTextStream::UnicodeUTF8); + + outStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + << "<!DOCTYPE rosegarden-chord-data>\n" + << "<rosegarden-chord-data version=\"" << VERSION + << "\" format-version-major=\"" << FILE_FORMAT_VERSION_MAJOR + << "\" format-version-minor=\"" << FILE_FORMAT_VERSION_MINOR + << "\" format-version-point=\"" << FILE_FORMAT_VERSION_POINT + << "\">\n"; + + outStream << "<chords>\n"; + + QString currentExt, currentRoot; + + for(iterator i = begin(); i != end(); ++i) { + const Chord& chord = *i; + + if (userChordsOnly && !chord.isUserChord()) + continue; // skip non-user chords + + if (chord.getRoot() != currentRoot) { + + currentRoot = chord.getRoot(); + + // close current chordset (if there was one) + if (i != begin()) + outStream << "\n</chordset>\n"; + + // open new chordset + outStream << "<chordset root=\"" << chord.getRoot() << "\">\n"; + currentExt = "NEWEXT"; // to make sure we open a new chord right after that + } + + if (chord.getExt() != currentExt) { + + currentExt = chord.getExt(); + + // close current chord (if there was one) + if (i != begin()) + outStream << "</chord>\n"; + + // open new chord + outStream << "<chord"; + if (!chord.getExt().isEmpty()) + outStream << " ext=\"" << chord.getExt() << "\""; + if (chord.isUserChord()) + outStream << " user=\"true\""; + + outStream << ">\n"; + } + + outStream << "<fingering>" << chord.getFingering().toString() << "</fingering>\n"; + } + + if (!m_map.empty()) + outStream << "</chord>\n"; // close last written chord + + outStream << "</chords>\n"; + outStream << "</rosegarden-chord-data>\n"; + + return outStream.device()->status() == IO_Ok; +} + +int ChordMap::FILE_FORMAT_VERSION_MAJOR = 1; +int ChordMap::FILE_FORMAT_VERSION_MINOR = 0; +int ChordMap::FILE_FORMAT_VERSION_POINT = 0; + + +void +ChordMap::debugDump() const +{ + for(chordset::const_iterator i = m_map.begin(); i != m_map.end(); ++i) { + Chord chord = *i; + NOTATION_DEBUG << "ChordMap::debugDump " << chord << endl; + } +} + +} + +} diff --git a/src/gui/editors/guitar/ChordMap.h b/src/gui/editors/guitar/ChordMap.h new file mode 100644 index 0000000..5b7488d --- /dev/null +++ b/src/gui/editors/guitar/ChordMap.h @@ -0,0 +1,87 @@ +/* -*- 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_CHORDMAP_H_ +#define _RG_CHORDMAP_H_ + +#include "Chord.h" + +#include <qstringlist.h> +#include <set> + +namespace Rosegarden +{ + +namespace Guitar +{ + +class ChordMap +{ + typedef std::set<Chord, Chord::ChordCmp> chordset; + +public: + typedef std::vector<Chord> chordarray; + + typedef chordset::iterator iterator; + typedef chordset::const_iterator const_iterator; + + static int FILE_FORMAT_VERSION_MAJOR; + static int FILE_FORMAT_VERSION_MINOR; + static int FILE_FORMAT_VERSION_POINT; + + ChordMap(); + + void insert(const Chord&); + void substitute(const Chord& oldChord, const Chord& newChord); + void remove(const Chord&); + + chordarray getChords(const QString& root, const QString& ext) const; + + QStringList getRootList() const; + QStringList getExtList(const QString& root) const; + + void debugDump() const; + + bool needSave() const { return m_needSave; } + void clearNeedSave() { m_needSave = false; } + + bool saveDocument(const QString& filename, bool userChordsOnly, QString& errMsg); + + iterator begin() { return m_map.begin(); } + iterator end() { return m_map.end(); } + const_iterator begin() const { return m_map.begin(); } + const_iterator end() const { return m_map.end(); } + +protected: + + chordset m_map; + + bool m_needSave; +}; + +} + +} + +#endif /*_RG_CHORDMAP2_H_*/ diff --git a/src/gui/editors/guitar/ChordXmlHandler.cpp b/src/gui/editors/guitar/ChordXmlHandler.cpp new file mode 100644 index 0000000..701c43c --- /dev/null +++ b/src/gui/editors/guitar/ChordXmlHandler.cpp @@ -0,0 +1,154 @@ +// -*- 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 "ChordXmlHandler.h" +#include "misc/Debug.h" + +namespace Rosegarden +{ + +ChordXmlHandler::ChordXmlHandler(Guitar::ChordMap& map) + : ProgressReporter(0), + m_chordMap(map) +{ +} + +ChordXmlHandler::~ChordXmlHandler() +{ +} + +bool ChordXmlHandler::startDocument() +{ + // nothing to do ? + return true; +} + +bool ChordXmlHandler::startElement(const QString& namespaceURI, + const QString& localName, + const QString& qName, + const QXmlAttributes& atts) +{ + QString lcName = qName.lower(); + + if (lcName == "chordset") { + // start new chord set + m_currentRoot = atts.value("root").stripWhiteSpace(); + + } else if (lcName == "chord") { + + m_currentChord = Guitar::Chord(m_currentRoot); + + if (atts.index("ext") >= 0) + m_currentChord.setExt(atts.value("ext").stripWhiteSpace()); + + if (atts.index("user") >= 0) { + QString userVal = atts.value("user").stripWhiteSpace().lower(); + bool res = (userVal == "yes" || userVal == "1" || userVal == "true"); + m_currentChord.setUserChord(res); + } else { + m_currentChord.setUserChord(false); + } + + } else if (lcName == "fingering") { + m_inFingering = true; + } + + return true; +} + +bool ChordXmlHandler::endElement(const QString& namespaceURI, + const QString& localName, + const QString& qName) +{ + QString lcName = qName.lower(); + + if (lcName == "fingering") { + + m_inFingering = false; + m_chordMap.insert(m_currentChord); + NOTATION_DEBUG << "ChordXmlHandler::endElement (fingering) : adding chord " << m_currentChord << endl; + + } else if (lcName == "chord") { + + // adding is done after each parsing of fingering + // +// m_chordMap.insert(m_currentChord); + + } + + return true; +} + +bool ChordXmlHandler::characters(const QString& ch) +{ + QString ch2 = ch.simplifyWhiteSpace(); + + if (!ch2.isEmpty() && m_inFingering) { + if (!parseFingering(ch2)) + return false; + } + + return true; +} + +bool ChordXmlHandler::endDocument() +{ + return true; +} + +bool ChordXmlHandler::parseFingering(const QString& ch) { + + QString errString; + + Guitar::Fingering fingering = Guitar::Fingering::parseFingering(ch, errString); + + if (m_errorString.isEmpty()) { + NOTATION_DEBUG << "ChordXmlHandler::parseFingering : fingering " << ch << endl; + m_currentChord.setFingering(fingering); + return true; + } else { + m_errorString = errString; + return false; + } +} + +bool +ChordXmlHandler::error(const QXmlParseException& exception) +{ + m_errorString = QString("%1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + return QXmlDefaultHandler::error( exception ); +} + +bool +ChordXmlHandler::fatalError(const QXmlParseException& exception) +{ + m_errorString = QString("%1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + return QXmlDefaultHandler::fatalError( exception ); +} + + +} diff --git a/src/gui/editors/guitar/ChordXmlHandler.h b/src/gui/editors/guitar/ChordXmlHandler.h new file mode 100644 index 0000000..ca25168 --- /dev/null +++ b/src/gui/editors/guitar/ChordXmlHandler.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_CHORDXMLHANDLER_H_ +#define _RG_CHORDXMLHANDLER_H_ + +#include "gui/general/ProgressReporter.h" +#include "Chord.h" +#include "ChordMap.h" + +#include <qxml.h> + + +namespace Rosegarden +{ + +class ChordXmlHandler : public ProgressReporter, public QXmlDefaultHandler +{ +public: + ChordXmlHandler(Guitar::ChordMap&); + virtual ~ChordXmlHandler(); + + /// overloaded handler functions + virtual bool startDocument(); + virtual bool startElement(const QString& namespaceURI, + const QString& localName, + const QString& qName, + const QXmlAttributes& atts); + + virtual bool endElement(const QString& namespaceURI, + const QString& localName, + const QString& qName); + + virtual bool characters(const QString& ch); + + virtual bool endDocument (); + + /// Return the error string set during the parsing (if any) + QString errorString() { return m_errorString; } + bool error(const QXmlParseException& exception); + bool fatalError(const QXmlParseException& exception); + +protected: + + bool parseFingering(const QString& ch); + + Guitar::Chord m_currentChord; + QString m_currentRoot; + QString m_errorString; + bool m_inFingering; + Guitar::ChordMap& m_chordMap; +}; + +} + +#endif /*_RG_CHORDXMLHANDLER_H_*/ diff --git a/src/gui/editors/guitar/Fingering.cpp b/src/gui/editors/guitar/Fingering.cpp new file mode 100644 index 0000000..dd1edbd --- /dev/null +++ b/src/gui/editors/guitar/Fingering.cpp @@ -0,0 +1,152 @@ +/* -*- 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 "Fingering.h" + +#include "misc/Debug.h" + +#include <qstringlist.h> +#include <sstream> +#include <algorithm> +#include <klocale.h> + +namespace Rosegarden +{ + +namespace Guitar +{ + +Fingering::Fingering(unsigned int nbStrings) : + m_strings(nbStrings, MUTED) +{ +} + +Fingering::Fingering(QString s) +{ + QString errString; + Fingering t = parseFingering(s, errString); + m_strings = t.m_strings; +} + +unsigned int +Fingering::getStartFret() const +{ + int min = 999, max = 0; + for(std::vector<int>::const_iterator i = m_strings.begin(); i != m_strings.end(); ++i) { + if (*i < min && *i > 0) + min = *i; + if (*i > max) + max = *i; + } + + if (max < 4) + min = 1; + + return min == 999 ? 1 : min; +} + +bool +Fingering::hasBarre() const +{ + int lastStringStatus = m_strings[getNbStrings() - 1]; + + return ((m_strings[0] > OPEN && m_strings[0] == lastStringStatus) || + (m_strings[1] > OPEN && m_strings[1] == lastStringStatus) || + (m_strings[2] > OPEN && m_strings[2] == lastStringStatus)); +} + +Fingering::Barre +Fingering::getBarre() const +{ + int lastStringStatus = m_strings[getNbStrings() - 1]; + + Barre res; + + res.fret = lastStringStatus; + + for(unsigned int i = 0; i < 3; ++i) { + if (m_strings[i] > OPEN && m_strings[i] == lastStringStatus) + res.start = i; + break; + } + + res.end = 5; + + return res; +} + +Fingering +Fingering::parseFingering(const QString& ch, QString& errorString) +{ + QStringList tokens = QStringList::split(' ', ch); + + unsigned int idx = 0; + Fingering fingering; + + for(QStringList::iterator i = tokens.begin(); i != tokens.end() && idx < fingering.getNbStrings(); ++i, ++idx) { + QString t = *i; + bool b = false; + unsigned int fn = t.toUInt(&b); + if (b) { +// NOTATION_DEBUG << "Fingering::parseFingering : '" << t << "' = " << fn << endl; + fingering[idx] = fn; + } else if (t.lower() == "x") { +// NOTATION_DEBUG << "Fingering::parseFingering : '" << t << "' = MUTED\n"; + fingering[idx] = MUTED; + } else { + errorString = i18n("couldn't parse fingering '%1' in '%2'").arg(t).arg(ch); + } + } + + return fingering; +} + + +std::string Fingering::toString() const +{ + std::stringstream s; + + for(std::vector<int>::const_iterator i = m_strings.begin(); i != m_strings.end(); ++i) { + if (*i >= 0) + s << *i << ' '; + else + s << "x "; + } + + return s.str(); +} + +bool operator<(const Fingering& a, const Fingering& b) +{ + for(unsigned int i = 0; i < Fingering::DEFAULT_NB_STRINGS; ++i) { + if (a.getStringStatus(i) != b.getStringStatus(i)) { + return a.getStringStatus(i) < b.getStringStatus(i); + } + } + return false; +} + +} + +} diff --git a/src/gui/editors/guitar/Fingering.h b/src/gui/editors/guitar/Fingering.h new file mode 100644 index 0000000..41d9799 --- /dev/null +++ b/src/gui/editors/guitar/Fingering.h @@ -0,0 +1,95 @@ +/* -*- 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_FINGERING_H_ +#define _RG_FINGERING_H_ + +#include <vector> +#include <qstring.h> +#include "base/Event.h" + +namespace Rosegarden +{ + +namespace Guitar +{ + +class Fingering +{ +public: + friend bool operator<(const Fingering&, const Fingering&); + + typedef std::vector<int>::iterator iterator; + typedef std::vector<int>::const_iterator const_iterator; + + struct Barre { + unsigned int fret; + unsigned int start; + unsigned int end; + }; + + static const unsigned int DEFAULT_NB_STRINGS = 6; + + Fingering(unsigned int nbStrings = DEFAULT_NB_STRINGS); + Fingering(QString); + + enum { MUTED = -1, OPEN = 0 }; + + /** + * returns the fret number on which the string is pressed, or one of MUTED and OPEN + * + */ + int getStringStatus(int stringNb) const { return m_strings[stringNb]; } + void setStringStatus(int stringNb, int status) { m_strings[stringNb] = status; } + unsigned int getStartFret() const; + unsigned int getNbStrings() const { return m_strings.size(); } + + bool hasBarre() const; + Barre getBarre() const; + + int operator[](int i) const { return m_strings[i]; } + int& operator[](int i) { return m_strings[i]; } + + bool operator==(const Fingering& o) const { return m_strings == o.m_strings; } + + iterator begin() { return m_strings.begin(); } + iterator end() { return m_strings.end(); } + const_iterator begin() const { return m_strings.begin(); } + const_iterator end() const { return m_strings.end(); } + + static Fingering parseFingering(const QString&, QString& errorString); + std::string toString() const; + +protected: + + std::vector<int> m_strings; +}; + +bool operator<(const Fingering&, const Fingering&); + +} + +} + +#endif /*_RG_FINGERING2_H_*/ diff --git a/src/gui/editors/guitar/FingeringBox.cpp b/src/gui/editors/guitar/FingeringBox.cpp new file mode 100644 index 0000000..885ba83 --- /dev/null +++ b/src/gui/editors/guitar/FingeringBox.cpp @@ -0,0 +1,293 @@ +/* -*- 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 "FingeringBox.h" +#include "Fingering.h" + +#include "misc/Debug.h" + +namespace Rosegarden +{ + +FingeringBox::FingeringBox(unsigned int nbFrets, unsigned int nbStrings, bool editable, QWidget *parent, const char* name) + : QFrame(parent, name), + m_nbFretsDisplayed(nbFrets), + m_startFret(1), + m_nbStrings(nbStrings), + m_transientFretNb(0), + m_transientStringNb(0), + m_editable(editable), + m_noteSymbols(m_nbStrings, m_nbFretsDisplayed) +{ + init(); +} + +FingeringBox::FingeringBox(bool editable, QWidget *parent, const char* name) + : QFrame(parent, name), + m_nbFretsDisplayed(DEFAULT_NB_DISPLAYED_FRETS), + m_startFret(1), + m_nbStrings(Guitar::Fingering::DEFAULT_NB_STRINGS), + m_editable(editable), + m_noteSymbols(m_nbStrings, m_nbFretsDisplayed) +{ + init(); +} + +void +FingeringBox::init() +{ + setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); + setFixedSize(IMG_WIDTH, IMG_HEIGHT); + setBackgroundMode(PaletteBase); + if (m_editable) + setMouseTracking(true); + +} + +void +FingeringBox::drawContents(QPainter* p) +{ +// NOTATION_DEBUG << "FingeringBox::drawContents()" << endl; + + // For all strings on guitar + // check state of string + // If pressed display note + // Else display muted or open symbol + // For all bars + // display bar + // Horizontal separator line + + // draw guitar chord fingering + // + m_noteSymbols.drawFretNumber(p, m_startFret); + m_noteSymbols.drawFrets(p); + m_noteSymbols.drawStrings(p); + + unsigned int stringNb = 0; + + // draw notes + // + for (Guitar::Fingering::const_iterator pos = m_fingering.begin(); + pos != m_fingering.end(); + ++pos, ++stringNb) { + + switch (*pos) { + case Guitar::Fingering::OPEN: +// NOTATION_DEBUG << "Fingering::drawContents - drawing Open symbol on string " << stringNb << endl; + m_noteSymbols.drawOpenSymbol(p, stringNb); + break; + + case Guitar::Fingering::MUTED: +// NOTATION_DEBUG << "Fingering::drawContents - drawing Mute symbol on string" << stringNb << endl; + m_noteSymbols.drawMuteSymbol(p, stringNb); + break; + + default: +// NOTATION_DEBUG << "Fingering::drawContents - drawing note symbol at " << *pos << " on string " << stringNb << endl; + m_noteSymbols.drawNoteSymbol(p, stringNb, *pos - (m_startFret - 1), false); + break; + } + } + + // TODO: detect barres and draw them in a special way ? + + // draw transient note (visual feedback for mouse move) + // + if (hasMouse() && + m_transientFretNb > 0 && m_transientFretNb <= m_nbFretsDisplayed && + m_transientStringNb >= 0 && m_transientStringNb <= m_nbStrings) { + m_noteSymbols.drawNoteSymbol(p, m_transientStringNb, m_transientFretNb - (m_startFret - 1), true); + } + + // DEBUG +// p->save(); +// p->setPen(Qt::red); +// unsigned int topBorderY = m_noteSymbols.getTopBorder(maximumHeight()); +// p->drawLine(0, topBorderY, 20, topBorderY); +// p->drawRect(m_r1); +// p->setPen(Qt::blue); +// p->drawRect(m_r2); +// p->restore(); +} + +void +FingeringBox::setFingering(const Guitar::Fingering& f) { + m_fingering = f; + m_startFret = m_fingering.getStartFret(); + update(); +} + +unsigned int +FingeringBox::getStringNumber(const QPoint& pos) +{ + PositionPair result = m_noteSymbols.getStringNumber(maximumHeight(), + pos.x(), + m_nbStrings); + unsigned int stringNum = -1; + + if(result.first){ + stringNum = result.second; +// RG_DEBUG << "FingeringBox::getStringNumber : res = " << stringNum << endl; + } + + return stringNum; +} + +unsigned int +FingeringBox::getFretNumber(const QPoint& pos) +{ + unsigned int fretNum = 0; + + if(true || pos.y() > m_noteSymbols.getTopBorder(maximumHeight())) { + // If fret position is below the top line of the guitar chord image. + PositionPair result = m_noteSymbols.getFretNumber(maximumWidth(), + pos.y(), + m_nbFretsDisplayed); + + if(result.first) { + fretNum = result.second + (m_startFret - 1); +// RG_DEBUG << "FingeringBox::getFretNumber : res = " << fretNum << " startFret = " << m_startFret << endl; + } else { +// RG_DEBUG << "FingeringBox::getFretNumber : no res\n"; + } + } + + return fretNum; +} + +void +FingeringBox::mousePressEvent(QMouseEvent *event) +{ + if (!m_editable) + return; + + if((event->button() == LeftButton) && m_editable) { + + // Find string position + m_press_string_num = getStringNumber(event->pos()); + + // Find fret position + m_press_fret_num = getFretNumber(event->pos()); + } +} + +void +FingeringBox::mouseReleaseEvent(QMouseEvent *event) +{ + if(!m_editable) + return ; + + unsigned int release_string_num = getStringNumber(event->pos()); + unsigned int release_fret_num = getFretNumber(event->pos()); + + processMouseRelease(release_string_num, release_fret_num); +} + +void +FingeringBox::processMouseRelease(unsigned int release_string_num, + unsigned int release_fret_num) +{ + if(m_press_fret_num == release_fret_num) { + // If press string & fret pos == release string & fret position, display chord + if(m_press_string_num == release_string_num) { + + if(m_press_fret_num < (m_startFret + m_nbFretsDisplayed)) { + + unsigned int aVal = m_press_fret_num; + + if(m_press_fret_num == 0) { + + int stringStatus = m_fingering.getStringStatus(m_press_string_num); + + if (stringStatus == Guitar::Fingering::OPEN) + aVal = Guitar::Fingering::MUTED; + else if (stringStatus > Guitar::Fingering::OPEN) + aVal = Guitar::Fingering::OPEN; + + } + + m_fingering.setStringStatus(m_press_string_num, aVal); + + update(); + } + } + // else if press fret pos == release fret pos & press string pos != release string pos, display bar + else { + if(((m_press_string_num > 0)&&(release_string_num > 0)) && + (( m_press_string_num <= m_nbStrings)&& + (release_string_num <= m_nbStrings)) && + (( m_press_fret_num <(m_startFret + m_nbFretsDisplayed)) && + (release_fret_num <(m_startFret + m_nbFretsDisplayed)))) { + + // TODO deal with barre later on + + } + } + } +} + + +void +FingeringBox::mouseMoveEvent( QMouseEvent *event ) +{ + if (!m_editable) + return; + + unsigned int transientStringNb = getStringNumber(event->pos()); + unsigned int transientFretNb = getFretNumber(event->pos()); + + if (transientStringNb != m_transientStringNb || + transientFretNb != m_transientFretNb) { + + QRect r1 = m_noteSymbols.getTransientNoteSymbolRect(size(), + m_transientStringNb, + m_transientFretNb - (m_startFret - 1)); + m_transientStringNb = transientStringNb; + m_transientFretNb = transientFretNb; + QRect r2 = m_noteSymbols.getTransientNoteSymbolRect(size(), + m_transientStringNb, + m_transientFretNb - (m_startFret - 1)); + + m_r1 = r1; + m_r2 = r2; + +// RG_DEBUG << "Fingering::updateTransientPos r1 = " << r1 << " - r2 = " << r2 << endl; + +// QRect updateRect = r1 | r2; +// update(updateRect); + + update(); + + } + +} + +void +FingeringBox::leaveEvent(QEvent*) +{ + update(); +} + +} diff --git a/src/gui/editors/guitar/FingeringBox.h b/src/gui/editors/guitar/FingeringBox.h new file mode 100644 index 0000000..b54c0a8 --- /dev/null +++ b/src/gui/editors/guitar/FingeringBox.h @@ -0,0 +1,106 @@ +/* -*- 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_FINGERINGBOX_H_ +#define _RG_FINGERINGBOX_H_ + +#include <qframe.h> + +#include "NoteSymbols.h" +#include "Fingering.h" + +namespace Rosegarden +{ + +class Fingering; + +class FingeringBox : public QFrame +{ + static const unsigned int IMG_WIDTH = 200; + static const unsigned int IMG_HEIGHT = 200; + +public: + FingeringBox(unsigned int nbFrets, unsigned int nbStrings, bool editable, QWidget *parent, const char* name = 0); + FingeringBox(bool editable, QWidget *parent, const char* name = 0); + + void setStartFret(unsigned int f) { m_startFret = f; update(); } + unsigned int getStartFret() const { return m_startFret; } + + void setFingering(const Guitar::Fingering&); + const Guitar::Fingering& getFingering() { return m_fingering; } + + const Guitar::NoteSymbols& getNoteSymbols() const { return m_noteSymbols; } + + static const unsigned int DEFAULT_NB_DISPLAYED_FRETS = 4; + +protected: + void init(); + + virtual void drawContents(QPainter*); + + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void leaveEvent(QEvent*); + + void processMouseRelease( unsigned int release_string_num, unsigned int release_fret_num); + + typedef std::pair<bool, unsigned int> PositionPair; + + unsigned int getStringNumber(const QPoint&); + + unsigned int getFretNumber(const QPoint&); + + //! Maximum number of frets displayed by FingeringBox + unsigned int m_nbFretsDisplayed; + + unsigned int m_startFret; + + unsigned int m_nbStrings; + + unsigned int m_transientFretNb; + unsigned int m_transientStringNb; + + //! Present mode + bool m_editable; + + //! Handle to the present fingering + Guitar::Fingering m_fingering; + + //! String number where a mouse press event was located + unsigned int m_press_string_num; + + //! Fret number where a mouse press event was located + unsigned int m_press_fret_num; + + Guitar::NoteSymbols m_noteSymbols; + + QRect m_r1, m_r2; +}; + +} + +#endif /*_RG_FINGERINGBOX2_H_*/ diff --git a/src/gui/editors/guitar/FingeringListBoxItem.cpp b/src/gui/editors/guitar/FingeringListBoxItem.cpp new file mode 100644 index 0000000..31b92e9 --- /dev/null +++ b/src/gui/editors/guitar/FingeringListBoxItem.cpp @@ -0,0 +1,36 @@ +/* -*- 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 "FingeringListBoxItem.h" + +namespace Rosegarden { + +FingeringListBoxItem::FingeringListBoxItem(const Guitar::Chord& chord, QListBox* parent, QPixmap pixmap, QString fingeringString) + : QListBoxPixmap(parent, pixmap, fingeringString), + m_chord(chord) +{ +} + +} diff --git a/src/gui/editors/guitar/FingeringListBoxItem.h b/src/gui/editors/guitar/FingeringListBoxItem.h new file mode 100644 index 0000000..b7625e2 --- /dev/null +++ b/src/gui/editors/guitar/FingeringListBoxItem.h @@ -0,0 +1,46 @@ +/* -*- 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_FINGERINGLISTBOXITEM_H_ +#define _RG_FINGERINGLISTBOXITEM_H_ + +#include <qlistbox.h> +#include "Chord.h" + +namespace Rosegarden { + +class FingeringListBoxItem : public QListBoxPixmap +{ +public: + FingeringListBoxItem(const Guitar::Chord& chord, QListBox* parent, QPixmap pixmap, QString fingeringString); + + const Guitar::Chord& getChord() { return m_chord; } +protected: + Guitar::Chord m_chord; +}; + +} + +#endif /*_RG_FINGERINGLISTBOXITEM_H_*/ diff --git a/src/gui/editors/guitar/GuitarChordEditorDialog.cpp b/src/gui/editors/guitar/GuitarChordEditorDialog.cpp new file mode 100644 index 0000000..60da8b6 --- /dev/null +++ b/src/gui/editors/guitar/GuitarChordEditorDialog.cpp @@ -0,0 +1,109 @@ +/* -*- 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 "GuitarChordEditorDialog.h" +#include "FingeringBox.h" +#include "Chord.h" +#include "ChordMap.h" + +#include <klineedit.h> +#include <qcombobox.h> +#include <qspinbox.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kstddirs.h> +#include <qlayout.h> +#include <qlabel.h> + +namespace Rosegarden +{ + +GuitarChordEditorDialog::GuitarChordEditorDialog(Guitar::Chord& chord, const Guitar::ChordMap& chordMap, QWidget *parent) + : KDialogBase(parent, "GuitarChordEditor", true, i18n("Guitar Chord Editor"), Ok|Cancel), + m_chord(chord), + m_chordMap(chordMap) +{ + QWidget *page = new QWidget(this); + setMainWidget(page); + QGridLayout *topLayout = new QGridLayout(page, 7, 2, spacingHint()); + + topLayout->addWidget(new QLabel(i18n("Start fret"), page), 0, 1); + m_startFret = new QSpinBox(1, 24, 1, page); + topLayout->addWidget(m_startFret, 1, 1); + + connect(m_startFret, SIGNAL(valueChanged(int)), + this, SLOT(slotStartFretChanged(int))); + + topLayout->addWidget(new QLabel(i18n("Root"), page), 2, 1); + m_rootNotesList = new QComboBox(page); + topLayout->addWidget(m_rootNotesList, 3, 1); + + topLayout->addWidget(new QLabel(i18n("Extension"), page), 4, 1); + m_ext = new QComboBox(true, page); + topLayout->addWidget(m_ext, 5, 1); + + topLayout->addItem(new QSpacerItem(1, 1), 6, 1); + + m_fingeringBox = new FingeringBox(true, page); + m_fingeringBox->setFingering(m_chord.getFingering()); + topLayout->addMultiCellWidget(m_fingeringBox, 0, 7, 0, 0); + + NOTATION_DEBUG << "GuitarChordEditorDialog : chord = " << m_chord << endl; + + + QStringList rootList = m_chordMap.getRootList(); + if (rootList.count() > 0) { + m_rootNotesList->insertStringList(rootList); + m_rootNotesList->setCurrentItem(rootList.findIndex(m_chord.getRoot())); + } + + QStringList extList = m_chordMap.getExtList(m_chord.getRoot()); + if (extList.count() > 0) { + m_ext->insertStringList(extList); + m_ext->setCurrentItem(extList.findIndex(m_chord.getExt())); + } + +} + +void +GuitarChordEditorDialog::slotStartFretChanged(int startFret) +{ + m_fingeringBox->setStartFret(startFret); +} + +void +GuitarChordEditorDialog::slotOk() +{ + m_chord.setFingering(m_fingeringBox->getFingering()); + m_chord.setExt(m_ext->currentText()); + m_chord.setRoot(m_rootNotesList->currentText()); + m_chord.setUserChord(true); + KDialogBase::slotOk(); +} + + +} + +#include "GuitarChordEditorDialog.moc" + diff --git a/src/gui/editors/guitar/GuitarChordEditorDialog.h b/src/gui/editors/guitar/GuitarChordEditorDialog.h new file mode 100644 index 0000000..fc01605 --- /dev/null +++ b/src/gui/editors/guitar/GuitarChordEditorDialog.h @@ -0,0 +1,67 @@ +/* -*- 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_GUITARCHORDEDITOR2_H_ +#define _RG_GUITARCHORDEDITOR2_H_ + +#include <kdialogbase.h> + +#include "Chord.h" +#include "ChordMap.h" + +class QComboBox; +class QSpinBox; + +namespace Rosegarden +{ + +class FingeringBox; + + +class GuitarChordEditorDialog : public KDialogBase +{ + Q_OBJECT + +public: + GuitarChordEditorDialog(Guitar::Chord&, const Guitar::ChordMap& chordMap, QWidget *parent=0); + +protected slots: + void slotStartFretChanged(int); + virtual void slotOk(); + +protected: + + void populateExtensions(const QStringList&); + + FingeringBox* m_fingeringBox; + QComboBox* m_rootNotesList; + QSpinBox* m_startFret; + QComboBox* m_ext; + Guitar::Chord& m_chord; + const Guitar::ChordMap& m_chordMap; +}; + +} + +#endif /*_RG_GUITARCHORDEDITOR2_H_*/ diff --git a/src/gui/editors/guitar/GuitarChordSelectorDialog.cpp b/src/gui/editors/guitar/GuitarChordSelectorDialog.cpp new file mode 100644 index 0000000..bd62c1f --- /dev/null +++ b/src/gui/editors/guitar/GuitarChordSelectorDialog.cpp @@ -0,0 +1,475 @@ +/* -*- 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 "GuitarChordSelectorDialog.h" +#include "GuitarChordEditorDialog.h" +#include "ChordXmlHandler.h" +#include "FingeringBox.h" +#include "FingeringListBoxItem.h" + +#include "misc/Debug.h" +#include <qlistbox.h> +#include <qlayout.h> +#include <qcombobox.h> +#include <qpushbutton.h> +#include <qlabel.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kstddirs.h> + +namespace Rosegarden +{ + +GuitarChordSelectorDialog::GuitarChordSelectorDialog(QWidget *parent) + : KDialogBase(parent, "GuitarChordSelector", true, i18n("Guitar Chord Selector"), Ok|Cancel) +{ + QWidget *page = new QWidget(this); + setMainWidget(page); + QGridLayout *topLayout = new QGridLayout(page, 3, 4, spacingHint()); + + topLayout->addWidget(new QLabel(i18n("Root"), page), 0, 0); + m_rootNotesList = new QListBox(page); + topLayout->addWidget(m_rootNotesList, 1, 0); + + topLayout->addWidget(new QLabel(i18n("Extension"), page), 0, 1); + m_chordExtList = new QListBox(page); + topLayout->addWidget(m_chordExtList, 1, 1); + + m_newFingeringButton = new QPushButton(i18n("New"), page); + m_deleteFingeringButton = new QPushButton(i18n("Delete"), page); + m_editFingeringButton = new QPushButton(i18n("Edit"), page); + + m_chordComplexityCombo = new QComboBox(page); + m_chordComplexityCombo->insertItem(i18n("beginner")); + m_chordComplexityCombo->insertItem(i18n("common")); + m_chordComplexityCombo->insertItem(i18n("all")); + + connect(m_chordComplexityCombo, SIGNAL(activated(int)), + this, SLOT(slotComplexityChanged(int))); + + QVBoxLayout* vboxLayout = new QVBoxLayout(page, 5); + topLayout->addMultiCellLayout(vboxLayout, 1, 3, 2, 2); + vboxLayout->addWidget(m_chordComplexityCombo); + vboxLayout->addStretch(10); + vboxLayout->addWidget(m_newFingeringButton); + vboxLayout->addWidget(m_deleteFingeringButton); + vboxLayout->addWidget(m_editFingeringButton); + + connect(m_newFingeringButton, SIGNAL(clicked()), + this, SLOT(slotNewFingering())); + connect(m_deleteFingeringButton, SIGNAL(clicked()), + this, SLOT(slotDeleteFingering())); + connect(m_editFingeringButton, SIGNAL(clicked()), + this, SLOT(slotEditFingering())); + + topLayout->addWidget(new QLabel(i18n("Fingerings"), page), 0, 3); + m_fingeringsList = new QListBox(page); + topLayout->addMultiCellWidget(m_fingeringsList, 1, 2, 3, 3); + + m_fingeringBox = new FingeringBox(false, page); + topLayout->addMultiCellWidget(m_fingeringBox, 2, 2, 0, 1); + + connect(m_rootNotesList, SIGNAL(highlighted(int)), + this, SLOT(slotRootHighlighted(int))); + connect(m_chordExtList, SIGNAL(highlighted(int)), + this, SLOT(slotChordExtHighlighted(int))); + connect(m_fingeringsList, SIGNAL(highlighted(QListBoxItem*)), + this, SLOT(slotFingeringHighlighted(QListBoxItem*))); +} + +void +GuitarChordSelectorDialog::init() +{ + // populate the listboxes + // + std::vector<QString> chordFiles = getAvailableChordFiles(); + + parseChordFiles(chordFiles); + +// m_chordMap.debugDump(); + + populate(); +} + +void +GuitarChordSelectorDialog::populate() +{ + QStringList rootList = m_chordMap.getRootList(); + if (rootList.count() > 0) { + m_rootNotesList->insertStringList(rootList); + + QStringList extList = m_chordMap.getExtList(rootList.first()); + populateExtensions(extList); + + Guitar::ChordMap::chordarray chords = m_chordMap.getChords(rootList.first(), extList.first()); + populateFingerings(chords); + + m_chord.setRoot(rootList.first()); + m_chord.setExt(extList.first()); + } + + m_rootNotesList->sort(); + + m_rootNotesList->setCurrentItem(0); +} + +void +GuitarChordSelectorDialog::clear() +{ + m_rootNotesList->clear(); + m_chordExtList->clear(); + m_fingeringsList->clear(); +} + +void +GuitarChordSelectorDialog::refresh() +{ + clear(); + populate(); +} + +void +GuitarChordSelectorDialog::slotRootHighlighted(int i) +{ + NOTATION_DEBUG << "GuitarChordSelectorDialog::slotRootHighlighted " << i << endl; + + m_chord.setRoot(m_rootNotesList->text(i)); + + QStringList extList = m_chordMap.getExtList(m_chord.getRoot()); + populateExtensions(extList); + if (m_chordExtList->count() > 0) + m_chordExtList->setCurrentItem(0); + else + m_fingeringsList->clear(); // clear any previous fingerings +} + +void +GuitarChordSelectorDialog::slotChordExtHighlighted(int i) +{ + NOTATION_DEBUG << "GuitarChordSelectorDialog::slotChordExtHighlighted " << i << endl; + + Guitar::ChordMap::chordarray chords = m_chordMap.getChords(m_chord.getRoot(), m_chordExtList->text(i)); + populateFingerings(chords); + + m_fingeringsList->setCurrentItem(0); +} + +void +GuitarChordSelectorDialog::slotFingeringHighlighted(QListBoxItem* listBoxItem) +{ + NOTATION_DEBUG << "GuitarChordSelectorDialog::slotFingeringHighlighted\n"; + + FingeringListBoxItem* fingeringItem = dynamic_cast<FingeringListBoxItem*>(listBoxItem); + if (fingeringItem) { + m_chord = fingeringItem->getChord(); + m_fingeringBox->setFingering(m_chord.getFingering()); + setEditionEnabled(m_chord.isUserChord()); + } +} + +void +GuitarChordSelectorDialog::slotComplexityChanged(int) +{ + // simply repopulate the extension list box + // + QStringList extList = m_chordMap.getExtList(m_chord.getRoot()); + populateExtensions(extList); + if (m_chordExtList->count() > 0) + m_chordExtList->setCurrentItem(0); + else + m_fingeringsList->clear(); // clear any previous fingerings +} + +void +GuitarChordSelectorDialog::slotNewFingering() +{ + Guitar::Chord newChord; + newChord.setRoot(m_chord.getRoot()); + newChord.setExt(m_chord.getExt()); + + GuitarChordEditorDialog* chordEditorDialog = new GuitarChordEditorDialog(newChord, m_chordMap, this); + + if (chordEditorDialog->exec() == QDialog::Accepted) { + m_chordMap.insert(newChord); + // populate lists + // + if (!m_rootNotesList->findItem(newChord.getRoot(), Qt::ExactMatch)) { + m_rootNotesList->insertItem(newChord.getRoot()); + m_rootNotesList->sort(); + } + + if (!m_chordExtList->findItem(newChord.getExt(), Qt::ExactMatch)) { + m_chordExtList->insertItem(newChord.getExt()); + m_chordExtList->sort(); + } + } + + delete chordEditorDialog; + + refresh(); +} + +void +GuitarChordSelectorDialog::slotDeleteFingering() +{ + if (m_chord.isUserChord()) { + m_chordMap.remove(m_chord); + delete m_fingeringsList->selectedItem(); + } +} + +void +GuitarChordSelectorDialog::slotEditFingering() +{ + Guitar::Chord newChord = m_chord; + GuitarChordEditorDialog* chordEditorDialog = new GuitarChordEditorDialog(newChord, m_chordMap, this); + + if (chordEditorDialog->exec() == QDialog::Accepted) { + NOTATION_DEBUG << "GuitarChordSelectorDialog::slotEditFingering() - current map state :\n"; + m_chordMap.debugDump(); + m_chordMap.substitute(m_chord, newChord); + NOTATION_DEBUG << "GuitarChordSelectorDialog::slotEditFingering() - new map state :\n"; + m_chordMap.debugDump(); + setChord(newChord); + } + + delete chordEditorDialog; + + refresh(); +} + +void +GuitarChordSelectorDialog::slotOk() +{ + if (m_chordMap.needSave()) { + saveUserChordMap(); + m_chordMap.clearNeedSave(); + } + + KDialogBase::slotOk(); +} + +void +GuitarChordSelectorDialog::setChord(const Guitar::Chord& chord) +{ + NOTATION_DEBUG << "GuitarChordSelectorDialog::setChord " << chord << endl; + + m_chord = chord; + + // select the chord's root + // + m_rootNotesList->setCurrentItem(0); + QListBoxItem* correspondingRoot = m_rootNotesList->findItem(chord.getRoot(), Qt::ExactMatch); + if (correspondingRoot) + m_rootNotesList->setSelected(correspondingRoot, true); + + // update the dialog's complexity setting if needed, then populate the extension list + // + QString chordExt = chord.getExt(); + int complexityLevel = m_chordComplexityCombo->currentItem(); + int chordComplexity = evaluateChordComplexity(chordExt); + + if (chordComplexity > complexityLevel) { + m_chordComplexityCombo->setCurrentItem(chordComplexity); + } + + QStringList extList = m_chordMap.getExtList(chord.getRoot()); + populateExtensions(extList); + + // select the chord's extension + // + if (chordExt.isEmpty()) { + chordExt = ""; + m_chordExtList->setSelected(0, true); + } else { + QListBoxItem* correspondingExt = m_chordExtList->findItem(chordExt, Qt::ExactMatch); + if (correspondingExt) + m_chordExtList->setSelected(correspondingExt, true); + } + + // populate fingerings and pass the current chord's fingering so it is selected + // + Guitar::ChordMap::chordarray similarChords = m_chordMap.getChords(chord.getRoot(), chord.getExt()); + populateFingerings(similarChords, chord.getFingering()); +} + +void +GuitarChordSelectorDialog::populateFingerings(const Guitar::ChordMap::chordarray& chords, const Guitar::Fingering& refFingering) +{ + m_fingeringsList->clear(); + + for(Guitar::ChordMap::chordarray::const_iterator i = chords.begin(); i != chords.end(); ++i) { + const Guitar::Chord& chord = *i; + QString fingeringString = chord.getFingering().toString(); + NOTATION_DEBUG << "GuitarChordSelectorDialog::populateFingerings " << chord << endl; + QPixmap fingeringPixmap = getFingeringPixmap(chord.getFingering()); + FingeringListBoxItem *item = new FingeringListBoxItem(chord, m_fingeringsList, fingeringPixmap, fingeringString); + if (refFingering == chord.getFingering()) { + NOTATION_DEBUG << "GuitarChordSelectorDialog::populateFingerings - fingering found " << fingeringString << endl; + m_fingeringsList->setSelected(item, true); + } + } + +} + + +QPixmap +GuitarChordSelectorDialog::getFingeringPixmap(const Guitar::Fingering& fingering) const +{ + QPixmap pixmap(FINGERING_PIXMAP_WIDTH, FINGERING_PIXMAP_HEIGHT); + pixmap.fill(); + + QPainter pp(&pixmap); + QPainter *p = &pp; + + p->setViewport(FINGERING_PIXMAP_H_MARGIN, FINGERING_PIXMAP_W_MARGIN, + FINGERING_PIXMAP_WIDTH - FINGERING_PIXMAP_W_MARGIN, + FINGERING_PIXMAP_HEIGHT - FINGERING_PIXMAP_H_MARGIN); + + Guitar::NoteSymbols::drawFingeringPixmap(fingering, m_fingeringBox->getNoteSymbols(), p); + + return pixmap; +} + +void +GuitarChordSelectorDialog::populateExtensions(const QStringList& extList) +{ + m_chordExtList->clear(); + + if (m_chordComplexityCombo->currentItem() != COMPLEXITY_ALL) { + // some filtering needs to be done + int complexityLevel = m_chordComplexityCombo->currentItem(); + + QStringList filteredList; + for(QStringList::const_iterator i = extList.constBegin(); i != extList.constEnd(); ++i) { + if (evaluateChordComplexity((*i).lower().stripWhiteSpace()) <= complexityLevel) { + NOTATION_DEBUG << "GuitarChordSelectorDialog::populateExtensions - adding '" << *i << "'\n"; + filteredList.append(*i); + } + } + + m_chordExtList->insertStringList(filteredList); + + } else { + m_chordExtList->insertStringList(extList); + } +} + +int +GuitarChordSelectorDialog::evaluateChordComplexity(const QString& ext) +{ + if (ext.isEmpty() || + ext == "7" || + ext == "m" || + ext == "5") + return COMPLEXITY_BEGINNER; + + if (ext == "dim" || + ext == "dim7" || + ext == "aug" || + ext == "sus2" || + ext == "sus4" || + ext == "maj7" || + ext == "m7" || + ext == "mmaj7" || + ext == "m7b5" || + ext == "7sus4") + + return COMPLEXITY_COMMON; + + return COMPLEXITY_ALL; +} + +void +GuitarChordSelectorDialog::parseChordFiles(const std::vector<QString>& chordFiles) +{ + for(std::vector<QString>::const_iterator i = chordFiles.begin(); i != chordFiles.end(); ++i) { + parseChordFile(*i); + } +} + +void +GuitarChordSelectorDialog::parseChordFile(const QString& chordFileName) +{ + ChordXmlHandler handler(m_chordMap); + QFile chordFile(chordFileName); + bool ok = chordFile.open(IO_ReadOnly); + if (!ok) + KMessageBox::error(0, i18n("couldn't open file '%1'").arg(handler.errorString())); + + QXmlInputSource source(chordFile); + QXmlSimpleReader reader; + reader.setContentHandler(&handler); + reader.setErrorHandler(&handler); + NOTATION_DEBUG << "GuitarChordSelectorDialog::parseChordFile() parsing " << chordFileName << endl; + reader.parse(source); + if (!ok) + KMessageBox::error(0, i18n("couldn't parse chord dictionnary : %1").arg(handler.errorString())); + +} + +void +GuitarChordSelectorDialog::setEditionEnabled(bool enabled) +{ + m_deleteFingeringButton->setEnabled(enabled); + m_editFingeringButton->setEnabled(enabled); +} + +std::vector<QString> +GuitarChordSelectorDialog::getAvailableChordFiles() +{ + std::vector<QString> names; + + // Read config for default directory + QStringList chordDictFiles = KGlobal::dirs()->findAllResources("appdata", "chords/*.xml"); + + for(QStringList::iterator i = chordDictFiles.begin(); i != chordDictFiles.end(); ++i) { + NOTATION_DEBUG << "GuitarChordSelectorDialog::getAvailableChordFiles : adding file " << *i << endl; + names.push_back(*i); + } + + return names; +} + +bool +GuitarChordSelectorDialog::saveUserChordMap() +{ + // Read config for user directory + QString userDir = KGlobal::dirs()->saveLocation("appdata", "chords/"); + + QString userChordDictPath = userDir + "/user_chords.xml"; + + NOTATION_DEBUG << "GuitarChordSelectorDialog::saveUserChordMap() : saving user chord map to " << userChordDictPath << endl; + QString errMsg; + + m_chordMap.saveDocument(userChordDictPath, true, errMsg); + + return errMsg.isEmpty(); +} + + +} + + +#include "GuitarChordSelectorDialog.moc" diff --git a/src/gui/editors/guitar/GuitarChordSelectorDialog.h b/src/gui/editors/guitar/GuitarChordSelectorDialog.h new file mode 100644 index 0000000..6c8f1ad --- /dev/null +++ b/src/gui/editors/guitar/GuitarChordSelectorDialog.h @@ -0,0 +1,120 @@ +/* -*- 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_GUITARCHORDSELECTORDIALOG_H_ +#define _RG_GUITARCHORDSELECTORDIALOG_H_ + +#include "Chord.h" +#include "ChordMap.h" + +#include <kdialogbase.h> +#include <qstring.h> +#include <vector> + +class QListBox; +class QListBoxItem; +class QComboBox; +class QPushButton; + +namespace Rosegarden +{ + +class FingeringBox; + +class GuitarChordSelectorDialog : public KDialogBase +{ + Q_OBJECT + + enum { COMPLEXITY_BEGINNER, COMPLEXITY_COMMON, COMPLEXITY_ALL }; + +public: + GuitarChordSelectorDialog(QWidget *parent=0); + + void init(); + + const Guitar::Chord& getChord() const { return m_chord; } + + void setChord(const Guitar::Chord&); + +protected slots: + void slotRootHighlighted(int); + void slotChordExtHighlighted(int); + void slotFingeringHighlighted(QListBoxItem*); + void slotComplexityChanged(int); + + void slotNewFingering(); + void slotDeleteFingering(); + void slotEditFingering(); + + virtual void slotOk(); + +protected: + + void parseChordFiles(const std::vector<QString>& chordFiles); + void parseChordFile(const QString& chordFileName); + void populateFingerings(const Guitar::ChordMap::chordarray&, const Guitar::Fingering& refFingering=Guitar::Fingering(0)); + void populateExtensions(const QStringList& extList); + + /// set enabled state of edit/delete buttons + void setEditionEnabled(bool); + + void populate(); + void clear(); + void refresh(); + + bool saveUserChordMap(); + int evaluateChordComplexity(const QString& ext); + + QPixmap getFingeringPixmap(const Guitar::Fingering& fingering) const; + + /// Find all chord list files on the system + std::vector<QString> getAvailableChordFiles(); + + Guitar::ChordMap m_chordMap; + + /// current selected chord + Guitar::Chord m_chord; + + // Chord data + QListBox* m_rootNotesList; + QListBox* m_chordExtList; + QListBox* m_fingeringsList; + FingeringBox* m_fingeringBox; + + QComboBox* m_chordComplexityCombo; + QPushButton* m_newFingeringButton; + QPushButton* m_deleteFingeringButton; + QPushButton* m_editFingeringButton; + + static const unsigned int FINGERING_PIXMAP_HEIGHT = 75; + static const unsigned int FINGERING_PIXMAP_WIDTH = 75; + static const unsigned int FINGERING_PIXMAP_H_MARGIN = 5; + static const unsigned int FINGERING_PIXMAP_W_MARGIN = 5; + +}; + +} + +#endif /*_RG_GUITARCHORDSELECTORDIALOG_H_*/ diff --git a/src/gui/editors/guitar/NoteSymbols.cpp b/src/gui/editors/guitar/NoteSymbols.cpp new file mode 100644 index 0000000..14379de --- /dev/null +++ b/src/gui/editors/guitar/NoteSymbols.cpp @@ -0,0 +1,486 @@ +/* -*- 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. + + This file contains code from + 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 "NoteSymbols.h" +#include "Fingering.h" +#include "misc/Debug.h" + +namespace Rosegarden +{ + +namespace Guitar +{ +NoteSymbols::posPair +NoteSymbols::getX ( int imgWidth, unsigned int stringNb, unsigned int nbOfStrings ) const +{ + /* + std::cout << "NoteSymbols::getX - input values" << std::endl + << " position: " << position << std::endl + << " string #: " << string_num << std::endl + << " scale: " << scale << std::endl; + */ + unsigned int lBorder = getLeftBorder( imgWidth ); + unsigned int guitarChordWidth = getGuitarChordWidth( imgWidth ); + unsigned int columnWidth = guitarChordWidth / nbOfStrings; + return std::make_pair( ( stringNb * columnWidth + lBorder ), columnWidth ); +} + +NoteSymbols::posPair +NoteSymbols::getY ( int imgHeight, unsigned int fretNb, unsigned int nbOfFrets ) const +{ + /* + std::cout << "NoteSymbols::getY - input values" << std::endl + << " position: " << fret_pos << std::endl + << " max frets: " << maxFretNum << std::endl + << " scale: " << scale << std::endl; + */ + unsigned int tBorder = getTopBorder( imgHeight ); + unsigned int guitarChordHeight = getGuitarChordHeight( imgHeight ); + unsigned int rowHeight = guitarChordHeight / nbOfFrets; + return std::make_pair( ( ( fretNb * rowHeight ) + tBorder ), rowHeight ); +} + +void +NoteSymbols::drawMuteSymbol ( QPainter* p, + unsigned int position ) const +{ + QRect v = p->viewport(); + + posPair x_pos = getX ( v.width(), position, m_nbOfStrings ); + unsigned int y_pos = getTopBorder( v.height() ) / 2; + double columnWidth = x_pos.second; + unsigned int width = static_cast<unsigned int>( columnWidth * 0.7 ); + unsigned int height = static_cast<unsigned int>( columnWidth * 0.7 ); + + //std::cout << "NoteSymbols::drawMuteSymbol - drawing Mute symbol at string #" << position + //<< std::endl; + + p->drawLine ( x_pos.first - ( width / 2 ), + y_pos - ( height / 2 ), + ( x_pos.first + ( width / 2 ) ), + y_pos + ( height / 2 ) ); + + p->drawLine( x_pos.first + ( width / 2 ), + y_pos - ( height / 2 ), + ( x_pos.first - ( width / 2 ) ), + y_pos + ( height / 2 ) ); +} + +void +NoteSymbols::drawOpenSymbol ( QPainter* p, + unsigned int position ) const +{ + QRect v = p->viewport(); + posPair x_pos = getX ( v.width(), position, m_nbOfStrings ); + unsigned int y_pos = getTopBorder( v.height() ) / 2; + double columnWidth = x_pos.second; + unsigned int radius = static_cast<unsigned int>( columnWidth * 0.7 ); + + //std::cout << "NoteSymbols::drawOpenSymbol - drawing Open symbol at string #" << position + //<< std::endl; + + p->setBrush( QBrush(p->brush().color(), Qt::NoBrush) ); + p->drawEllipse( x_pos.first - ( radius / 2 ), + y_pos - ( radius / 2 ), + radius, + radius ); +} + +void +NoteSymbols::drawNoteSymbol ( QPainter* p, + unsigned int stringNb, + int fretNb, + bool transient ) const +{ +// NOTATION_DEBUG << "NoteSymbols::drawNoteSymbol - string: " << stringNb << ", fret:" << fretNb << endl; + + QRect v = p->viewport(); + posPair x_pos = getX ( v.width(), stringNb, m_nbOfStrings ); + posPair y_pos = getY ( v.height(), fretNb, m_nbOfFrets ); + double columnWidth = x_pos.second; + unsigned int radius; + + if (transient) { + radius = static_cast<unsigned int>( columnWidth /* * 0.9 */ ); + p->setBrush( QBrush(p->brush().color(), Qt::NoBrush) ); + } else { + radius = static_cast<unsigned int>( columnWidth * 0.7 ); + p->setBrush( QBrush(p->brush().color(), Qt::SolidPattern) ); + } + + int x = x_pos.first - ( radius / 2 ), + y = y_pos.first + ( (y_pos.second - radius) / 2) - y_pos.second + TOP_GUITAR_CHORD_MARGIN; + +// y = y_pos.first - (radius / 2) - y_pos.second + TOP_GUITAR_CHORD_MARGIN; + +// RG_DEBUG << "NoteSymbols::drawNoteSymbol : rect = " << QRect(x,y, radius, radius) << endl; + + p->drawEllipse( x, + y, + radius, + radius ); + +// p->save(); +// p->setPen(Qt::red); +// p->drawRect( x, y, radius, radius ); +// p->restore(); +} + +void +NoteSymbols::drawBarreSymbol ( QPainter* p, + int fretNb, + unsigned int start, + unsigned int end ) const +{ + + //std::cout << "NoteSymbols::drawBarreSymbol - start: " << start << ", end:" << end << std::endl; + + drawNoteSymbol ( p, start, fretNb ); + + if ( ( end - start ) >= 1 ) { + QRect v = p->viewport(); + posPair startXPos = getX ( v.width(), start, m_nbOfStrings ); + posPair endXPos = getX ( v.width(), end, m_nbOfStrings ); + posPair y_pos = getY ( v.height(), fretNb, m_nbOfFrets ); + double columnWidth = startXPos.second; + unsigned int thickness = static_cast<unsigned int>( columnWidth * 0.7 ); + + p->drawRect( startXPos.first, + y_pos.first + ( y_pos.second / 4 ) + TOP_GUITAR_CHORD_MARGIN, + endXPos.first - startXPos.first, + thickness ); + } + + drawNoteSymbol ( p, end, fretNb ); +} + +void +NoteSymbols::drawFretNumber ( QPainter* p, + unsigned int fret_num ) const +{ + if ( fret_num > 1 ) { + QRect v = p->viewport(); + unsigned int imgWidth = v.width(); + unsigned int imgHeight = v.height(); + + p->save(); + QFont font; + font.setPixelSize(getFontPixelSize(v.width(), v.height())); + p->setFont(font); + + QString tmp; + tmp.setNum( fret_num ); + + // Use NoteSymbols to grab X and Y for first fret + posPair y_pos = getY( imgHeight, 0, m_nbOfFrets ); + + p->drawText( getLeftBorder( imgWidth ) / 4, + y_pos.first + ( y_pos.second / 2 ), + tmp ); + + p->restore(); + } +} + +void +NoteSymbols::drawFrets ( QPainter* p ) const +{ + /* + std::cout << "NoteSymbols::drawFretHorizontalLines" << std::endl + << " scale: " << scale << std::endl + << " frets: " << fretsDisplayed << std::endl + << " max string: " << maxStringNum << std::endl; + */ + + QRect v = p->viewport(); + unsigned int imgWidth = v.width(); + unsigned int imgHeight = v.height(); + //unsigned int endXPos = getGuitarChordWidth(imgWidth) + getLeftBorder(imgWidth); + posPair endXPos = getX ( imgWidth, m_nbOfStrings - 1, m_nbOfStrings ); + + unsigned int yGuitarChord = getGuitarChordHeight( imgHeight ); + unsigned int rowHeight = yGuitarChord / m_nbOfFrets; + + QPen pen(p->pen()); + pen.setWidth(imgHeight >= 100 ? FRET_PEN_WIDTH : FRET_PEN_WIDTH / 2); + p->save(); + p->setPen(pen); + unsigned int y_pos = (getY ( imgHeight, 0, m_nbOfFrets )).first + TOP_GUITAR_CHORD_MARGIN; + +// NOTATION_DEBUG << "NoteSymbols::drawFrets : " << m_nbOfFrets << endl; + + // Horizontal lines + for ( unsigned int i = 0; i <= m_nbOfFrets; ++i ) { + + /* This code borrowed from KGuitar 0.5 */ + p->drawLine( getLeftBorder( imgWidth ), + y_pos, + endXPos.first, + y_pos); +// NOTATION_DEBUG << "NoteSymbols::drawFrets : " << QPoint(getLeftBorder(imgWidth), y_pos) +// << " to " << QPoint(endXPos.first, y_pos) << endl; + + + y_pos += rowHeight; + } + + p->restore(); + +} + +void +NoteSymbols::drawStrings ( QPainter* p ) const +{ + // Vertical lines + QRect v = p->viewport(); + int imgHeight = v.height(); + int imgWidth = v.width(); + + unsigned int startPos = getTopBorder( imgHeight ) + TOP_GUITAR_CHORD_MARGIN; + unsigned int endPos = (getY ( imgHeight, m_nbOfFrets, m_nbOfFrets )).first + TOP_GUITAR_CHORD_MARGIN; + + unsigned int guitarChordWidth = getGuitarChordWidth( imgWidth ); + unsigned int columnWidth = guitarChordWidth / m_nbOfStrings; + + unsigned int x_pos = (getX ( imgWidth, 0, m_nbOfStrings )).first; + + QPen pen(p->pen()); + pen.setWidth(imgWidth >= 100 ? STRING_PEN_WIDTH : STRING_PEN_WIDTH / 2); + p->save(); + p->setPen(pen); + + for ( unsigned int i = 0; i < m_nbOfStrings; ++i ) { + + /* This code borrowed from KGuitar 0.5 */ + p->drawLine( x_pos, + startPos, + x_pos, + endPos ); + + x_pos += columnWidth; + } + + p->restore(); + +} + +QRect NoteSymbols::getTransientNoteSymbolRect(QSize guitarChordSize, + unsigned int stringNb, + int fretNb) const +{ + posPair x_pos = getX ( guitarChordSize.width(), stringNb, m_nbOfStrings ); + posPair y_pos = getY ( guitarChordSize.height(), fretNb, m_nbOfFrets ); + double columnWidth = x_pos.second; + unsigned int radius = static_cast<unsigned int>( columnWidth /* * 0.9 */ ); + + int x = x_pos.first - ( radius / 2 ), + y = y_pos.first + ( (y_pos.second - radius) / 2) - y_pos.second + TOP_GUITAR_CHORD_MARGIN; + + return QRect(x, y, radius, radius); +} + +unsigned int +NoteSymbols::getTopBorder ( unsigned int imgHeight ) const +{ + return static_cast<unsigned int>( TOP_BORDER_PERCENTAGE * imgHeight ); +} + +unsigned int +NoteSymbols::getBottomBorder ( unsigned int imgHeight ) const +{ + return static_cast<unsigned int>( imgHeight * BOTTOM_BORDER_PERCENTAGE ); +} + +unsigned int +NoteSymbols::getLeftBorder ( unsigned int imgWidth ) const +{ + unsigned int left = static_cast<unsigned int>( imgWidth * LEFT_BORDER_PERCENTAGE ); + if ( left < 15 ) { + left = 15; + } + return left; +} + +unsigned int +NoteSymbols::getRightBorder ( unsigned int imgWidth ) const +{ + return static_cast<unsigned int>( imgWidth * RIGHT_BORDER_PERCENTAGE ); +} + +unsigned int +NoteSymbols::getGuitarChordWidth ( int imgWidth ) const +{ + return static_cast<unsigned int>( imgWidth * GUITAR_CHORD_WIDTH_PERCENTAGE ); +} + +unsigned int +NoteSymbols::getGuitarChordHeight ( int imgHeight ) const +{ + return static_cast<unsigned int>( imgHeight * GUITAR_CHORD_HEIGHT_PERCENTAGE ); +} + +unsigned int +NoteSymbols::getFontPixelSize ( int imgWidth, int imgHeight ) const +{ + return std::max(8, imgHeight / 10); +} + +std::pair<bool, unsigned int> +NoteSymbols::getStringNumber ( int imgWidth, + unsigned int x_pos, + unsigned int maxStringNum ) const +{ + /* + std::cout << "NoteSymbols::getNumberOfStrings - input values" << std::endl + << " X position: " << x_pos << std::endl + << " string #: " << maxStringNum << std::endl + << " image width: " << imgWidth << std::endl; + */ + bool valueOk = false; + + posPair xPairPos; + unsigned int min = 0; + unsigned int max = 0; + unsigned int result = 0; + + for ( unsigned int i = 0; i < maxStringNum; ++i ) { + xPairPos = getX ( imgWidth, i, maxStringNum ); + + // If the counter equals zero then we are at the first + // string to the left + if ( i == 0 ) { + // Add 10 pixel buffer to range comparison + min = xPairPos.first - 10; + } else { + min = xPairPos.first - xPairPos.second / 2; + } + + // If the counter equals the maxString number -1 then we are at the last + // string to the right + if ( i == ( maxStringNum - 1 ) ) { + // Add 10 pixel buffer to range comparison + max = xPairPos.first + 10; + } else { + max = xPairPos.first + xPairPos.second / 2; + } + + if ( ( x_pos >= min ) && ( x_pos <= max ) ) { + result = i; + valueOk = true; + break; + } + } + + //std::cout << "NoteSymbols::getNumberOfStrings - string: #" << result << std::endl; + return std::make_pair( valueOk, result ); +} + +std::pair<bool, unsigned int> +NoteSymbols::getFretNumber ( int imgHeight, + unsigned int y_pos, + unsigned int maxFretNum ) const +{ + /* + std::cout << "NoteSymbols::getNumberOfFrets - input values" << std::endl + << " Y position: " << y_pos << std::endl + << " max frets: " << maxFretNum << std::endl + << " image height: " << imgHeight << std::endl; + */ + + bool valueOk = false; + unsigned int tBorder = getTopBorder( imgHeight ); + unsigned int result = 0; + + if ( y_pos < tBorder ) { + // User pressing above the guitar chord to mark line muted or opened + valueOk = true; + } else { + typedef std::pair<unsigned int, unsigned int> RangePair; + + posPair min_pos; + posPair max_pos; + + for ( unsigned int i = 0; i < maxFretNum; ++i ) { + min_pos = getY ( imgHeight, i, maxFretNum ); + max_pos = getY ( imgHeight, i + 1, maxFretNum ); + + if ( ( y_pos >= min_pos.first ) && y_pos <= max_pos.first - 1 ) { + result = i + 1; + valueOk = true; + break; + } + } + } + // std::cout << " fret #: " << result << std::endl; + return std::make_pair( valueOk, result ); +} + +void +NoteSymbols::drawFingeringPixmap(const Guitar::Fingering& fingering, const Guitar::NoteSymbols& noteSymbols, QPainter *p) +{ + unsigned int startFret = fingering.getStartFret(); + + noteSymbols.drawFretNumber(p, startFret); + noteSymbols.drawFrets(p); + noteSymbols.drawStrings(p); + + unsigned int stringNb = 0; + + for (Fingering::const_iterator pos = fingering.begin(); + pos != fingering.end(); + ++pos, ++stringNb) { + + switch (*pos) { + case Fingering::OPEN: + noteSymbols.drawOpenSymbol(p, stringNb); + break; + + case Fingering::MUTED: + noteSymbols.drawMuteSymbol(p, stringNb); + break; + + default: + noteSymbols.drawNoteSymbol(p, stringNb, *pos - (startFret - 1), false); + break; + } + } + +} + + +float const NoteSymbols::LEFT_BORDER_PERCENTAGE = 0.2; +float const NoteSymbols::RIGHT_BORDER_PERCENTAGE = 0.1; +float const NoteSymbols::GUITAR_CHORD_WIDTH_PERCENTAGE = 0.8; +float const NoteSymbols::TOP_BORDER_PERCENTAGE = 0.1; +float const NoteSymbols::BOTTOM_BORDER_PERCENTAGE = 0.1; +float const NoteSymbols::GUITAR_CHORD_HEIGHT_PERCENTAGE = 0.8; +int const NoteSymbols::TOP_GUITAR_CHORD_MARGIN = 5; +int const NoteSymbols::FRET_PEN_WIDTH = 2; +int const NoteSymbols::STRING_PEN_WIDTH = 2; + +} /* namespace Guitar */ + +} + diff --git a/src/gui/editors/guitar/NoteSymbols.h b/src/gui/editors/guitar/NoteSymbols.h new file mode 100644 index 0000000..f90fefb --- /dev/null +++ b/src/gui/editors/guitar/NoteSymbols.h @@ -0,0 +1,192 @@ +/* -*- 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. + + This file contains code from + 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_SYMBOLS_H_ +#define _RG_SYMBOLS_H_ + +#include <qbrush.h> +#include <qpainter.h> + +namespace Rosegarden +{ + +/** + *---------------------------------------- + * Finding X position on guitar chord pixmap + *---------------------------------------- + * + * Originally x = position * scale + FC::BORDER + FC::CIRCBORD + FC::FRETTEXT + * + * The last three can be condense into on term called XBorder + * XBorder = FC::BORDER + FC::CIRCBORD + FC::FRETTEXT + * = 5 + 2 + 10 (see fingers.h) + * = 17 + * + * The drawable guitar chord space on the x-axis: + * XGuitarChord = pixmap width - XBorder + * = width - 17 + * + * The guitar chord x-axis is broken up into colums which represent the drawable + * space for a guitar chord component (e.g. note, barre) + * Column Width = XGuitarChord / number of strings + * + * Therefore a new x can be calculated from the position and the column width + * x = (position * Column Width) + XBorder + * + *------------------------------------------- + * Finding Y position on guitar chord pixmap + *------------------------------------------- + * + * Originally y = (FC::BORDER * scale) + (2 * FC::SPACER) + (fret * scale) + FC::CIRCBORD + * + * As with the x-axis the equation can be separated into the position plus the border. In + * this case YBorder + * YBorder = (FC::BORDER*scale) + (2*FC::SPACER) + FC::CIRCBORD + * = 17 (If we want to use the same border as the x-axis) + * + * The drawable guitar chord space on the y-axis: + * YGuitarChord = pixmap height - YBorder + * + * The guitar chord y-axis is broken up into rows which represent the drawable + * space for a guitar chord component (e.g. note, barre) + * Row Height = YGuitarChord / number of frets + * + * Therefore a new y can be calculated from the fret position and the row height + * y = fret * Row Height + **/ + +namespace Guitar +{ + +class Fingering; + + +class NoteSymbols +{ +private: + typedef std::pair<unsigned int, unsigned int> posPair; + + static float const LEFT_BORDER_PERCENTAGE; + static float const RIGHT_BORDER_PERCENTAGE; + static float const GUITAR_CHORD_WIDTH_PERCENTAGE; + static float const TOP_BORDER_PERCENTAGE; + static float const BOTTOM_BORDER_PERCENTAGE; + static float const GUITAR_CHORD_HEIGHT_PERCENTAGE; + static int const TOP_GUITAR_CHORD_MARGIN; + static int const FRET_PEN_WIDTH; + static int const STRING_PEN_WIDTH; + +public: + + NoteSymbols(unsigned int nbOfStrings, unsigned int nbOfFrets) : + m_nbOfStrings(nbOfStrings), + m_nbOfFrets(nbOfFrets) {}; + + //! Display a mute symbol in the QPainter object + void + drawMuteSymbol ( QPainter* p, + unsigned int position ) const; + + /* This code borrowed from KGuitar 0.5 */ + //! Display a open symbol in the QPainter object (KGuitar) + void drawOpenSymbol ( QPainter* p, + unsigned int position ) const; + + /* This code borrowed from KGuitar 0.5 */ + //! Display a note symbol in the QPainter object (KGuitar) + void drawNoteSymbol ( QPainter* p, + unsigned int stringNb, + int fretNb, + bool transient = false ) const; + + /* This code borrowed from KGuitar 0.5 */ + /** + * Display a bar symbol in the QPainter object (KGuitar) + * The code from the KGuitar project was modified to display a bar. This feature was not + * available in that project + */ + void drawBarreSymbol ( QPainter* p, + int fretNb, + unsigned int start, + unsigned int end ) const; + + void drawFretNumber ( QPainter* p, + unsigned int fret_num ) const; + + void drawFrets ( QPainter* p ) const; + + void drawStrings ( QPainter* p ) const; + + unsigned int getTopBorder ( unsigned int imgHeight ) const; + + unsigned int getBottomBorder ( unsigned int imgHeight ) const; + + unsigned int getLeftBorder ( unsigned int imgWidth ) const; + + unsigned int getRightBorder ( unsigned int imgWidth ) const; + + unsigned int getGuitarChordWidth ( int imgWidth ) const; + + unsigned int getGuitarChordHeight ( int imgHeight ) const; + + unsigned int getFontPixelSize ( int imgWidth, int imgHeight ) const; + + std::pair<bool, unsigned int> + getStringNumber ( int imgWidth, + unsigned int x_pos, + unsigned int string_num ) const; + + std::pair<bool, unsigned int> + getFretNumber ( int imgHeight, + unsigned int y_pos, + unsigned int maxFretNum ) const; + + QRect getTransientNoteSymbolRect(QSize guitarChordSize, + unsigned int stringNb, + int fretNb) const; + + static void drawFingeringPixmap(const Fingering& fingering, const NoteSymbols& noteSymbols, QPainter *p); + +private: + + posPair + getX ( int imgWidth, unsigned int stringNb, unsigned int nbOfStrings ) const; + + posPair + getY ( int imgHeight, unsigned int fretNb, unsigned int nbOfFrets ) const; + + + unsigned int m_nbOfStrings; + unsigned int m_nbOfFrets; + +}; + +} /* namespace Guitar */ + +} + +#endif /* SYMBOLS_H_ */ + diff --git a/src/gui/editors/matrix/MatrixCanvasView.cpp b/src/gui/editors/matrix/MatrixCanvasView.cpp new file mode 100644 index 0000000..c92b4aa --- /dev/null +++ b/src/gui/editors/matrix/MatrixCanvasView.cpp @@ -0,0 +1,302 @@ +/* -*- 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 "MatrixCanvasView.h" + +#include "base/SnapGrid.h" +#include "gui/general/MidiPitchLabel.h" +#include "gui/general/RosegardenCanvasView.h" +#include "MatrixElement.h" +#include "MatrixStaff.h" +#include "QCanvasMatrixRectangle.h" +#include "QCanvasMatrixDiamond.h" +#include <qcanvas.h> +#include <qpoint.h> +#include <qwidget.h> +#include "misc/Debug.h" + + + +namespace Rosegarden +{ + +MatrixCanvasView::MatrixCanvasView(MatrixStaff& staff, + SnapGrid *snapGrid, + bool drumMode, + QCanvas *viewing, QWidget *parent, + const char *name, WFlags f) + : RosegardenCanvasView(viewing, parent, name, f), + m_staff(staff), + m_snapGrid(snapGrid), + m_drumMode(drumMode), + m_previousEvTime(0), + m_previousEvPitch(0), + m_mouseWasPressed(false), + m_ignoreClick(false), + m_smoothModifier(Qt::ShiftButton), + m_lastSnap(SnapGrid::SnapToBeat), + m_isSnapTemporary(false) +{ + viewport()->setMouseTracking(true); +} + +MatrixCanvasView::~MatrixCanvasView() +{} + +void MatrixCanvasView::contentsMousePressEvent(QMouseEvent* e) +{ + QPoint p = inverseMapPoint(e->pos()); + + updateGridSnap(e); + + MATRIX_DEBUG << "MatrixCanvasView::contentsMousePressEvent: snap time is " << m_snapGrid->getSnapTime(double(p.x())) << endl; + + timeT evTime; + + if (m_drumMode) { + evTime = m_snapGrid->snapX(p.x(), SnapGrid::SnapEither); + MATRIX_DEBUG << "MatrixCanvasView: drum mode: snapEither " << p.x() << " -> " << evTime << endl; + } else { + evTime = m_snapGrid->snapX(p.x(), SnapGrid::SnapLeft); + MATRIX_DEBUG << "MatrixCanvasView: normal mode: snapLeft " << p.x() << " -> " << evTime << endl; + } + + int evPitch = m_staff.getHeightAtCanvasCoords(p.x(), p.y()); + + timeT emTime = m_staff.getSegment().getEndMarkerTime(); + if (evTime > emTime) + evTime = emTime; + timeT esTime = m_staff.getSegment().getStartTime(); + if (evTime < esTime) + evTime = esTime; + +// std::cerr << "MatrixCanvasView::contentsMousePressEvent() at pitch " +// << evPitch << ", time " << evTime << std::endl; + + QCanvasItemList itemList = canvas()->collisions(p); + QCanvasItemList::Iterator it; + MatrixElement* mel = 0; + QCanvasItem* activeItem = 0; + + for (it = itemList.begin(); it != itemList.end(); ++it) { + + QCanvasItem *item = *it; + + QCanvasMatrixRectangle *mRect = 0; + + if (item->active()) { + activeItem = item; + break; + } + + if ((mRect = dynamic_cast<QCanvasMatrixRectangle*>(item))) { + +// std::cerr << "MatrixCanvasView: looking at element with rect " << mRect->rect().x() << "," << mRect->rect().y() << " (" << mRect->rect().width() << "x" << mRect->rect().height() << ")" << std::endl; + +// std::cerr << "MatrixCanvasView: point is " << p.x() << "," << p.y()<< std::endl; + + QRect rect = mRect->rect(); + if (dynamic_cast<QCanvasMatrixDiamond*>(mRect)) { + rect = QRect(rect.x() - rect.height()/2, + rect.y(), + rect.width(), + rect.height()); + } + +// std::cerr << "MatrixCanvasView: adjusted rect " << rect.x() << "," << rect.y() << " (" << rect.width() << "x" << rect.height() << ")" << std::endl; + + // QCanvas::collisions() can be a bit optimistic and report + // items which are close to the point but not actually under it. + // So a little sanity check helps. + if (!rect.contains(p, true)) continue; + + mel = &(mRect->getMatrixElement()); +// std::cerr << "MatrixCanvasView::contentsMousePressEvent: collision with an existing matrix element" << std::endl; + break; + } + } + + if (activeItem) { // active item takes precedence over notation elements + emit activeItemPressed(e, activeItem); + m_mouseWasPressed = true; + return ; + } + + emit mousePressed(evTime, evPitch, e, mel); + m_mouseWasPressed = true; + + // Ignore click if it was above the staff and not + // on an active item + // + if (!m_staff.containsCanvasCoords(p.x(), p.y()) && !activeItem) + m_ignoreClick = true; +} + +void MatrixCanvasView::contentsMouseMoveEvent(QMouseEvent* e) +{ + QPoint p = inverseMapPoint(e->pos()); + /* + if (m_snapGrid->getSnapTime(double(p.x()))) + m_lastSnap = m_snapGrid->getSnapTime(double(p.x())); + */ + updateGridSnap(e); + + if (m_ignoreClick) + return ; + + timeT evTime; + if (m_drumMode) { + evTime = m_snapGrid->snapX(p.x(), SnapGrid::SnapEither); + } else { + evTime = m_snapGrid->snapX(p.x(), SnapGrid::SnapLeft); + } + + int evPitch = m_staff.getHeightAtCanvasCoords(p.x(), p.y()); + + timeT emTime = m_staff.getSegment().getEndMarkerTime(); + if (evTime > emTime) + evTime = emTime; + + timeT stTime = m_staff.getSegment().getStartTime(); + if (evTime < stTime) + evTime = stTime; + + if (evTime != m_previousEvTime) { + emit hoveredOverAbsoluteTimeChanged(evTime); + m_previousEvTime = evTime; + } + + QCanvasItemList itemList = canvas()->collisions(p); + MatrixElement* mel = 0; + + for (QCanvasItemList::iterator it = itemList.begin(); + it != itemList.end(); ++it) { + + QCanvasItem *item = *it; + QCanvasMatrixRectangle *mRect = 0; + + if ((mRect = dynamic_cast<QCanvasMatrixRectangle*>(item))) { + if (!mRect->rect().contains(p, true)) + continue; + mel = &(mRect->getMatrixElement()); + MATRIX_DEBUG << "have element" << endl; + break; + } + } + + if (!m_mouseWasPressed && // if mouse pressed, leave this to the tool + (evPitch != m_previousEvPitch || mel)) { + MidiPitchLabel label(evPitch); + if (mel) { + emit hoveredOverNoteChanged(evPitch, true, + mel->event()->getAbsoluteTime()); + } else { + emit hoveredOverNoteChanged(evPitch, false, 0); + } + m_previousEvPitch = evPitch; + } + +// if (m_mouseWasPressed) + emit mouseMoved(evTime, evPitch, e); + +} + +void MatrixCanvasView::contentsMouseDoubleClickEvent (QMouseEvent* e) +{ + QPoint p = inverseMapPoint(e->pos()); + + if (!m_staff.containsCanvasCoords(p.x(), p.y())) { + m_ignoreClick = true; + return ; + } + + contentsMousePressEvent(e); +} + +void MatrixCanvasView::contentsMouseReleaseEvent(QMouseEvent* e) +{ + QPoint p = inverseMapPoint(e->pos()); + + if (m_ignoreClick) { + m_ignoreClick = false; + return ; + } + + timeT evTime; + if (m_drumMode) { + evTime = m_snapGrid->snapX(p.x(), SnapGrid::SnapEither); + } else { + evTime = m_snapGrid->snapX(p.x(), SnapGrid::SnapLeft); + } + + int evPitch = m_staff.getHeightAtCanvasCoords(p.x(), p.y()); + + timeT emTime = m_staff.getSegment().getEndMarkerTime(); + if (evTime > emTime) + evTime = emTime; + + emit mouseReleased(evTime, evPitch, e); + m_mouseWasPressed = false; +} + +void MatrixCanvasView::slotExternalWheelEvent(QWheelEvent* e) +{ + wheelEvent(e); +} + +void MatrixCanvasView::updateGridSnap(QMouseEvent *e) +{ + Qt::ButtonState bs = e->state(); + + // MATRIX_DEBUG << "MatrixCanvasView::updateGridSnap : bs = " + // << bs << " - sm = " << getSmoothModifier() << ", is temporary " << m_isSnapTemporary << ", saved is " << m_lastSnap << endl; + + if (bs & getSmoothModifier()) { + + if (!m_isSnapTemporary) { + m_lastSnap = m_snapGrid->getSnapSetting(); + } + m_snapGrid->setSnapTime(SnapGrid::NoSnap); + m_isSnapTemporary = true; + + } else if (m_isSnapTemporary) { + + m_snapGrid->setSnapTime(m_lastSnap); + m_isSnapTemporary = false; + } +} + +void MatrixCanvasView::enterEvent(QEvent *e) +{ + emit mouseEntered(); +} + +void MatrixCanvasView::leaveEvent(QEvent *e) +{ + emit mouseLeft(); +} + +} +#include "MatrixCanvasView.moc" diff --git a/src/gui/editors/matrix/MatrixCanvasView.h b/src/gui/editors/matrix/MatrixCanvasView.h new file mode 100644 index 0000000..2ec4c7e --- /dev/null +++ b/src/gui/editors/matrix/MatrixCanvasView.h @@ -0,0 +1,162 @@ + +/* -*- 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_MATRIXCANVASVIEW_H_ +#define _RG_MATRIXCANVASVIEW_H_ + +#include "gui/general/RosegardenCanvasView.h" +#include "base/Event.h" + + +class QWidget; +class QWheelEvent; +class QMouseEvent; +class QCanvasItem; +class QCanvas; + + +namespace Rosegarden +{ + +class SnapGrid; +class MatrixStaff; +class MatrixElement; + + +class MatrixCanvasView : public RosegardenCanvasView +{ + Q_OBJECT + +public: + MatrixCanvasView(MatrixStaff&, + SnapGrid *, + bool drumMode, + QCanvas *viewing, + QWidget *parent=0, const char *name=0, WFlags f=0); + + ~MatrixCanvasView(); + + void setSmoothModifier(Qt::ButtonState s) { m_smoothModifier = s; } + Qt::ButtonState getSmoothModifier() { return m_smoothModifier; } + +signals: + + /** + * Emitted when the user clicks on a QCanvasItem which is active + * + * @see QCanvasItem#setActive + */ + void activeItemPressed(QMouseEvent*, + QCanvasItem* item); + + /** + * Emitted when the mouse cursor moves to a different height + * on the staff. Returns the new pitch. + */ + void hoveredOverNoteChanged(int evPitch, bool haveEvent, + timeT evTime); + + /** + * 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); + + void mousePressed(timeT time, int pitch, + QMouseEvent*, MatrixElement*); + + void mouseMoved(timeT time, int pitch, QMouseEvent*); + + void mouseReleased(timeT time, int pitch, QMouseEvent*); + + void mouseEntered(); + void mouseLeft(); + +public slots: + void slotExternalWheelEvent(QWheelEvent*); + +protected: + /** + * Callback for a mouse button press event in the canvas + */ + virtual void contentsMousePressEvent(QMouseEvent*); + + /** + * Callback for a mouse move event in the canvas + */ + virtual void contentsMouseMoveEvent(QMouseEvent*); + + /** + * Callback for a mouse button release event in the canvas + */ + virtual void contentsMouseReleaseEvent(QMouseEvent*); + + /** + * Callback for a mouse double-click event in the canvas + * + * NOTE: a double click event is always preceded by a mouse press + * event + */ + virtual void contentsMouseDoubleClickEvent(QMouseEvent*); + + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); + + /** + * Update the value of snap grid according to the button's state + * + * If the button was pressed with the 'smooth' modifier, set the + * grid so it won't snap time. + * + * @see #setSmoothModifier + * @see #getSmoothModifier + */ + void updateGridSnap(QMouseEvent *e); + + //--------------- Data members --------------------------------- + + MatrixStaff &m_staff; + SnapGrid *m_snapGrid; + bool m_drumMode; + + timeT m_previousEvTime; + int m_previousEvPitch; + + bool m_mouseWasPressed; + bool m_ignoreClick; + + Qt::ButtonState m_smoothModifier; + timeT m_lastSnap; + bool m_isSnapTemporary; +}; + + + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixElement.cpp b/src/gui/editors/matrix/MatrixElement.cpp new file mode 100644 index 0000000..1101284 --- /dev/null +++ b/src/gui/editors/matrix/MatrixElement.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]> + + 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 "MatrixElement.h" +#include "misc/Debug.h" + +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/ViewElement.h" +#include "gui/general/GUIPalette.h" +#include "QCanvasMatrixDiamond.h" +#include "QCanvasMatrixRectangle.h" +#include <qbrush.h> +#include <qcanvas.h> +#include <qcolor.h> + + +namespace Rosegarden +{ + +MatrixElement::MatrixElement(Event *event, bool drum) : + ViewElement(event), + m_canvasRect(drum ? + new QCanvasMatrixDiamond(*this, 0) : + new QCanvasMatrixRectangle(*this, 0)), + m_overlapRectangles(NULL) +{ + // MATRIX_DEBUG << "new MatrixElement " + // << this << " wrapping " << event << endl; +} + +MatrixElement::~MatrixElement() +{ + // MATRIX_DEBUG << "MatrixElement " << this << "::~MatrixElement() wrapping " + // << event() << endl; + + m_canvasRect->hide(); + delete m_canvasRect; + + removeOverlapRectangles(); +} + +void MatrixElement::setCanvas(QCanvas* c) +{ + if (!m_canvasRect->canvas()) { + + m_canvasRect->setCanvas(c); + + // We set this by velocity now (matrixstaff.cpp) + // + //m_canvasRect->setBrush(RosegardenGUIColours::MatrixElementBlock); + + m_canvasRect->setPen(GUIPalette::getColour(GUIPalette::MatrixElementBorder)); + m_canvasRect->show(); + } +} + +bool MatrixElement::isNote() const +{ + return event()->isa(Note::EventType); +} + +void MatrixElement::drawOverlapRectangles() +{ + if (m_overlapRectangles) removeOverlapRectangles(); + + QRect elRect = m_canvasRect->rect(); + QCanvasItemList + itemList = m_canvasRect->canvas()->collisions(elRect); + QCanvasItemList::Iterator it; + MatrixElement* mel = 0; + + + for (it = itemList.begin(); it != itemList.end(); ++it) { + + QCanvasMatrixRectangle *mRect = 0; + if ((mRect = dynamic_cast<QCanvasMatrixRectangle*>(*it))) { + + // Element does'nt collide with itself + if (mRect == m_canvasRect) continue; + + QRect rect = mRect->rect() & elRect; + if (!rect.isEmpty()) { + if (!m_overlapRectangles) { + m_overlapRectangles = new OverlapRectangles(); + } + + QCanvasRectangle * + overlap = new QCanvasRectangle(rect, m_canvasRect->canvas()); + overlap->setBrush(GUIPalette::getColour(GUIPalette::MatrixOverlapBlock)); + overlap->setZ(getCanvasZ() + 1); + overlap->show(); + m_overlapRectangles->push_back(overlap); + } + } + } +} + +void MatrixElement::redrawOverlaps(QRect rect) +{ + QCanvasItemList + itemList = m_canvasRect->canvas()->collisions(rect); + QCanvasItemList::Iterator it; + MatrixElement* mel = 0; + + for (it = itemList.begin(); it != itemList.end(); ++it) { + QCanvasMatrixRectangle *mRect = 0; + if ((mRect = dynamic_cast<QCanvasMatrixRectangle*>(*it))) { + mRect->getMatrixElement().drawOverlapRectangles(); + } + } +} + +void MatrixElement::removeOverlapRectangles() +{ + if (!m_overlapRectangles) return; + + OverlapRectangles::iterator it; + for (it = m_overlapRectangles->begin(); it != m_overlapRectangles->end(); ++it) { + (*it)->hide(); + delete *it; + } + + delete m_overlapRectangles; + m_overlapRectangles = NULL; +} + +bool MatrixElement::getVisibleRectangle(QRect &rectangle) +{ + if (m_canvasRect && m_canvasRect->isVisible()) { + rectangle = m_canvasRect->rect(); + return true; + } + return false; +} + + +} diff --git a/src/gui/editors/matrix/MatrixElement.h b/src/gui/editors/matrix/MatrixElement.h new file mode 100644 index 0000000..d330991 --- /dev/null +++ b/src/gui/editors/matrix/MatrixElement.h @@ -0,0 +1,138 @@ + +/* -*- 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_MATRIXELEMENT_H_ +#define _RG_MATRIXELEMENT_H_ + +#include "base/ViewElement.h" +#include <qbrush.h> +#include <qcanvas.h> +#include "QCanvasMatrixRectangle.h" + +class QColor; + + +namespace Rosegarden +{ + +class Event; + +class MatrixElement : public ViewElement +{ + + typedef std::vector <QCanvasRectangle *> OverlapRectangles; + + +public: + MatrixElement(Event *event, bool drum); + + virtual ~MatrixElement(); + + void setCanvas(QCanvas* c); + + /** + * Returns the actual x coordinate of the element on the canvas + */ + double getCanvasX() const { return m_canvasRect->x(); } + + /** + * Returns the actual y coordinate of the element on the canvas + */ + double getCanvasY() const { return m_canvasRect->y(); } + + double getCanvasZ() const { return m_canvasRect->z(); } + + /** + * Sets the x coordinate of the element on the canvas + */ + void setCanvasX(double x) { m_canvasRect->setX(x); } + + /** + * Sets the y coordinate of the element on the canvas + */ + void setCanvasY(double y) { m_canvasRect->setY(y); } + + void setCanvasZ(double z) { m_canvasRect->setZ(z); } + + /** + * Sets the width of the rectangle on the canvas + */ + void setWidth(int w) { m_canvasRect->setSize(w, m_canvasRect->height()); } + int getWidth() { return m_canvasRect->width(); } + + /** + * Sets the height of the rectangle on the canvas + */ + void setHeight(int h) { m_canvasRect->setSize(m_canvasRect->width(), h); } + int getHeight() { return m_canvasRect->height(); } + + /// Returns true if the wrapped event is a note + bool isNote() const; + + /* + * Set the colour of the element + */ + void setColour(const QColor &colour) + { m_canvasRect->setBrush(QBrush(colour)); } + + /** + * Draws overlap rectangles (if any) + * (should not be called in drum mode) + */ + void drawOverlapRectangles(); + + /** + * Removes overlap rectangles if any + */ + void removeOverlapRectangles(); + + /** + * If element rectangle is currently visible gets its size and returns true. + * Returns false if element rectangle is undefined or not visible. + */ + bool getVisibleRectangle(QRect &rectangle); + + /** + * Redraw overlap rectangles of all matrix elements colliding with rect + */ + void redrawOverlaps(QRect rect); + +protected: + + //--------------- Data members --------------------------------- + + QCanvasMatrixRectangle *m_canvasRect; + + OverlapRectangles *m_overlapRectangles; + +}; + + +typedef ViewElementList MatrixElementList; + + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixEraser.cpp b/src/gui/editors/matrix/MatrixEraser.cpp new file mode 100644 index 0000000..6c2373e --- /dev/null +++ b/src/gui/editors/matrix/MatrixEraser.cpp @@ -0,0 +1,110 @@ +/* -*- 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 "MatrixEraser.h" +#include "misc/Debug.h" + +#include <klocale.h> +#include <kstddirs.h> +#include "base/ViewElement.h" +#include "commands/matrix/MatrixEraseCommand.h" +#include "gui/general/EditTool.h" +#include "MatrixStaff.h" +#include "MatrixTool.h" +#include "MatrixView.h" +#include <kaction.h> +#include <kglobal.h> +#include <qiconset.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +MatrixEraser::MatrixEraser(MatrixView* parent) + : MatrixTool("MatrixEraser", parent), + m_currentStaff(0) +{ + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QCanvasPixmap pixmap(pixmapDir + "/toolbar/select.xpm"); + QIconSet icon = QIconSet(pixmap); + + new KAction(i18n("Switch to Select Tool"), icon, 0, this, + SLOT(slotSelectSelected()), actionCollection(), + "select"); + + new KAction(i18n("Switch to Draw Tool"), "pencil", 0, this, + SLOT(slotDrawSelected()), actionCollection(), + "draw"); + + new KAction(i18n("Switch to Move Tool"), "move", 0, this, + SLOT(slotMoveSelected()), actionCollection(), + "move"); + + pixmap.load(pixmapDir + "/toolbar/resize.xpm"); + icon = QIconSet(pixmap); + new KAction(i18n("Switch to Resize Tool"), icon, 0, this, + SLOT(slotResizeSelected()), actionCollection(), + "resize"); + + createMenu("matrixeraser.rc"); +} + +void MatrixEraser::handleLeftButtonPress(timeT, + int, + int staffNo, + QMouseEvent*, + ViewElement* el) +{ + MATRIX_DEBUG << "MatrixEraser::handleLeftButtonPress : el = " + << el << endl; + + if (!el) + return ; // nothing to erase + + m_currentStaff = m_mParentView->getStaff(staffNo); + + MatrixEraseCommand* command = + new MatrixEraseCommand(m_currentStaff->getSegment(), el->event()); + + m_mParentView->addCommandToHistory(command); + + m_mParentView->update(); +} + +void MatrixEraser::ready() +{ + m_mParentView->setCanvasCursor(Qt::pointingHandCursor); + setBasicContextHelp(); +} + +void MatrixEraser::setBasicContextHelp() +{ + setContextHelp(i18n("Click on a note to delete it")); +} + +const QString MatrixEraser::ToolName = "eraser"; + +} diff --git a/src/gui/editors/matrix/MatrixEraser.h b/src/gui/editors/matrix/MatrixEraser.h new file mode 100644 index 0000000..4e3d65f --- /dev/null +++ b/src/gui/editors/matrix/MatrixEraser.h @@ -0,0 +1,69 @@ + +/* -*- 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_MATRIXERASER_H_ +#define _RG_MATRIXERASER_H_ + +#include "MatrixTool.h" +#include <qstring.h> + +class QMouseEvent; + + +namespace Rosegarden +{ + +class ViewElement; +class MatrixView; +class MatrixStaff; + + +class MatrixEraser : public MatrixTool +{ + friend class MatrixToolBox; + +public: + + virtual void handleLeftButtonPress(timeT, + int height, + int staffNo, + QMouseEvent *event, + ViewElement*); + + static const QString ToolName; + + virtual void ready(); + +protected: + MatrixEraser(MatrixView*); + + void setBasicContextHelp(); + + MatrixStaff* m_currentStaff; +}; + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixHLayout.cpp b/src/gui/editors/matrix/MatrixHLayout.cpp new file mode 100644 index 0000000..99b89c2 --- /dev/null +++ b/src/gui/editors/matrix/MatrixHLayout.cpp @@ -0,0 +1,220 @@ +/* -*- 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 "MatrixHLayout.h" +#include "MatrixElement.h" +#include "misc/Debug.h" + +#include "base/Composition.h" +#include "base/LayoutEngine.h" +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "base/Segment.h" +#include "base/Staff.h" +#include "MatrixStaff.h" + +#include <cmath> + + +namespace Rosegarden +{ + +MatrixHLayout::MatrixHLayout(Composition *c) : + HorizontalLayoutEngine(c), + m_totalWidth(0.0), + m_firstBar(0) +{} + +MatrixHLayout::~MatrixHLayout() +{} + +void MatrixHLayout::reset() +{} + +void MatrixHLayout::resetStaff(Staff&, timeT, timeT) +{} + +void MatrixHLayout::scanStaff(Staff &staffBase, + timeT startTime, timeT endTime) +{ + Profiler profiler("MatrixHLayout::scanStaff", true); + + // The Matrix layout is not currently designed to be able to lay + // out more than one staff, because we have no requirement to show + // more than one at once in the Matrix view. To make it work for + // multiple staffs should be straightforward; we just need to bear + // in mind that they might start and end at different times (hence + // the total width and bar list can't just be calculated from the + // last staff scanned as they are now). + + MatrixStaff &staff = static_cast<MatrixStaff &>(staffBase); + bool isFullScan = (startTime == endTime); + + MatrixElementList *notes = staff.getViewElementList(); + MatrixElementList::iterator startItr = notes->begin(); + MatrixElementList::iterator endItr = notes->end(); + + if (!isFullScan) { + startItr = notes->findNearestTime(startTime); + if (startItr == notes->end()) + startItr = notes->begin(); + endItr = notes->findTime(endTime); + } + + if (endItr == notes->end() && startItr == notes->begin()) { + isFullScan = true; + } + + // Do this in two parts: bar lines separately from elements. + // (We don't need to do all that stuff notationhlayout has to do, + // scanning the notes bar-by-bar; we can just place the bar lines + // in the theoretically-correct places and do the same with the + // notes quite independently.) + + Segment &segment = staff.getSegment(); + Composition *composition = segment.getComposition(); + m_firstBar = composition->getBarNumber(segment.getStartTime()); + timeT from = composition->getBarStart(m_firstBar), + to = composition->getBarEndForTime(segment.getEndMarkerTime()); + + double startPosition = from; + + // 1. Bar lines and time signatures. We only re-make these on + // full scans. + + if (isFullScan || m_barData.size() == 0) { + + m_barData.clear(); + int barNo = m_firstBar; + + MATRIX_DEBUG << "MatrixHLayout::scanStaff() : start time = " << startTime << ", first bar = " << m_firstBar << ", end time = " << endTime << ", end marker time = " << segment.getEndMarkerTime() << ", from = " << from << ", to = " << to << endl; + + // hack for partial bars + // + timeT adjTo = to; + + if (composition->getBarStartForTime(segment.getEndMarkerTime()) + != segment.getEndMarkerTime()) + adjTo++; + + while (from < adjTo) { + + bool isNew = false; + TimeSignature timeSig = + composition->getTimeSignatureInBar(barNo, isNew); + + if (isNew || barNo == m_firstBar) { + m_barData.push_back(BarData((from - startPosition) * + staff.getTimeScaleFactor(), + TimeSigData(true, timeSig))); + } else { + m_barData.push_back(BarData((from - startPosition) * + staff.getTimeScaleFactor(), + TimeSigData(false, timeSig))); + } + + from = composition->getBarEndForTime(from); + ++barNo; + } + + m_barData.push_back(BarData(to * staff.getTimeScaleFactor(), + TimeSigData(false, TimeSignature()))); + } + + // 2. Elements + + m_totalWidth = 0.0; + MatrixElementList::iterator i = startItr; + + while (i != endItr) { + + (*i)->setLayoutX(((*i)->getViewAbsoluteTime() - startPosition) + * staff.getTimeScaleFactor()); + + double width = (*i)->getViewDuration() * staff.getTimeScaleFactor(); + + // Make sure that very small elements can still be seen + // + if (width < 3) width = 3; + else width += 1; // fiddle factor + + static_cast<MatrixElement*>((*i))->setWidth(lrint(width)); + + if (isFullScan) { + m_totalWidth = (*i)->getLayoutX() + width; + } else { + m_totalWidth = std::max(m_totalWidth, (*i)->getLayoutX() + width); + } + + ++i; + } +} + +double MatrixHLayout::getTotalWidth() const +{ + return m_totalWidth; +} + +int MatrixHLayout::getFirstVisibleBar() const +{ + return m_firstBar; +} + +int MatrixHLayout::getLastVisibleBar() const +{ + int barNo = m_firstBar + m_barData.size() - 2; + if (barNo < m_firstBar + 1) + barNo = m_firstBar + 1; + + return barNo; +} + +double MatrixHLayout::getBarPosition(int barNo) const +{ + if (barNo < getFirstVisibleBar()) { + return getBarPosition(getFirstVisibleBar()); + } + + if (barNo > getLastVisibleBar()) { + return getBarPosition(getLastVisibleBar()); + } + + return m_barData[barNo - m_firstBar].first; +} + +bool MatrixHLayout::getTimeSignaturePosition(Staff &, + int barNo, + TimeSignature &timeSig, + double &timeSigX) +{ + timeSig = m_barData[barNo - m_firstBar].second.second; + timeSigX = m_barData[barNo - m_firstBar].first; + return m_barData[barNo - m_firstBar].second.first; +} + +void MatrixHLayout::finishLayout(timeT, timeT) +{} + +} diff --git a/src/gui/editors/matrix/MatrixHLayout.h b/src/gui/editors/matrix/MatrixHLayout.h new file mode 100644 index 0000000..76f1b31 --- /dev/null +++ b/src/gui/editors/matrix/MatrixHLayout.h @@ -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. +*/ + +#ifndef _RG_MATRIXHLAYOUT_H_ +#define _RG_MATRIXHLAYOUT_H_ + +#include "base/FastVector.h" +#include "base/LayoutEngine.h" +#include <utility> +#include "base/Event.h" + +#include "gui/general/HZoomable.h" + + + +namespace Rosegarden +{ + +class TimeSignature; +class Staff; +class Composition; + + +class MatrixHLayout : public HorizontalLayoutEngine +{ +public: + MatrixHLayout(Composition *c); + virtual ~MatrixHLayout(); + + /** + * Resets internal data stores for all staffs + */ + virtual void reset(); + + /** + * Resets internal data stores for a specific staff + */ + virtual void resetStaff(Staff &staff, + timeT = 0, + timeT = 0); + + /** + * 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 + */ + virtual double getTotalWidth() const; + + /** + * Returns the number of the first visible bar line + */ + virtual int getFirstVisibleBar() const; + + /** + * Returns the number of the first visible bar line + */ + virtual int getLastVisibleBar() const; + + /** + * Returns the x-coordinate of the given bar number + */ + virtual double getBarPosition(int barNo) const; + + /** + * Precomputes layout data for a single staff, updating any + * internal data stores associated with that staff and updating + * any layout-related properties in the events on the staff's + * segment. + */ + virtual void scanStaff(Staff&, + timeT = 0, + timeT = 0); + + /** + * Computes any layout data that may depend on the results of + * scanning more than one staff. This may mean doing most of + * the layout (likely for horizontal layout) or nothing at all + * (likely for vertical layout). + */ + virtual void finishLayout(timeT = 0, + timeT = 0); + + /** + * 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); + +protected: + + //--------------- Data members --------------------------------- + + // pair of has-time-sig and time-sig + typedef std::pair<bool, TimeSignature> TimeSigData; + // pair of layout-x and time-signature if there is one + typedef std::pair<double, TimeSigData> BarData; + typedef FastVector<BarData> BarDataList; + BarDataList m_barData; + double m_totalWidth; + int m_firstBar; +}; + +/** + * "zoomable" version of the above, used in the MatrixView + * to properly scale Tempo and Chord rulers. + * + */ +class ZoomableMatrixHLayoutRulerScale : public RulerScale, public HZoomable { +public: + ZoomableMatrixHLayoutRulerScale(MatrixHLayout& layout) : RulerScale(layout.getComposition()), m_referenceHLayout(layout) {}; + + virtual double getBarPosition(int n) const { return m_referenceHLayout.getBarPosition(n) * getHScaleFactor(); } + virtual double getXForTime(timeT time) const { return m_referenceHLayout.getXForTime(time) * getHScaleFactor(); } + virtual timeT getTimeForX(double x) const { return m_referenceHLayout.getTimeForX(x / getHScaleFactor()); } + virtual double getBarWidth(int n) const { return m_referenceHLayout.getBarWidth(n) * getHScaleFactor(); } + virtual int getLastVisibleBar() const { return m_referenceHLayout.getLastVisibleBar(); } + +protected: + MatrixHLayout& m_referenceHLayout; +}; + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixMover.cpp b/src/gui/editors/matrix/MatrixMover.cpp new file mode 100644 index 0000000..d725f16 --- /dev/null +++ b/src/gui/editors/matrix/MatrixMover.cpp @@ -0,0 +1,481 @@ +/* -*- 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 "MatrixMover.h" + +#include "base/BaseProperties.h" +#include <klocale.h> +#include <kstddirs.h> +#include "base/Event.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/SnapGrid.h" +#include "base/ViewElement.h" +#include "commands/matrix/MatrixModifyCommand.h" +#include "commands/matrix/MatrixInsertionCommand.h" +#include "commands/notation/NormalizeRestsCommand.h" +#include "gui/general/EditTool.h" +#include "gui/general/RosegardenCanvasView.h" +#include "MatrixElement.h" +#include "MatrixStaff.h" +#include "MatrixTool.h" +#include "MatrixView.h" +#include "MatrixVLayout.h" +#include <kaction.h> +#include <kglobal.h> +#include <qiconset.h> +#include <qpoint.h> +#include <qstring.h> +#include "misc/Debug.h" + + +namespace Rosegarden +{ + +MatrixMover::MatrixMover(MatrixView* parent) : + MatrixTool("MatrixMover", parent), + m_currentElement(0), + m_currentStaff(0), + m_lastPlayedPitch(-1) +{ + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QCanvasPixmap pixmap(pixmapDir + "/toolbar/select.xpm"); + QIconSet icon = QIconSet(pixmap); + + new KAction(i18n("Switch to Select Tool"), icon, 0, this, + SLOT(slotSelectSelected()), actionCollection(), + "select"); + + new KAction(i18n("Switch to Draw Tool"), "pencil", 0, this, + SLOT(slotDrawSelected()), actionCollection(), + "draw"); + + new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this, + SLOT(slotEraseSelected()), actionCollection(), + "erase"); + + pixmap.load(pixmapDir + "/toolbar/resize.xpm"); + icon = QIconSet(pixmap); + new KAction(i18n("Switch to Resize Tool"), icon, 0, this, + SLOT(slotResizeSelected()), actionCollection(), + "resize"); + + createMenu("matrixmover.rc"); +} + +void MatrixMover::handleEventRemoved(Event *event) +{ + if (m_currentElement && m_currentElement->event() == event) { + m_currentElement = 0; + } +} + +void MatrixMover::handleLeftButtonPress(timeT time, + int pitch, + int staffNo, + QMouseEvent* e, + ViewElement* el) +{ + MATRIX_DEBUG << "MatrixMover::handleLeftButtonPress() : time = " << time << ", el = " << el << endl; + if (!el) return; + + m_quickCopy = (e->state() & Qt::ControlButton); + + if (!m_duplicateElements.empty()) { + for (size_t i = 0; i < m_duplicateElements.size(); ++i) { + delete m_duplicateElements[i]->event(); + delete m_duplicateElements[i]; + } + m_duplicateElements.clear(); + } + + m_currentElement = dynamic_cast<MatrixElement*>(el); + m_currentStaff = m_mParentView->getStaff(staffNo); + + if (m_currentElement) { + + // Add this element and allow movement + // + EventSelection* selection = m_mParentView->getCurrentSelection(); + + if (selection) { + EventSelection *newSelection; + + if ((e->state() & Qt::ShiftButton) || + selection->contains(m_currentElement->event())) + newSelection = new EventSelection(*selection); + else + newSelection = new EventSelection(m_currentStaff->getSegment()); + + // if the selection already contains the event, remove it from the + // selection if shift is pressed + if (selection->contains(m_currentElement->event())){ + if (e->state() & Qt::ShiftButton) + newSelection->removeEvent(m_currentElement->event()); + } else { + newSelection->addEvent(m_currentElement->event()); + } + m_mParentView->setCurrentSelection(newSelection, true, true); + m_mParentView->canvas()->update(); + selection = newSelection; + } else { + m_mParentView->setSingleSelectedEvent(m_currentStaff->getSegment(), + m_currentElement->event(), + true); + m_mParentView->canvas()->update(); + } + + long velocity = m_mParentView->getCurrentVelocity(); + m_currentElement->event()->get<Int>(BaseProperties::VELOCITY, velocity); + m_mParentView->playNote(m_currentStaff->getSegment(), pitch, velocity); + m_lastPlayedPitch = pitch; + + if (m_quickCopy && selection) { + for (EventSelection::eventcontainer::iterator i = + selection->getSegmentEvents().begin(); + i != selection->getSegmentEvents().end(); ++i) { + + MatrixElement *element = m_currentStaff->getElement(*i); + if (!element) continue; + + MatrixElement *duplicate = new MatrixElement + (new Event(**i), m_mParentView->isDrumMode()); + duplicate->setLayoutY(element->getLayoutY()); + duplicate->setLayoutX(element->getLayoutX()); + duplicate->setWidth(element->getWidth()); + duplicate->setHeight(element->getHeight()); + duplicate->setCanvasZ(-1); + m_currentStaff->positionElement(duplicate); + m_duplicateElements.push_back(duplicate); + } + } + } + + m_clickX = m_mParentView->inverseMapPoint(e->pos()).x(); +} + +timeT +MatrixMover::getDragTime(QMouseEvent *e, timeT candidate) +{ + int x = m_mParentView->inverseMapPoint(e->pos()).x(); + int xdiff = x - m_clickX; + + const SnapGrid &grid = getSnapGrid(); + const RulerScale &scale = *grid.getRulerScale(); + + timeT eventTime = m_currentElement->getViewAbsoluteTime(); + int eventX = scale.getXForTime(eventTime); + timeT preSnapTarget = scale.getTimeForX(eventX + xdiff); + + candidate = grid.snapTime(preSnapTarget, SnapGrid::SnapEither); + + if (xdiff == 0 || + (abs(eventTime - preSnapTarget) < abs(candidate - preSnapTarget))) { + candidate = eventTime; + } + + return candidate; +} + +int MatrixMover::handleMouseMove(timeT newTime, + int newPitch, + QMouseEvent *e) +{ + MATRIX_DEBUG << "MatrixMover::handleMouseMove() time = " + << newTime << endl; + + if (e) { + setBasicContextHelp(e->state() & Qt::ControlButton); + } + + if (!m_currentElement || !m_currentStaff) + return RosegardenCanvasView::NoFollow; + + if (getSnapGrid().getSnapSetting() != SnapGrid::NoSnap) { + setContextHelp(i18n("Hold Shift to avoid snapping to beat grid")); + } else { + clearContextHelp(); + } + + if (e) newTime = getDragTime(e, newTime); + + emit hoveredOverNoteChanged(newPitch, true, newTime); + + using BaseProperties::PITCH; + int diffPitch = 0; + if (m_currentElement->event()->has(PITCH)) { + diffPitch = newPitch - m_currentElement->event()->get<Int>(PITCH); + } + + int diffY = + int(((m_currentStaff->getLayoutYForHeight(newPitch) - + m_currentStaff->getElementHeight() / 2) - + m_currentElement->getLayoutY())); + + EventSelection* selection = m_mParentView->getCurrentSelection(); + EventSelection::eventcontainer::iterator it = + selection->getSegmentEvents().begin(); + + MatrixElement *element = 0; + int maxY = m_currentStaff->getCanvasYForHeight(0); + + for (; it != selection->getSegmentEvents().end(); it++) { + element = m_currentStaff->getElement(*it); + + if (element) { + + timeT diffTime = element->getViewAbsoluteTime() - + m_currentElement->getViewAbsoluteTime(); + + int newX = getSnapGrid().getRulerScale()-> + getXForTime(newTime + diffTime); + + if (newX < 0) newX = 0; + + int newY = int(element->getLayoutY() + diffY); + + if (newY < 0) newY = 0; + if (newY > maxY) newY = maxY; + + element->setLayoutX(newX); + element->setLayoutY(newY); + + m_currentStaff->positionElement(element); + } + } + + if (newPitch != m_lastPlayedPitch) { + long velocity = m_mParentView->getCurrentVelocity(); + m_currentElement->event()->get<Int>(BaseProperties::VELOCITY, velocity); + m_mParentView->playNote(m_currentStaff->getSegment(), newPitch, velocity); + m_lastPlayedPitch = newPitch; + } + + m_mParentView->canvas()->update(); + return RosegardenCanvasView::FollowHorizontal | + RosegardenCanvasView::FollowVertical; +} + +void MatrixMover::handleMouseRelease(timeT newTime, + int newPitch, + QMouseEvent *e) +{ + MATRIX_DEBUG << "MatrixMover::handleMouseRelease() - newPitch = " + << newPitch << endl; + + if (!m_currentElement || !m_currentStaff) + return; + + if (newPitch > MatrixVLayout::maxMIDIPitch) + newPitch = MatrixVLayout::maxMIDIPitch; + if (newPitch < 0) + newPitch = 0; + + if (e) newTime = getDragTime(e, newTime); + + using BaseProperties::PITCH; + timeT diffTime = newTime - m_currentElement->getViewAbsoluteTime(); + int diffPitch = 0; + if (m_currentElement->event()->has(PITCH)) { + diffPitch = newPitch - m_currentElement->event()->get<Int>(PITCH); + } + + EventSelection *selection = m_mParentView->getCurrentSelection(); + + if ((diffTime == 0 && diffPitch == 0) || selection->getAddedEvents() == 0) { + for (size_t i = 0; i < m_duplicateElements.size(); ++i) { + delete m_duplicateElements[i]->event(); + delete m_duplicateElements[i]; + } + m_duplicateElements.clear(); + m_mParentView->canvas()->update(); + m_currentElement = 0; + return; + } + + if (newPitch != m_lastPlayedPitch) { + long velocity = m_mParentView->getCurrentVelocity(); + m_currentElement->event()->get<Int>(BaseProperties::VELOCITY, velocity); + m_mParentView->playNote(m_currentStaff->getSegment(), newPitch, velocity); + m_lastPlayedPitch = newPitch; + } + + QString commandLabel; + if (m_quickCopy) { + if (selection->getAddedEvents() < 2) { + commandLabel = i18n("Copy and Move Event"); + } else { + commandLabel = i18n("Copy and Move Events"); + } + } else { + if (selection->getAddedEvents() < 2) { + commandLabel = i18n("Move Event"); + } else { + commandLabel = i18n("Move Events"); + } + } + + KMacroCommand *macro = new KMacroCommand(commandLabel); + + EventSelection::eventcontainer::iterator it = + selection->getSegmentEvents().begin(); + + Segment &segment = m_currentStaff->getSegment(); + + EventSelection *newSelection = new EventSelection(segment); + + timeT normalizeStart = selection->getStartTime(); + timeT normalizeEnd = selection->getEndTime(); + + if (m_quickCopy) { + for (size_t i = 0; i < m_duplicateElements.size(); ++i) { + timeT time = m_duplicateElements[i]->getViewAbsoluteTime(); + timeT endTime = time + m_duplicateElements[i]->getViewDuration(); + if (time < normalizeStart) normalizeStart = time; + if (endTime > normalizeEnd) normalizeEnd = endTime; + macro->addCommand(new MatrixInsertionCommand + (segment, time, endTime, + m_duplicateElements[i]->event())); + delete m_duplicateElements[i]->event(); + delete m_duplicateElements[i]; + } + m_duplicateElements.clear(); + m_quickCopy = false; + } + + for (; it != selection->getSegmentEvents().end(); it++) { + + timeT newTime = (*it)->getAbsoluteTime() + diffTime; + + int newPitch = 60; + if ((*it)->has(PITCH)) { + newPitch = (*it)->get<Int>(PITCH) + diffPitch; + } + + Event *newEvent = 0; + + if (newTime < segment.getStartTime()) { + newTime = segment.getStartTime(); + } + + if (newTime + (*it)->getDuration() >= segment.getEndMarkerTime()) { + timeT limit = getSnapGrid().snapTime + (segment.getEndMarkerTime() - 1, SnapGrid::SnapLeft); + if (newTime > limit) newTime = limit; + timeT newDuration = std::min + ((*it)->getDuration(), segment.getEndMarkerTime() - newTime); + newEvent = new Event(**it, newTime, newDuration); + } else { + newEvent = new Event(**it, newTime); + } + + newEvent->set<Int>(BaseProperties::PITCH, newPitch); + + macro->addCommand(new MatrixModifyCommand(segment, + (*it), + newEvent, + true, + false)); + newSelection->addEvent(newEvent); + } + + normalizeStart = std::min(normalizeStart, newSelection->getStartTime()); + normalizeEnd = std::max(normalizeEnd, newSelection->getEndTime()); + + macro->addCommand(new NormalizeRestsCommand(segment, + normalizeStart, + normalizeEnd)); + + m_mParentView->setCurrentSelection(0, false, false); + m_mParentView->addCommandToHistory(macro); + m_mParentView->setCurrentSelection(newSelection, false, false); + + m_mParentView->canvas()->update(); + m_currentElement = 0; + + setBasicContextHelp(); +} + +void MatrixMover::ready() +{ + connect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)), + this, SLOT(slotMatrixScrolled(int, int))); + connect(this, SIGNAL(hoveredOverNoteChanged(int, bool, timeT)), + m_mParentView, SLOT(slotHoveredOverNoteChanged(int, bool, timeT))); + m_mParentView->setCanvasCursor(Qt::sizeAllCursor); + setBasicContextHelp(); +} + +void MatrixMover::stow() +{ + disconnect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)), + this, SLOT(slotMatrixScrolled(int, int))); + disconnect(this, SIGNAL(hoveredOverNoteChanged(int, bool, timeT)), + m_mParentView, SLOT(slotHoveredOverNoteChanged(int, bool, timeT))); +} + +void MatrixMover::slotMatrixScrolled(int newX, int newY) +{ + if (!m_currentElement) + return ; + + QPoint newP1(newX, newY), oldP1(m_parentView->getCanvasView()->contentsX(), + m_parentView->getCanvasView()->contentsY()); + + QPoint offset = newP1 - oldP1; + + offset = m_mParentView->inverseMapPoint(offset); + + QPoint p(m_currentElement->getCanvasX(), m_currentElement->getCanvasY()); + p += offset; + + timeT newTime = getSnapGrid().snapX(p.x()); + int newPitch = m_currentStaff->getHeightAtCanvasCoords(p.x(), p.y()); + + handleMouseMove(newTime, newPitch, 0); +} + +void MatrixMover::setBasicContextHelp(bool ctrlPressed) +{ + EventSelection *selection = m_mParentView->getCurrentSelection(); + if (!selection || selection->getAddedEvents() < 2) { + if (!ctrlPressed) { + setContextHelp(i18n("Click and drag to move a note; hold Ctrl as well to copy it")); + } else { + setContextHelp(i18n("Click and drag to copy a note")); + } + } else { + if (!ctrlPressed) { + setContextHelp(i18n("Click and drag to move selected notes; hold Ctrl as well to copy")); + } else { + setContextHelp(i18n("Click and drag to copy selected notes")); + } + } +} + +const QString MatrixMover::ToolName = "mover"; + +} +#include "MatrixMover.moc" diff --git a/src/gui/editors/matrix/MatrixMover.h b/src/gui/editors/matrix/MatrixMover.h new file mode 100644 index 0000000..ac95c5f --- /dev/null +++ b/src/gui/editors/matrix/MatrixMover.h @@ -0,0 +1,112 @@ + +/* -*- 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_MATRIXMOVER_H_ +#define _RG_MATRIXMOVER_H_ + +#include "MatrixTool.h" +#include <qstring.h> +#include "base/Event.h" + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class ViewElement; +class MatrixView; +class MatrixStaff; +class MatrixElement; +class Event; + + +class MatrixMover : public MatrixTool +{ + Q_OBJECT + + friend class MatrixToolBox; + +public: + virtual void handleLeftButtonPress(timeT, + int height, + int staffNo, + QMouseEvent *event, + ViewElement*); + + /** + * Set the duration of the element + */ + virtual int handleMouseMove(timeT, + int height, + QMouseEvent*); + + /** + * Actually insert the new element + */ + virtual void handleMouseRelease(timeT, + int height, + QMouseEvent*); + + static const QString ToolName; + + /** + * Respond to an event being deleted -- it may be the one the tool + * is remembering as the current event. + */ + virtual void handleEventRemoved(Event *event); + + virtual void ready(); + virtual void stow(); + +signals: + void hoveredOverNoteChanged(int evPitch, bool haveEvent, timeT evTime); + +protected slots: + void slotMatrixScrolled(int x, int y); + +protected: + MatrixMover(MatrixView*); + + void setBasicContextHelp(bool ctrlPressed = false); + + timeT getDragTime(QMouseEvent *e, timeT candidate); + + MatrixElement* m_currentElement; + MatrixStaff* m_currentStaff; + + std::vector<MatrixElement *> m_duplicateElements; + bool m_quickCopy; + + int m_lastPlayedPitch; + int m_clickX; +}; + + + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixPainter.cpp b/src/gui/editors/matrix/MatrixPainter.cpp new file mode 100644 index 0000000..be63bd7 --- /dev/null +++ b/src/gui/editors/matrix/MatrixPainter.cpp @@ -0,0 +1,370 @@ +/* -*- 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 "MatrixPainter.h" + +#include "base/BaseProperties.h" +#include <klocale.h> +#include <kstddirs.h> +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/SegmentMatrixHelper.h" +#include "base/SnapGrid.h" +#include "base/ViewElement.h" +#include "commands/matrix/MatrixInsertionCommand.h" +#include "commands/matrix/MatrixEraseCommand.h" +#include "commands/matrix/MatrixPercussionInsertionCommand.h" +#include "gui/general/EditTool.h" +#include "gui/general/RosegardenCanvasView.h" +#include "MatrixElement.h" +#include "MatrixStaff.h" +#include "MatrixTool.h" +#include "MatrixView.h" +#include <kaction.h> +#include <kglobal.h> +#include <qiconset.h> +#include <qpoint.h> +#include <qstring.h> +#include "misc/Debug.h" + + +namespace Rosegarden +{ + +MatrixPainter::MatrixPainter(MatrixView* parent) + : MatrixTool("MatrixPainter", parent), + m_currentElement(0), + m_currentStaff(0) +{ + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QCanvasPixmap pixmap(pixmapDir + "/toolbar/select.xpm"); + QIconSet icon = QIconSet(pixmap); + + 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"); + + new KAction(i18n("Switch to Move Tool"), "move", 0, this, + SLOT(slotMoveSelected()), actionCollection(), + "move"); + + pixmap.load(pixmapDir + "/toolbar/resize.xpm"); + icon = QIconSet(pixmap); + new KAction(i18n("Switch to Resize Tool"), icon, 0, this, + SLOT(slotResizeSelected()), actionCollection(), + "resize"); + + createMenu("matrixpainter.rc"); +} + +MatrixPainter::MatrixPainter(QString name, MatrixView* parent) + : MatrixTool(name, parent), + m_currentElement(0), + m_currentStaff(0) +{} + +void MatrixPainter::handleEventRemoved(Event *event) +{ + if (m_currentElement && m_currentElement->event() == event) { + m_currentElement = 0; + } +} + +void MatrixPainter::handleLeftButtonPress(timeT time, + int pitch, + int staffNo, + QMouseEvent *e, + ViewElement *element) +{ + MATRIX_DEBUG << "MatrixPainter::handleLeftButtonPress : pitch = " + << pitch << ", time : " << time << endl; + + QPoint p = m_mParentView->inverseMapPoint(e->pos()); + + m_currentStaff = m_mParentView->getStaff(staffNo); + + // Don't create an overlapping event on the same note on the same channel + if (dynamic_cast<MatrixElement*>(element)) { + std::cerr << "MatrixPainter::handleLeftButtonPress : overlap with an other matrix element" << std::endl; + // In percussion matrix, we delete the existing event rather + // than just ignoring it -- this is reasonable as the event + // has no meaningful duration, so we can just toggle it on and + // off with repeated clicks + if (m_mParentView->isDrumMode()) { + if (element->event()) { + MatrixEraseCommand *command = + new MatrixEraseCommand(m_currentStaff->getSegment(), + element->event()); + m_mParentView->addCommandToHistory(command); + } + } + m_currentElement = 0; + return ; + } + + // This is needed for the event duration rounding + SnapGrid grid(getSnapGrid()); + + Event *ev = new Event(Note::EventType, time, + grid.getSnapTime(double(p.x()))); + ev->set<Int>(BaseProperties::PITCH, pitch); + ev->set<Int>(BaseProperties::VELOCITY, m_mParentView->getCurrentVelocity()); + + m_currentElement = new MatrixElement(ev, m_mParentView->isDrumMode()); + + int y = m_currentStaff->getLayoutYForHeight(pitch) - + m_currentStaff->getElementHeight() / 2; + + m_currentElement->setLayoutY(y); + m_currentElement->setLayoutX(grid.getRulerScale()->getXForTime(time)); + m_currentElement->setHeight(m_currentStaff->getElementHeight()); + + int width = grid.getRulerScale()->getXForTime(time + ev->getDuration()) + - m_currentElement->getLayoutX() + 1; + + m_currentElement->setWidth(width); + + m_currentStaff->positionElement(m_currentElement); + m_mParentView->update(); + + // preview + m_mParentView->playNote(ev); +} + +int MatrixPainter::handleMouseMove(timeT time, + int pitch, + QMouseEvent *e) +{ + // sanity check + if (!m_currentElement) + return RosegardenCanvasView::NoFollow; + + if (getSnapGrid().getSnapSetting() != SnapGrid::NoSnap) { + setContextHelp(i18n("Hold Shift to avoid snapping to beat grid")); + } else { + clearContextHelp(); + } + + // We don't want to use the time passed in, because it's snapped + // to the left and we want a more particular policy + + if (e) { + QPoint p = m_mParentView->inverseMapPoint(e->pos()); + time = getSnapGrid().snapX(p.x(), SnapGrid::SnapEither); + if (time >= m_currentElement->getViewAbsoluteTime()) { + time = getSnapGrid().snapX(p.x(), SnapGrid::SnapRight); + } else { + time = getSnapGrid().snapX(p.x(), SnapGrid::SnapLeft); + } + } + + MATRIX_DEBUG << "MatrixPainter::handleMouseMove : pitch = " + << pitch << ", time : " << time << endl; + + using BaseProperties::PITCH; + + if (time == m_currentElement->getViewAbsoluteTime()) { + time = + m_currentElement->getViewAbsoluteTime() + + m_currentElement->getViewDuration(); + } + + int width = getSnapGrid().getRulerScale()->getXForTime(time) + - getSnapGrid().getRulerScale()->getXForTime + (m_currentElement->getViewAbsoluteTime()) + 1; + + m_currentElement->setWidth(width); +// std::cerr << "current element width "<< width << std::endl; + + if (m_currentElement->event()->has(PITCH) && + pitch != m_currentElement->event()->get<Int>(PITCH)) { + + m_currentElement->event()->set<Int>(PITCH, pitch); + + int y = m_currentStaff->getLayoutYForHeight(pitch) - + m_currentStaff->getElementHeight() / 2; + + m_currentElement->setLayoutY(y); + + m_currentStaff->positionElement(m_currentElement); + + // preview + m_mParentView->playNote(m_currentElement->event()); + } + + m_mParentView->update(); + + return RosegardenCanvasView::FollowHorizontal | + RosegardenCanvasView::FollowVertical; +} + +void MatrixPainter::handleMouseRelease(timeT endTime, + int, + QMouseEvent *e) +{ + // This can happen in case of screen/window capture - + // we only get a mouse release, the window snapshot tool + // got the mouse down + if (!m_currentElement) + return ; + + // We don't want to use the time passed in, because it's snapped + // to the left and we want a more particular policy + + if (e) { + QPoint p = m_mParentView->inverseMapPoint(e->pos()); + endTime = getSnapGrid().snapX(p.x(), SnapGrid::SnapEither); + if (endTime >= m_currentElement->getViewAbsoluteTime()) { + endTime = getSnapGrid().snapX(p.x(), SnapGrid::SnapRight); + } else { + endTime = getSnapGrid().snapX(p.x(), SnapGrid::SnapLeft); + } + } + + timeT time = m_currentElement->getViewAbsoluteTime(); + timeT segmentEndTime = m_currentStaff->getSegment().getEndMarkerTime(); + + if (m_mParentView->isDrumMode()) { + + if (time > segmentEndTime) + time = segmentEndTime; + + MatrixPercussionInsertionCommand *command = + new MatrixPercussionInsertionCommand(m_currentStaff->getSegment(), + time, + m_currentElement->event()); + m_mParentView->addCommandToHistory(command); + + Event* ev = m_currentElement->event(); + delete m_currentElement; + delete ev; + + ev = command->getLastInsertedEvent(); + if (ev) + m_mParentView->setSingleSelectedEvent(m_currentStaff->getSegment(), + ev); + } else { + + // Insert element if it has a non null duration, + // discard it otherwise + // + if (time > endTime) + std::swap(time, endTime); + + if (endTime == time) + endTime = time + m_currentElement->getViewDuration(); + + if (time < segmentEndTime) { + + if (endTime > segmentEndTime) + endTime = segmentEndTime; + + SegmentMatrixHelper helper(m_currentStaff->getSegment()); + MATRIX_DEBUG << "MatrixPainter::handleMouseRelease() : helper.insertNote()" << endl; + + MatrixInsertionCommand* command = + new MatrixInsertionCommand(m_currentStaff->getSegment(), + time, + endTime, + m_currentElement->event()); + + m_mParentView->addCommandToHistory(command); + + Event* ev = m_currentElement->event(); + delete m_currentElement; + delete ev; + + ev = command->getLastInsertedEvent(); + if (ev) + m_mParentView->setSingleSelectedEvent(m_currentStaff->getSegment(), + ev); + } else { + + Event* ev = m_currentElement->event(); + delete m_currentElement; + delete ev; + } + } + + m_mParentView->update(); + m_currentElement = 0; + + setBasicContextHelp(); +} + +void MatrixPainter::ready() +{ + connect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)), + this, SLOT(slotMatrixScrolled(int, int))); + + m_mParentView->setCanvasCursor(Qt::crossCursor); + + setBasicContextHelp(); +} + +void MatrixPainter::stow() +{ + disconnect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)), + this, SLOT(slotMatrixScrolled(int, int))); +} + +void MatrixPainter::slotMatrixScrolled(int newX, int newY) +{ + if (!m_currentElement) + return ; + + QPoint newP1(newX, newY), oldP1(m_parentView->getCanvasView()->contentsX(), + m_parentView->getCanvasView()->contentsY()); + + QPoint offset = newP1 - oldP1; + + offset = m_mParentView->inverseMapPoint(offset); + + QPoint p(m_currentElement->getCanvasX() + m_currentElement->getWidth(), m_currentElement->getCanvasY()); + p += offset; + + timeT newTime = getSnapGrid().snapX(p.x()); + int newPitch = m_currentStaff->getHeightAtCanvasCoords(p.x(), p.y()); + + handleMouseMove(newTime, newPitch, 0); +} + +void MatrixPainter::setBasicContextHelp() +{ + if (getSnapGrid().getSnapSetting() != SnapGrid::NoSnap) { + setContextHelp(i18n("Click and drag to draw a note; Shift to avoid snapping to grid")); + } else { + setContextHelp(i18n("Click and drag to draw a note")); + } +} + +const QString MatrixPainter::ToolName = "painter"; + +} +#include "MatrixPainter.moc" diff --git a/src/gui/editors/matrix/MatrixPainter.h b/src/gui/editors/matrix/MatrixPainter.h new file mode 100644 index 0000000..570243a --- /dev/null +++ b/src/gui/editors/matrix/MatrixPainter.h @@ -0,0 +1,105 @@ + +/* -*- 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_MATRIXPAINTER_H_ +#define _RG_MATRIXPAINTER_H_ + +#include "MatrixTool.h" +#include <qstring.h> +#include "base/Event.h" + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class ViewElement; +class MatrixView; +class MatrixStaff; +class MatrixElement; +class Event; + + +class MatrixPainter : public MatrixTool +{ + Q_OBJECT + + friend class MatrixToolBox; + +public: + + virtual void handleLeftButtonPress(timeT, + int height, + int staffNo, + QMouseEvent *event, + ViewElement*); + + /** + * Set the duration of the element + */ + virtual int handleMouseMove(timeT, + int height, + QMouseEvent*); + + /** + * Actually insert the new element + */ + virtual void handleMouseRelease(timeT, + int height, + QMouseEvent*); + + static const QString ToolName; + + /** + * Respond to an event being deleted -- it may be the one the tool + * is remembering as the current event. + */ + virtual void handleEventRemoved(Event *event); + + virtual void ready(); + virtual void stow(); + +protected slots: + + void slotMatrixScrolled(int x, int y); + +protected: + MatrixPainter(MatrixView*); + MatrixPainter(QString name, MatrixView*); + + void setBasicContextHelp(); + + MatrixElement* m_currentElement; + MatrixStaff* m_currentStaff; +}; + + + + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixParameterBox.cpp b/src/gui/editors/matrix/MatrixParameterBox.cpp new file mode 100644 index 0000000..c330b94 --- /dev/null +++ b/src/gui/editors/matrix/MatrixParameterBox.cpp @@ -0,0 +1,99 @@ +/* -*- 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 "MatrixParameterBox.h" + +#include "base/Instrument.h" +#include "base/BasicQuantizer.h" +#include "base/Selection.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/editors/parameters/InstrumentParameterBox.h" +#include <kcombobox.h> +#include <qfont.h> +#include <qfontmetrics.h> +#include <qframe.h> +#include <qlayout.h> +#include <qwidget.h> + + +namespace Rosegarden +{ + +MatrixParameterBox::MatrixParameterBox(RosegardenGUIDoc *doc, + QWidget *parent, const char* name): + QFrame(parent, name), + m_quantizations(BasicQuantizer::getStandardQuantizations()), + m_doc(doc) +{ + setFrameStyle(NoFrame); + initBox(); +} + +MatrixParameterBox::~MatrixParameterBox() +{} + +void +MatrixParameterBox::initBox() +{ + QFont boldFont; + boldFont.setPointSize(int(boldFont.pointSize() * 9.5 / 10.0 + 0.5)); + boldFont.setBold(true); + + QFont plainFont; + plainFont.setPointSize(plainFont.pointSize() * 9 / 10); + QFont font = plainFont; + + QFontMetrics fontMetrics(font); + // magic numbers: 13 is the height of the menu pixmaps, 10 is just 10 + //int comboHeight = std::max(fontMetrics.height(), 13) + 10; + + QGridLayout *gridLayout = new QGridLayout(this, 20, 3, 8, 1); + + m_instrumentParameterBox = new InstrumentParameterBox(m_doc, this); + gridLayout->addMultiCellWidget(m_instrumentParameterBox, 0, 7, 0, 2); + +} + +void +MatrixParameterBox::setSelection(EventSelection *selection) +{ + if (!selection) + return ; + + EventSelection::eventcontainer::iterator + it = selection->getSegmentEvents().begin(); + +for (; it != selection->getSegmentEvents().end(); it++) {} + +} + +void +MatrixParameterBox::useInstrument(Instrument *instrument) +{ + m_instrumentParameterBox->useInstrument(instrument); +} + +} +#include "MatrixParameterBox.moc" diff --git a/src/gui/editors/matrix/MatrixParameterBox.h b/src/gui/editors/matrix/MatrixParameterBox.h new file mode 100644 index 0000000..d8d4a4d --- /dev/null +++ b/src/gui/editors/matrix/MatrixParameterBox.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_MATRIXPARAMETERBOX_H_ +#define _RG_MATRIXPARAMETERBOX_H_ + +#include <qframe.h> +#include <vector> +#include "base/Event.h" + + +class QWidget; +class KComboBox; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class InstrumentParameterBox; +class Instrument; +class EventSelection; + + +class MatrixParameterBox : public QFrame +{ + Q_OBJECT + +public: + MatrixParameterBox(RosegardenGUIDoc *doc=0, QWidget *parent=0, const char* name=0); + ~MatrixParameterBox(); + + void initBox(); + void setSelection(EventSelection *); + void useInstrument(Instrument *instrument); + +protected: + + KComboBox *m_quantizeCombo; + KComboBox *m_snapGridCombo; + InstrumentParameterBox *m_instrumentParameterBox; + + std::vector<timeT> m_quantizations; + std::vector<timeT> m_snapValues; + + RosegardenGUIDoc *m_doc; + +}; + + + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixResizer.cpp b/src/gui/editors/matrix/MatrixResizer.cpp new file mode 100644 index 0000000..2fab5e8 --- /dev/null +++ b/src/gui/editors/matrix/MatrixResizer.cpp @@ -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. +*/ + + +#include "MatrixResizer.h" + +#include <klocale.h> +#include <kstddirs.h> +#include "base/Event.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/SnapGrid.h" +#include "base/ViewElement.h" +#include "commands/matrix/MatrixModifyCommand.h" +#include "commands/notation/NormalizeRestsCommand.h" +#include "gui/general/EditTool.h" +#include "gui/general/RosegardenCanvasView.h" +#include "MatrixElement.h" +#include "MatrixStaff.h" +#include "MatrixTool.h" +#include "MatrixView.h" +#include <kaction.h> +#include <kglobal.h> +#include <qiconset.h> +#include <qpoint.h> +#include <qstring.h> +#include "misc/Debug.h" + + +namespace Rosegarden +{ + +MatrixResizer::MatrixResizer(MatrixView* parent) + : MatrixTool("MatrixResizer", parent), + m_currentElement(0), + m_currentStaff(0) +{ + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QCanvasPixmap pixmap(pixmapDir + "/toolbar/select.xpm"); + QIconSet icon = QIconSet(pixmap); + + new KAction(i18n("Switch to Select Tool"), icon, 0, this, + SLOT(slotSelectSelected()), actionCollection(), + "select"); + + new KAction(i18n("Switch to Draw Tool"), "pencil", 0, this, + SLOT(slotDrawSelected()), actionCollection(), + "draw"); + + new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this, + SLOT(slotEraseSelected()), actionCollection(), + "erase"); + + new KAction(i18n("Switch to Move Tool"), "move", 0, this, + SLOT(slotMoveSelected()), actionCollection(), + "move"); + + createMenu("matrixresizer.rc"); +} + +void MatrixResizer::handleEventRemoved(Event *event) +{ + if (m_currentElement && m_currentElement->event() == event) { + m_currentElement = 0; + } +} + +void MatrixResizer::handleLeftButtonPress(timeT, + int, + int staffNo, + QMouseEvent* e, + ViewElement* el) +{ + MATRIX_DEBUG << "MatrixResizer::handleLeftButtonPress() : el = " + << el << endl; + + if (!el) + return ; // nothing to erase + + m_currentElement = dynamic_cast<MatrixElement*>(el); + m_currentStaff = m_mParentView->getStaff(staffNo); + + if (m_currentElement) { + + // Add this element and allow movement + // + EventSelection* selection = m_mParentView->getCurrentSelection(); + + if (selection) { + EventSelection *newSelection; + + if ((e->state() & Qt::ShiftButton) || + selection->contains(m_currentElement->event())) + newSelection = new EventSelection(*selection); + else + newSelection = new EventSelection(m_currentStaff->getSegment()); + + newSelection->addEvent(m_currentElement->event()); + m_mParentView->setCurrentSelection(newSelection, true, true); + m_mParentView->canvas()->update(); + } else { + m_mParentView->setSingleSelectedEvent(m_currentStaff->getSegment(), + m_currentElement->event(), + true); + m_mParentView->canvas()->update(); + } + } +} + +int MatrixResizer::handleMouseMove(timeT newTime, + int, + QMouseEvent *e) +{ + setBasicContextHelp(); + + if (!m_currentElement || !m_currentStaff) + return RosegardenCanvasView::NoFollow; + + if (getSnapGrid().getSnapSetting() != SnapGrid::NoSnap) { + setContextHelp(i18n("Hold Shift to avoid snapping to beat grid")); + } else { + clearContextHelp(); + } + + // For the resizer we normally don't want to use the official + // time, because it's snapped to the left and we want to snap in + // the closest direction instead + + if (e) { + QPoint p = m_mParentView->inverseMapPoint(e->pos()); + newTime = getSnapGrid().snapX(p.x(), SnapGrid::SnapEither); + } + + timeT newDuration = newTime - m_currentElement->getViewAbsoluteTime(); + + if (newDuration == 0) { + newDuration += getSnapGrid().getSnapTime + (m_currentElement->getViewAbsoluteTime()); + } + + int width = getSnapGrid().getRulerScale()->getXForTime + (m_currentElement->getViewAbsoluteTime() + newDuration) + - m_currentElement->getLayoutX() + 1; + + int initialWidth = m_currentElement->getWidth(); + + int diffWidth = initialWidth - width; + + EventSelection* selection = m_mParentView->getCurrentSelection(); + EventSelection::eventcontainer::iterator it = + selection->getSegmentEvents().begin(); + + MatrixElement *element = 0; + for (; it != selection->getSegmentEvents().end(); it++) { + element = m_currentStaff->getElement(*it); + + if (element) { + int newWidth = element->getWidth() - diffWidth; + + MATRIX_DEBUG << "MatrixResizer::handleMouseMove - " + << "new width = " << newWidth << endl; + + element->setWidth(newWidth); + m_currentStaff->positionElement(element); + } + } + + m_mParentView->canvas()->update(); + return RosegardenCanvasView::FollowHorizontal; +} + +void MatrixResizer::handleMouseRelease(timeT newTime, + int, + QMouseEvent *e) +{ + if (!m_currentElement || !m_currentStaff) + return ; + + // For the resizer we don't want to use the time passed in, + // because it's snapped to the left and we want to snap in the + // closest direction instead + + if (e) { + QPoint p = m_mParentView->inverseMapPoint(e->pos()); + newTime = getSnapGrid().snapX(p.x(), SnapGrid::SnapEither); + } + + timeT diffDuration = + newTime - m_currentElement->getViewAbsoluteTime() - + m_currentElement->getViewDuration(); + + EventSelection *selection = m_mParentView->getCurrentSelection(); + + if (selection->getAddedEvents() == 0) + return ; + else { + QString commandLabel = i18n("Resize Event"); + + if (selection->getAddedEvents() > 1) + commandLabel = i18n("Resize Events"); + + KMacroCommand *macro = new KMacroCommand(commandLabel); + + EventSelection::eventcontainer::iterator it = + selection->getSegmentEvents().begin(); + + Segment &segment = m_currentStaff->getSegment(); + + EventSelection *newSelection = new EventSelection(segment); + + timeT normalizeStart = selection->getStartTime(); + timeT normalizeEnd = selection->getEndTime(); + + for (; it != selection->getSegmentEvents().end(); it++) { + timeT eventTime = (*it)->getAbsoluteTime(); + timeT eventDuration = (*it)->getDuration() + diffDuration; + + + MATRIX_DEBUG << "MatrixResizer::handleMouseRelease - " + << "Time = " << eventTime + << ", Duration = " << eventDuration << endl; + + + if (eventDuration < 0) { + eventTime += eventDuration; + eventDuration = -eventDuration; + } + + if (eventDuration == 0) { + eventDuration += getSnapGrid().getSnapTime(eventTime); + } + + if (eventTime + eventDuration >= segment.getEndMarkerTime()) { + eventDuration = std::min(eventDuration, + segment.getEndMarkerTime() - eventTime); + } + + Event *newEvent = + new Event(**it, + eventTime, + eventDuration); + + macro->addCommand(new MatrixModifyCommand(segment, + *it, + newEvent, + false, + false)); + + newSelection->addEvent(newEvent); + } + + normalizeStart = std::min(normalizeStart, newSelection->getStartTime()); + normalizeEnd = std::max(normalizeEnd, newSelection->getEndTime()); + + macro->addCommand(new NormalizeRestsCommand(segment, + normalizeStart, + normalizeEnd)); + + m_mParentView->setCurrentSelection(0, false, false); + m_mParentView->addCommandToHistory(macro); + m_mParentView->setCurrentSelection(newSelection, false, false); + } + + m_mParentView->update(); + m_currentElement = 0; + setBasicContextHelp(); +} + +void MatrixResizer::ready() +{ + connect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)), + this, SLOT(slotMatrixScrolled(int, int))); + m_mParentView->setCanvasCursor(Qt::sizeHorCursor); + setBasicContextHelp(); +} + +void MatrixResizer::stow() +{ + disconnect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)), + this, SLOT(slotMatrixScrolled(int, int))); +} + +void MatrixResizer::slotMatrixScrolled(int newX, int newY) +{ + QPoint newP1(newX, newY), oldP1(m_parentView->getCanvasView()->contentsX(), + m_parentView->getCanvasView()->contentsY()); + + QPoint p(newX, newY); + + if (newP1.x() > oldP1.x()) { + p.setX(newX + m_parentView->getCanvasView()->visibleWidth()); + } + + p = m_mParentView->inverseMapPoint(p); + int newTime = getSnapGrid().snapX(p.x()); + handleMouseMove(newTime, 0, 0); +} + +void MatrixResizer::setBasicContextHelp() +{ + EventSelection *selection = m_mParentView->getCurrentSelection(); + if (selection && selection->getAddedEvents() > 1) { + setContextHelp(i18n("Click and drag to resize selected notes")); + } else { + setContextHelp(i18n("Click and drag to resize a note")); + } +} + +const QString MatrixResizer::ToolName = "resizer"; + +} +#include "MatrixResizer.moc" diff --git a/src/gui/editors/matrix/MatrixResizer.h b/src/gui/editors/matrix/MatrixResizer.h new file mode 100644 index 0000000..e623cac --- /dev/null +++ b/src/gui/editors/matrix/MatrixResizer.h @@ -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. +*/ + +#ifndef _RG_MATRIXRESIZER_H_ +#define _RG_MATRIXRESIZER_H_ + +#include "MatrixTool.h" +#include <qstring.h> +#include "base/Event.h" + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class ViewElement; +class MatrixView; +class MatrixStaff; +class MatrixElement; +class Event; + + +class MatrixResizer : public MatrixTool +{ + Q_OBJECT + + friend class MatrixToolBox; + +public: + virtual void handleLeftButtonPress(timeT, + int height, + int staffNo, + QMouseEvent *event, + ViewElement*); + + /** + * Set the duration of the element + */ + virtual int handleMouseMove(timeT, + int height, + QMouseEvent*); + + /** + * Actually insert the new element + */ + virtual void handleMouseRelease(timeT, + int height, + QMouseEvent*); + + static const QString ToolName; + + /** + * Respond to an event being deleted -- it may be the one the tool + * is remembering as the current event. + */ + virtual void handleEventRemoved(Event *event); + + virtual void ready(); + virtual void stow(); + +protected slots: + + void slotMatrixScrolled(int x, int y); + +protected: + MatrixResizer(MatrixView*); + + void setBasicContextHelp(); + + MatrixElement* m_currentElement; + MatrixStaff* m_currentStaff; +}; + + + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixSelector.cpp b/src/gui/editors/matrix/MatrixSelector.cpp new file mode 100644 index 0000000..fbb9689 --- /dev/null +++ b/src/gui/editors/matrix/MatrixSelector.cpp @@ -0,0 +1,629 @@ +/* -*- 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 "MatrixSelector.h" + +#include "base/BaseProperties.h" +#include <klocale.h> +#include <kstddirs.h> +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "base/ViewElement.h" +#include "commands/edit/EventEditCommand.h" +#include "gui/dialogs/EventEditDialog.h" +#include "gui/dialogs/SimpleEventEditDialog.h" +#include "gui/general/EditTool.h" +#include "gui/general/EditToolBox.h" +#include "gui/general/GUIPalette.h" +#include "gui/general/RosegardenCanvasView.h" +#include "MatrixElement.h" +#include "MatrixMover.h" +#include "MatrixPainter.h" +#include "MatrixResizer.h" +#include "MatrixStaff.h" +#include "MatrixTool.h" +#include "MatrixView.h" +#include <kaction.h> +#include <kglobal.h> +#include <kapplication.h> +#include <kconfig.h> +#include <qdialog.h> +#include <qiconset.h> +#include <qpoint.h> +#include <qstring.h> +#include "misc/Debug.h" + + +namespace Rosegarden +{ + +MatrixSelector::MatrixSelector(MatrixView* view) + : MatrixTool("MatrixSelector", view), + m_selectionRect(0), + m_updateRect(false), + m_currentStaff(0), + m_clickedElement(0), + m_dispatchTool(0), + m_justSelectedBar(false), + m_matrixView(view), + m_selectionToMerge(0) +{ + connect(m_parentView, SIGNAL(usedSelection()), + this, SLOT(slotHideSelection())); + + new KAction(i18n("Switch to Draw Tool"), "pencil", 0, this, + SLOT(slotDrawSelected()), actionCollection(), + "draw"); + + new KAction(i18n("Switch to Erase Tool"), "eraser", 0, this, + SLOT(slotEraseSelected()), actionCollection(), + "erase"); + + new KAction(i18n("Switch to Move Tool"), "move", 0, this, + SLOT(slotMoveSelected()), actionCollection(), + "move"); + + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QCanvasPixmap pixmap(pixmapDir + "/toolbar/resize.xpm"); + QIconSet icon = QIconSet(pixmap); + + new KAction(i18n("Switch to Resize Tool"), icon, 0, this, + SLOT(slotResizeSelected()), actionCollection(), + "resize"); + + createMenu("matrixselector.rc"); +} + +void MatrixSelector::handleEventRemoved(Event *event) +{ + if (m_dispatchTool) + m_dispatchTool->handleEventRemoved(event); + if (m_clickedElement && m_clickedElement->event() == event) { + m_clickedElement = 0; + } +} + +void MatrixSelector::slotClickTimeout() +{ + m_justSelectedBar = false; +} + +void MatrixSelector::handleLeftButtonPress(timeT time, + int height, + int staffNo, + QMouseEvent* e, + ViewElement *element) +{ + MATRIX_DEBUG << "MatrixSelector::handleMousePress" << endl; + + if (m_justSelectedBar) { + handleMouseTripleClick(time, height, staffNo, e, element); + m_justSelectedBar = false; + return ; + } + + QPoint p = m_mParentView->inverseMapPoint(e->pos()); + + m_currentStaff = m_mParentView->getStaff(staffNo); + + // Do the merge selection thing + // + delete m_selectionToMerge; // you can safely delete 0, you know? + const EventSelection *selectionToMerge = 0; + if (e->state() & Qt::ShiftButton) + selectionToMerge = m_mParentView->getCurrentSelection(); + + m_selectionToMerge = + (selectionToMerge ? new EventSelection(*selectionToMerge) : 0); + + // Now the rest of the element stuff + // + m_clickedElement = dynamic_cast<MatrixElement*>(element); + + if (m_clickedElement) { + int x = int(m_clickedElement->getLayoutX()); + int width = m_clickedElement->getWidth(); + int resizeStart = int(double(width) * 0.85) + x; + + // max size of 10 + if ((x + width ) - resizeStart > 10) + resizeStart = x + width - 10; + + if (p.x() > resizeStart) { + m_dispatchTool = m_parentView-> + getToolBox()->getTool(MatrixResizer::ToolName); + } else { + m_dispatchTool = m_parentView-> + getToolBox()->getTool(MatrixMover::ToolName); + } + + m_dispatchTool->ready(); + + m_dispatchTool->handleLeftButtonPress(time, + height, + staffNo, + e, + element); + return ; + + } else if (e->state() & Qt::ControlButton) { + + handleMidButtonPress(time, height, staffNo, e, element); + return; + + } else { + + // Workaround for #930420 Positional error in sweep-selection box + // boundary + int zoomValue = (int)m_matrixView->m_hZoomSlider->getCurrentSize(); + MatrixStaff *staff = m_mParentView->getStaff(staffNo); + int pitch = m_currentStaff->getHeightAtCanvasCoords(p.x(), p.y()); + int pitchCentreHeight = staff->getTotalHeight() - + pitch * staff->getLineSpacing() - 2; // 2 or ? + int pitchLineHeight = pitchCentreHeight + staff->getLineSpacing() / 2; + int drawHeight = p.y(); + if (drawHeight <= pitchLineHeight + 1 && + drawHeight >= pitchLineHeight - 1) { + if (drawHeight == pitchLineHeight) + drawHeight += 2; + else + drawHeight += 2 * (drawHeight - pitchLineHeight); + } + MATRIX_DEBUG << "#### MatrixSelector::handleLeftButtonPress() : zoom " + << zoomValue + << " pitch " << pitch + << " pitchCentreHeight " << pitchCentreHeight + << " pitchLineHeight " << pitchLineHeight + << " lineSpacing " << staff->getLineSpacing() + << " drawHeight " << drawHeight << endl; + m_selectionRect->setX(int(p.x() / 4)*4); // more workaround for #930420 + m_selectionRect->setY(drawHeight); + m_selectionRect->setSize(0, 0); + + m_selectionRect->show(); + m_updateRect = true; + + // Clear existing selection if we're not merging + // + if (!m_selectionToMerge) { + m_mParentView->setCurrentSelection(0, false, true); + m_mParentView->canvas()->update(); + } + } + + //m_parentView->setCursorPosition(p.x()); +} + +void MatrixSelector::handleMidButtonPress(timeT time, + int height, + int staffNo, + QMouseEvent* e, + ViewElement *element) +{ + m_clickedElement = 0; // should be used for left-button clicks only + + // Don't allow overlapping elements on the same channel + if (dynamic_cast<MatrixElement*>(element)) + return ; + + m_dispatchTool = m_parentView-> + getToolBox()->getTool(MatrixPainter::ToolName); + + m_dispatchTool->ready(); + + m_dispatchTool->handleLeftButtonPress(time, height, staffNo, e, element); +} + +void MatrixSelector::handleMouseDoubleClick(timeT , + int , + int staffNo, + QMouseEvent *ev, + ViewElement *element) +{ + /* + if (m_dispatchTool) + { + m_dispatchTool->handleMouseDoubleClick(time, height, staffNo, e, element); + } + */ + + m_clickedElement = dynamic_cast<MatrixElement*>(element); + + MatrixStaff *staff = m_mParentView->getStaff(staffNo); + if (!staff) + return ; + + if (m_clickedElement) { + + if (m_clickedElement->event()->isa(Note::EventType) && + m_clickedElement->event()->has(BaseProperties::TRIGGER_SEGMENT_ID)) { + + int id = m_clickedElement->event()->get + <Int> + (BaseProperties::TRIGGER_SEGMENT_ID); + emit editTriggerSegment(id); + return ; + } + + if (ev->state() & ShiftButton) { // advanced edit + + EventEditDialog dialog(m_mParentView, *m_clickedElement->event(), true); + + if (dialog.exec() == QDialog::Accepted && + dialog.isModified()) { + + EventEditCommand *command = new EventEditCommand + (staff->getSegment(), + m_clickedElement->event(), + dialog.getEvent()); + + m_mParentView->addCommandToHistory(command); + } + } else { + + SimpleEventEditDialog dialog(m_mParentView, m_mParentView->getDocument(), + *m_clickedElement->event(), false); + + if (dialog.exec() == QDialog::Accepted && + dialog.isModified()) { + + EventEditCommand *command = new EventEditCommand + (staff->getSegment(), + m_clickedElement->event(), + dialog.getEvent()); + + m_mParentView->addCommandToHistory(command); + } + } + + } /* + + #988167: Matrix:Multiclick select methods don't work in matrix editor + Postponing this, as it falls foul of world-matrix transformation + etiquette and other such niceties + + else { + + QRect rect = staff->getBarExtents(ev->x(), ev->y()); + + m_selectionRect->setX(rect.x() + 2); + m_selectionRect->setY(rect.y()); + m_selectionRect->setSize(rect.width() - 4, rect.height()); + + m_selectionRect->show(); + m_updateRect = false; + + m_justSelectedBar = true; + QTimer::singleShot(QApplication::doubleClickInterval(), this, + SLOT(slotClickTimeout())); + } */ +} + +void MatrixSelector::handleMouseTripleClick(timeT t, + int height, + int staffNo, + QMouseEvent *ev, + ViewElement *element) +{ + if (!m_justSelectedBar) + return ; + m_justSelectedBar = false; + + MatrixStaff *staff = m_mParentView->getStaff(staffNo); + if (!staff) + return ; + + if (m_clickedElement) { + + // should be safe, as we've already set m_justSelectedBar false + handleLeftButtonPress(t, height, staffNo, ev, 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; + } +} + +int MatrixSelector::handleMouseMove(timeT time, int height, + QMouseEvent *e) +{ + QPoint p = m_mParentView->inverseMapPoint(e->pos()); + + if (m_dispatchTool) { + return m_dispatchTool->handleMouseMove(time, height, e); + } + + + if (!m_updateRect) { + setContextHelpFor(e->pos(), + getSnapGrid().getSnapSetting() == SnapGrid::NoSnap); + return RosegardenCanvasView::NoFollow; + } else { + clearContextHelp(); + } + + int w = int(p.x() - m_selectionRect->x()); + int h = int(p.y() - m_selectionRect->y()); + + // Qt rectangle dimensions appear to be 1-based + if (w > 0) + ++w; + else + --w; + if (h > 0) + ++h; + else + --h; + + // Workaround for #930420 Positional error in sweep-selection box boundary + int wFix = (w > 0) ? 3 : 0; + int hFix = (h > 0) ? 3 : 0; + int xFix = (w < 0) ? 3 : 0; + m_selectionRect->setSize(w - wFix, h - hFix); + m_selectionRect->setX(m_selectionRect->x() + xFix); + setViewCurrentSelection(); + m_selectionRect->setSize(w, h); + m_selectionRect->setX(m_selectionRect->x() - xFix); + m_mParentView->canvas()->update(); + + return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical; +} + +void MatrixSelector::handleMouseRelease(timeT time, int height, QMouseEvent *e) +{ + MATRIX_DEBUG << "MatrixSelector::handleMouseRelease" << endl; + + if (m_dispatchTool) { + m_dispatchTool->handleMouseRelease(time, height, e); + + m_dispatchTool->stow(); + ready(); + + // don't delete the tool as it's still part of the toolbox + m_dispatchTool = 0; + + return ; + } + + m_updateRect = false; + + if (m_clickedElement) { + m_mParentView->setSingleSelectedEvent(m_currentStaff->getSegment(), + m_clickedElement->event(), + false, true); + m_mParentView->canvas()->update(); + m_clickedElement = 0; + + } else if (m_selectionRect) { + setViewCurrentSelection(); + m_selectionRect->hide(); + m_mParentView->canvas()->update(); + } + + // Tell anyone who's interested that the selection has changed + emit gotSelection(); + + setContextHelpFor(e->pos()); +} + +void MatrixSelector::ready() +{ + if (m_mParentView) { + m_selectionRect = new QCanvasRectangle(m_mParentView->canvas()); + m_selectionRect->hide(); + m_selectionRect->setPen(QPen(GUIPalette::getColour(GUIPalette::SelectionRectangle), 2)); + + m_mParentView->setCanvasCursor(Qt::arrowCursor); + //m_mParentView->setPositionTracking(false); + } + + connect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)), + this, SLOT(slotMatrixScrolled(int, int))); + + setContextHelp(i18n("Click and drag to select; middle-click and drag to draw new note")); +} + +void MatrixSelector::stow() +{ + if (m_selectionRect) { + delete m_selectionRect; + m_selectionRect = 0; + m_mParentView->canvas()->update(); + } + + disconnect(m_parentView->getCanvasView(), SIGNAL(contentsMoving (int, int)), + this, SLOT(slotMatrixScrolled(int, int))); + +} + +void MatrixSelector::slotHideSelection() +{ + if (!m_selectionRect) + return ; + m_selectionRect->hide(); + m_selectionRect->setSize(0, 0); + m_mParentView->canvas()->update(); +} + +void MatrixSelector::slotMatrixScrolled(int newX, int newY) +{ + if (m_updateRect) { + int offsetX = newX - m_parentView->getCanvasView()->contentsX(); + int offsetY = newY - m_parentView->getCanvasView()->contentsY(); + + int w = int(m_selectionRect->width() + offsetX); + int h = int(m_selectionRect->height() + offsetY); + + // 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(); + m_mParentView->canvas()->update(); + } +} + +void MatrixSelector::setViewCurrentSelection() +{ + EventSelection* selection = getSelection(); + + if (m_selectionToMerge && selection && + m_selectionToMerge->getSegment() == selection->getSegment()) { + + selection->addFromSelection(m_selectionToMerge); + m_mParentView->setCurrentSelection(selection, true, true); + + } else if (!m_selectionToMerge) { + + m_mParentView->setCurrentSelection(selection, true, true); + + } + +} + +EventSelection* MatrixSelector::getSelection() +{ + if (!m_selectionRect->visible()) return 0; + + Segment& originalSegment = m_currentStaff->getSegment(); + EventSelection* selection = new EventSelection(originalSegment); + + // get the selections + // + QCanvasItemList l = m_selectionRect->collisions(true); + + if (l.count()) + { + for (QCanvasItemList::Iterator it=l.begin(); it!=l.end(); ++it) + { + QCanvasItem *item = *it; + QCanvasMatrixRectangle *matrixRect = 0; + + if ((matrixRect = dynamic_cast<QCanvasMatrixRectangle*>(item))) + { + MatrixElement *mE = &matrixRect->getMatrixElement(); + selection->addEvent(mE->event()); + } + } + } + + if (selection->getAddedEvents() > 0) { + return selection; + } else { + delete selection; + return 0; + } +} + +void MatrixSelector::setContextHelpFor(QPoint p, bool ctrlPressed) +{ + kapp->config()->setGroup(GeneralOptionsConfigGroup); + if (!kapp->config()->readBoolEntry("toolcontexthelp", true)) return; + + p = m_mParentView->inverseMapPoint(p); + + // same logic as in MatrixCanvasView::contentsMousePressEvent + + QCanvasItemList itemList = m_mParentView->canvas()->collisions(p); + QCanvasItemList::Iterator it; + MatrixElement* mel = 0; + QCanvasItem* activeItem = 0; + + for (it = itemList.begin(); it != itemList.end(); ++it) { + + QCanvasItem *item = *it; + QCanvasMatrixRectangle *mRect = 0; + + if (item->active()) { + break; + } + + if ((mRect = dynamic_cast<QCanvasMatrixRectangle*>(item))) { + if (! mRect->rect().contains(p, true)) continue; + mel = &(mRect->getMatrixElement()); + break; + } + } + + if (!mel) { + setContextHelp(i18n("Click and drag to select; middle-click and drag to draw new note")); + + } else { + + // same logic as in handleMouseButtonPress + + int x = int(mel->getLayoutX()); + int width = mel->getWidth(); + int resizeStart = int(double(width) * 0.85) + x; + + // max size of 10 + if ((x + width ) - resizeStart > 10) + resizeStart = x + width - 10; + + EventSelection *s = m_mParentView->getCurrentSelection(); + + if (p.x() > resizeStart) { + if (s && s->getAddedEvents() > 1) { + setContextHelp(i18n("Click and drag to resize selected notes")); + } else { + setContextHelp(i18n("Click and drag to resize note")); + } + } else { + if (s && s->getAddedEvents() > 1) { + if (!ctrlPressed) { + setContextHelp(i18n("Click and drag to move selected notes; hold Ctrl as well to copy")); + } else { + setContextHelp(i18n("Click and drag to copy selected notes")); + } + } else { + if (!ctrlPressed) { + setContextHelp(i18n("Click and drag to move note; hold Ctrl as well to copy")); + } else { + setContextHelp(i18n("Click and drag to copy note")); + } + } + } + } +} + +const QString MatrixSelector::ToolName = "selector"; + +} +#include "MatrixSelector.moc" diff --git a/src/gui/editors/matrix/MatrixSelector.h b/src/gui/editors/matrix/MatrixSelector.h new file mode 100644 index 0000000..a1d1ca4 --- /dev/null +++ b/src/gui/editors/matrix/MatrixSelector.h @@ -0,0 +1,177 @@ + +/* -*- 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_MATRIXSELECTOR_H_ +#define _RG_MATRIXSELECTOR_H_ + +#include "MatrixTool.h" +#include <qstring.h> +#include "base/Event.h" + + +class QMouseEvent; +class QCanvasRectangle; + + +namespace Rosegarden +{ + +class ViewElement; +class MatrixView; +class MatrixStaff; +class MatrixElement; +class EventSelection; +class Event; +class EditTool; + + +class MatrixSelector : public MatrixTool +{ + Q_OBJECT + + friend class MatrixToolBox; + +public: + + virtual void handleLeftButtonPress(timeT time, + int height, + int staffNo, + QMouseEvent *event, + ViewElement *element); + + virtual void handleMidButtonPress(timeT time, + int height, + int staffNo, + QMouseEvent *event, + ViewElement *element); + + virtual int handleMouseMove(timeT time, + int height, + QMouseEvent *event); + + virtual void handleMouseRelease(timeT, + int height, + QMouseEvent *event); + + /** + * Double-click: edit an event or make a whole-bar selection + */ + virtual void handleMouseDoubleClick(timeT time, + int height, + int staffNo, + QMouseEvent* event, + ViewElement *element); + + /** + * Triple-click: maybe make a whole-staff selection + */ + virtual void handleMouseTripleClick(timeT time, + int height, + int staffNo, + QMouseEvent* event, + ViewElement *element); + + + /** + * Create the selection rect + * + * We need this because MatrixView deletes all QCanvasItems + * along with it. This happens before the MatrixSelector is + * deleted, so we can't delete the selection rect in + * ~MatrixSelector 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); + + static const QString ToolName; + +public slots: + /** + * Hide the selection rectangle + * + * Should be called after a cut or a copy has been + * performed + */ + void slotHideSelection(); + + void slotClickTimeout(); + +protected slots: + + void slotMatrixScrolled(int x, int y); + +signals: + void gotSelection(); // inform that we've got a new selection + void editTriggerSegment(int); + +protected: + MatrixSelector(MatrixView*); + + void setContextHelpFor(QPoint p, bool ctrlPressed = false); + + void setViewCurrentSelection(); + + //--------------- Data members --------------------------------- + + QCanvasRectangle* m_selectionRect; + bool m_updateRect; + + int m_clickedStaff; + MatrixStaff* m_currentStaff; + + MatrixElement* m_clickedElement; + + // tool to delegate to + EditTool* m_dispatchTool; + + bool m_justSelectedBar; + + MatrixView * m_matrixView; + + EventSelection *m_selectionToMerge; +}; + + + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixStaff.cpp b/src/gui/editors/matrix/MatrixStaff.cpp new file mode 100644 index 0000000..b6be79f --- /dev/null +++ b/src/gui/editors/matrix/MatrixStaff.cpp @@ -0,0 +1,232 @@ +/* -*- 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 "MatrixStaff.h" +#include "misc/Debug.h" + +#include "base/BaseProperties.h" +#include "base/Composition.h" +#include "base/Event.h" +#include "base/Instrument.h" +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/SnapGrid.h" +#include "base/Staff.h" +#include "base/Track.h" +#include "base/ViewElement.h" +#include "base/SegmentMatrixHelper.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/general/GUIPalette.h" +#include "gui/general/LinedStaff.h" +#include "gui/rulers/DefaultVelocityColour.h" +#include "MatrixElement.h" +#include "MatrixView.h" +#include "MatrixVLayout.h" +#include <qcanvas.h> + + +namespace Rosegarden +{ + +MatrixStaff::MatrixStaff(QCanvas *canvas, + Segment *segment, + SnapGrid *snapGrid, + int id, + int vResolution, + MatrixView *view) : + LinedStaff(canvas, segment, snapGrid, id, vResolution, 1), + m_scaleFactor(2.0 / + Note(Note::Shortest).getDuration()), + m_view(view) +{} + +MatrixStaff::~MatrixStaff() +{ + // nothing +} + +int MatrixStaff::getLineCount() const +{ + // MATRIX_DEBUG << "MatrixStaff::getLineCount: isDrumMode " << m_view->isDrumMode() << ", key mapping " << (getKeyMapping() ? getKeyMapping()->getName() : "<none>") << endl; + + if (m_view->isDrumMode()) { + const MidiKeyMapping *km = getKeyMapping(); + if (km) + return km->getPitchExtent() + 1; + } + return MatrixVLayout::maxMIDIPitch + 2; +} + +int MatrixStaff::getLegerLineCount() const +{ + return 0; +} + +int MatrixStaff::getBottomLineHeight() const +{ + if (m_view->isDrumMode()) { + const MidiKeyMapping *km = getKeyMapping(); + if (km) + return km->getPitchForOffset(0); + } + return 0; +} + +int MatrixStaff::getHeightPerLine() const +{ + return 1; +} + +bool MatrixStaff::elementsInSpaces() const +{ + return true; +} + +bool MatrixStaff::showBeatLines() const +{ + return true; +} + +bool MatrixStaff::wrapEvent(Event* e) +{ + // Changed from "Note or Time signature" to just "Note" because + // there should be no time signature events in any ordinary + // segments, they're only in the composition's ref segment + + return e->isa(Note::EventType) && + Staff::wrapEvent(e); +} + +void +MatrixStaff::positionElements(timeT from, timeT to) +{ + MatrixElementList *mel = getViewElementList(); + + MatrixElementList::iterator beginAt = mel->findTime(from); + if (beginAt != mel->begin()) + --beginAt; + + MatrixElementList::iterator endAt = mel->findTime(to); + + for (MatrixElementList::iterator i = beginAt; i != endAt; ++i) { + positionElement(*i); + } +} + +void MatrixStaff::positionElement(ViewElement* vel) +{ + MatrixElement* el = dynamic_cast<MatrixElement*>(vel); + + // Memorize initial rectangle position. May be some overlap rectangles + // belonging to other notes are here and should be refreshed after + // current element is moved. + QRect initialRect; + bool rectWasVisible; + if (! m_view->isDrumMode()) + rectWasVisible = el->getVisibleRectangle(initialRect); + + LinedStaffCoords coords = getCanvasCoordsForLayoutCoords + (el->getLayoutX(), int(el->getLayoutY())); + + // Get velocity for colouring + // + using BaseProperties::VELOCITY; + long velocity = 127; + if (el->event()->has(VELOCITY)) + el->event()->get + <Int>(VELOCITY, velocity); + + el->setCanvas(m_canvas); + + // Is the event currently selected? Colour accordingly. + // + EventSelection *selection = m_view->getCurrentSelection(); + + if (selection && selection->contains(el->event())) + el->setColour(GUIPalette::getColour(GUIPalette::SelectedElement)); + else if (el->event()->has(BaseProperties::TRIGGER_SEGMENT_ID)) + el->setColour(Qt::gray); + else + el->setColour(DefaultVelocityColour::getInstance()->getColour(velocity)); + + el->setCanvasX(coords.first); + el->setCanvasY((double)coords.second); + + // Display overlaps + if (m_view->isDrumMode()) { + SegmentMatrixHelper helper(m_segment); + if (helper.isDrumColliding(el->event())) + el->setColour(GUIPalette::getColour(GUIPalette::MatrixOverlapBlock)); + } else { + el->drawOverlapRectangles(); + + // Refresh other overlap rectangles + if (rectWasVisible) el->redrawOverlaps(initialRect); + } + +} + +MatrixElement* +MatrixStaff::getElement(Event *event) +{ + ViewElementList::iterator i = findEvent(event); + if (i == m_viewElementList->end()) + return 0; + return dynamic_cast<MatrixElement*>(*i); +} + +void +MatrixStaff::eventRemoved(const Segment *segment, + Event *event) +{ + LinedStaff::eventRemoved(segment, event); + m_view->handleEventRemoved(event); +} + +ViewElement* +MatrixStaff::makeViewElement(Event* e) +{ + return new MatrixElement(e, m_view->isDrumMode()); +} + +const MidiKeyMapping* +MatrixStaff::getKeyMapping() const +{ + Composition *comp = getSegment().getComposition(); + if (!comp) + return 0; + TrackId trackId = getSegment().getTrack(); + Track *track = comp->getTrackById(trackId); + Instrument *instr = m_view->getDocument()->getStudio(). + getInstrumentById(track->getInstrument()); + if (!instr) + return 0; + return m_view->getKeyMapping(); +} + + +} diff --git a/src/gui/editors/matrix/MatrixStaff.h b/src/gui/editors/matrix/MatrixStaff.h new file mode 100644 index 0000000..cd0a9dc --- /dev/null +++ b/src/gui/editors/matrix/MatrixStaff.h @@ -0,0 +1,111 @@ + +/* -*- 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_MATRIXSTAFF_H_ +#define _RG_MATRIXSTAFF_H_ + +#include "base/Staff.h" +#include "gui/general/LinedStaff.h" +#include "base/Event.h" + + +class QCanvas; + + +namespace Rosegarden +{ + +class ViewElement; +class SnapGrid; +class Segment; +class MidiKeyMapping; +class MatrixView; +class MatrixElement; +class Event; + + +class MatrixStaff : public LinedStaff +{ +public: + MatrixStaff(QCanvas *canvas, + Segment *segment, + SnapGrid *snapGrid, + int id, + int vResolution, + MatrixView *view); + virtual ~MatrixStaff(); + +protected: + virtual int getLineCount() const; + virtual int getLegerLineCount() const; + virtual int getBottomLineHeight() const; + virtual int getHeightPerLine() const; + virtual bool elementsInSpaces() const; + virtual bool showBeatLines() const; + + const MidiKeyMapping *getKeyMapping() const; + + /** + * Override from Staff<T> + * Wrap only notes + */ + virtual bool wrapEvent(Event*); + + /** + * Override from Staff<T> + * Let tools know if their current element has gone + */ + virtual void eventRemoved(const Segment *, Event *); + + virtual ViewElement* makeViewElement(Event*); + +public: + LinedStaff::setResolution; + +// double getTimeScaleFactor() const { return m_scaleFactor * 2; } // TODO: GROSS HACK to enhance matrix resolution (see also in matrixview.cpp) - BREAKS MATRIX VIEW, see bug 1000595 + double getTimeScaleFactor() const { return m_scaleFactor; } + void setTimeScaleFactor(double f) { m_scaleFactor = f; } + + int getElementHeight() { return m_resolution; } + + virtual void positionElements(timeT from, + timeT to); + + virtual void positionElement(ViewElement*); + + // Get an element for an Event + // + MatrixElement* getElement(Event *event); + +private: + double m_scaleFactor; + + MatrixView *m_view; +}; + + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixTool.cpp b/src/gui/editors/matrix/MatrixTool.cpp new file mode 100644 index 0000000..b036559 --- /dev/null +++ b/src/gui/editors/matrix/MatrixTool.cpp @@ -0,0 +1,79 @@ +/* -*- 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 "MatrixTool.h" + +#include "gui/general/EditTool.h" +#include "MatrixView.h" +#include <kaction.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +MatrixTool::MatrixTool(const QString& menuName, MatrixView* parent) + : EditTool(menuName, parent), + m_mParentView(parent) +{} + +void +MatrixTool::slotSelectSelected() +{ + m_parentView->actionCollection()->action("select")->activate(); +} + +void +MatrixTool::slotMoveSelected() +{ + m_parentView->actionCollection()->action("move")->activate(); +} + +void +MatrixTool::slotEraseSelected() +{ + m_parentView->actionCollection()->action("erase")->activate(); +} + +void +MatrixTool::slotResizeSelected() +{ + m_parentView->actionCollection()->action("resize")->activate(); +} + +void +MatrixTool::slotDrawSelected() +{ + m_parentView->actionCollection()->action("draw")->activate(); +} + +const SnapGrid & +MatrixTool::getSnapGrid() const +{ + return m_mParentView->getSnapGrid(); +} + +} +#include "MatrixTool.moc" diff --git a/src/gui/editors/matrix/MatrixTool.h b/src/gui/editors/matrix/MatrixTool.h new file mode 100644 index 0000000..5127f57 --- /dev/null +++ b/src/gui/editors/matrix/MatrixTool.h @@ -0,0 +1,74 @@ + +/* -*- 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_MATRIXTOOL_H_ +#define _RG_MATRIXTOOL_H_ + +#include "gui/general/EditTool.h" + + +class QString; + + +namespace Rosegarden +{ + +class MatrixView; +class SnapGrid; + + +////////////////////////////////////////////////////////////////////// + +class MatrixTool : public EditTool +{ + Q_OBJECT + +public: +// virtual void ready(); + +protected slots: + + // For switching between tools on RMB + // + void slotSelectSelected(); + void slotMoveSelected(); + void slotEraseSelected(); + void slotResizeSelected(); + void slotDrawSelected(); + + const SnapGrid &getSnapGrid() const; + +protected: + MatrixTool(const QString& menuName, MatrixView*); + + //--------------- Data members --------------------------------- + + MatrixView* m_mParentView; +}; + + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixToolBox.cpp b/src/gui/editors/matrix/MatrixToolBox.cpp new file mode 100644 index 0000000..466cfea --- /dev/null +++ b/src/gui/editors/matrix/MatrixToolBox.cpp @@ -0,0 +1,87 @@ +/* -*- 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 "MatrixToolBox.h" + +#include "gui/general/EditToolBox.h" +#include "gui/general/EditTool.h" +#include "MatrixView.h" +#include "MatrixPainter.h" +#include "MatrixEraser.h" +#include "MatrixSelector.h" +#include "MatrixMover.h" +#include "MatrixResizer.h" + +#include <qstring.h> +#include <kmessagebox.h> + +namespace Rosegarden +{ + +MatrixToolBox::MatrixToolBox(MatrixView* parent) + : EditToolBox(parent), + m_mParentView(parent) +{} + +EditTool* MatrixToolBox::createTool(const QString& toolName) +{ + MatrixTool* tool = 0; + + QString toolNamelc = toolName.lower(); + + if (toolNamelc == MatrixPainter::ToolName) + + tool = new MatrixPainter(m_mParentView); + + else if (toolNamelc == MatrixEraser::ToolName) + + tool = new MatrixEraser(m_mParentView); + + else if (toolNamelc == MatrixSelector::ToolName) + + tool = new MatrixSelector(m_mParentView); + + else if (toolNamelc == MatrixMover::ToolName) + + tool = new MatrixMover(m_mParentView); + + else if (toolNamelc == MatrixResizer::ToolName) + + tool = new MatrixResizer(m_mParentView); + + else { + KMessageBox::error(0, QString("MatrixToolBox::createTool : unrecognised toolname %1 (%2)") + .arg(toolName).arg(toolNamelc)); + return 0; + } + + m_tools.insert(toolName, tool); + + return tool; + +} + +} +#include "MatrixToolBox.moc" diff --git a/src/gui/editors/matrix/MatrixToolBox.h b/src/gui/editors/matrix/MatrixToolBox.h new file mode 100644 index 0000000..3bf0818 --- /dev/null +++ b/src/gui/editors/matrix/MatrixToolBox.h @@ -0,0 +1,60 @@ + +/* -*- 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_MATRIXTOOLBOX_H_ +#define _RG_MATRIXTOOLBOX_H_ + +#include "gui/general/EditToolBox.h" + +class QString; + + +namespace Rosegarden +{ + +class EditTool; +class MatrixView; +class MatrixElement; +class MatrixStaff; + +class MatrixToolBox : public EditToolBox +{ + Q_OBJECT +public: + MatrixToolBox(MatrixView* parent); + +protected: + + virtual EditTool* createTool(const QString& toolName); + + //--------------- Data members --------------------------------- + + MatrixView* m_mParentView; +}; + + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixVLayout.cpp b/src/gui/editors/matrix/MatrixVLayout.cpp new file mode 100644 index 0000000..aadcdf3 --- /dev/null +++ b/src/gui/editors/matrix/MatrixVLayout.cpp @@ -0,0 +1,100 @@ +/* -*- 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 "MatrixVLayout.h" +#include "misc/Debug.h" + +#include "base/BaseProperties.h" +#include "base/LayoutEngine.h" +#include "base/Staff.h" +#include "MatrixElement.h" +#include "MatrixStaff.h" + + +namespace Rosegarden +{ + +MatrixVLayout::MatrixVLayout() +{} + +MatrixVLayout::~MatrixVLayout() +{} + +void MatrixVLayout::reset() +{} + +void MatrixVLayout::resetStaff(Staff&, timeT, timeT) +{} + +void MatrixVLayout::scanStaff(Staff& staffBase, + timeT startTime, timeT endTime) +{ + MatrixStaff& staff = dynamic_cast<MatrixStaff&>(staffBase); + + using BaseProperties::PITCH; + + MatrixElementList *notes = staff.getViewElementList(); + + MatrixElementList::iterator from = notes->begin(); + MatrixElementList::iterator to = notes->end(); + MatrixElementList::iterator i; + + if (startTime != endTime) { + from = notes->findNearestTime(startTime); + if (from == notes->end()) + from = notes->begin(); + to = notes->findTime(endTime); + } + + MATRIX_DEBUG << "MatrixVLayout::scanStaff : id = " + << staff.getId() << endl; + + + for (i = from; i != to; ++i) { + + MatrixElement *el = dynamic_cast<MatrixElement*>((*i)); + + if (!el->isNote()) + continue; // notes only + + long pitch = 60; + el->event()->get + <Int>(PITCH, pitch); + + int y = staff.getLayoutYForHeight(pitch) - staff.getElementHeight() / 2; + + el->setLayoutY(y); + el->setHeight(staff.getElementHeight()); + } + +} + +void MatrixVLayout::finishLayout(timeT, timeT) +{} + +const int MatrixVLayout::minMIDIPitch = 0; +const int MatrixVLayout::maxMIDIPitch = 127; + +} diff --git a/src/gui/editors/matrix/MatrixVLayout.h b/src/gui/editors/matrix/MatrixVLayout.h new file mode 100644 index 0000000..a33e0d1 --- /dev/null +++ b/src/gui/editors/matrix/MatrixVLayout.h @@ -0,0 +1,91 @@ + +/* -*- 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_MATRIXVLAYOUT_H_ +#define _RG_MATRIXVLAYOUT_H_ + +#include "base/LayoutEngine.h" +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Staff; + + +class MatrixVLayout : public VerticalLayoutEngine +{ +public: + MatrixVLayout(); + + virtual ~MatrixVLayout(); + + /** + * Resets internal data stores for all staffs + */ + virtual void reset(); + + /** + * Resets internal data stores for a specific staff + */ + virtual void resetStaff(Staff &staff, + timeT = 0, + timeT = 0); + + /** + * Precomputes layout data for a single staff, updating any + * internal data stores associated with that staff and updating + * any layout-related properties in the events on the staff's + * segment. + */ + virtual void scanStaff(Staff &staff, + timeT = 0, + timeT = 0); + + /** + * Computes any layout data that may depend on the results of + * scanning more than one staff. This may mean doing most of + * the layout (likely for horizontal layout) or nothing at all + * (likely for vertical layout). + */ + virtual void finishLayout(timeT = 0, + timeT = 0); + + static const int minMIDIPitch; + static const int maxMIDIPitch; + +protected: + //--------------- Data members --------------------------------- + + +}; + + +} + +#endif diff --git a/src/gui/editors/matrix/MatrixView.cpp b/src/gui/editors/matrix/MatrixView.cpp new file mode 100644 index 0000000..38abe20 --- /dev/null +++ b/src/gui/editors/matrix/MatrixView.cpp @@ -0,0 +1,3076 @@ +/* -*- 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 "MatrixView.h" + +#include "base/BaseProperties.h" +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/AudioLevel.h" +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "base/Event.h" +#include "base/Instrument.h" +#include "base/LayoutEngine.h" +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "base/PropertyName.h" +#include "base/BasicQuantizer.h" +#include "base/LegatoQuantizer.h" +#include "base/RealTime.h" +#include "base/RulerScale.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 "commands/edit/ChangeVelocityCommand.h" +#include "commands/edit/ClearTriggersCommand.h" +#include "commands/edit/CollapseNotesCommand.h" +#include "commands/edit/CopyCommand.h" +#include "commands/edit/CutCommand.h" +#include "commands/edit/EraseCommand.h" +#include "commands/edit/EventQuantizeCommand.h" +#include "commands/edit/EventUnquantizeCommand.h" +#include "commands/edit/PasteEventsCommand.h" +#include "commands/edit/SelectionPropertyCommand.h" +#include "commands/edit/SetTriggerCommand.h" +#include "commands/matrix/MatrixInsertionCommand.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "gui/application/RosegardenGUIApp.h" +#include "gui/dialogs/EventFilterDialog.h" +#include "gui/dialogs/EventParameterDialog.h" +#include "gui/dialogs/QuantizeDialog.h" +#include "gui/dialogs/TriggerSegmentDialog.h" +#include "gui/editors/guitar/Chord.h" +#include "gui/editors/notation/NotationElement.h" +#include "gui/editors/notation/NotationStrings.h" +#include "gui/editors/notation/NotePixmapFactory.h" +#include "gui/editors/parameters/InstrumentParameterBox.h" +#include "gui/rulers/StandardRuler.h" +#include "gui/general/ActiveItem.h" +#include "gui/general/EditViewBase.h" +#include "gui/general/EditView.h" +#include "gui/general/GUIPalette.h" +#include "gui/general/MidiPitchLabel.h" +#include "gui/kdeext/KTmpStatusMsg.h" +#include "gui/rulers/ChordNameRuler.h" +#include "gui/rulers/LoopRuler.h" +#include "gui/rulers/PercussionPitchRuler.h" +#include "gui/rulers/PitchRuler.h" +#include "gui/rulers/PropertyBox.h" +#include "gui/rulers/PropertyViewRuler.h" +#include "gui/rulers/TempoRuler.h" +#include "gui/studio/StudioControl.h" +#include "gui/widgets/QDeferScrollView.h" +#include "MatrixCanvasView.h" +#include "MatrixElement.h" +#include "MatrixEraser.h" +#include "MatrixHLayout.h" +#include "MatrixMover.h" +#include "MatrixPainter.h" +#include "MatrixResizer.h" +#include "MatrixSelector.h" +#include "MatrixStaff.h" +#include "MatrixToolBox.h" +#include "MatrixVLayout.h" +#include "PianoKeyboard.h" +#include "sound/MappedEvent.h" +#include "sound/SequencerDataBlock.h" +#include <klocale.h> +#include <kstddirs.h> +#include <kaction.h> +#include <kcombobox.h> +#include <kconfig.h> +#include <kdockwidget.h> +#include <kglobal.h> +#include <kmessagebox.h> +#include <kstatusbar.h> +#include <ktoolbar.h> +#include <kxmlguiclient.h> +#include <qcanvas.h> +#include <qcursor.h> +#include <qdialog.h> +#include <qlayout.h> +#include <qiconset.h> +#include <qlabel.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qscrollview.h> +#include <qsize.h> +#include <qslider.h> +#include <qstring.h> +#include <qwidget.h> +#include <qwmatrix.h> + + +namespace Rosegarden +{ + +static double xorigin = 0.0; + + +MatrixView::MatrixView(RosegardenGUIDoc *doc, + std::vector<Segment *> segments, + QWidget *parent, + bool drumMode) + : EditView(doc, segments, 3, parent, "matrixview"), + m_hlayout(&doc->getComposition()), + m_referenceRuler(new ZoomableMatrixHLayoutRulerScale(m_hlayout)), + m_vlayout(), + m_snapGrid(new SnapGrid(&m_hlayout)), + m_lastEndMarkerTime(0), + m_hoveredOverAbsoluteTime(0), + m_hoveredOverNoteName(0), + m_selectionCounter(0), + m_insertModeLabel(0), + m_haveHoveredOverNote(false), + m_previousEvPitch(0), + m_dockLeft(0), + m_canvasView(0), + m_pianoView(0), + m_localMapping(0), + m_lastNote(0), + m_quantizations(BasicQuantizer::getStandardQuantizations()), + m_chordNameRuler(0), + m_tempoRuler(0), + m_playTracking(true), + m_dockVisible(true), + m_drumMode(drumMode), + m_mouseInCanvasView(false) +{ + RG_DEBUG << "MatrixView ctor: drumMode " << drumMode << "\n"; + + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/toolbar"); + QPixmap matrixPixmap(pixmapDir + "/matrix.xpm"); + + m_dockLeft = createDockWidget("params dock", matrixPixmap, 0L, + i18n("Instrument Parameters")); + m_dockLeft->manualDock(m_mainDockWidget, // dock target + KDockWidget::DockLeft, // dock site + 20); // relation target/this (in percent) + + connect(m_dockLeft, SIGNAL(iMBeingClosed()), + this, SLOT(slotParametersClosed())); + connect(m_dockLeft, SIGNAL(hasUndocked()), + this, SLOT(slotParametersClosed())); + // Apparently, hasUndocked() is emitted when the dock widget's + // 'close' button on the dock handle is clicked. + connect(m_mainDockWidget, SIGNAL(docking(KDockWidget*, KDockWidget::DockPosition)), + this, SLOT(slotParametersDockedBack(KDockWidget*, KDockWidget::DockPosition))); + + Composition &comp = doc->getComposition(); + + m_toolBox = new MatrixToolBox(this); + + initStatusBar(); + + connect(m_toolBox, SIGNAL(showContextHelp(const QString &)), + this, SLOT(slotToolHelpChanged(const QString &))); + + QCanvas *tCanvas = new QCanvas(this); + + m_config->setGroup(MatrixViewConfigGroup); + if (m_config->readBoolEntry("backgroundtextures-1.6-plus", true)) { + QPixmap background; + QString pixmapDir = + KGlobal::dirs()->findResource("appdata", "pixmaps/"); + // We now use a lined background for the non-percussion matrix, + // suggested and supplied by Alessandro Preziosi + QString backgroundPixmap = isDrumMode() ? "bg-paper-white.xpm" : "bg-matrix-lines.xpm"; + if (background.load(QString("%1/misc/%2"). + arg(pixmapDir, backgroundPixmap))) { + tCanvas->setBackgroundPixmap(background); + } + } + + MATRIX_DEBUG << "MatrixView : creating staff\n"; + + Track *track = + comp.getTrackById(segments[0]->getTrack()); + + Instrument *instr = getDocument()->getStudio(). + getInstrumentById(track->getInstrument()); + + int resolution = 8; + + if (isDrumMode() && instr && instr->getKeyMapping()) { + resolution = 11; + } + + for (unsigned int i = 0; i < segments.size(); ++i) { + m_staffs.push_back(new MatrixStaff(tCanvas, + segments[i], + m_snapGrid, + i, + resolution, + this)); + // staff has one too many rows to avoid a half-row at the top: + m_staffs[i]->setY( -resolution / 2); + //!!! if (isDrumMode()) m_staffs[i]->setX(resolution); + if (i == 0) + m_staffs[i]->setCurrent(true); + } + + MATRIX_DEBUG << "MatrixView : creating canvas view\n"; + + const MidiKeyMapping *mapping = 0; + + if (instr) { + mapping = instr->getKeyMapping(); + if (mapping) { + RG_DEBUG << "MatrixView: Instrument has key mapping: " + << mapping->getName() << endl; + m_localMapping = new MidiKeyMapping(*mapping); + extendKeyMapping(); + } else { + RG_DEBUG << "MatrixView: Instrument has no key mapping\n"; + } + } + + m_pianoView = new QDeferScrollView(getCentralWidget()); + + QWidget* vport = m_pianoView->viewport(); + + if (isDrumMode() && mapping && + !m_localMapping->getMap().empty()) { + m_pitchRuler = new PercussionPitchRuler(vport, + m_localMapping, + resolution); // line spacing + } else { + m_pitchRuler = new PianoKeyboard(vport); + } + + m_pianoView->setVScrollBarMode(QScrollView::AlwaysOff); + m_pianoView->setHScrollBarMode(QScrollView::AlwaysOff); + m_pianoView->addChild(m_pitchRuler); + m_pianoView->setFixedWidth(m_pianoView->contentsWidth()); + + m_grid->addWidget(m_pianoView, CANVASVIEW_ROW, 1); + + m_parameterBox = new InstrumentParameterBox(getDocument(), m_dockLeft); + m_dockLeft->setWidget(m_parameterBox); + + RosegardenGUIApp *app = RosegardenGUIApp::self(); + connect(app, + SIGNAL(pluginSelected(InstrumentId, int, int)), + m_parameterBox, + SLOT(slotPluginSelected(InstrumentId, int, int))); + connect(app, + SIGNAL(pluginBypassed(InstrumentId, int, bool)), + m_parameterBox, + SLOT(slotPluginBypassed(InstrumentId, int, bool))); + connect(app, + SIGNAL(instrumentParametersChanged(InstrumentId)), + m_parameterBox, + SLOT(slotInstrumentParametersChanged(InstrumentId))); + connect(m_parameterBox, + SIGNAL(instrumentParametersChanged(InstrumentId)), + app, + SIGNAL(instrumentParametersChanged(InstrumentId))); + connect(m_parameterBox, + SIGNAL(selectPlugin(QWidget *, InstrumentId, int)), + app, + SLOT(slotShowPluginDialog(QWidget *, InstrumentId, int))); + connect(m_parameterBox, + SIGNAL(showPluginGUI(InstrumentId, int)), + app, + SLOT(slotShowPluginGUI(InstrumentId, int))); + connect(parent, // RosegardenGUIView + SIGNAL(checkTrackAssignments()), + this, + SLOT(slotCheckTrackAssignments())); + + // Assign the instrument + // + m_parameterBox->useInstrument(instr); + + if (m_drumMode) { + connect(m_parameterBox, + SIGNAL(instrumentPercussionSetChanged(Instrument *)), + this, + SLOT(slotPercussionSetChanged(Instrument *))); + } + + // Set the snap grid from the stored size in the segment + // + int snapGridSize = m_staffs[0]->getSegment().getSnapGridSize(); + + MATRIX_DEBUG << "MatrixView : Snap Grid Size = " << snapGridSize << endl; + + if (snapGridSize != -1) { + m_snapGrid->setSnapTime(snapGridSize); + } else { + m_config->setGroup(MatrixViewConfigGroup); + snapGridSize = m_config->readNumEntry + ("Snap Grid Size", SnapGrid::SnapToBeat); + m_snapGrid->setSnapTime(snapGridSize); + m_staffs[0]->getSegment().setSnapGridSize(snapGridSize); + } + + m_canvasView = new MatrixCanvasView(*m_staffs[0], + m_snapGrid, + m_drumMode, + tCanvas, + getCentralWidget()); + setCanvasView(m_canvasView); + + // do this after we have a canvas + setupActions(); + setupAddControlRulerMenu(); + + stateChanged("parametersbox_closed", KXMLGUIClient::StateReverse); + + // tool bars + initActionsToolbar(); + initZoomToolbar(); + + // Connect vertical scrollbars between matrix and piano + // + connect(m_canvasView->verticalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(slotVerticalScrollPianoKeyboard(int))); + + connect(m_canvasView->verticalScrollBar(), SIGNAL(sliderMoved(int)), + this, SLOT(slotVerticalScrollPianoKeyboard(int))); + + connect(m_canvasView, SIGNAL(zoomIn()), this, SLOT(slotZoomIn())); + connect(m_canvasView, SIGNAL(zoomOut()), this, SLOT(slotZoomOut())); + + connect(m_pianoView, SIGNAL(gotWheelEvent(QWheelEvent*)), + m_canvasView, SLOT(slotExternalWheelEvent(QWheelEvent*))); + + // ensure the piano keyb keeps the right margins when the user toggles + // the canvas view rulers + // + connect(m_canvasView, SIGNAL(bottomWidgetHeightChanged(int)), + this, SLOT(slotCanvasBottomWidgetHeightChanged(int))); + + connect(m_canvasView, SIGNAL(mouseEntered()), + this, SLOT(slotMouseEnteredCanvasView())); + + connect(m_canvasView, SIGNAL(mouseLeft()), + this, SLOT(slotMouseLeftCanvasView())); + + /* + QObject::connect + (getCanvasView(), SIGNAL(activeItemPressed(QMouseEvent*, QCanvasItem*)), + this, SLOT (activeItemPressed(QMouseEvent*, QCanvasItem*))); + */ + + QObject::connect + (getCanvasView(), + SIGNAL(mousePressed(timeT, + int, QMouseEvent*, MatrixElement*)), + this, + SLOT(slotMousePressed(timeT, + int, QMouseEvent*, MatrixElement*))); + + QObject::connect + (getCanvasView(), + SIGNAL(mouseMoved(timeT, int, QMouseEvent*)), + this, + SLOT(slotMouseMoved(timeT, int, QMouseEvent*))); + + QObject::connect + (getCanvasView(), + SIGNAL(mouseReleased(timeT, int, QMouseEvent*)), + this, + SLOT(slotMouseReleased(timeT, int, QMouseEvent*))); + + QObject::connect + (getCanvasView(), SIGNAL(hoveredOverNoteChanged(int, bool, timeT)), + this, SLOT(slotHoveredOverNoteChanged(int, bool, timeT))); + + QObject::connect + (m_pitchRuler, SIGNAL(hoveredOverKeyChanged(unsigned int)), + this, SLOT (slotHoveredOverKeyChanged(unsigned int))); + + QObject::connect + (m_pitchRuler, SIGNAL(keyPressed(unsigned int, bool)), + this, SLOT (slotKeyPressed(unsigned int, bool))); + + QObject::connect + (m_pitchRuler, SIGNAL(keySelected(unsigned int, bool)), + this, SLOT (slotKeySelected(unsigned int, bool))); + + QObject::connect + (m_pitchRuler, SIGNAL(keyReleased(unsigned int, bool)), + this, SLOT (slotKeyReleased(unsigned int, bool))); + + QObject::connect + (getCanvasView(), SIGNAL(hoveredOverAbsoluteTimeChanged(unsigned int)), + this, SLOT (slotHoveredOverAbsoluteTimeChanged(unsigned int))); + + QObject::connect + (doc, SIGNAL(pointerPositionChanged(timeT)), + this, SLOT(slotSetPointerPosition(timeT))); + + MATRIX_DEBUG << "MatrixView : applying layout\n"; + + bool layoutApplied = applyLayout(); + if (!layoutApplied) + KMessageBox::sorry(0, i18n("Couldn't apply piano roll layout")); + else { + MATRIX_DEBUG << "MatrixView : rendering elements\n"; + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + + m_staffs[i]->positionAllElements(); + m_staffs[i]->getSegment().getRefreshStatus + (m_segmentsRefreshStatusIds[i]).setNeedsRefresh(false); + } + } + + StandardRuler *topStandardRuler = new StandardRuler(getDocument(), + &m_hlayout, int(xorigin), 25, + false, getCentralWidget()); + topStandardRuler->setSnapGrid(m_snapGrid); + setTopStandardRuler(topStandardRuler); + + StandardRuler *bottomStandardRuler = new StandardRuler(getDocument(), + &m_hlayout, 0, 25, + true, getBottomWidget()); + bottomStandardRuler->setSnapGrid(m_snapGrid); + setBottomStandardRuler(bottomStandardRuler); + + topStandardRuler->connectRulerToDocPointer(doc); + bottomStandardRuler->connectRulerToDocPointer(doc); + + // Disconnect the default connections for this signal from the + // top ruler, and connect our own instead + + QObject::disconnect + (topStandardRuler->getLoopRuler(), + SIGNAL(setPointerPosition(timeT)), 0, 0); + + QObject::connect + (topStandardRuler->getLoopRuler(), + SIGNAL(setPointerPosition(timeT)), + this, SLOT(slotSetInsertCursorPosition(timeT))); + + QObject::connect + (topStandardRuler, + SIGNAL(dragPointerToPosition(timeT)), + this, SLOT(slotSetInsertCursorPosition(timeT))); + + topStandardRuler->getLoopRuler()->setBackgroundColor + (GUIPalette::getColour(GUIPalette::InsertCursorRuler)); + + connect(topStandardRuler->getLoopRuler(), SIGNAL(startMouseMove(int)), + m_canvasView, SLOT(startAutoScroll(int))); + connect(topStandardRuler->getLoopRuler(), SIGNAL(stopMouseMove()), + m_canvasView, SLOT(stopAutoScroll())); + + connect(bottomStandardRuler->getLoopRuler(), SIGNAL(startMouseMove(int)), + m_canvasView, SLOT(startAutoScroll(int))); + connect(bottomStandardRuler->getLoopRuler(), SIGNAL(stopMouseMove()), + m_canvasView, SLOT(stopAutoScroll())); + connect(m_bottomStandardRuler, SIGNAL(dragPointerToPosition(timeT)), + this, SLOT(slotSetPointerPosition(timeT))); + + // Force height for the moment + // + m_pitchRuler->setFixedHeight(canvas()->height()); + + + updateViewCaption(); + + // Add a velocity ruler + // + //!!! addPropertyViewRuler(BaseProperties::VELOCITY); + + m_chordNameRuler = new ChordNameRuler + (m_referenceRuler, doc, segments, 0, 20, getCentralWidget()); + m_chordNameRuler->setStudio(&getDocument()->getStudio()); + addRuler(m_chordNameRuler); + + m_tempoRuler = new TempoRuler + (m_referenceRuler, doc, this, 0, 24, false, getCentralWidget()); + static_cast<TempoRuler *>(m_tempoRuler)->connectSignals(); + addRuler(m_tempoRuler); + + stateChanged("have_selection", KXMLGUIClient::StateReverse); + slotTestClipboard(); + + timeT start = doc->getComposition().getLoopStart(); + timeT end = doc->getComposition().getLoopEnd(); + m_topStandardRuler->getLoopRuler()->slotSetLoopMarker(start, end); + m_bottomStandardRuler->getLoopRuler()->slotSetLoopMarker(start, end); + + setCurrentSelection(0, false); + + // Change this if the matrix view ever has its own page + // in the config dialog. + setConfigDialogPageIndex(0); + + // default zoom + m_config->setGroup(MatrixViewConfigGroup); + double zoom = m_config->readDoubleNumEntry("Zoom Level", + m_hZoomSlider->getCurrentSize()); + m_hZoomSlider->setSize(zoom); + m_referenceRuler->setHScaleFactor(zoom); + + // Scroll view to centre middle-C and warp to pointer position + // + m_canvasView->scrollBy(0, m_staffs[0]->getCanvasYForHeight(60) / 2); + + slotSetPointerPosition(comp.getPosition()); + + // All toolbars should be created before this is called + setAutoSaveSettings("MatrixView", true); + + readOptions(); + setOutOfCtor(); + + // Property and Control Rulers + // + if (getCurrentSegment()->getViewFeatures()) + slotShowVelocityControlRuler(); + setupControllerTabs(); + + setRewFFwdToAutoRepeat(); + slotCompositionStateUpdate(); +} + +MatrixView::~MatrixView() +{ + slotSaveOptions(); + + delete m_chordNameRuler; + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + delete m_staffs[i]; // this will erase all "notes" canvas items + } + + // This looks silly but the reason is that on destruction of the + // MatrixCanvasView, setCanvas() is called (this is in + // ~QCanvasView so we can't do anything about it). This calls + // QCanvasView::updateContentsSize(), which in turn updates the + // view's scrollbars, hence calling QScrollBar::setValue(), and + // sending the QSCrollbar::valueChanged() signal. But we have a + // slot connected to that signal + // (MatrixView::slotVerticalScrollPianoKeyboard), which scrolls + // the pianoView. However at this stage the pianoView has already + // been deleted, so a likely outcome is a crash. + // + // A solution is to zero out m_pianoView here, and to check if + // it's non null in slotVerticalScrollPianoKeyboard. + // + m_pianoView = 0; + + delete m_snapGrid; + + if (m_localMapping) + delete m_localMapping; +} + +void MatrixView::slotSaveOptions() +{ + m_config->setGroup(MatrixViewConfigGroup); + + m_config->writeEntry("Show Chord Name Ruler", getToggleAction("show_chords_ruler")->isChecked()); + m_config->writeEntry("Show Tempo Ruler", getToggleAction("show_tempo_ruler")->isChecked()); + m_config->writeEntry("Show Parameters", m_dockVisible); + //getToggleAction("m_dockLeft->isVisible()); + + m_config->sync(); +} + +void MatrixView::readOptions() +{ + EditView::readOptions(); + m_config->setGroup(MatrixViewConfigGroup); + + bool opt = false; + + opt = m_config->readBoolEntry("Show Chord Name Ruler", false); + getToggleAction("show_chords_ruler")->setChecked(opt); + slotToggleChordsRuler(); + + opt = m_config->readBoolEntry("Show Tempo Ruler", true); + getToggleAction("show_tempo_ruler")->setChecked(opt); + slotToggleTempoRuler(); + + opt = m_config->readBoolEntry("Show Parameters", true); + if (!opt) { + m_dockLeft->undock(); + m_dockLeft->hide(); + stateChanged("parametersbox_closed", KXMLGUIClient::StateNoReverse); + m_dockVisible = false; + } + +} + +void MatrixView::setupActions() +{ + EditViewBase::setupActions("matrix.rc"); + EditView::setupActions(); + + // + // Edition tools (eraser, selector...) + // + KRadioAction* toolAction = 0; + + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QIconSet icon(QPixmap(pixmapDir + "/toolbar/select.xpm")); + + toolAction = new KRadioAction(i18n("&Select and Edit"), icon, Key_F2, + this, SLOT(slotSelectSelected()), + actionCollection(), "select"); + toolAction->setExclusiveGroup("tools"); + + toolAction = new KRadioAction(i18n("&Draw"), "pencil", Key_F3, + this, SLOT(slotPaintSelected()), + actionCollection(), "draw"); + toolAction->setExclusiveGroup("tools"); + + toolAction = new KRadioAction(i18n("&Erase"), "eraser", Key_F4, + this, SLOT(slotEraseSelected()), + actionCollection(), "erase"); + toolAction->setExclusiveGroup("tools"); + + toolAction = new KRadioAction(i18n("&Move"), "move", Key_F5, + this, SLOT(slotMoveSelected()), + actionCollection(), "move"); + toolAction->setExclusiveGroup("tools"); + + QCanvasPixmap pixmap(pixmapDir + "/toolbar/resize.xpm"); + icon = QIconSet(pixmap); + toolAction = new KRadioAction(i18n("Resi&ze"), icon, Key_F6, + this, SLOT(slotResizeSelected()), + actionCollection(), "resize"); + toolAction->setExclusiveGroup("tools"); + + 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); + + pixmap.load(pixmapDir + "/toolbar/step_by_step.xpm"); + icon = QIconSet(pixmap); + new KToggleAction(i18n("Ste&p Recording"), icon, 0, this, + SLOT(slotToggleStepByStep()), actionCollection(), + "toggle_step_by_step"); + + pixmap.load(pixmapDir + "/toolbar/quantize.png"); + icon = QIconSet(pixmap); + new KAction(EventQuantizeCommand::getGlobalName(), icon, Key_Equal, this, + SLOT(slotTransformsQuantize()), actionCollection(), + "quantize"); + + new KAction(i18n("Repeat Last Quantize"), Key_Plus, this, + SLOT(slotTransformsRepeatQuantize()), actionCollection(), + "repeat_quantize"); + + new KAction(CollapseNotesCommand::getGlobalName(), Key_Equal + CTRL, this, + SLOT(slotTransformsCollapseNotes()), actionCollection(), + "collapse_notes"); + + new KAction(i18n("&Legato"), Key_Minus, this, + SLOT(slotTransformsLegato()), actionCollection(), + "legatoize"); + + new KAction(ChangeVelocityCommand::getGlobalName(10), 0, + Key_Up + SHIFT, this, + SLOT(slotVelocityUp()), actionCollection(), + "velocity_up"); + + new KAction(ChangeVelocityCommand::getGlobalName( -10), 0, + Key_Down + SHIFT, this, + SLOT(slotVelocityDown()), actionCollection(), + "velocity_down"); + + new KAction(i18n("Set to Current Velocity"), 0, this, + SLOT(slotSetVelocitiesToCurrent()), actionCollection(), + "set_to_current_velocity"); + + new KAction(i18n("Set Event &Velocities..."), 0, this, + SLOT(slotSetVelocities()), actionCollection(), + "set_velocities"); + + new KAction(i18n("Trigger Se&gment..."), 0, this, + SLOT(slotTriggerSegment()), actionCollection(), + "trigger_segment"); + + new KAction(i18n("Remove Triggers..."), 0, this, + SLOT(slotRemoveTriggers()), actionCollection(), + "remove_trigger"); + + new KAction(i18n("Select &All"), Key_A + CTRL, this, + SLOT(slotSelectAll()), actionCollection(), + "select_all"); + + new KAction(i18n("&Delete"), Key_Delete, this, + SLOT(slotEditDelete()), actionCollection(), + "delete"); + + 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"); + + 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"); + + 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"); + + // icon = QIconSet(QCanvasPixmap(pixmapDir + "/toolbar/eventfilter.xpm")); + new KAction(i18n("&Filter Selection"), "filter", Key_F + CTRL, this, + SLOT(slotFilterSelection()), actionCollection(), + "filter_selection"); + + timeT crotchetDuration = Note(Note::Crotchet).getDuration(); + m_snapValues.push_back(SnapGrid::NoSnap); + m_snapValues.push_back(SnapGrid::SnapToUnit); + m_snapValues.push_back(crotchetDuration / 16); + m_snapValues.push_back(crotchetDuration / 12); + m_snapValues.push_back(crotchetDuration / 8); + m_snapValues.push_back(crotchetDuration / 6); + m_snapValues.push_back(crotchetDuration / 4); + m_snapValues.push_back(crotchetDuration / 3); + m_snapValues.push_back(crotchetDuration / 2); + m_snapValues.push_back(crotchetDuration); + m_snapValues.push_back((crotchetDuration * 3) / 2); + m_snapValues.push_back(crotchetDuration * 2); + m_snapValues.push_back(SnapGrid::SnapToBeat); + m_snapValues.push_back(SnapGrid::SnapToBar); + + for (unsigned int i = 0; i < m_snapValues.size(); i++) { + + timeT d = m_snapValues[i]; + + if (d == SnapGrid::NoSnap) { + new KAction(i18n("&No Snap"), 0, this, + SLOT(slotSetSnapFromAction()), + actionCollection(), "snap_none"); + } else if (d == SnapGrid::SnapToUnit) { + } else if (d == SnapGrid::SnapToBeat) { + new KAction(i18n("Snap to Bea&t"), Key_1, this, + SLOT(slotSetSnapFromAction()), + actionCollection(), "snap_beat"); + } else if (d == SnapGrid::SnapToBar) { + new KAction(i18n("Snap to &Bar"), Key_5, this, + SLOT(slotSetSnapFromAction()), + actionCollection(), "snap_bar"); + } else { + + timeT err = 0; + QString label = NotationStrings::makeNoteMenuLabel(d, true, err); + QPixmap pixmap = NotePixmapFactory::toQPixmap + (NotePixmapFactory::makeNoteMenuPixmap(d, err)); + + KShortcut cut = 0; + if (d == crotchetDuration / 16) cut = Key_0; + else if (d == crotchetDuration / 8) cut = Key_3; + else if (d == crotchetDuration / 4) cut = Key_6; + else if (d == crotchetDuration / 2) cut = Key_8; + else if (d == crotchetDuration) cut = Key_4; + else if (d == crotchetDuration * 2) cut = Key_2; + + QString actionName = QString("snap_%1").arg(int((crotchetDuration * 4) / d)); + if (d == (crotchetDuration * 3) / 2) actionName = "snap_3"; + new KAction(i18n("Snap to %1").arg(label), pixmap, cut, this, + SLOT(slotSetSnapFromAction()), actionCollection(), + actionName); + } + } + + // + // Settings menu + // + new KAction(i18n("Show Instrument Parameters"), 0, this, + SLOT(slotDockParametersBack()), + actionCollection(), + "show_inst_parameters"); + + new KToggleAction(i18n("Show Ch&ord Name Ruler"), 0, this, + SLOT(slotToggleChordsRuler()), + actionCollection(), "show_chords_ruler"); + + new KToggleAction(i18n("Show &Tempo Ruler"), 0, this, + SLOT(slotToggleTempoRuler()), + actionCollection(), "show_tempo_ruler"); + + createGUI(getRCFileName(), false); + + if (getSegmentsOnlyRestsAndClefs()) + actionCollection()->action("draw")->activate(); + else + actionCollection()->action("select")->activate(); +} + +bool +MatrixView::isInChordMode() +{ + return ((KToggleAction *)actionCollection()->action("chord_mode"))-> + isChecked(); +} + +void MatrixView::slotDockParametersBack() +{ + m_dockLeft->dockBack(); +} + +void MatrixView::slotParametersClosed() +{ + stateChanged("parametersbox_closed"); + m_dockVisible = false; +} + +void MatrixView::slotParametersDockedBack(KDockWidget* dw, KDockWidget::DockPosition) +{ + if (dw == m_dockLeft) { + stateChanged("parametersbox_closed", KXMLGUIClient::StateReverse); + m_dockVisible = true; + } +} + +void MatrixView::slotCheckTrackAssignments() +{ + Track *track = + m_staffs[0]->getSegment().getComposition()-> + getTrackById(m_staffs[0]->getSegment().getTrack()); + + Instrument *instr = getDocument()->getStudio(). + getInstrumentById(track->getInstrument()); + + m_parameterBox->useInstrument(instr); +} + +void MatrixView::initStatusBar() +{ + KStatusBar* sb = statusBar(); + + m_hoveredOverAbsoluteTime = new QLabel(sb); + m_hoveredOverNoteName = new QLabel(sb); + + m_hoveredOverAbsoluteTime->setMinimumWidth(175); + m_hoveredOverNoteName->setMinimumWidth(65); + + sb->addWidget(m_hoveredOverAbsoluteTime); + sb->addWidget(m_hoveredOverNoteName); + + m_insertModeLabel = new QLabel(sb); + m_insertModeLabel->setMinimumWidth(20); + sb->addWidget(m_insertModeLabel); + + sb->insertItem(KTmpStatusMsg::getDefaultMsg(), + KTmpStatusMsg::getDefaultId(), 1); + sb->setItemAlignment(KTmpStatusMsg::getDefaultId(), + AlignLeft | AlignVCenter); + + m_selectionCounter = new QLabel(sb); + sb->addWidget(m_selectionCounter); +} + +void MatrixView::slotToolHelpChanged(const QString &s) +{ + QString msg = " " + s; + if (m_toolContextHelp == msg) return; + m_toolContextHelp = msg; + + m_config->setGroup(GeneralOptionsConfigGroup); + if (!m_config->readBoolEntry("toolcontexthelp", true)) return; + + if (m_mouseInCanvasView) statusBar()->changeItem(m_toolContextHelp, 1); +} + +void MatrixView::slotMouseEnteredCanvasView() +{ + m_config->setGroup(GeneralOptionsConfigGroup); + if (!m_config->readBoolEntry("toolcontexthelp", true)) return; + + m_mouseInCanvasView = true; + statusBar()->changeItem(m_toolContextHelp, 1); +} + +void MatrixView::slotMouseLeftCanvasView() +{ + m_mouseInCanvasView = false; + statusBar()->changeItem(KTmpStatusMsg::getDefaultMsg(), 1); +} + +bool MatrixView::applyLayout(int staffNo, + timeT startTime, + timeT endTime) +{ + Profiler profiler("MatrixView::applyLayout", true); + + m_hlayout.reset(); + m_vlayout.reset(); + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + + if (staffNo >= 0 && (int)i != staffNo) + continue; + + m_hlayout.scanStaff(*m_staffs[i], startTime, endTime); + m_vlayout.scanStaff(*m_staffs[i], startTime, endTime); + } + + m_hlayout.finishLayout(); + m_vlayout.finishLayout(); + + if (m_staffs[0]->getSegment().getEndMarkerTime() != m_lastEndMarkerTime || + m_lastEndMarkerTime == 0 || + isCompositionModified()) { + readjustCanvasSize(); + m_lastEndMarkerTime = m_staffs[0]->getSegment().getEndMarkerTime(); + } + + return true; +} + +void MatrixView::refreshSegment(Segment *segment, + timeT startTime, timeT endTime) +{ + Profiler profiler("MatrixView::refreshSegment", true); + + MATRIX_DEBUG << "MatrixView::refreshSegment(" << startTime + << ", " << endTime << ")\n"; + + applyLayout( -1, startTime, endTime); + + if (!segment) + segment = m_segments[0]; + + if (endTime == 0) + endTime = segment->getEndTime(); + else if (startTime == endTime) { + startTime = segment->getStartTime(); + endTime = segment->getEndTime(); + } + + m_staffs[0]->positionElements(startTime, endTime); + repaintRulers(); +} + +QSize MatrixView::getViewSize() +{ + return canvas()->size(); +} + +void MatrixView::setViewSize(QSize s) +{ + MATRIX_DEBUG << "MatrixView::setViewSize() w = " << s.width() << endl; + + canvas()->resize(getXbyInverseWorldMatrix(s.width()), s.height()); + getCanvasView()->resizeContents(s.width(), s.height()); + + MATRIX_DEBUG << "MatrixView::setViewSize() contentsWidth = " << getCanvasView()->contentsWidth() << endl; +} + +void MatrixView::repaintRulers() +{ + for (unsigned int i = 0; i != m_propertyViewRulers.size(); i++) + m_propertyViewRulers[i].first->repaint(); +} + +void MatrixView::updateView() +{ + canvas()->update(); +} + +void MatrixView::setCurrentSelection(EventSelection* s, bool preview, + bool redrawNow) +{ + //!!! rather too much here shared with notationview -- could much of + // this be in editview? + + if (m_currentEventSelection == s) { + updateQuantizeCombo(); + return ; + } + + if (m_currentEventSelection) { + getStaff(0)->positionElements(m_currentEventSelection->getStartTime(), + m_currentEventSelection->getEndTime()); + } + + EventSelection *oldSelection = m_currentEventSelection; + m_currentEventSelection = s; + + 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; + + if (s) { + + 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; + + if (preview) { + long pitch; + if ((*i)->get<Int>(BaseProperties::PITCH, pitch)) { + 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 ((endA >= startB && endB >= startA) && + (!s || !oldSelection || + oldSelection->getSegment() == s->getSegment())) { + + Segment &segment(s ? s->getSegment() : + oldSelection->getSegment()); + + if (redrawNow) { + // recolour the events now + getStaff(segment)->positionElements(std::min(startA, startB), + std::max(endA, endB)); + } else { + // mark refresh status and then request a repaint + segment.getRefreshStatus + (m_segmentsRefreshStatusIds + [getStaff(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 + getStaff(oldSelection->getSegment())->positionElements(startA, + endA); + + getStaff(s->getSegment())->positionElements(startB, endB); + } else { + // mark refresh status and then request a repaint + + oldSelection->getSegment().getRefreshStatus + (m_segmentsRefreshStatusIds + [getStaff(oldSelection->getSegment())->getId()]). + push(startA, endA); + + s->getSegment().getRefreshStatus + (m_segmentsRefreshStatusIds + [getStaff(s->getSegment())->getId()]). + push(startB, endB); + } + } + } + + delete oldSelection; + + 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(); + + slotSetCurrentVelocityFromSelection(); + + // 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 (s) { + stateChanged("have_selection", KXMLGUIClient::StateNoReverse); + if (s->contains(Note::EventType)) { + stateChanged("have_notes_in_selection", + KXMLGUIClient::StateNoReverse); + } + if (s->contains(Note::EventRestType)) { + stateChanged("have_rests_in_selection", + KXMLGUIClient::StateNoReverse); + } + } + + updateQuantizeCombo(); + + if (redrawNow) + updateView(); + else + update(); +} + +void MatrixView::updateQuantizeCombo() +{ + timeT unit = 0; + + if (m_currentEventSelection) { + unit = + BasicQuantizer::getStandardQuantization + (m_currentEventSelection); + } else { + unit = + BasicQuantizer::getStandardQuantization + (&(m_staffs[0]->getSegment())); + } + + for (unsigned int i = 0; i < m_quantizations.size(); ++i) { + if (unit == m_quantizations[i]) { + m_quantizeCombo->setCurrentItem(i); + return ; + } + } + + m_quantizeCombo->setCurrentItem(m_quantizeCombo->count() - 1); // "Off" +} + +void MatrixView::slotPaintSelected() +{ + EditTool* painter = m_toolBox->getTool(MatrixPainter::ToolName); + + setTool(painter); +} + +void MatrixView::slotEraseSelected() +{ + EditTool* eraser = m_toolBox->getTool(MatrixEraser::ToolName); + + setTool(eraser); +} + +void MatrixView::slotSelectSelected() +{ + EditTool* selector = m_toolBox->getTool(MatrixSelector::ToolName); + + connect(selector, SIGNAL(gotSelection()), + this, SLOT(slotNewSelection())); + + connect(selector, SIGNAL(editTriggerSegment(int)), + this, SIGNAL(editTriggerSegment(int))); + + setTool(selector); +} + +void MatrixView::slotMoveSelected() +{ + EditTool* mover = m_toolBox->getTool(MatrixMover::ToolName); + + setTool(mover); +} + +void MatrixView::slotResizeSelected() +{ + EditTool* resizer = m_toolBox->getTool(MatrixResizer::ToolName); + + setTool(resizer); +} + +void MatrixView::slotTransformsQuantize() +{ + if (!m_currentEventSelection) + return ; + + QuantizeDialog dialog(this); + + if (dialog.exec() == QDialog::Accepted) { + KTmpStatusMsg msg(i18n("Quantizing..."), this); + addCommandToHistory(new EventQuantizeCommand + (*m_currentEventSelection, + dialog.getQuantizer())); + } +} + +void MatrixView::slotTransformsRepeatQuantize() +{ + if (!m_currentEventSelection) + return ; + + KTmpStatusMsg msg(i18n("Quantizing..."), this); + addCommandToHistory(new EventQuantizeCommand + (*m_currentEventSelection, + "Quantize Dialog Grid", false)); // no i18n (config group name) +} + +void MatrixView::slotTransformsCollapseNotes() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Collapsing notes..."), this); + + addCommandToHistory(new CollapseNotesCommand + (*m_currentEventSelection)); +} + +void MatrixView::slotTransformsLegato() +{ + if (!m_currentEventSelection) + return ; + + KTmpStatusMsg msg(i18n("Making legato..."), this); + addCommandToHistory(new EventQuantizeCommand + (*m_currentEventSelection, + new LegatoQuantizer(0))); // no quantization +} + +void MatrixView::slotMousePressed(timeT time, int pitch, + QMouseEvent* e, MatrixElement* el) +{ + MATRIX_DEBUG << "MatrixView::mousePressed at pitch " + << pitch << ", time " << time << endl; + + // Don't allow moving/insertion before the beginning of the + // segment + timeT curSegmentStartTime = getCurrentSegment()->getStartTime(); + if (curSegmentStartTime > time) + time = curSegmentStartTime; + + m_tool->handleMousePress(time, pitch, 0, e, el); + + if (e->button() != RightButton) { + getCanvasView()->startAutoScroll(); + } + + // play a preview + //playPreview(pitch); +} + +void MatrixView::slotMouseMoved(timeT time, int pitch, QMouseEvent* e) +{ + // Don't allow moving/insertion before the beginning of the + // segment + timeT curSegmentStartTime = getCurrentSegment()->getStartTime(); + if (curSegmentStartTime > time) + time = curSegmentStartTime; + + if (activeItem()) { + activeItem()->handleMouseMove(e); + updateView(); + } else { + int follow = m_tool->handleMouseMove(time, pitch, e); + getCanvasView()->setScrollDirectionConstraint(follow); + + // if (follow != RosegardenCanvasView::NoFollow) { + // getCanvasView()->doAutoScroll(); + // } + + // play a preview + if (pitch != m_previousEvPitch) { + //playPreview(pitch); + m_previousEvPitch = pitch; + } + } + +} + +void MatrixView::slotMouseReleased(timeT time, int pitch, QMouseEvent* e) +{ + // Don't allow moving/insertion before the beginning of the + // segment + timeT curSegmentStartTime = getCurrentSegment()->getStartTime(); + if (curSegmentStartTime > time) + time = curSegmentStartTime; + + if (activeItem()) { + activeItem()->handleMouseRelease(e); + setActiveItem(0); + updateView(); + } + + // send the real event time now (not adjusted for beginning of bar) + m_tool->handleMouseRelease(time, pitch, e); + m_previousEvPitch = 0; + getCanvasView()->stopAutoScroll(); +} + +void +MatrixView::slotHoveredOverNoteChanged(int evPitch, + bool haveEvent, + timeT evTime) +{ + MidiPitchLabel label(evPitch); + + if (haveEvent) { + + m_haveHoveredOverNote = true; + + int bar, beat, fraction, remainder; + getDocument()->getComposition().getMusicalTimeForAbsoluteTime + (evTime, bar, beat, fraction, remainder); + + RealTime rt = + getDocument()->getComposition().getElapsedRealTime(evTime); + long ms = rt.msec(); + + QString msg = i18n("Note: %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(msg); + } + + m_haveHoveredOverNote = false; + + m_hoveredOverNoteName->setText(i18n("%1 (%2)") + .arg(label.getQString()) + .arg(evPitch)); + + m_pitchRuler->drawHoverNote(evPitch); +} + +void +MatrixView::slotHoveredOverKeyChanged(unsigned int y) +{ + MatrixStaff& staff = *(m_staffs[0]); + + int evPitch = staff.getHeightAtCanvasCoords( -1, y); + + if (evPitch != m_previousEvPitch) { + MidiPitchLabel label(evPitch); + m_hoveredOverNoteName->setText(QString("%1 (%2)"). + arg(label.getQString()).arg(evPitch)); + m_previousEvPitch = evPitch; + } +} + +void +MatrixView::slotHoveredOverAbsoluteTimeChanged(unsigned int time) +{ + if (m_haveHoveredOverNote) return; + + timeT t = time; + + int bar, beat, fraction, remainder; + getDocument()->getComposition().getMusicalTimeForAbsoluteTime + (t, bar, beat, fraction, remainder); + + RealTime rt = + getDocument()->getComposition().getElapsedRealTime(t); + long ms = rt.msec(); + + // At the advice of doc.trolltech.com/3.0/qstring.html#sprintf + // we replaced this QString format("%ld (%ld.%03lds)"); + // to support Unicode + + 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 +MatrixView::slotSetPointerPosition(timeT time) +{ + slotSetPointerPosition(time, m_playTracking); +} + +void +MatrixView::slotSetPointerPosition(timeT time, bool scroll) +{ + Composition &comp = getDocument()->getComposition(); + int barNo = comp.getBarNumber(time); + + if (barNo >= m_hlayout.getLastVisibleBarOnStaff(*m_staffs[0])) { + + Segment &seg = m_staffs[0]->getSegment(); + + if (seg.isRepeating() && time < seg.getRepeatEndTime()) { + time = + seg.getStartTime() + + ((time - seg.getStartTime()) % + (seg.getEndMarkerTime() - seg.getStartTime())); + m_staffs[0]->setPointerPosition(m_hlayout, time); + } else { + m_staffs[0]->hidePointer(); + scroll = false; + } + } else if (barNo < m_hlayout.getFirstVisibleBarOnStaff(*m_staffs[0])) { + m_staffs[0]->hidePointer(); + scroll = false; + } else { + m_staffs[0]->setPointerPosition(m_hlayout, time); + } + + if (scroll && !getCanvasView()->isAutoScrolling()) + getCanvasView()->slotScrollHoriz(static_cast<int>(getXbyWorldMatrix(m_hlayout.getXForTime(time)))); + + updateView(); +} + +void +MatrixView::slotSetInsertCursorPosition(timeT time, bool scroll) +{ + //!!! For now. Probably unlike slotSetPointerPosition this one + // should snap to the nearest event or grid line. + + m_staffs[0]->setInsertCursorPosition(m_hlayout, time); + + if (scroll && !getCanvasView()->isAutoScrolling()) { + getCanvasView()->slotScrollHoriz + (static_cast<int>(getXbyWorldMatrix(m_hlayout.getXForTime(time)))); + } + + updateView(); +} + +void MatrixView::slotEditCut() +{ + MATRIX_DEBUG << "MatrixView::slotEditCut()\n"; + + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Cutting selection to clipboard..."), this); + + addCommandToHistory(new CutCommand(*m_currentEventSelection, + getDocument()->getClipboard())); +} + +void MatrixView::slotEditCopy() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Copying selection to clipboard..."), this); + + addCommandToHistory(new CopyCommand(*m_currentEventSelection, + getDocument()->getClipboard())); + + emit usedSelection(); +} + +void MatrixView::slotEditPaste() +{ + if (getDocument()->getClipboard()->isEmpty()) { + slotStatusHelpMsg(i18n("Clipboard is empty")); + return ; + } + + KTmpStatusMsg msg(i18n("Inserting clipboard contents..."), this); + + PasteEventsCommand *command = new PasteEventsCommand + (m_staffs[0]->getSegment(), getDocument()->getClipboard(), + getInsertionTime(), PasteEventsCommand::MatrixOverlay); + + if (!command->isPossible()) { + slotStatusHelpMsg(i18n("Couldn't paste at this point")); + } else { + addCommandToHistory(command); + setCurrentSelection(new EventSelection(command->getPastedEvents())); + } +} + +void MatrixView::slotEditDelete() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Deleting selection..."), this); + + addCommandToHistory(new EraseCommand(*m_currentEventSelection)); + + // clear and clear + setCurrentSelection(0, false); +} + +void MatrixView::slotKeyPressed(unsigned int y, bool repeating) +{ + slotHoveredOverKeyChanged(y); + + getCanvasView()->slotScrollVertSmallSteps(y); + + Composition &comp = getDocument()->getComposition(); + Studio &studio = getDocument()->getStudio(); + + MatrixStaff& staff = *(m_staffs[0]); + MidiByte evPitch = staff.getHeightAtCanvasCoords( -1, y); + + // Don't do anything if we're part of a run up the keyboard + // and the pitch hasn't changed + // + if (m_lastNote == evPitch && repeating) + return ; + + // Save value + m_lastNote = evPitch; + if (!repeating) + m_firstNote = evPitch; + + Track *track = comp.getTrackById( + staff.getSegment().getTrack()); + + Instrument *ins = + studio.getInstrumentById(track->getInstrument()); + + // check for null instrument + // + if (ins == 0) + return ; + + MappedEvent mE(ins->getId(), + MappedEvent::MidiNote, + evPitch + staff.getSegment().getTranspose(), + MidiMaxValue, + RealTime::zeroTime, + RealTime::zeroTime, + RealTime::zeroTime); + StudioControl::sendMappedEvent(mE); + +} + +void MatrixView::slotKeySelected(unsigned int y, bool repeating) +{ + slotHoveredOverKeyChanged(y); + + getCanvasView()->slotScrollVertSmallSteps(y); + + MatrixStaff& staff = *(m_staffs[0]); + Segment &segment(staff.getSegment()); + MidiByte evPitch = staff.getHeightAtCanvasCoords( -1, y); + + // Don't do anything if we're part of a run up the keyboard + // and the pitch hasn't changed + // + if (m_lastNote == evPitch && repeating) + return ; + + // Save value + m_lastNote = evPitch; + if (!repeating) + m_firstNote = evPitch; + + EventSelection *s = new EventSelection(segment); + + for (Segment::iterator i = segment.begin(); + segment.isBeforeEndMarker(i); ++i) { + + if ((*i)->isa(Note::EventType) && + (*i)->has(BaseProperties::PITCH)) { + + MidiByte p = (*i)->get + <Int> + (BaseProperties::PITCH); + if (p >= std::min(m_firstNote, evPitch) && + p <= std::max(m_firstNote, evPitch)) { + s->addEvent(*i); + } + } + } + + if (m_currentEventSelection) { + // allow addFromSelection to deal with eliminating duplicates + s->addFromSelection(m_currentEventSelection); + } + + setCurrentSelection(s, false); + + // now play the note as well + + Composition &comp = getDocument()->getComposition(); + Studio &studio = getDocument()->getStudio(); + Track *track = comp.getTrackById(segment.getTrack()); + Instrument *ins = + studio.getInstrumentById(track->getInstrument()); + + // check for null instrument + // + if (ins == 0) + return ; + + MappedEvent mE(ins->getId(), + MappedEvent::MidiNoteOneShot, + evPitch + segment.getTranspose(), + MidiMaxValue, + RealTime::zeroTime, + RealTime(0, 250000000), + RealTime::zeroTime); + StudioControl::sendMappedEvent(mE); +} + +void MatrixView::slotKeyReleased(unsigned int y, bool repeating) +{ + MatrixStaff& staff = *(m_staffs[0]); + int evPitch = staff.getHeightAtCanvasCoords(-1, y); + + if (m_lastNote == evPitch && repeating) + return; + + Rosegarden::Segment &segment(staff.getSegment()); + + // send note off (note on at zero velocity) + + Rosegarden::Composition &comp = getDocument()->getComposition(); + Rosegarden::Studio &studio = getDocument()->getStudio(); + Rosegarden::Track *track = comp.getTrackById(segment.getTrack()); + Rosegarden::Instrument *ins = + studio.getInstrumentById(track->getInstrument()); + + // check for null instrument + // + if (ins == 0) + return; + + evPitch = evPitch + segment.getTranspose(); + if (evPitch < 0 || evPitch > 127) return; + + Rosegarden::MappedEvent mE(ins->getId(), + Rosegarden::MappedEvent::MidiNote, + evPitch, + 0, + Rosegarden::RealTime::zeroTime, + Rosegarden::RealTime::zeroTime, + Rosegarden::RealTime::zeroTime); + Rosegarden::StudioControl::sendMappedEvent(mE); +} + +void MatrixView::slotVerticalScrollPianoKeyboard(int y) +{ + if (m_pianoView) // check that the piano view still exists (see dtor) + m_pianoView->setContentsPos(0, y); +} + +void MatrixView::slotInsertNoteFromAction() +{ + const QObject *s = sender(); + QString name = s->name(); + + Segment &segment = *getCurrentSegment(); + 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); + + MATRIX_DEBUG << "Inserting note at pitch " << pitch << endl; + + Event modelEvent(Note::EventType, 0, 1); + modelEvent.set<Int>(BaseProperties::PITCH, pitch); + modelEvent.set<String>(BaseProperties::ACCIDENTAL, accidental); + timeT endTime(time + m_snapGrid->getSnapTime(time)); + + MatrixInsertionCommand* command = + new MatrixInsertionCommand(segment, time, endTime, &modelEvent); + + addCommandToHistory(command); + + if (!isInChordMode()) { + slotSetInsertCursorPosition(endTime); + } +} + +void MatrixView::closeWindow() +{ + delete this; +} + +bool MatrixView::canPreviewAnotherNote() +{ + static time_t lastCutOff = 0; + static int sinceLastCutOff = 0; + + time_t now = time(0); + ++sinceLastCutOff; + + if ((now - lastCutOff) > 0) { + sinceLastCutOff = 0; + lastCutOff = now; + } else { + if (sinceLastCutOff >= 20) { + // don't permit more than 20 notes per second, to avoid + // gungeing up the sound drivers + MATRIX_DEBUG << "Rejecting preview (too busy)" << endl; + return false; + } + } + + return true; +} + +void MatrixView::playNote(Event *event) +{ + // Only play note events + // + if (!event->isa(Note::EventType)) + return ; + + Composition &comp = getDocument()->getComposition(); + Studio &studio = getDocument()->getStudio(); + + // Get the Instrument + // + Track *track = comp.getTrackById( + m_staffs[0]->getSegment().getTrack()); + + Instrument *ins = + studio.getInstrumentById(track->getInstrument()); + + if (ins == 0) + return ; + + if (!canPreviewAnotherNote()) + return ; + + // Get a velocity + // + MidiByte velocity = MidiMaxValue / 4; // be easy on the user's ears + long eventVelocity = 0; + if (event->get + <Int>(BaseProperties::VELOCITY, eventVelocity)) + velocity = eventVelocity; + + RealTime duration = + comp.getElapsedRealTime(event->getDuration()); + + // create + MappedEvent mE(ins->getId(), + MappedEvent::MidiNoteOneShot, + (MidiByte) + event->get + <Int> + (BaseProperties::PITCH) + + m_staffs[0]->getSegment().getTranspose(), + velocity, + RealTime::zeroTime, + duration, + RealTime::zeroTime); + + StudioControl::sendMappedEvent(mE); +} + +void MatrixView::playNote(const Segment &segment, int pitch, + int velocity) +{ + Composition &comp = getDocument()->getComposition(); + Studio &studio = getDocument()->getStudio(); + + Track *track = comp.getTrackById(segment.getTrack()); + + Instrument *ins = + studio.getInstrumentById(track->getInstrument()); + + // check for null instrument + // + if (ins == 0) + return ; + + if (velocity < 0) + velocity = getCurrentVelocity(); + + MappedEvent mE(ins->getId(), + MappedEvent::MidiNoteOneShot, + pitch + segment.getTranspose(), + velocity, + RealTime::zeroTime, + RealTime(0, 250000000), + RealTime::zeroTime); + + StudioControl::sendMappedEvent(mE); +} + +MatrixStaff* +MatrixView::getStaff(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; +} + +void +MatrixView::setSingleSelectedEvent(int staffNo, Event *event, + bool preview, bool redrawNow) +{ + setSingleSelectedEvent(getStaff(staffNo)->getSegment(), event, + preview, redrawNow); +} + +void +MatrixView::setSingleSelectedEvent(Segment &segment, + Event *event, + bool preview, bool redrawNow) +{ + setCurrentSelection(0, false); + + EventSelection *selection = new EventSelection(segment); + selection->addEvent(event); + + //!!! + // this used to say + // setCurrentSelection(selection, true) + // since the default arg for preview is false, this changes the + // default semantics -- test what circumstance this matters in + // and choose an acceptable solution for both matrix & notation + setCurrentSelection(selection, preview, redrawNow); +} + +void +MatrixView::slotNewSelection() +{ + MATRIX_DEBUG << "MatrixView::slotNewSelection\n"; + + // m_parameterBox->setSelection(m_currentEventSelection); +} + +void +MatrixView::slotSetSnapFromIndex(int s) +{ + slotSetSnap(m_snapValues[s]); +} + +void +MatrixView::slotSetSnapFromAction() +{ + const QObject *s = sender(); + QString name = s->name(); + + if (name.left(5) == "snap_") { + int snap = name.right(name.length() - 5).toInt(); + if (snap > 0) { + slotSetSnap(Note(Note::Semibreve).getDuration() / snap); + } else if (name == "snap_none") { + slotSetSnap(SnapGrid::NoSnap); + } else if (name == "snap_beat") { + slotSetSnap(SnapGrid::SnapToBeat); + } else if (name == "snap_bar") { + slotSetSnap(SnapGrid::SnapToBar); + } else if (name == "snap_unit") { + slotSetSnap(SnapGrid::SnapToUnit); + } else { + MATRIX_DEBUG << "Warning: MatrixView::slotSetSnapFromAction: unrecognised action " << name << endl; + } + } +} + +void +MatrixView::slotSetSnap(timeT t) +{ + MATRIX_DEBUG << "MatrixView::slotSetSnap: time is " << t << endl; + m_snapGrid->setSnapTime(t); + + for (unsigned int i = 0; i < m_snapValues.size(); ++i) { + if (m_snapValues[i] == t) { + m_snapGridCombo->setCurrentItem(i); + break; + } + } + + for (unsigned int i = 0; i < m_staffs.size(); ++i) + m_staffs[i]->sizeStaff(m_hlayout); + + m_segments[0]->setSnapGridSize(t); + + m_config->setGroup(MatrixViewConfigGroup); + m_config->writeEntry("Snap Grid Size", t); + + updateView(); +} + +void +MatrixView::slotQuantizeSelection(int q) +{ + MATRIX_DEBUG << "MatrixView::slotQuantizeSelection\n"; + + timeT unit = + ((unsigned int)q < m_quantizations.size() ? m_quantizations[q] : 0); + + Quantizer *quant = + new BasicQuantizer + (unit ? unit : + Note(Note::Shortest).getDuration(), false); + + if (unit) { + KTmpStatusMsg msg(i18n("Quantizing..."), this); + if (m_currentEventSelection && + m_currentEventSelection->getAddedEvents()) { + addCommandToHistory(new EventQuantizeCommand + (*m_currentEventSelection, quant)); + } else { + Segment &s = m_staffs[0]->getSegment(); + addCommandToHistory(new EventQuantizeCommand + (s, s.getStartTime(), s.getEndMarkerTime(), + quant)); + } + } else { + KTmpStatusMsg msg(i18n("Unquantizing..."), this); + if (m_currentEventSelection && + m_currentEventSelection->getAddedEvents()) { + addCommandToHistory(new EventUnquantizeCommand + (*m_currentEventSelection, quant)); + } else { + Segment &s = m_staffs[0]->getSegment(); + addCommandToHistory(new EventUnquantizeCommand + (s, s.getStartTime(), s.getEndMarkerTime(), + quant)); + } + } +} + +void +MatrixView::initActionsToolbar() +{ + MATRIX_DEBUG << "MatrixView::initActionsToolbar" << endl; + + KToolBar *actionsToolbar = toolBar("Actions Toolbar"); + + if (!actionsToolbar) { + MATRIX_DEBUG << "MatrixView::initActionsToolbar - " + << "tool bar not found" << endl; + return ; + } + + // The SnapGrid combo and Snap To... menu items + // + QLabel *sLabel = new QLabel(i18n(" Grid: "), actionsToolbar, "kde toolbar widget"); + sLabel->setIndent(10); + + QPixmap noMap = NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("menu-no-note")); + + m_snapGridCombo = new KComboBox(actionsToolbar); + + for (unsigned int i = 0; i < m_snapValues.size(); i++) { + + timeT d = m_snapValues[i]; + + if (d == SnapGrid::NoSnap) { + m_snapGridCombo->insertItem(i18n("None")); + } else if (d == SnapGrid::SnapToUnit) { + m_snapGridCombo->insertItem(i18n("Unit")); + } else if (d == SnapGrid::SnapToBeat) { + m_snapGridCombo->insertItem(i18n("Beat")); + } else if (d == SnapGrid::SnapToBar) { + m_snapGridCombo->insertItem(i18n("Bar")); + } else { + timeT err = 0; + QString label = NotationStrings::makeNoteMenuLabel(d, true, err); + QPixmap pixmap = NotePixmapFactory::toQPixmap + (NotePixmapFactory::makeNoteMenuPixmap(d, err)); + m_snapGridCombo->insertItem((err ? noMap : pixmap), label); + } + + if (d == m_snapGrid->getSnapSetting()) { + m_snapGridCombo->setCurrentItem(m_snapGridCombo->count() - 1); + } + } + + connect(m_snapGridCombo, SIGNAL(activated(int)), + this, SLOT(slotSetSnapFromIndex(int))); + + // Velocity combo. Not a spin box, because the spin box is too + // slow to use unless we make it typeable into, and then it takes + // focus away from our more important widgets + + QLabel *vlabel = new QLabel(i18n(" Velocity: "), actionsToolbar, "kde toolbar widget"); + vlabel->setIndent(10); + + m_velocityCombo = new KComboBox(actionsToolbar); + for (int i = 0; i <= 127; ++i) { + m_velocityCombo->insertItem(QString("%1").arg(i)); + } + m_velocityCombo->setCurrentItem(100); //!!! associate with segment + + // Quantize combo + // + QLabel *qLabel = new QLabel(i18n(" Quantize: "), actionsToolbar, "kde toolbar widget"); + qLabel->setIndent(10); + + m_quantizeCombo = new KComboBox(actionsToolbar); + + for (unsigned int i = 0; i < m_quantizations.size(); ++i) { + + timeT time = m_quantizations[i]; + timeT error = 0; + QString label = NotationStrings::makeNoteMenuLabel(time, true, error); + QPixmap pmap = NotePixmapFactory::toQPixmap(NotePixmapFactory::makeNoteMenuPixmap(time, error)); + m_quantizeCombo->insertItem(error ? noMap : pmap, label); + } + + m_quantizeCombo->insertItem(noMap, i18n("Off")); + + connect(m_quantizeCombo, SIGNAL(activated(int)), + this, SLOT(slotQuantizeSelection(int))); +} + +void +MatrixView::initZoomToolbar() +{ + MATRIX_DEBUG << "MatrixView::initZoomToolbar" << endl; + + KToolBar *zoomToolbar = toolBar("Zoom Toolbar"); + + if (!zoomToolbar) { + MATRIX_DEBUG << "MatrixView::initZoomToolbar - " + << "tool bar not found" << endl; + return ; + } + + std::vector<double> zoomSizes; // in units-per-pixel + + //double defaultBarWidth44 = 100.0; + //double duration44 = TimeSignature(4,4).getBarDuration(); + + static double factors[] = { 0.025, 0.05, 0.1, 0.2, 0.5, + 1.0, 1.5, 2.5, 5.0, 10.0, 20.0 }; + // Zoom labels + // + for (unsigned int i = 0; i < sizeof(factors) / sizeof(factors[0]); ++i) { +// zoomSizes.push_back(duration44 / (defaultBarWidth44 * factors[i])); + +// zoomSizes.push_back(factors[i] / 2); // GROSS HACK - see in matrixstaff.h - BREAKS MATRIX VIEW, see bug 1000595 + zoomSizes.push_back(factors[i]); + } + + m_hZoomSlider = new ZoomSlider<double> + (zoomSizes, -1, QSlider::Horizontal, zoomToolbar, "kde toolbar widget"); + m_hZoomSlider->setTracking(true); + m_hZoomSlider->setFocusPolicy(QWidget::NoFocus); + + m_zoomLabel = new QLabel(zoomToolbar, "kde toolbar widget"); + m_zoomLabel->setIndent(10); + m_zoomLabel->setFixedWidth(80); + + connect(m_hZoomSlider, + SIGNAL(valueChanged(int)), + SLOT(slotChangeHorizontalZoom(int))); + +} + +void +MatrixView::slotChangeHorizontalZoom(int) +{ + double zoomValue = m_hZoomSlider->getCurrentSize(); + + // m_zoomLabel->setText(i18n("%1%").arg(zoomValue*100.0 * 2)); // GROSS HACK - see in matrixstaff.h - BREAKS MATRIX VIEW, see bug 1000595 + m_zoomLabel->setText(i18n("%1%").arg(zoomValue*100.0)); + + MATRIX_DEBUG << "MatrixView::slotChangeHorizontalZoom() : zoom factor = " + << zoomValue << endl; + + m_referenceRuler->setHScaleFactor(zoomValue); + + if (m_tempoRuler) + m_tempoRuler->repaint(); + if (m_chordNameRuler) + m_chordNameRuler->repaint(); + + // Set zoom matrix + // + QWMatrix zoomMatrix; + zoomMatrix.scale(zoomValue, 1.0); + m_canvasView->setWorldMatrix(zoomMatrix); + + // make control rulers zoom too + // + setControlRulersZoom(zoomMatrix); + + if (m_topStandardRuler) + m_topStandardRuler->setHScaleFactor(zoomValue); + if (m_bottomStandardRuler) + m_bottomStandardRuler->setHScaleFactor(zoomValue); + + for (unsigned int i = 0; i < m_propertyViewRulers.size(); ++i) { + m_propertyViewRulers[i].first->setHScaleFactor(zoomValue); + m_propertyViewRulers[i].first->repaint(); + } + + if (m_topStandardRuler) + m_topStandardRuler->update(); + if (m_bottomStandardRuler) + m_bottomStandardRuler->update(); + + m_config->setGroup(MatrixViewConfigGroup); + m_config->writeEntry("Zoom Level", zoomValue); + + // If you do adjust the viewsize then please remember to + // either re-center() or remember old scrollbar position + // and restore. + // + + int newWidth = computePostLayoutWidth(); + + // int newWidth = int(getXbyWorldMatrix(getCanvasView()->canvas()->width())); + + // We DO NOT resize the canvas(), only the area it's displaying on + // + getCanvasView()->resizeContents(newWidth, getViewSize().height()); + + // This forces a refresh of the h. scrollbar, even if the canvas width + // hasn't changed + // + getCanvasView()->polish(); + + getCanvasView()->slotScrollHoriz + (getXbyWorldMatrix(m_staffs[0]->getLayoutXOfInsertCursor())); +} + +void +MatrixView::slotZoomIn() +{ + m_hZoomSlider->increment(); +} + +void +MatrixView::slotZoomOut() +{ + m_hZoomSlider->decrement(); +} + +void +MatrixView::scrollToTime(timeT t) +{ + double layoutCoord = m_hlayout.getXForTime(t); + getCanvasView()->slotScrollHoriz(int(layoutCoord)); +} + +int +MatrixView::getCurrentVelocity() const +{ + return m_velocityCombo->currentItem(); +} + +void +MatrixView::slotSetCurrentVelocity(int value) +{ + m_velocityCombo->setCurrentItem(value); +} + + +void +MatrixView::slotSetCurrentVelocityFromSelection() +{ + if (!m_currentEventSelection) return; + + float totalVelocity = 0; + int count = 0; + + for (EventSelection::eventcontainer::iterator i = + m_currentEventSelection->getSegmentEvents().begin(); + i != m_currentEventSelection->getSegmentEvents().end(); ++i) { + + if ((*i)->has(BaseProperties::VELOCITY)) { + totalVelocity += (*i)->get<Int>(BaseProperties::VELOCITY); + ++count; + } + } + + if (count > 0) { + slotSetCurrentVelocity((totalVelocity / count) + 0.5); + } +} + +unsigned int +MatrixView::addPropertyViewRuler(const PropertyName &property) +{ + // Try and find this controller if it exists + // + for (unsigned int i = 0; i != m_propertyViewRulers.size(); i++) { + if (m_propertyViewRulers[i].first->getPropertyName() == property) + return i; + } + + int height = 20; + + PropertyViewRuler *newRuler = new PropertyViewRuler(&m_hlayout, + m_segments[0], + property, + xorigin, + height, + getCentralWidget()); + + addRuler(newRuler); + + PropertyBox *newControl = new PropertyBox(strtoqstr(property), + m_parameterBox->width() + m_pitchRuler->width(), + height, + getCentralWidget()); + + addPropertyBox(newControl); + + m_propertyViewRulers.push_back( + std::pair<PropertyViewRuler*, PropertyBox*>(newRuler, newControl)); + + return m_propertyViewRulers.size() - 1; +} + +bool +MatrixView::removePropertyViewRuler(unsigned int number) +{ + if (number > m_propertyViewRulers.size() - 1) + return false; + + std::vector<std::pair<PropertyViewRuler*, PropertyBox*> >::iterator it + = m_propertyViewRulers.begin(); + while (number--) + it++; + + delete it->first; + delete it->second; + m_propertyViewRulers.erase(it); + + return true; +} + +RulerScale* +MatrixView::getHLayout() +{ + return &m_hlayout; +} + +Staff* +MatrixView::getCurrentStaff() +{ + return getStaff(0); +} + +Segment * +MatrixView::getCurrentSegment() +{ + MatrixStaff *staff = getStaff(0); + return (staff ? &staff->getSegment() : 0); +} + +timeT +MatrixView::getInsertionTime() +{ + MatrixStaff *staff = m_staffs[0]; + return staff->getInsertCursorTime(m_hlayout); +} + +void +MatrixView::slotStepBackward() +{ + timeT time(getInsertionTime()); + slotSetInsertCursorPosition(SnapGrid(&m_hlayout).snapTime + (time - 1, + SnapGrid::SnapLeft)); +} + +void +MatrixView::slotStepForward() +{ + timeT time(getInsertionTime()); + slotSetInsertCursorPosition(SnapGrid(&m_hlayout).snapTime + (time + 1, + SnapGrid::SnapRight)); +} + +void +MatrixView::slotJumpCursorToPlayback() +{ + slotSetInsertCursorPosition(getDocument()->getComposition().getPosition()); +} + +void +MatrixView::slotJumpPlaybackToCursor() +{ + emit jumpPlaybackTo(getInsertionTime()); +} + +void +MatrixView::slotToggleTracking() +{ + m_playTracking = !m_playTracking; +} + +void +MatrixView::slotSelectAll() +{ + Segment *segment = m_segments[0]; + Segment::iterator it = segment->begin(); + EventSelection *selection = new EventSelection(*segment); + + for (; segment->isBeforeEndMarker(it); it++) + if ((*it)->isa(Note::EventType)) + selection->addEvent(*it); + + setCurrentSelection(selection, false); +} + +void MatrixView::slotPreviewSelection() +{ + if (!m_currentEventSelection) + return ; + + getDocument()->slotSetLoop(m_currentEventSelection->getStartTime(), + m_currentEventSelection->getEndTime()); +} + +void MatrixView::slotClearLoop() +{ + getDocument()->slotSetLoop(0, 0); +} + +void MatrixView::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. + + MatrixSelector *selector = dynamic_cast<MatrixSelector *>(m_tool); + + if (!selector) { + slotSelectSelected(); + } else { + setCurrentSelection(0); + } +} + +void MatrixView::slotFilterSelection() +{ + RG_DEBUG << "MatrixView::slotFilterSelection" << endl; + + Segment *segment = getCurrentSegment(); + EventSelection *existingSelection = m_currentEventSelection; + if (!segment || !existingSelection) + return ; + + EventFilterDialog dialog(this); + if (dialog.exec() == QDialog::Accepted) { + RG_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 +MatrixView::readjustCanvasSize() +{ + int maxHeight = 0; + + for (unsigned int i = 0; i < m_staffs.size(); ++i) { + + MatrixStaff &staff = *m_staffs[i]; + + staff.sizeStaff(m_hlayout); + + // if (staff.getTotalWidth() + staff.getX() > maxWidth) { + // maxWidth = staff.getTotalWidth() + staff.getX() + 1; + // } + + if (staff.getTotalHeight() + staff.getY() > maxHeight) { + if (isDrumMode()) { + maxHeight = staff.getTotalHeight() + staff.getY() + 5; + } else { + maxHeight = staff.getTotalHeight() + staff.getY() + 1; + } + } + + } + + int newWidth = computePostLayoutWidth(); + + // now get the EditView to do the biz + readjustViewSize(QSize(newWidth, maxHeight), true); + + repaintRulers(); +} + +void MatrixView::slotVelocityUp() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Raising velocities..."), this); + + addCommandToHistory + (new ChangeVelocityCommand(10, *m_currentEventSelection)); + + slotSetCurrentVelocityFromSelection(); +} + +void MatrixView::slotVelocityDown() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Lowering velocities..."), this); + + addCommandToHistory + (new ChangeVelocityCommand( -10, *m_currentEventSelection)); + + slotSetCurrentVelocityFromSelection(); +} + +void +MatrixView::slotSetVelocities() +{ + if (!m_currentEventSelection) + return ; + + EventParameterDialog dialog(this, + i18n("Set Event Velocities"), + BaseProperties::VELOCITY, + getCurrentVelocity()); + + if (dialog.exec() == QDialog::Accepted) { + KTmpStatusMsg msg(i18n("Setting Velocities..."), this); + addCommandToHistory(new SelectionPropertyCommand + (m_currentEventSelection, + BaseProperties::VELOCITY, + dialog.getPattern(), + dialog.getValue1(), + dialog.getValue2())); + } +} + +void +MatrixView::slotSetVelocitiesToCurrent() +{ + if (!m_currentEventSelection) return; + + addCommandToHistory(new SelectionPropertyCommand + (m_currentEventSelection, + BaseProperties::VELOCITY, + FlatPattern, + getCurrentVelocity(), + getCurrentVelocity())); +} + +void +MatrixView::slotTriggerSegment() +{ + if (!m_currentEventSelection) + return ; + + TriggerSegmentDialog dialog(this, &getDocument()->getComposition()); + if (dialog.exec() != QDialog::Accepted) + return ; + + addCommandToHistory(new SetTriggerCommand(*m_currentEventSelection, + dialog.getId(), + true, + dialog.getRetune(), + dialog.getTimeAdjust(), + Marks::NoMark, + i18n("Trigger Segment"))); +} + +void +MatrixView::slotRemoveTriggers() +{ + if (!m_currentEventSelection) + return ; + + addCommandToHistory(new ClearTriggersCommand(*m_currentEventSelection, + i18n("Remove Triggers"))); +} + +void +MatrixView::slotToggleChordsRuler() +{ + toggleWidget(m_chordNameRuler, "show_chords_ruler"); +} + +void +MatrixView::slotToggleTempoRuler() +{ + toggleWidget(m_tempoRuler, "show_tempo_ruler"); +} + +void +MatrixView::paintEvent(QPaintEvent* e) +{ + //!!! There's a lot of code shared between matrix and notation for + // dealing with step recording (the insertable note event stuff). + // It should probably be factored out into a base class, but I'm + // not sure I wouldn't rather wait until the functionality is all + // sorted in both matrix and notation so we can be sure how much + // of it is actually common. + + EditView::paintEvent(e); + + // 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); + } + } +} + +void +MatrixView::updateViewCaption() +{ + // Set client label + // + QString view = i18n("Matrix"); + if (isDrumMode()) + view = i18n("Percussion"); + + 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(); + + setCaption(i18n("%1 - Segment Track #%2 - %3") + .arg(getDocument()->getTitle()) + .arg(trackPosition + 1) + .arg(view)); + + } else if (m_segments.size() == getDocument()->getComposition().getNbSegments()) { + + setCaption(i18n("%1 - All Segments - %2") + .arg(getDocument()->getTitle()) + .arg(view)); + + } else { + + setCaption(i18n("%1 - 1 Segment - %2", + "%1 - %n Segments - %2", + m_segments.size()) + .arg(getDocument()->getTitle()) + .arg(view)); + } +} + +int MatrixView::computePostLayoutWidth() +{ + Segment *segment = m_segments[0]; + Composition *composition = segment->getComposition(); + int endX = int(m_hlayout.getXForTime + (composition->getBarEndForTime + (segment->getEndMarkerTime()))); + int startX = int(m_hlayout.getXForTime + (composition->getBarStartForTime + (segment->getStartTime()))); + + int newWidth = int(getXbyWorldMatrix(endX - startX)); + + MATRIX_DEBUG << "MatrixView::readjustCanvasSize() : startX = " + << startX + << " endX = " << endX + << " newWidth = " << newWidth + << " endmarkertime : " << segment->getEndMarkerTime() + << " barEnd for time : " << composition->getBarEndForTime(segment->getEndMarkerTime()) + << endl; + + newWidth += 12; + if (isDrumMode()) + newWidth += 12; + + return newWidth; +} + +bool MatrixView::getMinMaxPitches(int& minPitch, int& maxPitch) +{ + minPitch = MatrixVLayout::maxMIDIPitch + 1; + maxPitch = MatrixVLayout::minMIDIPitch - 1; + + std::vector<MatrixStaff*>::iterator sit; + for (sit = m_staffs.begin(); sit != m_staffs.end(); ++sit) { + + MatrixElementList *mel = (*sit)->getViewElementList(); + MatrixElementList::iterator eit; + for (eit = mel->begin(); eit != mel->end(); ++eit) { + + NotationElement *el = static_cast<NotationElement*>(*eit); + if (el->isNote()) { + Event* ev = el->event(); + int pitch = ev->get + <Int> + (BaseProperties::PITCH); + if (minPitch > pitch) + minPitch = pitch; + if (maxPitch < pitch) + maxPitch = pitch; + } + } + } + + return maxPitch >= minPitch; +} + +void MatrixView::extendKeyMapping() +{ + int minStaffPitch, maxStaffPitch; + if (getMinMaxPitches(minStaffPitch, maxStaffPitch)) { + int minKMPitch = m_localMapping->getPitchForOffset(0); + int maxKMPitch = m_localMapping->getPitchForOffset(0) + + m_localMapping->getPitchExtent() - 1; + if (minStaffPitch < minKMPitch) + m_localMapping->getMap()[minStaffPitch] = std::string(""); + if (maxStaffPitch > maxKMPitch) + m_localMapping->getMap()[maxStaffPitch] = std::string(""); + } +} + +void +MatrixView::slotInsertableNoteEventReceived(int pitch, int velocity, bool noteOn) +{ + // hjj: + // The default insertion mode is implemented equivalently in + // notationviewslots.cpp: + // - proceed if notes do not overlap + // - make the chord if notes do overlap, and do not proceed + + static int numberOfNotesOn = 0; + static time_t lastInsertionTime = 0; + if (!noteOn) { + numberOfNotesOn--; + return ; + } + + KToggleAction *action = dynamic_cast<KToggleAction *> + (actionCollection()->action("toggle_step_by_step")); + if (!action) { + MATRIX_DEBUG << "WARNING: No toggle_step_by_step action" << endl; + return ; + } + if (!action->isChecked()) + return ; + + if (m_inPaintEvent) { + m_pendingInsertableNotes.push_back(std::pair<int, int>(pitch, velocity)); + return ; + } + + Segment &segment = *getCurrentSegment(); + + // 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); + + MATRIX_DEBUG << "Inserting note at pitch " << pitch << endl; + + Event modelEvent(Note::EventType, 0, 1); + modelEvent.set<Int>(BaseProperties::PITCH, pitch); + static timeT insertionTime(getInsertionTime()); + if (insertionTime >= segment.getEndMarkerTime()) { + MATRIX_DEBUG << "WARNING: off end of segment" << endl; + return ; + } + time_t now; + time (&now); + double elapsed = difftime(now, lastInsertionTime); + time (&lastInsertionTime); + + if (numberOfNotesOn <= 0 || elapsed > 10.0 ) { + numberOfNotesOn = 0; + insertionTime = getInsertionTime(); + } + numberOfNotesOn++; + timeT endTime(insertionTime + m_snapGrid->getSnapTime(insertionTime)); + + if (endTime <= insertionTime) { + static bool showingError = false; + if (showingError) + return ; + showingError = true; + KMessageBox::sorry(this, i18n("Can't insert note: No grid duration selected")); + showingError = false; + return ; + } + + MatrixInsertionCommand* command = + new MatrixInsertionCommand(segment, insertionTime, endTime, &modelEvent); + + addCommandToHistory(command); + + if (!isInChordMode()) { + slotSetInsertCursorPosition(endTime); + } +} + +void +MatrixView::slotInsertableNoteOnReceived(int pitch, int velocity) +{ + MATRIX_DEBUG << "MatrixView::slotInsertableNoteOnReceived: " << pitch << endl; + slotInsertableNoteEventReceived(pitch, velocity, true); +} + +void +MatrixView::slotInsertableNoteOffReceived(int pitch, int velocity) +{ + MATRIX_DEBUG << "MatrixView::slotInsertableNoteOffReceived: " << pitch << endl; + slotInsertableNoteEventReceived(pitch, velocity, false); +} + +void +MatrixView::slotToggleStepByStep() +{ + KToggleAction *action = dynamic_cast<KToggleAction *> + (actionCollection()->action("toggle_step_by_step")); + if (!action) { + MATRIX_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 +MatrixView::slotUpdateInsertModeStatus() +{ + QString message; + if (isInChordMode()) { + message = i18n(" Chord "); + } else { + message = ""; + } + m_insertModeLabel->setText(message); +} + +void +MatrixView::slotStepByStepTargetRequested(QObject *obj) +{ + KToggleAction *action = dynamic_cast<KToggleAction *> + (actionCollection()->action("toggle_step_by_step")); + if (!action) { + MATRIX_DEBUG << "WARNING: No toggle_step_by_step action" << endl; + return ; + } + action->setChecked(obj == this); +} + +void +MatrixView::slotInstrumentLevelsChanged(InstrumentId id, + const LevelInfo &info) +{ + if (!m_parameterBox) + return ; + + Composition &comp = getDocument()->getComposition(); + + Track *track = + comp.getTrackById(m_staffs[0]->getSegment().getTrack()); + if (!track || track->getInstrument() != id) + return ; + + Instrument *instr = getDocument()->getStudio(). + getInstrumentById(track->getInstrument()); + if (!instr || instr->getType() != Instrument::SoftSynth) + return ; + + float dBleft = AudioLevel::fader_to_dB + (info.level, 127, AudioLevel::LongFader); + float dBright = AudioLevel::fader_to_dB + (info.levelRight, 127, AudioLevel::LongFader); + + m_parameterBox->setAudioMeter(dBleft, dBright, + AudioLevel::DB_FLOOR, + AudioLevel::DB_FLOOR); +} + +void +MatrixView::slotPercussionSetChanged(Instrument * newInstr) +{ + // Must be called only when in drum mode + assert(m_drumMode); + + int resolution = 8; + if (newInstr && newInstr->getKeyMapping()) { + resolution = 11; + } + + const MidiKeyMapping *mapping = 0; + if (newInstr) { + mapping = newInstr->getKeyMapping(); + } + + // Construct a local new keymapping : + if (m_localMapping) + delete m_localMapping; + if (mapping) { + m_localMapping = new MidiKeyMapping(*mapping); + extendKeyMapping(); + } else { + m_localMapping = 0; + } + + m_staffs[0]->setResolution(resolution); + + delete m_pitchRuler; + + QWidget *vport = m_pianoView->viewport(); + + // Create a new pitchruler widget + PitchRuler *pitchRuler; + if (newInstr && newInstr->getKeyMapping() && + !newInstr->getKeyMapping()->getMap().empty()) { + pitchRuler = new PercussionPitchRuler(vport, + m_localMapping, + resolution); // line spacing + } else { + pitchRuler = new PianoKeyboard(vport); + } + + + QObject::connect + (pitchRuler, SIGNAL(hoveredOverKeyChanged(unsigned int)), + this, SLOT (slotHoveredOverKeyChanged(unsigned int))); + + QObject::connect + (pitchRuler, SIGNAL(keyPressed(unsigned int, bool)), + this, SLOT (slotKeyPressed(unsigned int, bool))); + + QObject::connect + (pitchRuler, SIGNAL(keySelected(unsigned int, bool)), + this, SLOT (slotKeySelected(unsigned int, bool))); + + QObject::connect + (pitchRuler, SIGNAL(keyReleased(unsigned int, bool)), + this, SLOT (slotKeyReleased(unsigned int, bool))); + + // Replace the old pitchruler widget + m_pitchRuler = pitchRuler; + m_pianoView->addChild(m_pitchRuler); + m_pitchRuler->show(); + m_pianoView->setFixedWidth(pitchRuler->sizeHint().width()); + + // Update matrix canvas + readjustCanvasSize(); + bool layoutApplied = applyLayout(); + if (!layoutApplied) + KMessageBox::sorry(0, i18n("Couldn't apply piano roll layout")); + else { + MATRIX_DEBUG << "MatrixView : rendering elements\n"; + m_staffs[0]->positionAllElements(); + m_staffs[0]->getSegment().getRefreshStatus + (m_segmentsRefreshStatusIds[0]).setNeedsRefresh(false); + update(); + } +} + +void +MatrixView::slotCanvasBottomWidgetHeightChanged(int newHeight) +{ + m_pianoView->setBottomMargin(newHeight + + m_canvasView->horizontalScrollBar()->height()); +} + +MatrixCanvasView* MatrixView::getCanvasView() +{ + return dynamic_cast<MatrixCanvasView *>(m_canvasView); +} + +} +#include "MatrixView.moc" diff --git a/src/gui/editors/matrix/MatrixView.h b/src/gui/editors/matrix/MatrixView.h new file mode 100644 index 0000000..49e0358 --- /dev/null +++ b/src/gui/editors/matrix/MatrixView.h @@ -0,0 +1,692 @@ + +/* -*- 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_MATRIXVIEW_H_ +#define _RG_MATRIXVIEW_H_ + +#include "base/MidiProgram.h" +#include "base/PropertyName.h" +#include "base/SnapGrid.h" +#include "gui/general/EditView.h" +#include "gui/widgets/ZoomSlider.h" +#include "MatrixHLayout.h" +#include "MatrixVLayout.h" +#include "MatrixCanvasView.h" +#include <kdockwidget.h> +#include <qpoint.h> +#include <qsize.h> +#include <vector> +#include "base/Event.h" +#include "document/ConfigGroups.h" + + +class QWidget; +class QPaintEvent; +class QObject; +class QMouseEvent; +class QLabel; +class QCursor; +class QCanvas; +class KComboBox; + + +namespace Rosegarden +{ + +class Staff; +class Segment; +class RulerScale; +class RosegardenGUIDoc; +class QDeferScrollView; +class PropertyViewRuler; +class PropertyBox; +class PitchRuler; +class MidiKeyMapping; +class MatrixStaff; +class MatrixElement; +class InstrumentParameterBox; +class Instrument; +class EventSelection; +class Event; +class ChordNameRuler; +class LevelInfo; + + +/** + * Matrix ("Piano Roll") View + * + * Note: we currently display only one staff + */ +class MatrixView : public EditView +{ + Q_OBJECT + + friend class MatrixSelector; + +public: + MatrixView(RosegardenGUIDoc *doc, + std::vector<Segment *> segments, + QWidget *parent, bool drumMode); + + virtual ~MatrixView(); + + virtual bool applyLayout(int staffNo = -1, + timeT startTime = 0, + timeT endTime = 0); + + virtual void refreshSegment(Segment *segment, + timeT startTime = 0, + timeT endTime = 0); + + QCanvas* canvas() { return getCanvasView()->canvas(); } + + void setCanvasCursor(const QCursor &cursor) { + getCanvasView()->viewport()->setCursor(cursor); + } + + MatrixStaff* getStaff(int i) + { + if (i >= 0 && unsigned(i) < m_staffs.size()) return m_staffs[i]; + else return 0; + } + + MatrixStaff *getStaff(const Segment &segment); + + virtual void updateView(); + + bool isDrumMode() { return m_drumMode; } + + /** + * Discover whether chord-mode insertions are enabled (as opposed + * to the default melody-mode) + */ + bool isInChordMode(); + + /** + * 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* s, + 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); + + + /** + * Play a Note Event using the keyPressed() signal + */ + void playNote(Event *event); + + /** + * Play a preview (same as above but a simpler interface) + */ + void playNote(const Segment &segment, int pitch, int velocity = -1); + + /** + * Get the SnapGrid + */ + const SnapGrid &getSnapGrid() const { return *m_snapGrid; } + + /** + * Add a ruler that allows control of a single property - + * return the number of the added ruler + * + */ + unsigned int addPropertyViewRuler(const PropertyName &property); + + /** + * Remove a control ruler - return true if it's a valid ruler number + */ + bool removePropertyViewRuler(unsigned int number); + + /** + * Adjust an X coord by world matrix + */ + double getXbyWorldMatrix(double value) + { return m_canvasView->worldMatrix().m11() * value; } + + double getXbyInverseWorldMatrix(double value) + { return m_canvasView->inverseWorldMatrix().m11() * value; } + + QPoint inverseMapPoint(const QPoint& p) { return m_canvasView->inverseMapPoint(p); } + + /* + * Repaint the control rulers + * + */ + void repaintRulers(); + + /* + * Readjust the canvas size + * + */ + void readjustCanvasSize(); + + /* + * Scrolls the view such that the given time is centered + */ + void scrollToTime(timeT t); + + /** + * Get the local keyMapping (when in drum mode) + */ + MidiKeyMapping *getKeyMapping() { return m_localMapping; } + + /** + * Get the velocity currently set in the velocity menu. + */ + int getCurrentVelocity() const; + +signals: + /** + * Emitted when the selection has been cut or copied + * + * @see MatrixSelector#hideSelection + */ + void usedSelection(); + + void play(); + void stop(); + void fastForwardPlayback(); + void rewindPlayback(); + void fastForwardPlaybackToEnd(); + void rewindPlaybackToBeginning(); + void jumpPlaybackTo(timeT); + void panic(); + + void stepByStepTargetRequested(QObject *); + + void editTriggerSegment(int); + + void editTimeSignature(timeT); + +public slots: + + /** + * put the indicationed text/object into the clipboard and remove * it + * from the document + */ + virtual void slotEditCut(); + + /** + * put the indicationed text/object into the clipboard + */ + virtual void slotEditCopy(); + + /** + * paste the clipboard into the document + */ + virtual void slotEditPaste(); + + /** + * Delete the current selection + */ + void slotEditDelete(); + + virtual void slotStepBackward(); // override from EditView + virtual void slotStepForward(); // override from EditView + + void slotPreviewSelection(); + void slotClearLoop(); + void slotClearSelection(); + + /** + * Filter selection by event type + */ + void slotFilterSelection(); // dummy - not actually functional yet + + /// edition tools + void slotPaintSelected(); + void slotEraseSelected(); + void slotSelectSelected(); + void slotMoveSelected(); + void slotResizeSelected(); + + void slotToggleStepByStep(); + + /// status stuff + void slotUpdateInsertModeStatus(); + + /// transforms + void slotTransformsQuantize(); + void slotTransformsRepeatQuantize(); + void slotTransformsLegato(); + void slotVelocityUp(); + void slotVelocityDown(); + + /// settings + void slotToggleChordsRuler(); + void slotToggleTempoRuler(); + + /// cursor moves + void slotJumpCursorToPlayback(); + void slotJumpPlaybackToCursor(); + void slotToggleTracking(); + + /// Canvas actions slots + + /** + * Called when a mouse press occurred on a matrix element + * or somewhere on the staff + */ + void slotMousePressed(timeT time, int pitch, + QMouseEvent*, MatrixElement*); + + void slotMouseMoved(timeT time, int pitch, QMouseEvent*); + void slotMouseReleased(timeT time, int pitch, QMouseEvent*); + + /** + * Called when the mouse cursor moves over a different height on + * the staff + * + * @see MatrixCanvasView#hoveredOverNoteChanged() + */ + void slotHoveredOverNoteChanged(int evPitch, bool haveEvent, + timeT evTime); + + /** + * Called when the mouse cursor moves over a different key on + * the piano keyboard + * + * @see PianoKeyboard#hoveredOverKeyChanged() + */ + void slotHoveredOverKeyChanged(unsigned int); + + /** + * Called when the mouse cursor moves over a note which is at a + * different time on the staff + * + * @see MatrixCanvasView#hoveredOverNoteChange() + */ + void slotHoveredOverAbsoluteTimeChanged(unsigned int); + + /** + * Set the time pointer position during playback + */ + void slotSetPointerPosition(timeT time); + + /** + * Set the time pointer position during playback + */ + void slotSetPointerPosition(timeT time, + bool scroll); + + /** + * Set the insertion pointer position (from the bottom LoopRuler) + */ + void slotSetInsertCursorPosition(timeT position, bool scroll); + + virtual void slotSetInsertCursorPosition(timeT position) { + slotSetInsertCursorPosition(position, true); + } + + /** + * Catch the keyboard being pressed + */ + void slotKeyPressed(unsigned int y, bool repeating); + + /** + * Catch the keyboard being released + */ + void slotKeyReleased(unsigned int y, bool repeating); + + /** + * Catch the keyboard being pressed with selection modifier + */ + void slotKeySelected(unsigned int y, bool repeating); + + /** + * Handle scrolling between view and PianoKeyboard + */ + void slotVerticalScrollPianoKeyboard(int y); + + /** + * Close + */ + void closeWindow(); + + /** + * A new selection has been acquired by a tool + */ + void slotNewSelection(); + + /** + * Set the snaptime of the grid from an item in the snap combo + */ + void slotSetSnapFromIndex(int); + + /** + * Set the snaptime of the grid based on the name of the invoking action + */ + void slotSetSnapFromAction(); + + /** + * Set the snaptime of the grid + */ + void slotSetSnap(timeT); + + /** + * Quantize a selection to a given level + */ + void slotQuantizeSelection(int); + + /** + * Collapse equal pitch notes + */ + void slotTransformsCollapseNotes(); + + /** + * Pop-up the velocity modification dialog + */ + void slotSetVelocities(); + + /** + * Set selected event velocities to whatever's in the velocity widget + */ + void slotSetVelocitiesToCurrent(); + + /** + * Pop-up the select trigger segment dialog + */ + void slotTriggerSegment(); + + /** + * Clear triggers from selection + */ + void slotRemoveTriggers(); + + /** + * Change horizontal zoom + */ + void slotChangeHorizontalZoom(int); + + void slotZoomIn(); + void slotZoomOut(); + + /** + * Select all + */ + void slotSelectAll(); + + /** + * Keyboard insert + */ + void slotInsertNoteFromAction(); + + /// 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); + + /// The given QObject has originated a step-by-step-editing request + void slotStepByStepTargetRequested(QObject *); + + void slotInstrumentLevelsChanged(InstrumentId, + const LevelInfo &); + + /// Set the velocity menu to the given value + void slotSetCurrentVelocity(int); + void slotSetCurrentVelocityFromSelection(); + +protected slots: + void slotCanvasBottomWidgetHeightChanged(int newHeight); + + /** + * A new percussion key mapping has to be displayed + */ + void slotPercussionSetChanged(Instrument *); + + /** + * Re-dock the parameters box to its initial position + */ + void slotDockParametersBack(); + + /** + * The parameters box was closed + */ + void slotParametersClosed(); + + /** + * The parameters box was docked back + */ + void slotParametersDockedBack(KDockWidget*, KDockWidget::DockPosition); + + /** + * The instrument for this track may have changed + */ + void slotCheckTrackAssignments(); + + void slotToolHelpChanged(const QString &); + void slotMouseEnteredCanvasView(); + void slotMouseLeftCanvasView(); + +protected: + virtual RulerScale* getHLayout(); + + virtual Segment *getCurrentSegment(); + virtual Staff *getCurrentStaff(); + virtual timeT getInsertionTime(); + + /** + * 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(); + + /** + * read general Options again and initialize all variables like the recent file list + */ + virtual void readOptions(); + + /** + * create menus and toolbars + */ + virtual void setupActions(); + + /** + * setup status bar + */ + virtual void initStatusBar(); + + /** + * update the current quantize level from selection or entire segment + */ + virtual void updateQuantizeCombo(); + + /** + * Return the size of the MatrixCanvasView + */ + virtual QSize getViewSize(); + + /** + * Set the size of the MatrixCanvasView + */ + virtual void setViewSize(QSize); + + virtual MatrixCanvasView *getCanvasView(); + + /** + * Init matrix actions toolbar + */ + void initActionsToolbar(); + + /** + * Zoom toolbar + */ + void initZoomToolbar(); + + /** + * Test whether we've had too many preview notes recently + */ + bool canPreviewAnotherNote(); + + virtual void paintEvent(QPaintEvent* e); + + virtual void updateViewCaption(); + + int computePostLayoutWidth(); + + /** + * Get min and max pitches of notes on matrix. + * Return false if no notes. + */ + bool getMinMaxPitches(int& minPitch, int& maxPitch); + + /** + * If necessary, extend local keymapping to contain + * all notes currently on staff + */ + void extendKeyMapping(); + + //--------------- Data members --------------------------------- + + std::vector<MatrixStaff*> m_staffs; + + MatrixHLayout m_hlayout; + MatrixVLayout m_vlayout; + SnapGrid *m_snapGrid; + + timeT m_lastEndMarkerTime; + + // Status bar elements + QLabel* m_hoveredOverAbsoluteTime; + QLabel* m_hoveredOverNoteName; + QLabel *m_selectionCounter; + QLabel *m_insertModeLabel; + bool m_haveHoveredOverNote; + + /** + * used in slotHoveredOverKeyChanged to track moves over the piano + * keyboard + */ + int m_previousEvPitch; + + KDockWidget *m_dockLeft; + MatrixCanvasView *m_canvasView; + QDeferScrollView *m_pianoView; + PitchRuler *m_pitchRuler; + + MidiKeyMapping *m_localMapping; + + // The last note we sent in case we're swooshing up and + // down the keyboard and don't want repeat notes sending + // + MidiByte m_lastNote; + + // The first note we sent in similar case (only used for + // doing effective sweep selections + // + MidiByte m_firstNote; + + PropertyName m_selectedProperty; + + // The parameter box + // + InstrumentParameterBox *m_parameterBox; + + // Toolbar flora + // + KComboBox *m_velocityCombo; + KComboBox *m_quantizeCombo; + KComboBox *m_snapGridCombo; + ZoomSlider<double> *m_hZoomSlider; + ZoomSlider<double> *m_vZoomSlider; + QLabel *m_zoomLabel; + + // Hold our matrix quantization values and snap values + // + std::vector<timeT> m_quantizations; + std::vector<timeT> m_snapValues; + + std::vector<std::pair<PropertyViewRuler*, PropertyBox*> > m_propertyViewRulers; + + ChordNameRuler *m_chordNameRuler; + QWidget *m_tempoRuler; + + // ruler used to scale tempo and chord name ruler + ZoomableMatrixHLayoutRulerScale* m_referenceRuler; + + std::vector<std::pair<int, int> > m_pendingInsertableNotes; + + bool m_playTracking; + bool m_dockVisible; + bool m_drumMode; + + bool m_mouseInCanvasView; + QString m_toolContextHelp; +}; + +// Commented this out - was a MatrixView inner class, but we get a warning +// that Q_OBJECT can't be used in an inner class - gl +// + +// class NoteSender : public QObject +// { +// Q_OBJECT + +// public: +// NoteSender(int i, int p) : m_insid(i), m_pitch(p) { } +// virtual ~NoteSender(); + +// public slots: +// void sendNote(); + +// private: +// int m_insid, m_pitch; +// }; + + +} + +#endif diff --git a/src/gui/editors/matrix/PianoKeyboard.cpp b/src/gui/editors/matrix/PianoKeyboard.cpp new file mode 100644 index 0000000..e4641d0 --- /dev/null +++ b/src/gui/editors/matrix/PianoKeyboard.cpp @@ -0,0 +1,299 @@ +/* -*- 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 "PianoKeyboard.h" +#include "misc/Debug.h" + +#include "gui/general/GUIPalette.h" +#include "gui/general/MidiPitchLabel.h" +#include "gui/rulers/PitchRuler.h" +#include "MatrixStaff.h" +#include "MatrixView.h" +#include <qcolor.h> +#include <qcursor.h> +#include <qevent.h> +#include <qfont.h> +#include <qpainter.h> +#include <qsize.h> +#include <qwidget.h> + + +namespace Rosegarden +{ + +const unsigned int _smallWhiteKeyHeight = 14; +const unsigned int _whiteKeyHeight = 18; + +PianoKeyboard::PianoKeyboard(QWidget *parent, int keys) + : PitchRuler(parent), + m_keySize(48, 18), + m_blackKeySize(24, 8), + m_nbKeys(keys), + m_mouseDown(false), + m_hoverHighlight(new QWidget(this)), + m_lastHoverHighlight(0), + m_lastKeyPressed(0) +{ + m_hoverHighlight->hide(); + m_hoverHighlight->setPaletteBackgroundColor(GUIPalette::getColour(GUIPalette::MatrixKeyboardFocus)); + + setPaletteBackgroundColor(QColor(238, 238, 224)); + + computeKeyPos(); + setMouseTracking(true); +} + +QSize PianoKeyboard::sizeHint() const +{ + return QSize(m_keySize.width(), + m_keySize.height() * m_nbKeys); +} + +QSize PianoKeyboard::minimumSizeHint() const +{ + return m_keySize; +} + +void PianoKeyboard::computeKeyPos() +{ + // int y = -9; + int y = -4; + + unsigned int posInOctave = 0, + keyHeight = _smallWhiteKeyHeight; + + for (unsigned int i = 0; i < m_nbKeys; ++i) { + posInOctave = (i + 5) % 7; + + if (y >= 0) { + m_whiteKeyPos.push_back(y); + m_allKeyPos.push_back(y); + } + + if (posInOctave == 2) + m_labelKeyPos.push_back(y + (keyHeight * 3 / 4) - 2); + + if (posInOctave == 0 || + posInOctave == 2 || + posInOctave == 6 || + posInOctave == 3) { // draw shorter white key + + + keyHeight = _smallWhiteKeyHeight; + + if (posInOctave == 2 || + posInOctave == 6) + --keyHeight; + + } else { + + keyHeight = _whiteKeyHeight; + } + + if (posInOctave != 2 && posInOctave != 6) { // draw black key + + unsigned int bY = y + keyHeight - m_blackKeySize.height() / 2; + + m_blackKeyPos.push_back(bY); + m_allKeyPos.push_back(bY); + + } + + y += keyHeight; + } +} + +void PianoKeyboard::paintEvent(QPaintEvent*) +{ + static QFont *pFont = 0; + if (!pFont) { + pFont = new QFont(); + pFont->setPixelSize(9); + } + + QPainter paint(this); + + paint.setFont(*pFont); + + for (unsigned int i = 0; i < m_whiteKeyPos.size(); ++i) + paint.drawLine(0, m_whiteKeyPos[i], + m_keySize.width(), m_whiteKeyPos[i]); + + for (unsigned int i = 0; i < m_labelKeyPos.size(); ++i) { + + int pitch = (m_labelKeyPos.size() - i) * 12; + + // for some reason I don't immediately comprehend, + // m_labelKeyPos contains two more octaves than we need + pitch -= 24; + + MidiPitchLabel label(pitch); + paint.drawText(m_blackKeySize.width(), m_labelKeyPos[i], + label.getQString()); + } + + paint.setBrush(colorGroup().foreground()); + + for (unsigned int i = 0; i < m_blackKeyPos.size(); ++i) + paint.drawRect(0, m_blackKeyPos[i], + m_blackKeySize.width(), m_blackKeySize.height()); +} + +void PianoKeyboard::enterEvent(QEvent *) +{ + //drawHoverNote(e->y()); +} + +void PianoKeyboard::leaveEvent(QEvent*) +{ + m_hoverHighlight->hide(); + + int pos = mapFromGlobal( cursor().pos() ).x(); + if ( pos > m_keySize.width() - 5 || pos < 0 ) { // bit of a hack + emit keyReleased(m_lastKeyPressed, false); + } +} + +void PianoKeyboard::drawHoverNote(int evPitch) +{ + if (m_lastHoverHighlight != evPitch) { + //MATRIX_DEBUG << "PianoKeyboard::drawHoverNote : note = " << evPitch << endl; + m_lastHoverHighlight = evPitch; + + int count = 0; + std::vector<unsigned int>::iterator it; + for (it = m_allKeyPos.begin(); it != m_allKeyPos.end(); ++it, ++count) { + if (126 - evPitch == count) { + int width = m_keySize.width() - 8; + int yPos = *it + 5; + + // check if this is a black key + // + std::vector<unsigned int>::iterator bIt; + bool isBlack = false; + for (bIt = m_blackKeyPos.begin(); bIt != m_blackKeyPos.end(); ++bIt) { + if (*bIt == *it) { + isBlack = true; + break; + } + } + + // Adjust for black note + // + if (isBlack) { + width = m_blackKeySize.width() - 8; + yPos -= 3; + } else { + // If a white note then ensure that we allow for short/tall ones + // + std::vector<unsigned int>::iterator wIt = m_whiteKeyPos.begin(), tIt; + + while (wIt != m_whiteKeyPos.end()) { + if (*wIt == *it) { + tIt = wIt; + + if (++tIt != m_whiteKeyPos.end()) { + //MATRIX_DEBUG << "WHITE KEY HEIGHT = " << *tIt - *wIt << endl; + if (*tIt - *wIt == _whiteKeyHeight) { + yPos += 2; + } + + } + } + + ++wIt; + } + + + } + + m_hoverHighlight->setFixedSize(width, 4); + m_hoverHighlight->move(3, yPos); + m_hoverHighlight->show(); + + return ; + } + } + } + + +} + +void PianoKeyboard::mouseMoveEvent(QMouseEvent* e) +{ + // The routine to work out where this should appear doesn't coincide with the note + // that we send to the sequencer - hence this is a bit pointless and crap at the moment. + // My own fault it's so crap but there you go. + // + // RWB (20040220) + // + MatrixView *matrixView = dynamic_cast<MatrixView*>(topLevelWidget()); + if (matrixView) { + MatrixStaff *staff = matrixView->getStaff(0); + + if (staff) { + drawHoverNote(staff->getHeightAtCanvasCoords(e->x(), e->y())); + } + } + + if (e->state() & Qt::LeftButton) { + if (m_selecting) + emit keySelected(e->y(), true); + else + emit keyPressed(e->y(), true); // we're swooshing + + emit keyReleased(m_lastKeyPressed, true); + m_lastKeyPressed = e->y(); + } else + emit hoveredOverKeyChanged(e->y()); +} + +void PianoKeyboard::mousePressEvent(QMouseEvent *e) +{ + Qt::ButtonState bs = e->state(); + + if (e->button() == LeftButton) { + m_mouseDown = true; + m_selecting = (bs & Qt::ShiftButton); + m_lastKeyPressed = e->y(); + + if (m_selecting) + emit keySelected(e->y(), false); + else + emit keyPressed(e->y(), false); + } +} + +void PianoKeyboard::mouseReleaseEvent(QMouseEvent *e) +{ + if (e->button() == LeftButton) { + m_mouseDown = false; + m_selecting = false; + emit keyReleased(e->y(), false); + } +} + +} +#include "PianoKeyboard.moc" diff --git a/src/gui/editors/matrix/PianoKeyboard.h b/src/gui/editors/matrix/PianoKeyboard.h new file mode 100644 index 0000000..e8b06bb --- /dev/null +++ b/src/gui/editors/matrix/PianoKeyboard.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_PIANOKEYBOARD_H_ +#define _RG_PIANOKEYBOARD_H_ + +#include "gui/rulers/PitchRuler.h" +#include <qsize.h> +#include <vector> + + +class QWidget; +class QPaintEvent; +class QMouseEvent; +class QEvent; + + +namespace Rosegarden +{ + + + +class PianoKeyboard : public PitchRuler +{ + Q_OBJECT +public: + PianoKeyboard(QWidget *parent, int keys = 88); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + /* + * We want to be able to call this from the matrix view + */ + void drawHoverNote(int evPitch); + +signals: + + /** + * A key has been clicked on the keyboard. + * + * The repeating flag is there to tell the MatrixView not to send + * the same note again as we're in the middle of a swoosh. + * MatrixView does the y -> Note calculation. + */ + void keyPressed(unsigned int y, bool repeating); + + /** + * A key has been clicked with the selection modifier pressed. + * The MatrixView will probably interpret this as meaning to + * select all notes of that pitch. + * + * The repeating flag is there to tell the MatrixView not to + * clear the selection as we're in the middle of a swoosh. + * MatrixView does the y -> Note calculation. + */ + void keySelected(unsigned int y, bool repeating); + + /** + * A key has been released on the keyboard. + * + * The repeating flag is there to tell the MatrixView not to send + * the same note again as we're in the middle of a swoosh. + * MatrixView does the y -> Note calculation. + */ + void keyReleased(unsigned int y, bool repeating); + + /** + * Emitted when the mouse cursor moves to a different key when + * not clicking or selecting. + * MatrixView does the y -> Note calculation. + */ + void hoveredOverKeyChanged(unsigned int y); + +protected: + + virtual void paintEvent(QPaintEvent*); + + virtual void mouseMoveEvent(QMouseEvent*); + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); + + // compute all key positions and store them + // + void computeKeyPos(); + + //--------------- Data members --------------------------------- + QSize m_keySize; + QSize m_blackKeySize; + unsigned int m_nbKeys; + + std::vector<unsigned int> m_whiteKeyPos; + std::vector<unsigned int> m_blackKeyPos; + std::vector<unsigned int> m_labelKeyPos; + std::vector<unsigned int> m_allKeyPos; + + bool m_mouseDown; + bool m_selecting; + + // highlight element on the keyboard + QWidget *m_hoverHighlight; + int m_lastHoverHighlight; + int m_lastKeyPressed; +}; + + +} + +#endif diff --git a/src/gui/editors/matrix/QCanvasMatrixDiamond.cpp b/src/gui/editors/matrix/QCanvasMatrixDiamond.cpp new file mode 100644 index 0000000..582b53a --- /dev/null +++ b/src/gui/editors/matrix/QCanvasMatrixDiamond.cpp @@ -0,0 +1,82 @@ +/* -*- 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 "QCanvasMatrixDiamond.h" + +#include "MatrixElement.h" +#include "QCanvasMatrixRectangle.h" +#include <qcanvas.h> +#include <qpainter.h> +#include <qpointarray.h> +#include <qpoint.h> + + +namespace Rosegarden +{ + +QCanvasMatrixDiamond::QCanvasMatrixDiamond(MatrixElement &n, + QCanvas* canvas) : + QCanvasMatrixRectangle(n, canvas) +{} + +QCanvasMatrixDiamond::~QCanvasMatrixDiamond() +{ + hide(); +} + +QPointArray QCanvasMatrixDiamond::areaPoints() const +{ + QPointArray pa(4); + int pw = (pen().width() + 1) / 2; + if ( pw < 1 ) + pw = 1; + if ( pen() == NoPen ) + pw = 0; + pa[0] = QPoint((int)x() - height() / 2 - pw, (int)y() - pw); + pa[1] = pa[0] + QPoint(height() + pw * 2, 0); + pa[2] = pa[1] + QPoint(0, height() + pw * 2); + pa[3] = pa[0] + QPoint(0, height() + pw * 2); + return pa; +} + +void QCanvasMatrixDiamond::drawShape(QPainter & p) +{ + p.save(); + p.setWorldXForm(false); + + QPointArray pa(4); + int q = height() / 2 + 2; + QPoint mapPos = p.worldMatrix().map(QPoint(int(x()), int(y()))); + + pa[0] = QPoint(mapPos.x(), mapPos.y() - 3); + pa[1] = QPoint(mapPos.x() + q, mapPos.y() - 3 + q); + pa[2] = pa[0] + QPoint(0, q * 2); + pa[3] = pa[1] - QPoint(q * 2, 0); + p.drawConvexPolygon(pa); + + p.restore(); +} + +} diff --git a/src/gui/editors/matrix/QCanvasMatrixDiamond.h b/src/gui/editors/matrix/QCanvasMatrixDiamond.h new file mode 100644 index 0000000..5163b12 --- /dev/null +++ b/src/gui/editors/matrix/QCanvasMatrixDiamond.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_QCANVASMATRIXDIAMOND_H_ +#define _RG_QCANVASMATRIXDIAMOND_H_ + +#include "QCanvasMatrixRectangle.h" +#include <qpointarray.h> + + +class QPainter; +class QCanvas; + + +namespace Rosegarden +{ + +class MatrixElement; + + +/** + * A QCanvas diamond shape referencing a MatrixElement + */ +class QCanvasMatrixDiamond : public QCanvasMatrixRectangle +{ +public: + QCanvasMatrixDiamond(MatrixElement&, QCanvas *); + ~QCanvasMatrixDiamond(); + + QPointArray areaPoints() const; + +protected: + void drawShape(QPainter &); +}; + + +} + +#endif diff --git a/src/gui/editors/matrix/QCanvasMatrixRectangle.cpp b/src/gui/editors/matrix/QCanvasMatrixRectangle.cpp new file mode 100644 index 0000000..a27b480 --- /dev/null +++ b/src/gui/editors/matrix/QCanvasMatrixRectangle.cpp @@ -0,0 +1,44 @@ +/* -*- 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 "QCanvasMatrixRectangle.h" + +#include "MatrixElement.h" +#include <qcanvas.h> + + +namespace Rosegarden +{ + +QCanvasMatrixRectangle::QCanvasMatrixRectangle(MatrixElement& n, + QCanvas* canvas) + : QCanvasRectangle(canvas), + m_matrixElement(n) +{} + +QCanvasMatrixRectangle::~QCanvasMatrixRectangle() +{} + +} diff --git a/src/gui/editors/matrix/QCanvasMatrixRectangle.h b/src/gui/editors/matrix/QCanvasMatrixRectangle.h new file mode 100644 index 0000000..64b6e65 --- /dev/null +++ b/src/gui/editors/matrix/QCanvasMatrixRectangle.h @@ -0,0 +1,60 @@ + +/* -*- 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_QCANVASMATRIXRECTANGLE_H_ +#define _RG_QCANVASMATRIXRECTANGLE_H_ + +#include <qcanvas.h> + + +namespace Rosegarden +{ + +class MatrixElement; + + +/** + * A QCanvasRectangle referencing a MatrixElement + */ +class QCanvasMatrixRectangle : public QCanvasRectangle +{ +public: + QCanvasMatrixRectangle(MatrixElement&, QCanvas*); + + virtual ~QCanvasMatrixRectangle(); + + MatrixElement& getMatrixElement() { return m_matrixElement; } + +protected: + //--------------- Data members --------------------------------- + + MatrixElement& m_matrixElement; + +}; + + +} + +#endif 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 diff --git a/src/gui/editors/parameters/AudioInstrumentParameterPanel.cpp b/src/gui/editors/parameters/AudioInstrumentParameterPanel.cpp new file mode 100644 index 0000000..44a202b --- /dev/null +++ b/src/gui/editors/parameters/AudioInstrumentParameterPanel.cpp @@ -0,0 +1,437 @@ +/* -*- 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 "AudioInstrumentParameterPanel.h" +#include <qlayout.h> +#include <kapplication.h> + +#include <klocale.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/AudioPluginInstance.h" +#include "base/Instrument.h" +#include "base/MidiProgram.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/studio/AudioPluginManager.h" +#include "gui/studio/AudioPlugin.h" +#include "gui/studio/StudioControl.h" +#include "gui/widgets/AudioFaderBox.h" +#include "gui/widgets/AudioVUMeter.h" +#include "gui/widgets/Fader.h" +#include "gui/widgets/Rotary.h" +#include "gui/widgets/AudioRouteMenu.h" +#include "InstrumentParameterPanel.h" +#include "sound/MappedCommon.h" +#include "sound/MappedStudio.h" +#include <qcolor.h> +#include <qframe.h> +#include <qlabel.h> +#include <qpalette.h> +#include <qpixmap.h> +#include <qpushbutton.h> +#include <qstring.h> +#include <qtooltip.h> +#include <qwidget.h> +#include <qsignalmapper.h> + + +namespace Rosegarden +{ + +void +AudioInstrumentParameterPanel::slotSelectAudioLevel(float dB) +{ + if (m_selectedInstrument == 0) + return ; + + if (m_selectedInstrument->getType() == Instrument::Audio || + m_selectedInstrument->getType() == Instrument::SoftSynth) { + m_selectedInstrument->setLevel(dB); + + StudioControl::setStudioObjectProperty + (MappedObjectId(m_selectedInstrument->getMappedId()), + MappedAudioFader::FaderLevel, + MappedObjectValue(dB)); + } + + emit updateAllBoxes(); + emit instrumentParametersChanged(m_selectedInstrument->getId()); +} + +void +AudioInstrumentParameterPanel::slotSelectAudioRecordLevel(float dB) +{ + if (m_selectedInstrument == 0) + return ; + + // std::cerr << "AudioInstrumentParameterPanel::slotSelectAudioRecordLevel(" + // << dB << ")" << std::endl; + + if (m_selectedInstrument->getType() == Instrument::Audio) { + m_selectedInstrument->setRecordLevel(dB); + + StudioControl::setStudioObjectProperty + (MappedObjectId(m_selectedInstrument->getMappedId()), + MappedAudioFader::FaderRecordLevel, + MappedObjectValue(dB)); + + emit updateAllBoxes(); + emit instrumentParametersChanged(m_selectedInstrument->getId()); + } +} + +void +AudioInstrumentParameterPanel::slotPluginSelected(InstrumentId instrumentId, + int index, int plugin) +{ + if (!m_selectedInstrument || + instrumentId != m_selectedInstrument->getId()) + return ; + + RG_DEBUG << "AudioInstrumentParameterPanel::slotPluginSelected - " + << "instrument = " << instrumentId + << ", index = " << index + << ", plugin = " << plugin << endl; + + QColor pluginBackgroundColour = Qt::black; + bool bypassed = false; + + QPushButton *button = 0; + QString noneText; + + // updates synth gui button &c: + m_audioFader->slotSetInstrument(&m_doc->getStudio(), m_selectedInstrument); + + if (index == (int)Instrument::SYNTH_PLUGIN_POSITION) { + button = m_audioFader->m_synthButton; + noneText = i18n("<no synth>"); + } else { + button = m_audioFader->m_plugins[index]; + noneText = i18n("<no plugin>"); + } + + if (!button) + return ; + + if (plugin == -1) { + + button->setText(noneText); + QToolTip::add + (button, noneText); + + } else { + + AudioPlugin *pluginClass = m_doc->getPluginManager()->getPlugin(plugin); + + if (pluginClass) { + button->setText(pluginClass->getLabel()); + + QToolTip::add + (button, pluginClass->getLabel()); + + pluginBackgroundColour = pluginClass->getColour(); + } + } + + AudioPluginInstance *inst = + m_selectedInstrument->getPlugin(index); + + if (inst) + bypassed = inst->isBypassed(); + + setButtonColour(index, bypassed, pluginBackgroundColour); + + if (index == (int)Instrument::SYNTH_PLUGIN_POSITION) { + emit changeInstrumentLabel(instrumentId, button->text()); + } +} + +void +AudioInstrumentParameterPanel::slotPluginBypassed(InstrumentId instrumentId, + int pluginIndex, bool bp) +{ + if (!m_selectedInstrument || + instrumentId != m_selectedInstrument->getId()) + return ; + + AudioPluginInstance *inst = + m_selectedInstrument->getPlugin(pluginIndex); + + QColor backgroundColour = Qt::black; // default background colour + + if (inst && inst->isAssigned()) { + AudioPlugin *pluginClass + = m_doc->getPluginManager()->getPlugin( + m_doc->getPluginManager()-> + getPositionByIdentifier(inst->getIdentifier().c_str())); + + /// Set the colour on the button + // + if (pluginClass) + backgroundColour = pluginClass->getColour(); + } + + setButtonColour(pluginIndex, bp, backgroundColour); +} + +void +AudioInstrumentParameterPanel::setButtonColour( + int pluginIndex, bool bypassState, const QColor &colour) +{ + RG_DEBUG << "AudioInstrumentParameterPanel::setButtonColour " + << "pluginIndex = " << pluginIndex + << ", bypassState = " << bypassState + << ", rgb = " << colour.name() << endl; + + QPushButton *button = 0; + + if (pluginIndex == Instrument::SYNTH_PLUGIN_POSITION) { + button = m_audioFader->m_synthButton; + } else { + button = m_audioFader->m_plugins[pluginIndex]; + } + + if (!button) + return ; + + // Set the bypass colour on the plugin button + if (bypassState) { + button-> + setPaletteForegroundColor(kapp->palette(). + color(QPalette::Active, QColorGroup::Button)); + + button-> + setPaletteBackgroundColor(kapp->palette(). + color(QPalette::Active, QColorGroup::ButtonText)); + } else if (colour == Qt::black) { + button-> + setPaletteForegroundColor(kapp->palette(). + color(QPalette::Active, QColorGroup::ButtonText)); + + button-> + setPaletteBackgroundColor(kapp->palette(). + color(QPalette::Active, QColorGroup::Button)); + } else { + button-> + setPaletteForegroundColor(Qt::white); + + button-> + setPaletteBackgroundColor(colour); + } +} + +AudioInstrumentParameterPanel::AudioInstrumentParameterPanel(RosegardenGUIDoc* doc, QWidget* parent) + : InstrumentParameterPanel(doc, parent), + m_audioFader(new AudioFaderBox(this)) +{ + QGridLayout *gridLayout = new QGridLayout(this, 3, 2, 5, 5); + + // Instrument label : first row, all cols + gridLayout->addMultiCellWidget(m_instrumentLabel, 0, 0, 0, 1, AlignCenter); + + // fader and connect it + gridLayout->addMultiCellWidget(m_audioFader, 1, 1, 0, 1); + + gridLayout->setRowStretch(2, 1); + + connect(m_audioFader, SIGNAL(audioChannelsChanged(int)), + this, SLOT(slotAudioChannels(int))); + + connect(m_audioFader->m_signalMapper, SIGNAL(mapped(int)), + this, SLOT(slotSelectPlugin(int))); + + connect(m_audioFader->m_fader, SIGNAL(faderChanged(float)), + this, SLOT(slotSelectAudioLevel(float))); + + connect(m_audioFader->m_recordFader, SIGNAL(faderChanged(float)), + this, SLOT(slotSelectAudioRecordLevel(float))); + + connect(m_audioFader->m_pan, SIGNAL(valueChanged(float)), + this, SLOT(slotSetPan(float))); + + connect(m_audioFader->m_audioOutput, SIGNAL(changed()), + this, SLOT(slotAudioRoutingChanged())); + + connect(m_audioFader->m_audioInput, SIGNAL(changed()), + this, SLOT(slotAudioRoutingChanged())); + + connect(m_audioFader->m_synthButton, SIGNAL(clicked()), + this, SLOT(slotSynthButtonClicked())); + + connect(m_audioFader->m_synthGUIButton, SIGNAL(clicked()), + this, SLOT(slotSynthGUIButtonClicked())); +} + +void +AudioInstrumentParameterPanel::slotSynthButtonClicked() +{ + slotSelectPlugin(Instrument::SYNTH_PLUGIN_POSITION); +} + +void +AudioInstrumentParameterPanel::slotSynthGUIButtonClicked() +{ + emit showPluginGUI(m_selectedInstrument->getId(), + Instrument::SYNTH_PLUGIN_POSITION); +} + +void +AudioInstrumentParameterPanel::slotSetPan(float pan) +{ + RG_DEBUG << "AudioInstrumentParameterPanel::slotSetPan - " + << "pan = " << pan << endl; + + StudioControl::setStudioObjectProperty + (MappedObjectId(m_selectedInstrument->getMappedId()), + MappedAudioFader::Pan, + MappedObjectValue(pan)); + + m_selectedInstrument->setPan(MidiByte(pan + 100.0)); + emit instrumentParametersChanged(m_selectedInstrument->getId()); +} + +void +AudioInstrumentParameterPanel::setAudioMeter(float dBleft, float dBright, + float recDBleft, float recDBright) +{ + // RG_DEBUG << "AudioInstrumentParameterPanel::setAudioMeter: (" << dBleft + // << "," << dBright << ")" << endl; + + if (m_selectedInstrument) { + // Always set stereo, because we have to reflect what's happening + // with the pan setting even on mono tracks + m_audioFader->m_vuMeter->setLevel(dBleft, dBright); + m_audioFader->m_vuMeter->setRecordLevel(recDBleft, recDBright); + } +} + +void +AudioInstrumentParameterPanel::setupForInstrument(Instrument* instrument) +{ + blockSignals(true); + + m_selectedInstrument = instrument; + + m_instrumentLabel->setText(strtoqstr(instrument->getName())); + + m_audioFader->m_recordFader->setFader(instrument->getRecordLevel()); + m_audioFader->m_fader->setFader(instrument->getLevel()); + + m_audioFader->slotSetInstrument(&m_doc->getStudio(), instrument); + + int start = 0; + + if (instrument->getType() == Instrument::SoftSynth) + start = -1; + + for (int i = start; i < int(m_audioFader->m_plugins.size()); i++) { + int index; + QPushButton *button; + QString noneText; + + if (i == -1) { + index = Instrument::SYNTH_PLUGIN_POSITION; + button = m_audioFader->m_synthButton; + noneText = i18n("<no synth>"); + } else { + index = i; + button = m_audioFader->m_plugins[i]; + noneText = i18n("<no plugin>"); + } + + button->show(); + + AudioPluginInstance *inst = instrument->getPlugin(index); + + if (inst && inst->isAssigned()) { + AudioPlugin *pluginClass + = m_doc->getPluginManager()->getPlugin( + m_doc->getPluginManager()-> + getPositionByIdentifier(inst->getIdentifier().c_str())); + + if (pluginClass) { + button->setText(pluginClass->getLabel()); + QToolTip::add + (button, pluginClass->getLabel()); + setButtonColour(index, inst->isBypassed(), + pluginClass->getColour()); + } + } else { + button->setText(noneText); + QToolTip::add + (button, noneText); + setButtonColour(index, inst ? inst->isBypassed() : false, Qt::black); + } + } + + // Set the number of channels on the fader widget + // + m_audioFader->setAudioChannels(instrument->getAudioChannels()); + + // Pan - adjusted backwards + // + m_audioFader->m_pan->setPosition(instrument->getPan() - 100); + + // Tell fader box whether to include e.g. audio input selection + // + m_audioFader->setIsSynth(instrument->getType() == Instrument::SoftSynth); + + blockSignals(false); +} + +void +AudioInstrumentParameterPanel::slotAudioChannels(int channels) +{ + RG_DEBUG << "AudioInstrumentParameterPanel::slotAudioChannels - " + << "channels = " << channels << endl; + + m_selectedInstrument->setAudioChannels(channels); + + StudioControl::setStudioObjectProperty + (MappedObjectId(m_selectedInstrument->getMappedId()), + MappedAudioFader::Channels, + MappedObjectValue(channels)); + + emit instrumentParametersChanged(m_selectedInstrument->getId()); + +} + +void +AudioInstrumentParameterPanel::slotAudioRoutingChanged() +{ + if (m_selectedInstrument) + emit instrumentParametersChanged(m_selectedInstrument->getId()); +} + +void +AudioInstrumentParameterPanel::slotSelectPlugin(int index) +{ + if (m_selectedInstrument) { + emit selectPlugin(0, m_selectedInstrument->getId(), index); + } +} + +} +#include "AudioInstrumentParameterPanel.moc" diff --git a/src/gui/editors/parameters/AudioInstrumentParameterPanel.h b/src/gui/editors/parameters/AudioInstrumentParameterPanel.h new file mode 100644 index 0000000..932e6bc --- /dev/null +++ b/src/gui/editors/parameters/AudioInstrumentParameterPanel.h @@ -0,0 +1,107 @@ + +/* -*- 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_AUDIOINSTRUMENTPARAMETERPANEL_H_ +#define _RG_AUDIOINSTRUMENTPARAMETERPANEL_H_ + +#include "base/MidiProgram.h" +#include "InstrumentParameterPanel.h" +#include <qpixmap.h> +#include <qstring.h> + + +class QWidget; +class QColor; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class Instrument; +class AudioFaderBox; + + +class AudioInstrumentParameterPanel : public InstrumentParameterPanel +{ + Q_OBJECT +public: + AudioInstrumentParameterPanel(RosegardenGUIDoc* doc, QWidget* parent); + + virtual void setupForInstrument(Instrument*); + + // Set the audio meter to a given level for a maximum of + // two channels. + // + void setAudioMeter(float dBleft, float dBright, + float recDBleft, float recDBright); + + // Set the button colour + // + void setButtonColour(int pluginIndex, bool bypassState, + const QColor &color); + +public slots: + // From AudioFaderBox + // + void slotSelectAudioLevel(float dB); + void slotSelectAudioRecordLevel(float dB); + void slotAudioChannels(int channels); + void slotAudioRoutingChanged(); + void slotSelectPlugin(int index); + + // From the parameter box clicks + void slotSetPan(float pan); + + // From Plugin dialog + // + void slotPluginSelected(InstrumentId id, int index, int plugin); + void slotPluginBypassed(InstrumentId id, int pluginIndex, bool bp); + + void slotSynthButtonClicked(); + void slotSynthGUIButtonClicked(); + +signals: + void selectPlugin(QWidget *, InstrumentId, int index); + void instrumentParametersChanged(InstrumentId); + void showPluginGUI(InstrumentId, int index); + void changeInstrumentLabel(InstrumentId id, QString label); + +protected: + //--------------- Data members --------------------------------- + + AudioFaderBox *m_audioFader; + +private: + + QPixmap m_monoPixmap; + QPixmap m_stereoPixmap; + +}; + + +} + +#endif diff --git a/src/gui/editors/parameters/InstrumentParameterBox.cpp b/src/gui/editors/parameters/InstrumentParameterBox.cpp new file mode 100644 index 0000000..8114e0d --- /dev/null +++ b/src/gui/editors/parameters/InstrumentParameterBox.cpp @@ -0,0 +1,265 @@ +/* -*- 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 "InstrumentParameterBox.h" +#include <qlayout.h> + +#include <klocale.h> +#include "misc/Debug.h" +#include "AudioInstrumentParameterPanel.h" +#include "base/Instrument.h" +#include "base/MidiProgram.h" +#include "document/RosegardenGUIDoc.h" +#include "MIDIInstrumentParameterPanel.h" +#include "RosegardenParameterArea.h" +#include "RosegardenParameterBox.h" +#include <ktabwidget.h> +#include <qfont.h> +#include <qframe.h> +#include <qscrollview.h> +#include <qstring.h> +#include <qvbox.h> +#include <qwidget.h> +#include <qwidgetstack.h> + + +namespace Rosegarden +{ + +InstrumentParameterBox::InstrumentParameterBox(RosegardenGUIDoc *doc, + QWidget *parent) + : RosegardenParameterBox(i18n("Instrument"), + i18n("Instrument Parameters"), + parent), + m_widgetStack(new QWidgetStack(this)), + m_noInstrumentParameters(new QVBox(this)), + m_midiInstrumentParameters(new MIDIInstrumentParameterPanel(doc, this)), + m_audioInstrumentParameters(new AudioInstrumentParameterPanel(doc, this)), + m_selectedInstrument(-1), + m_doc(doc), + m_lastShowAdditionalControlsArg(false) +{ + m_widgetStack->setFont(m_font); + m_noInstrumentParameters->setFont(m_font); + m_midiInstrumentParameters->setFont(m_font); + m_audioInstrumentParameters->setFont(m_font); + + bool contains = false; + + std::vector<InstrumentParameterBox*>::iterator it = + instrumentParamBoxes.begin(); + + for (; it != instrumentParamBoxes.end(); it++) + if ((*it) == this) + contains = true; + + if (!contains) + instrumentParamBoxes.push_back(this); + + m_widgetStack->addWidget(m_midiInstrumentParameters); + m_widgetStack->addWidget(m_audioInstrumentParameters); + m_widgetStack->addWidget(m_noInstrumentParameters); + + m_midiInstrumentParameters->adjustSize(); + m_audioInstrumentParameters->adjustSize(); + m_noInstrumentParameters->adjustSize(); + + connect(m_audioInstrumentParameters, SIGNAL(updateAllBoxes()), + this, SLOT(slotUpdateAllBoxes())); + + connect(m_audioInstrumentParameters, + SIGNAL(instrumentParametersChanged(InstrumentId)), + this, + SIGNAL(instrumentParametersChanged(InstrumentId))); + + connect(m_audioInstrumentParameters, + SIGNAL(selectPlugin(QWidget *, InstrumentId, int)), + this, + SIGNAL(selectPlugin(QWidget *, InstrumentId, int))); + + connect(m_audioInstrumentParameters, + SIGNAL(showPluginGUI(InstrumentId, int)), + this, + SIGNAL(showPluginGUI(InstrumentId, int))); + + connect(m_midiInstrumentParameters, SIGNAL(updateAllBoxes()), + this, SLOT(slotUpdateAllBoxes())); + + connect(m_midiInstrumentParameters, + SIGNAL(changeInstrumentLabel(InstrumentId, QString)), + this, SIGNAL(changeInstrumentLabel(InstrumentId, QString))); + + connect(m_audioInstrumentParameters, + SIGNAL(changeInstrumentLabel(InstrumentId, QString)), + this, SIGNAL(changeInstrumentLabel(InstrumentId, QString))); + + connect(m_midiInstrumentParameters, + SIGNAL(instrumentParametersChanged(InstrumentId)), + this, + SIGNAL(instrumentParametersChanged(InstrumentId))); + + // Layout the groups left to right. + + QBoxLayout* layout = new QVBoxLayout(this); + layout->addWidget(m_widgetStack); + +} + +InstrumentParameterBox::~InstrumentParameterBox() +{ + // deregister this parameter box + std::vector<InstrumentParameterBox*>::iterator it = + instrumentParamBoxes.begin(); + + for (; it != instrumentParamBoxes.end(); it++) { + if ((*it) == this) { + instrumentParamBoxes.erase(it); + break; + } + } +} + +Instrument * +InstrumentParameterBox::getSelectedInstrument() +{ + if (m_selectedInstrument < 0) return 0; + if (!m_doc) return 0; + return m_doc->getStudio().getInstrumentById(m_selectedInstrument); +} + +QString +InstrumentParameterBox::getPreviousBox(RosegardenParameterArea::Arrangement arrangement) const +{ + return i18n("Track"); +} + +void +InstrumentParameterBox::setAudioMeter(float ch1, float ch2, float ch1r, float ch2r) +{ + m_audioInstrumentParameters->setAudioMeter(ch1, ch2, ch1r, ch2r); +} + +void +InstrumentParameterBox::setDocument(RosegardenGUIDoc* doc) +{ + m_doc = doc; + m_midiInstrumentParameters->setDocument(m_doc); + m_audioInstrumentParameters->setDocument(m_doc); +} + +void +InstrumentParameterBox::slotPluginSelected(InstrumentId id, int index, int plugin) +{ + m_audioInstrumentParameters->slotPluginSelected(id, index, plugin); +} + +void +InstrumentParameterBox::slotPluginBypassed(InstrumentId id, int index, bool bypassState) +{ + m_audioInstrumentParameters->slotPluginBypassed(id, index, bypassState); +} + +void +InstrumentParameterBox::useInstrument(Instrument *instrument) +{ + RG_DEBUG << "useInstrument() - populate Instrument\n"; + + if (instrument == 0) { + m_widgetStack->raiseWidget(m_noInstrumentParameters); + emit instrumentPercussionSetChanged(instrument); + return ; + } + + // ok + if (instrument) { + m_selectedInstrument = instrument->getId(); + } else { + m_selectedInstrument = -1; + } + + // Hide or Show according to Instrument type + // + if (instrument->getType() == Instrument::Audio || + instrument->getType() == Instrument::SoftSynth) { + + m_audioInstrumentParameters->setupForInstrument(getSelectedInstrument()); + m_widgetStack->raiseWidget(m_audioInstrumentParameters); + + } else { // Midi + + m_midiInstrumentParameters->setupForInstrument(getSelectedInstrument()); + m_midiInstrumentParameters->showAdditionalControls(m_lastShowAdditionalControlsArg); + m_widgetStack->raiseWidget(m_midiInstrumentParameters); + emit instrumentPercussionSetChanged(instrument); + + } + +} + +void +InstrumentParameterBox::slotUpdateAllBoxes() +{ + emit instrumentPercussionSetChanged(getSelectedInstrument()); + + std::vector<InstrumentParameterBox*>::iterator it = + instrumentParamBoxes.begin(); + + // To update all open IPBs + // + for (; it != instrumentParamBoxes.end(); it++) { + if ((*it) != this && getSelectedInstrument() && + (*it)->getSelectedInstrument() == getSelectedInstrument()) + (*it)->useInstrument(getSelectedInstrument()); + } +} + +void +InstrumentParameterBox::slotInstrumentParametersChanged(InstrumentId id) +{ + std::vector<InstrumentParameterBox*>::iterator it = + instrumentParamBoxes.begin(); + + blockSignals(true); + + for (; it != instrumentParamBoxes.end(); it++) { + if ((*it)->getSelectedInstrument()) { + if ((*it)->getSelectedInstrument()->getId() == id) { + (*it)->useInstrument((*it)->getSelectedInstrument()); // refresh + } + } + } + + blockSignals(false); +} + +void +InstrumentParameterBox::showAdditionalControls(bool showThem) +{ + m_midiInstrumentParameters->showAdditionalControls(showThem); + m_lastShowAdditionalControlsArg = showThem; +} + +} +#include "InstrumentParameterBox.moc" diff --git a/src/gui/editors/parameters/InstrumentParameterBox.h b/src/gui/editors/parameters/InstrumentParameterBox.h new file mode 100644 index 0000000..f406567 --- /dev/null +++ b/src/gui/editors/parameters/InstrumentParameterBox.h @@ -0,0 +1,126 @@ + +/* -*- 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_INSTRUMENTPARAMETERBOX_H_ +#define _RG_INSTRUMENTPARAMETERBOX_H_ + +#include "base/MidiProgram.h" +#include "RosegardenParameterArea.h" +#include "RosegardenParameterBox.h" +#include <qstring.h> +#include <vector> + + +class QWidgetStack; +class QWidget; +class QFrame; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class MIDIInstrumentParameterPanel; +class Instrument; +class AudioInstrumentParameterPanel; + + +/** + * Display and allow modification of Instrument parameters + */ +class InstrumentParameterBox : public RosegardenParameterBox +{ +Q_OBJECT + +public: + InstrumentParameterBox(RosegardenGUIDoc *doc, + QWidget *parent = 0); + ~InstrumentParameterBox(); + + void useInstrument(Instrument *instrument); + + Instrument* getSelectedInstrument(); + + void setAudioMeter(float dBleft, float dBright, + float recDBleft, float recDBright); + + void setDocument(RosegardenGUIDoc* doc); + + virtual void showAdditionalControls(bool showThem); + + virtual QString getPreviousBox(RosegardenParameterArea::Arrangement) const; + +public slots: + + // To update all InstrumentParameterBoxen for an Instrument. Called + // from one of the parameter panels when something changes. + // + void slotUpdateAllBoxes(); + + // Update InstrumentParameterBoxes that are showing a given instrument. + // Called from the Outside. + // + void slotInstrumentParametersChanged(InstrumentId id); + + // From Plugin dialog + // + void slotPluginSelected(InstrumentId id, int index, int plugin); + void slotPluginBypassed(InstrumentId id, int pluginIndex, bool bp); + +signals: + + void changeInstrumentLabel(InstrumentId id, QString label); + + void selectPlugin(QWidget*, InstrumentId id, int index); + void showPluginGUI(InstrumentId id, int index); + + void instrumentParametersChanged(InstrumentId); + void instrumentPercussionSetChanged(Instrument *); + +protected: + + //--------------- Data members --------------------------------- + QWidgetStack *m_widgetStack; + QFrame *m_noInstrumentParameters; + MIDIInstrumentParameterPanel *m_midiInstrumentParameters; + AudioInstrumentParameterPanel *m_audioInstrumentParameters; + + // -1 if no instrument, InstrumentId otherwise + int m_selectedInstrument; + + // So we can setModified() + // + RosegardenGUIDoc *m_doc; + bool m_lastShowAdditionalControlsArg; +}; + +// Global references +// +static std::vector<InstrumentParameterBox*> instrumentParamBoxes; + + +} + +#endif diff --git a/src/gui/editors/parameters/InstrumentParameterPanel.cpp b/src/gui/editors/parameters/InstrumentParameterPanel.cpp new file mode 100644 index 0000000..9437daf --- /dev/null +++ b/src/gui/editors/parameters/InstrumentParameterPanel.cpp @@ -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. +*/ + + +#include "InstrumentParameterPanel.h" + +#include "base/Instrument.h" +#include "document/RosegardenGUIDoc.h" +#include <ksqueezedtextlabel.h> +#include <qfontmetrics.h> +#include <qframe.h> +#include <qlabel.h> +#include <qwidget.h> + + +namespace Rosegarden +{ + +InstrumentParameterPanel::InstrumentParameterPanel(RosegardenGUIDoc *doc, + QWidget* parent) + : QFrame(parent), + m_instrumentLabel(new KSqueezedTextLabel(this)), + m_selectedInstrument(0), + m_doc(doc) +{ + QFontMetrics metrics(m_instrumentLabel->fontMetrics()); + int width25 = metrics.width("1234567890123456789012345"); + + m_instrumentLabel->setFixedWidth(width25); + m_instrumentLabel->setAlignment(Qt::AlignCenter); +} + +void +InstrumentParameterPanel::setDocument(RosegardenGUIDoc* doc) +{ + m_doc = doc; +} + +} +#include "InstrumentParameterPanel.moc" diff --git a/src/gui/editors/parameters/InstrumentParameterPanel.h b/src/gui/editors/parameters/InstrumentParameterPanel.h new file mode 100644 index 0000000..9a794d0 --- /dev/null +++ b/src/gui/editors/parameters/InstrumentParameterPanel.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_INSTRUMENTPARAMETERPANEL_H_ +#define _RG_INSTRUMENTPARAMETERPANEL_H_ + +#include <qframe.h> +#include <vector> +#include <utility> + +class QWidget; +class QLabel; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class Instrument; +class Rotary; + +typedef std::pair<Rotary *, QLabel *> RotaryPair; +typedef std::vector<std::pair<int, RotaryPair> > RotaryMap; + + +//////////////////////////////////////////////////////////////////////// + +class InstrumentParameterPanel : public QFrame +{ + Q_OBJECT +public: + InstrumentParameterPanel(RosegardenGUIDoc *doc, QWidget* parent); + + virtual ~InstrumentParameterPanel() {}; + + virtual void setupForInstrument(Instrument*) = 0; + + void setDocument(RosegardenGUIDoc* doc); + + void showAdditionalControls(bool showThem); + +signals: + void updateAllBoxes(); + +protected: + //--------------- Data members --------------------------------- + QLabel *m_instrumentLabel; + Instrument *m_selectedInstrument; + RosegardenGUIDoc *m_doc; +}; + + + +} + +#endif diff --git a/src/gui/editors/parameters/MIDIInstrumentParameterPanel.cpp b/src/gui/editors/parameters/MIDIInstrumentParameterPanel.cpp new file mode 100644 index 0000000..fcd4247 --- /dev/null +++ b/src/gui/editors/parameters/MIDIInstrumentParameterPanel.cpp @@ -0,0 +1,1175 @@ +/* -*- 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 "MIDIInstrumentParameterPanel.h" +#include <qlayout.h> + +#include "sound/Midi.h" +#include <klocale.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Colour.h" +#include "base/Composition.h" +#include "base/ControlParameter.h" +#include "base/Instrument.h" +#include "base/MidiDevice.h" +#include "base/MidiProgram.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/studio/StudioControl.h" +#include "gui/widgets/Rotary.h" +#include "InstrumentParameterPanel.h" +#include "sound/MappedEvent.h" +#include "sound/MappedInstrument.h" +#include <kcombobox.h> +#include <ksqueezedtextlabel.h> +#include <qcheckbox.h> +#include <qcolor.h> +#include <qfontmetrics.h> +#include <qframe.h> +#include <qhbox.h> +#include <qlabel.h> +#include <qregexp.h> +#include <qsignalmapper.h> +#include <qstring.h> +#include <qwidget.h> +#include <algorithm> + + +namespace Rosegarden +{ + +MIDIInstrumentParameterPanel::MIDIInstrumentParameterPanel(RosegardenGUIDoc *doc, QWidget* parent): + InstrumentParameterPanel(doc, parent), + m_rotaryFrame(0), + m_rotaryMapper(new QSignalMapper(this)) +{ + m_mainGrid = new QGridLayout(this, 10, 3, 2, 1); + + m_connectionLabel = new KSqueezedTextLabel(this); + m_bankValue = new KComboBox(this); + m_channelValue = new KComboBox(this); + m_programValue = new KComboBox(this); + m_variationValue = new KComboBox(this); + m_bankCheckBox = new QCheckBox(this); + m_programCheckBox = new QCheckBox(this); + m_variationCheckBox = new QCheckBox(this); + m_percussionCheckBox = new QCheckBox(this); + + m_bankValue->setSizeLimit(20); + m_programValue->setSizeLimit(20); + m_variationValue->setSizeLimit(20); + + m_bankLabel = new QLabel(i18n("Bank"), this); + m_variationLabel = new QLabel(i18n("Variation"), this); + m_programLabel = new QLabel(i18n("Program"), this); + QLabel *channelLabel = new QLabel(i18n("Channel out"), this); + QLabel *percussionLabel = new QLabel(i18n("Percussion"), this); + + // Ensure a reasonable amount of space in the program dropdowns even + // if no instrument initially selected + QFontMetrics metrics(m_programValue->font()); + int width22 = metrics.width("1234567890123456789012"); + int width25 = metrics.width("1234567890123456789012345"); + + m_bankValue->setMinimumWidth(width22); + m_programValue->setMinimumWidth(width22); + m_variationValue->setMinimumWidth(width22); + + m_connectionLabel->setFixedWidth(width25); + m_connectionLabel->setAlignment(Qt::AlignCenter); + + // Configure the empty final row to accomodate any extra vertical space. + + m_mainGrid->setRowStretch(m_mainGrid->numRows() - 1, 1); + + + m_mainGrid->setColStretch(2, 1); + + m_mainGrid->addMultiCellWidget(m_instrumentLabel, 0, 0, 0, 2, AlignCenter); + m_mainGrid->addMultiCellWidget(m_connectionLabel, 1, 1, 0, 2, AlignCenter); + + m_mainGrid->addMultiCellWidget(channelLabel, 2, 2, 0, 1, AlignLeft); + m_mainGrid->addWidget(m_channelValue, 2, 2, AlignRight); + + m_mainGrid->addMultiCellWidget(percussionLabel, 3, 3, 0, 1, AlignLeft); + m_mainGrid->addWidget(m_percussionCheckBox, 3, 2, AlignRight); + + m_mainGrid->addWidget(m_bankLabel, 4, 0, AlignLeft); + m_mainGrid->addWidget(m_bankCheckBox, 4, 1, AlignRight); + m_mainGrid->addWidget(m_bankValue, 4, 2, AlignRight); + + m_mainGrid->addWidget(m_programLabel, 5, 0, AlignLeft); + m_mainGrid->addWidget(m_programCheckBox, 5, 1, AlignRight); + m_mainGrid->addWidget(m_programValue, 5, 2, AlignRight); + + m_mainGrid->addWidget(m_variationLabel, 6, 0); + m_mainGrid->addWidget(m_variationCheckBox, 6, 1); + m_mainGrid->addWidget(m_variationValue, 6, 2, AlignRight); + + // Populate channel lists + // + for (int i = 0; i < 16; i++) { + m_channelValue->insertItem(QString("%1").arg(i + 1)); + } + + m_channelValue->setSizeLimit(16); + + // Disable these by default - they are activate by their + // checkboxes + // + m_programValue->setDisabled(true); + m_bankValue->setDisabled(true); + m_variationValue->setDisabled(true); + + // Only active if we have an Instrument selected + // + m_percussionCheckBox->setDisabled(true); + m_programCheckBox->setDisabled(true); + m_bankCheckBox->setDisabled(true); + m_variationCheckBox->setDisabled(true); + + // Connect up the toggle boxes + // + connect(m_percussionCheckBox, SIGNAL(toggled(bool)), + this, SLOT(slotTogglePercussion(bool))); + + connect(m_programCheckBox, SIGNAL(toggled(bool)), + this, SLOT(slotToggleProgramChange(bool))); + + connect(m_bankCheckBox, SIGNAL(toggled(bool)), + this, SLOT(slotToggleBank(bool))); + + connect(m_variationCheckBox, SIGNAL(toggled(bool)), + this, SLOT(slotToggleVariation(bool))); + + + // Connect activations + // + connect(m_bankValue, SIGNAL(activated(int)), + this, SLOT(slotSelectBank(int))); + + connect(m_variationValue, SIGNAL(activated(int)), + this, SLOT(slotSelectVariation(int))); + + connect(m_programValue, SIGNAL(activated(int)), + this, SLOT(slotSelectProgram(int))); + + connect(m_channelValue, SIGNAL(activated(int)), + this, SLOT(slotSelectChannel(int))); + + // don't select any of the options in any dropdown + m_programValue->setCurrentItem( -1); + m_bankValue->setCurrentItem( -1); + m_channelValue->setCurrentItem( -1); + m_variationValue->setCurrentItem( -1); + + connect(m_rotaryMapper, SIGNAL(mapped(int)), + this, SLOT(slotControllerChanged(int))); +} + +void +MIDIInstrumentParameterPanel::setupForInstrument(Instrument *instrument) +{ + RG_DEBUG << "MIDIInstrumentParameterPanel::setupForInstrument" << endl; + MidiDevice *md = dynamic_cast<MidiDevice*> + (instrument->getDevice()); + if (!md) { + RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::setupForInstrument:" + << " No MidiDevice for Instrument " + << instrument->getId() << endl; + return ; + } + + m_selectedInstrument = instrument; + + // Set instrument name + // + m_instrumentLabel->setText(strtoqstr(instrument->getPresentationName())); + + // Set Studio Device name + // + QString connection(strtoqstr(md->getConnection())); + if (connection == "") { + m_connectionLabel->setText(i18n("[ %1 ]").arg(i18n("No connection"))); + } else { + + // remove trailing "(duplex)", "(read only)", "(write only)" etc + connection.replace(QRegExp("\\s*\\([^)0-9]+\\)\\s*$"), ""); + + QString text = i18n("[ %1 ]").arg(connection); + /*QString origText(text); + + QFontMetrics metrics(m_connectionLabel->fontMetrics()); + int maxwidth = metrics.width + ("Program: [X] Acoustic Grand Piano 123");// kind of arbitrary! + + int hlen = text.length() / 2; + while (metrics.width(text) > maxwidth && text.length() > 10) { + --hlen; + text = origText.left(hlen) + "..." + origText.right(hlen); + } + + if (text.length() > origText.length() - 7) text = origText;*/ + m_connectionLabel->setText(text); + } + + // Enable all check boxes + // + m_percussionCheckBox->setDisabled(false); + m_programCheckBox->setDisabled(false); + m_bankCheckBox->setDisabled(false); + m_variationCheckBox->setDisabled(false); + + // Activate all checkboxes + // + m_percussionCheckBox->setChecked(instrument->isPercussion()); + m_programCheckBox->setChecked(instrument->sendsProgramChange()); + m_bankCheckBox->setChecked(instrument->sendsBankSelect()); + m_variationCheckBox->setChecked(instrument->sendsBankSelect()); + + // Basic parameters + // + m_channelValue->setCurrentItem((int)instrument->getMidiChannel()); + + // Check for program change + // + populateBankList(); + populateProgramList(); + populateVariationList(); + + // Setup the ControlParameters + // + setupControllers(md); + + // Set all the positions by controller number + // + for (RotaryMap::iterator it = m_rotaries.begin() ; + it != m_rotaries.end(); ++it) { + MidiByte value = 0; + + // Special cases + // + if (it->first == MIDI_CONTROLLER_PAN) + value = int(instrument->getPan()); + else if (it->first == MIDI_CONTROLLER_VOLUME) + value = int(instrument->getVolume()); + else { + try { + value = instrument->getControllerValue( + MidiByte(it->first)); + } catch (...) { + continue; + } + } + + setRotaryToValue(it->first, int(value)); + } +} + +void +MIDIInstrumentParameterPanel::setupControllers(MidiDevice *md) +{ + if (!m_rotaryFrame) { + m_rotaryFrame = new QFrame(this); + m_mainGrid->addMultiCellWidget(m_rotaryFrame, 8, 8, 0, 2, Qt::AlignHCenter); + m_rotaryGrid = new QGridLayout(m_rotaryFrame, 10, 3, 8, 1); + m_rotaryGrid->addItem(new QSpacerItem(10, 4), 0, 1); + } + + // To cut down on flicker, we avoid destroying and recreating + // widgets as far as possible here. If a label already exists, + // we just set its text; if a rotary exists, we only replace it + // if we actually need a different one. + + Composition &comp = m_doc->getComposition(); + ControlList list = md->getControlParameters(); + + // sort by IPB position + // + std::sort(list.begin(), list.end(), + ControlParameter::ControlPositionCmp()); + + int count = 0; + RotaryMap::iterator rmi = m_rotaries.begin(); + + for (ControlList::iterator it = list.begin(); + it != list.end(); ++it) { + if (it->getIPBPosition() == -1) + continue; + + // Get the knob colour - only if the colour is non-default (>0) + // + QColor knobColour = Qt::black; // special case for Rotary + if (it->getColourIndex() > 0) { + Colour c = + comp.getGeneralColourMap().getColourByIndex + (it->getColourIndex()); + knobColour = QColor(c.getRed(), c.getGreen(), c.getBlue()); + } + + Rotary *rotary = 0; + + if (rmi != m_rotaries.end()) { + + // Update the controller number that is associated with the + // existing rotary widget. + + rmi->first = it->getControllerValue(); + + // Update the properties of the existing rotary widget. + + rotary = rmi->second.first; + int redraw = 0; // 1 -> position, 2 -> all + + if (rotary->getMinValue() != it->getMin()) { + rotary->setMinValue(it->getMin()); + redraw = 1; + } + if (rotary->getMaxValue() != it->getMax()) { + rotary->setMaxValue(it->getMax()); + redraw = 1; + } + if (rotary->getKnobColour() != knobColour) { + rotary->setKnobColour(knobColour); + redraw = 2; + } + if (redraw == 1 || rotary->getPosition() != it->getDefault()) { + rotary->setPosition(it->getDefault()); + if (redraw == 1) + redraw = 0; + } + if (redraw == 2) { + rotary->repaint(); + } + + // Update the controller name that is associated with + // with the existing rotary widget. + + QLabel *label = rmi->second.second; + label->setText(strtoqstr(it->getName())); + + ++rmi; + + } else { + + QHBox *hbox = new QHBox(m_rotaryFrame); + hbox->setSpacing(8); + + float smallStep = 1.0; + + float bigStep = 5.0; + if (it->getMax() - it->getMin() < 10) + bigStep = 1.0; + else if (it->getMax() - it->getMin() < 20) + bigStep = 2.0; + + rotary = new Rotary + (hbox, + it->getMin(), + it->getMax(), + smallStep, + bigStep, + it->getDefault(), + 20, + Rotary::NoTicks, + false, + it->getDefault() == 64); //!!! hacky + + rotary->setKnobColour(knobColour); + + // Add a label + QLabel *label = new KSqueezedTextLabel(strtoqstr(it->getName()), hbox); + + RG_DEBUG << "Adding new widget at " << (count / 2) << "," << (count % 2) << endl; + + // Add the compound widget + // + m_rotaryGrid->addWidget(hbox, count / 2, (count % 2) * 2, AlignLeft); + hbox->show(); + + // Add to list + // + m_rotaries.push_back(std::pair<int, RotaryPair> + (it->getControllerValue(), + RotaryPair(rotary, label))); + + // Connect + // + connect(rotary, SIGNAL(valueChanged(float)), + m_rotaryMapper, SLOT(map())); + + rmi = m_rotaries.end(); + } + + // Add signal mapping + // + m_rotaryMapper->setMapping(rotary, + int(it->getControllerValue())); + + count++; + } + + if (rmi != m_rotaries.end()) { + for (RotaryMap::iterator rmj = rmi; rmj != m_rotaries.end(); ++rmj) { + delete rmj->second.first; + delete rmj->second.second; + } + m_rotaries = std::vector<std::pair<int, RotaryPair> > + (m_rotaries.begin(), rmi); + } + + m_rotaryFrame->show(); +} + +void +MIDIInstrumentParameterPanel::setRotaryToValue(int controller, int value) +{ + /* + RG_DEBUG << "MIDIInstrumentParameterPanel::setRotaryToValue - " + << "controller = " << controller + << ", value = " << value << std::endl; + */ + + for (RotaryMap::iterator it = m_rotaries.begin() ; it != m_rotaries.end(); ++it) { + if (it->first == controller) { + it->second.first->setPosition(float(value)); + return ; + } + } +} + +void +MIDIInstrumentParameterPanel::slotSelectChannel(int index) +{ + if (m_selectedInstrument == 0) + return ; + + m_selectedInstrument->setMidiChannel(index); + + // don't use the emit - use this method instead + StudioControl::sendMappedInstrument( + MappedInstrument(m_selectedInstrument)); + emit updateAllBoxes(); +} + +void +MIDIInstrumentParameterPanel::populateBankList() +{ + if (m_selectedInstrument == 0) + return ; + + m_bankValue->clear(); + m_banks.clear(); + + MidiDevice *md = dynamic_cast<MidiDevice*> + (m_selectedInstrument->getDevice()); + if (!md) { + RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::populateBankList:" + << " No MidiDevice for Instrument " + << m_selectedInstrument->getId() << endl; + return ; + } + + int currentBank = -1; + BankList banks; + + /* + RG_DEBUG << "MIDIInstrumentParameterPanel::populateBankList: " + << "variation type is " << md->getVariationType() << endl; + */ + + if (md->getVariationType() == MidiDevice::NoVariations) { + + banks = md->getBanks(m_selectedInstrument->isPercussion()); + + if (!banks.empty()) { + if (m_bankLabel->isHidden()) { + m_bankLabel->show(); + m_bankCheckBox->show(); + m_bankValue->show(); + } + } else { + m_bankLabel->hide(); + m_bankCheckBox->hide(); + m_bankValue->hide(); + } + + for (unsigned int i = 0; i < banks.size(); ++i) { + if (m_selectedInstrument->getProgram().getBank() == banks[i]) { + currentBank = i; + } + } + + } else { + + MidiByteList bytes; + bool useMSB = (md->getVariationType() == MidiDevice::VariationFromLSB); + + if (useMSB) { + bytes = md->getDistinctMSBs(m_selectedInstrument->isPercussion()); + } else { + bytes = md->getDistinctLSBs(m_selectedInstrument->isPercussion()); + } + + if (bytes.size() < 2) { + if (!m_bankLabel->isHidden()) { + m_bankLabel->hide(); + m_bankCheckBox->hide(); + m_bankValue->hide(); + } + } else { + if (m_bankLabel->isHidden()) { + m_bankLabel->show(); + m_bankCheckBox->show(); + m_bankValue->show(); + } + } + + if (useMSB) { + for (unsigned int i = 0; i < bytes.size(); ++i) { + BankList bl = md->getBanksByMSB + (m_selectedInstrument->isPercussion(), bytes[i]); + RG_DEBUG << "MIDIInstrumentParameterPanel::populateBankList: have " << bl.size() << " variations for msb " << bytes[i] << endl; + + if (bl.size() == 0) + continue; + if (m_selectedInstrument->getMSB() == bytes[i]) { + currentBank = banks.size(); + } + banks.push_back(bl[0]); + } + } else { + for (unsigned int i = 0; i < bytes.size(); ++i) { + BankList bl = md->getBanksByLSB + (m_selectedInstrument->isPercussion(), bytes[i]); + RG_DEBUG << "MIDIInstrumentParameterPanel::populateBankList: have " << bl.size() << " variations for lsb " << bytes[i] << endl; + if (bl.size() == 0) + continue; + if (m_selectedInstrument->getLSB() == bytes[i]) { + currentBank = banks.size(); + } + banks.push_back(bl[0]); + } + } + } + + for (BankList::const_iterator i = banks.begin(); + i != banks.end(); ++i) { + m_banks.push_back(*i); + m_bankValue->insertItem(strtoqstr(i->getName())); + } + + m_bankValue->setEnabled(m_selectedInstrument->sendsBankSelect()); + + if (currentBank < 0 && !banks.empty()) { + m_bankValue->setCurrentItem(0); + slotSelectBank(0); + } else { + m_bankValue->setCurrentItem(currentBank); + } +} + +void +MIDIInstrumentParameterPanel::populateProgramList() +{ + if (m_selectedInstrument == 0) + return ; + + m_programValue->clear(); + m_programs.clear(); + + MidiDevice *md = dynamic_cast<MidiDevice*> + (m_selectedInstrument->getDevice()); + if (!md) { + RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::populateProgramList: No MidiDevice for Instrument " + << m_selectedInstrument->getId() << endl; + return ; + } + + /* + RG_DEBUG << "MIDIInstrumentParameterPanel::populateProgramList:" + << " variation type is " << md->getVariationType() << endl; + */ + + MidiBank bank( m_selectedInstrument->isPercussion(), + m_selectedInstrument->getMSB(), + m_selectedInstrument->getLSB()); + + if (m_selectedInstrument->sendsBankSelect()) { + bank = m_selectedInstrument->getProgram().getBank(); + } + + int currentProgram = -1; + + ProgramList programs = md->getPrograms(bank); + + if (!programs.empty()) { + if (m_programLabel->isHidden()) { + m_programLabel->show(); + m_programCheckBox->show(); + m_programValue->show(); + } + } else { + m_programLabel->hide(); + m_programCheckBox->hide(); + m_programValue->hide(); + } + + for (unsigned int i = 0; i < programs.size(); ++i) { + std::string programName = programs[i].getName(); + if (programName != "") { + m_programValue->insertItem(QString("%1. %2") + .arg(programs[i].getProgram() + 1) + .arg(strtoqstr(programName))); + if (m_selectedInstrument->getProgram() == programs[i]) { + currentProgram = m_programs.size(); + } + m_programs.push_back(programs[i]); + } + } + + m_programValue->setEnabled(m_selectedInstrument->sendsProgramChange()); + + if (currentProgram < 0 && !m_programs.empty()) { + m_programValue->setCurrentItem(0); + slotSelectProgram(0); + } else { + m_programValue->setCurrentItem(currentProgram); + + // Ensure that stored program change value is same as the one + // we're now showing (BUG 937371) + // + if (!m_programs.empty()) { + m_selectedInstrument->setProgramChange + ((m_programs[m_programValue->currentItem()]).getProgram()); + } + } +} + +void +MIDIInstrumentParameterPanel::populateVariationList() +{ + if (m_selectedInstrument == 0) + return ; + + m_variationValue->clear(); + m_variations.clear(); + + MidiDevice *md = dynamic_cast<MidiDevice*> + (m_selectedInstrument->getDevice()); + if (!md) { + RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::populateVariationList: No MidiDevice for Instrument " + << m_selectedInstrument->getId() << endl; + return ; + } + + /* + RG_DEBUG << "MIDIInstrumentParameterPanel::populateVariationList:" + << " variation type is " << md->getVariationType() << endl; + */ + + if (md->getVariationType() == MidiDevice::NoVariations) { + if (!m_variationLabel->isHidden()) { + m_variationLabel->hide(); + m_variationCheckBox->hide(); + m_variationValue->hide(); + } + return ; + } + + bool useMSB = (md->getVariationType() == MidiDevice::VariationFromMSB); + MidiByteList variations; + + if (useMSB) { + MidiByte lsb = m_selectedInstrument->getLSB(); + variations = md->getDistinctMSBs(m_selectedInstrument->isPercussion(), + lsb); + RG_DEBUG << "MIDIInstrumentParameterPanel::populateVariationList: have " << variations.size() << " variations for lsb " << lsb << endl; + + } else { + MidiByte msb = m_selectedInstrument->getMSB(); + variations = md->getDistinctLSBs(m_selectedInstrument->isPercussion(), + msb); + RG_DEBUG << "MIDIInstrumentParameterPanel::populateVariationList: have " << variations.size() << " variations for msb " << msb << endl; + } + + m_variationValue->setCurrentItem( -1); + + MidiProgram defaultProgram; + + if (useMSB) { + defaultProgram = MidiProgram + (MidiBank(m_selectedInstrument->isPercussion(), + 0, + m_selectedInstrument->getLSB()), + m_selectedInstrument->getProgramChange()); + } else { + defaultProgram = MidiProgram + (MidiBank(m_selectedInstrument->isPercussion(), + m_selectedInstrument->getMSB(), + 0), + m_selectedInstrument->getProgramChange()); + } + std::string defaultProgramName = md->getProgramName(defaultProgram); + + int currentVariation = -1; + + for (unsigned int i = 0; i < variations.size(); ++i) { + + MidiProgram program; + + if (useMSB) { + program = MidiProgram + (MidiBank(m_selectedInstrument->isPercussion(), + variations[i], + m_selectedInstrument->getLSB()), + m_selectedInstrument->getProgramChange()); + } else { + program = MidiProgram + (MidiBank(m_selectedInstrument->isPercussion(), + m_selectedInstrument->getMSB(), + variations[i]), + m_selectedInstrument->getProgramChange()); + } + + std::string programName = md->getProgramName(program); + + if (programName != "") { // yes, that is how you know whether it exists + /* + m_variationValue->insertItem(programName == defaultProgramName ? + i18n("(default)") : + strtoqstr(programName)); + */ + m_variationValue->insertItem(QString("%1. %2") + .arg(variations[i] + 1) + .arg(strtoqstr(programName))); + if (m_selectedInstrument->getProgram() == program) { + currentVariation = m_variations.size(); + } + m_variations.push_back(variations[i]); + } + } + + if (currentVariation < 0 && !m_variations.empty()) { + m_variationValue->setCurrentItem(0); + slotSelectVariation(0); + } else { + m_variationValue->setCurrentItem(currentVariation); + } + + if (m_variations.size() < 2) { + if (!m_variationLabel->isHidden()) { + m_variationLabel->hide(); + m_variationCheckBox->hide(); + m_variationValue->hide(); + } + + } else { + //!!! seem to have problems here -- the grid layout doesn't + //like us adding stuff in the middle so if we go from 1 + //visible row (say program) to 2 (program + variation) the + //second one overlaps the control knobs + + if (m_variationLabel->isHidden()) { + m_variationLabel->show(); + m_variationCheckBox->show(); + m_variationValue->show(); + } + + if (m_programValue->width() > m_variationValue->width()) { + m_variationValue->setMinimumWidth(m_programValue->width()); + } else { + m_programValue->setMinimumWidth(m_variationValue->width()); + } + } + + m_variationValue->setEnabled(m_selectedInstrument->sendsBankSelect()); +} + +void +MIDIInstrumentParameterPanel::slotTogglePercussion(bool value) +{ + if (m_selectedInstrument == 0) { + m_percussionCheckBox->setChecked(false); + emit updateAllBoxes(); + return ; + } + + m_selectedInstrument->setPercussion(value); + + populateBankList(); + populateProgramList(); + populateVariationList(); + + sendBankAndProgram(); + + emit changeInstrumentLabel(m_selectedInstrument->getId(), + strtoqstr(m_selectedInstrument-> + getProgramName())); + emit updateAllBoxes(); + + emit instrumentParametersChanged(m_selectedInstrument->getId()); +} + +void +MIDIInstrumentParameterPanel::slotToggleBank(bool value) +{ + if (m_selectedInstrument == 0) { + m_bankCheckBox->setChecked(false); + emit updateAllBoxes(); + return ; + } + + m_variationCheckBox->setChecked(value); + m_selectedInstrument->setSendBankSelect(value); + + m_bankValue->setDisabled(!value); + populateBankList(); + populateProgramList(); + populateVariationList(); + + sendBankAndProgram(); + + emit changeInstrumentLabel(m_selectedInstrument->getId(), + strtoqstr(m_selectedInstrument-> + getProgramName())); + emit updateAllBoxes(); + + emit instrumentParametersChanged(m_selectedInstrument->getId()); +} + +void +MIDIInstrumentParameterPanel::slotToggleProgramChange(bool value) +{ + if (m_selectedInstrument == 0) { + m_programCheckBox->setChecked(false); + emit updateAllBoxes(); + return ; + } + + m_selectedInstrument->setSendProgramChange(value); + + m_programValue->setDisabled(!value); + populateProgramList(); + populateVariationList(); + + if (value) + sendBankAndProgram(); + + emit changeInstrumentLabel(m_selectedInstrument->getId(), + strtoqstr(m_selectedInstrument-> + getProgramName())); + emit updateAllBoxes(); + + emit instrumentParametersChanged(m_selectedInstrument->getId()); +} + +void +MIDIInstrumentParameterPanel::slotToggleVariation(bool value) +{ + if (m_selectedInstrument == 0) { + m_variationCheckBox->setChecked(false); + emit updateAllBoxes(); + return ; + } + + m_bankCheckBox->setChecked(value); + m_selectedInstrument->setSendBankSelect(value); + + m_variationValue->setDisabled(!value); + populateVariationList(); + + sendBankAndProgram(); + + emit changeInstrumentLabel(m_selectedInstrument->getId(), + strtoqstr(m_selectedInstrument-> + getProgramName())); + emit updateAllBoxes(); + + emit instrumentParametersChanged(m_selectedInstrument->getId()); +} + +void +MIDIInstrumentParameterPanel::slotSelectBank(int index) +{ + if (m_selectedInstrument == 0) + return ; + + MidiDevice *md = dynamic_cast<MidiDevice*> + (m_selectedInstrument->getDevice()); + if (!md) { + RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::slotSelectBank: No MidiDevice for Instrument " + << m_selectedInstrument->getId() << endl; + return ; + } + + const MidiBank *bank = &m_banks[index]; + + bool change = false; + + if (md->getVariationType() != MidiDevice::VariationFromLSB) { + if (m_selectedInstrument->getLSB() != bank->getLSB()) { + m_selectedInstrument->setLSB(bank->getLSB()); + change = true; + } + } + if (md->getVariationType() != MidiDevice::VariationFromMSB) { + if (m_selectedInstrument->getMSB() != bank->getMSB()) { + m_selectedInstrument->setMSB(bank->getMSB()); + change = true; + } + } + + populateProgramList(); + + if (change) { + sendBankAndProgram(); + emit updateAllBoxes(); + } + + emit instrumentParametersChanged(m_selectedInstrument->getId()); +} + +void +MIDIInstrumentParameterPanel::slotSelectProgram(int index) +{ + const MidiProgram *prg = &m_programs[index]; + if (prg == 0) { + RG_DEBUG << "program change not found in bank" << endl; + return ; + } + + bool change = false; + if (m_selectedInstrument->getProgramChange() != prg->getProgram()) { + m_selectedInstrument->setProgramChange(prg->getProgram()); + change = true; + } + + populateVariationList(); + + if (change) { + sendBankAndProgram(); + emit changeInstrumentLabel(m_selectedInstrument->getId(), + strtoqstr(m_selectedInstrument-> + getProgramName())); + emit updateAllBoxes(); + } + + emit instrumentParametersChanged(m_selectedInstrument->getId()); +} + +void +MIDIInstrumentParameterPanel::slotSelectVariation(int index) +{ + MidiDevice *md = dynamic_cast<MidiDevice*> + (m_selectedInstrument->getDevice()); + if (!md) { + RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::slotSelectVariation: No MidiDevice for Instrument " + << m_selectedInstrument->getId() << endl; + return ; + } + + if (index < 0 || index > int(m_variations.size())) { + RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::slotSelectVariation: index " << index << " out of range" << endl; + return ; + } + + MidiByte v = m_variations[index]; + + bool change = false; + + if (md->getVariationType() == MidiDevice::VariationFromLSB) { + if (m_selectedInstrument->getLSB() != v) { + m_selectedInstrument->setLSB(v); + change = true; + } + } else if (md->getVariationType() == MidiDevice::VariationFromMSB) { + if (m_selectedInstrument->getMSB() != v) { + m_selectedInstrument->setMSB(v); + change = true; + } + } + + if (change) { + sendBankAndProgram(); + } + + emit instrumentParametersChanged(m_selectedInstrument->getId()); +} + +void +MIDIInstrumentParameterPanel::sendBankAndProgram() +{ + if (m_selectedInstrument == 0) + return ; + + MidiDevice *md = dynamic_cast<MidiDevice*> + (m_selectedInstrument->getDevice()); + if (!md) { + RG_DEBUG << "WARNING: MIDIInstrumentParameterPanel::sendBankAndProgram: No MidiDevice for Instrument " + << m_selectedInstrument->getId() << endl; + return ; + } + + if (m_selectedInstrument->sendsBankSelect()) { + + // Send the bank select message before any PC message + // + MappedEvent mEMSB(m_selectedInstrument->getId(), + MappedEvent::MidiController, + MIDI_CONTROLLER_BANK_MSB, + m_selectedInstrument->getMSB()); + + RG_DEBUG << "MIDIInstrumentParameterPanel::sendBankAndProgram - " + << "sending MSB = " + << int(m_selectedInstrument->getMSB()) + << endl; + + StudioControl::sendMappedEvent(mEMSB); + + MappedEvent mELSB(m_selectedInstrument->getId(), + MappedEvent::MidiController, + MIDI_CONTROLLER_BANK_LSB, + m_selectedInstrument->getLSB()); + + RG_DEBUG << "MIDIInstrumentParameterPanel::sendBankAndProgram - " + << "sending LSB = " + << int(m_selectedInstrument->getLSB()) + << endl; + + StudioControl::sendMappedEvent(mELSB); + } + + MappedEvent mE(m_selectedInstrument->getId(), + MappedEvent::MidiProgramChange, + m_selectedInstrument->getProgramChange(), + (MidiByte)0); + + RG_DEBUG << "MIDIInstrumentParameterPanel::sendBankAndProgram - " + << "sending program change = " + << int(m_selectedInstrument->getProgramChange()) + << endl; + + + // Send the controller change + // + StudioControl::sendMappedEvent(mE); +} + +void +MIDIInstrumentParameterPanel::slotControllerChanged(int controllerNumber) +{ + + RG_DEBUG << "MIDIInstrumentParameterPanel::slotControllerChanged - " + << "controller = " << controllerNumber << "\n"; + + + if (m_selectedInstrument == 0) + return ; + + MidiDevice *md = dynamic_cast<MidiDevice*> + (m_selectedInstrument->getDevice()); + if (!md) + return ; + + /* + ControlParameter *controller = + md->getControlParameter(MidiByte(controllerNumber)); + */ + + int value = getValueFromRotary(controllerNumber); + + if (value == -1) { + RG_DEBUG << "MIDIInstrumentParameterPanel::slotControllerChanged - " + << "couldn't get value of rotary for controller " + << controllerNumber << endl; + return ; + } + + + // two special cases + if (controllerNumber == int(MIDI_CONTROLLER_PAN)) { + float adjValue = value; + if (m_selectedInstrument->getType() == Instrument::Audio || + m_selectedInstrument->getType() == Instrument::SoftSynth) + value += 100; + + m_selectedInstrument->setPan(MidiByte(adjValue)); + } else if (controllerNumber == int(MIDI_CONTROLLER_VOLUME)) { + m_selectedInstrument->setVolume(MidiByte(value)); + } else // just set the controller (this will create it on the instrument if + // it doesn't exist) + { + m_selectedInstrument->setControllerValue(MidiByte(controllerNumber), + MidiByte(value)); + + RG_DEBUG << "SET CONTROLLER VALUE (" << controllerNumber << ") = " << value << endl; + } + /* + else + { + RG_DEBUG << "MIDIInstrumentParameterPanel::slotControllerChanged - " + << "no controller retrieved\n"; + return; + } + */ + + MappedEvent mE(m_selectedInstrument->getId(), + MappedEvent::MidiController, + (MidiByte)controllerNumber, + (MidiByte)value); + StudioControl::sendMappedEvent(mE); + + emit updateAllBoxes(); + emit instrumentParametersChanged(m_selectedInstrument->getId()); + +} + +int +MIDIInstrumentParameterPanel::getValueFromRotary(int rotary) +{ + for (RotaryMap::iterator it = m_rotaries.begin(); it != m_rotaries.end(); ++it) { + if (it->first == rotary) + return int(it->second.first->getPosition()); + } + + return -1; +} + +void +MIDIInstrumentParameterPanel::showAdditionalControls(bool showThem) +{ + m_instrumentLabel->setShown(showThem); + int index = 0; + for (RotaryMap::iterator it = m_rotaries.begin(); it != m_rotaries.end(); ++it) { + it->second.first->parentWidget()->setShown(showThem || (index < 8)); + //it->second.first->setShown(showThem || (index < 8)); + //it->second.second->setShown(showThem || (index < 8)); + index++; + } +} + +} +#include "MIDIInstrumentParameterPanel.moc" diff --git a/src/gui/editors/parameters/MIDIInstrumentParameterPanel.h b/src/gui/editors/parameters/MIDIInstrumentParameterPanel.h new file mode 100644 index 0000000..7f1a1c5 --- /dev/null +++ b/src/gui/editors/parameters/MIDIInstrumentParameterPanel.h @@ -0,0 +1,137 @@ + +/* -*- 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_MIDIINSTRUMENTPARAMETERPANEL_H_ +#define _RG_MIDIINSTRUMENTPARAMETERPANEL_H_ + +#include "base/MidiProgram.h" +#include "base/MidiDevice.h" +#include "InstrumentParameterPanel.h" +#include <qstring.h> + + +class QWidget; +class QSignalMapper; +class QLabel; +class QGridLayout; +class QFrame; +class QCheckBox; +class KComboBox; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class MidiDevice; +class Instrument; + + +class MIDIInstrumentParameterPanel : public InstrumentParameterPanel +{ + Q_OBJECT +public: + + MIDIInstrumentParameterPanel(RosegardenGUIDoc *doc, QWidget* parent); + + void setupControllers(MidiDevice *); // setup ControlParameters on box + + virtual void setupForInstrument(Instrument*); + + void showAdditionalControls(bool showThem); + +signals: + void changeInstrumentLabel(InstrumentId id, QString label); + void instrumentParametersChanged(InstrumentId); + +public slots: + void slotSelectProgram(int index); + void slotSelectBank(int index); + void slotSelectVariation(int index); + void slotSelectChannel(int index); + //void slotSelectInputChannel(int index); + + void slotControllerChanged(int index); + + void slotTogglePercussion(bool value); + void slotToggleProgramChange(bool value); + void slotToggleBank(bool value); + void slotToggleVariation(bool value); + +protected: + + // fill (or hide) bank combo based on whether the instrument is percussion + void populateBankList(); + + // fill program combo based on current bank + void populateProgramList(); + + // fill (or hide) variation combo based on current bank and program + void populateVariationList(); + + // send the bank and program events relevant to this instrument + void sendBankAndProgram(); + + // get value of a specific rotary (keyed by controller value) + int getValueFromRotary(int rotary); + + // set rotary to value + void setRotaryToValue(int controller, int value); + + //--------------- Data members --------------------------------- + + QLabel *m_connectionLabel; + + KComboBox *m_bankValue; + KComboBox *m_variationValue; + KComboBox *m_channelValue; + KComboBox *m_programValue; + //KComboBox *m_channelInValue; + + QCheckBox *m_percussionCheckBox; + QCheckBox *m_bankCheckBox; + QCheckBox *m_variationCheckBox; + QCheckBox *m_programCheckBox; + + QLabel *m_bankLabel; + QLabel *m_variationLabel; + QLabel *m_programLabel; + + QGridLayout *m_mainGrid; + QFrame *m_rotaryFrame; + QGridLayout *m_rotaryGrid; + RotaryMap m_rotaries; + QSignalMapper *m_rotaryMapper; + + BankList m_banks; + ProgramList m_programs; + MidiByteList m_variations; +}; + + + +} + +#endif diff --git a/src/gui/editors/parameters/RosegardenParameterArea.cpp b/src/gui/editors/parameters/RosegardenParameterArea.cpp new file mode 100644 index 0000000..968c737 --- /dev/null +++ b/src/gui/editors/parameters/RosegardenParameterArea.cpp @@ -0,0 +1,227 @@ +/* -*- 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. + + This file Copyright 2006 Martin Shepherd <[email protected]>. + + 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 "RosegardenParameterArea.h" + +#include "RosegardenParameterBox.h" +#include <ktabwidget.h> +#include <qfont.h> +#include <qframe.h> +#include <qpoint.h> +#include <qscrollview.h> +#include <qstring.h> +#include <qvbox.h> +#include <qlayout.h> +#include <qvgroupbox.h> +#include <qwidget.h> +#include <qwidgetstack.h> +#include <iostream> +#include <set> + + +namespace Rosegarden +{ + +RosegardenParameterArea::RosegardenParameterArea(QWidget *parent, + const char *name, WFlags f) + : QWidgetStack(parent, name, f), + m_style(RosegardenParameterArea::CLASSIC_STYLE), + m_scrollView(new QScrollView(this, 0, Qt::WStaticContents)), + m_classic(new QVBox(m_scrollView->viewport())), + m_tabBox(new KTabWidget(this)), + m_active(0), + m_spacing(0) +{ + m_scrollView->addChild(m_classic); + m_scrollView->setHScrollBarMode(QScrollView::AlwaysOff); + m_scrollView->setVScrollBarMode(QScrollView::Auto); + m_scrollView->setResizePolicy(QScrollView::AutoOneFit); + + // Install the classic-style VBox widget in the widget-stack. + + addWidget(m_scrollView, CLASSIC_STYLE); + + // Install the widget that implements the tab-style to the widget-stack. + + addWidget(m_tabBox, TAB_BOX_STYLE); + +} + +void RosegardenParameterArea::addRosegardenParameterBox( + RosegardenParameterBox *b) +{ + // Check that the box hasn't been added before. + + for (unsigned int i = 0; i < m_parameterBoxes.size(); i++) { + if (m_parameterBoxes[i] == b) + return ; + } + + // Append the parameter box to the list to be displayed. + + m_parameterBoxes.push_back(b); + + m_scrollView->setMinimumWidth(std::max(m_scrollView->minimumWidth(), + b->sizeHint().width()) + 8); + + // Create a titled group box for the parameter box, parented by the + // classic layout widget, so that it can be used to provide a title + // and outline, in classic mode. Add this container to an array that + // parallels the above array of parameter boxes. + + QVGroupBox *box = new QVGroupBox(b->getLongLabel(), m_classic); + box->layout()->setMargin( 4 ); // about half the default value + QFont f; + f.setBold( true ); + box->setFont( f ); + m_groupBoxes.push_back(box); + + if (m_spacing) + delete m_spacing; + m_spacing = new QFrame(m_classic); + m_classic->setStretchFactor(m_spacing, 100); + + // Add the parameter box to the current container of the displayed + // widgets, unless the current container has been set up yet. + + if (m_active) + moveWidget(0, m_active, b); + + // Queue a redisplay of the parameter area, to incorporate the new box. + + update(); +} + +void RosegardenParameterArea::setArrangement(Arrangement style) +{ + // Lookup the container of the specified style. + + QWidget *container; + switch (style) { + case CLASSIC_STYLE: + container = m_classic; + break; + case TAB_BOX_STYLE: + container = m_tabBox; + break; + default: + std::cerr << "setArrangement() was passed an unknown arrangement style." + << std::endl; + return ; + } + + // Does the current container of the parameter-box widgets differ + // from the one that is associated with the currently configured + // style? + + if (container != m_active) { + + // Move the parameter boxes from the old container to the new one. + + std::vector<RosegardenParameterBox *> sorted; + std::set<RosegardenParameterBox *> unsorted; + + for (unsigned int i = 0; i < m_parameterBoxes.size(); i++) { + unsorted.insert(m_parameterBoxes[i]); + } + + QString previous = ""; + + while (!unsorted.empty()) { + std::set<RosegardenParameterBox *>::iterator i = unsorted.begin(); + bool have = false; + while (i != unsorted.end()) { + if ((*i)->getPreviousBox(style) == previous) { + sorted.push_back(*i); + previous = (*i)->getShortLabel(); + unsorted.erase(i); + have = true; + break; + } + ++i; + } + if (!have) { + while (!unsorted.empty()) { + sorted.push_back(*unsorted.begin()); + unsorted.erase(unsorted.begin()); + } + break; + } + } + + for (std::vector<RosegardenParameterBox *>::iterator i = sorted.begin(); + i != sorted.end(); ++i) { + moveWidget(m_active, container, *i); + (*i)->showAdditionalControls(style == TAB_BOX_STYLE); + } + + // Switch the widget stack to displaying the new container. + + raiseWidget(style); + } + + // Record the identity of the active container, and the associated + // arrangement style. + + m_active = container; + m_style = style; +} + +void RosegardenParameterArea::moveWidget(QWidget *old_container, + QWidget *new_container, + RosegardenParameterBox *box) +{ + // Remove any state that is associated with the parameter boxes, + // from the active container. + + if (old_container == m_classic) { + ; + } else if (old_container == m_tabBox) { + m_tabBox->removePage(box); + } + + // Reparent the parameter box, and perform any container-specific + // configuration. + + if (new_container == m_classic) { + int index = 0; + while (index < m_parameterBoxes.size()) { + if (box == m_parameterBoxes[index]) + break; + ++index; + } + if (index < m_parameterBoxes.size()) { + box->reparent(m_groupBoxes[index], 0, QPoint(0, 0), FALSE); + } + } else if (new_container == m_tabBox) { + box->reparent(new_container, 0, QPoint(0, 0), FALSE); + m_tabBox->insertTab(box, box->getShortLabel()); + } +} + +} +#include "RosegardenParameterArea.moc" diff --git a/src/gui/editors/parameters/RosegardenParameterArea.h b/src/gui/editors/parameters/RosegardenParameterArea.h new file mode 100644 index 0000000..1236a43 --- /dev/null +++ b/src/gui/editors/parameters/RosegardenParameterArea.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. + + This file Copyright 2006 Martin Shepherd <[email protected]>. + + 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_ROSEGARDENPARAMETERAREA_H_ +#define _RG_ROSEGARDENPARAMETERAREA_H_ + +#include <qwidgetstack.h> +#include <vector> + + +class QWidget; +class QVGroupBox; +class QVBox; +class QScrollView; +class KTabWidget; + + +namespace Rosegarden +{ + +class RosegardenParameterBox; + + +/** + * A widget that arranges a set of Rosegarden parameter-box widgets + * within a frame, in a dynamically configurable manner. + */ +class RosegardenParameterArea : public QWidgetStack +{ + Q_OBJECT +public: + + // Create the parameter display area. + + RosegardenParameterArea(QWidget *parent=0, const char *name=0, WFlags f=0); + + // Add a rosegarden parameter box to the list that are to be displayed. + + void addRosegardenParameterBox(RosegardenParameterBox *b); + + + // List the supported methods of arranging the various parameter-box + // widgets within the parameter area. + + enum Arrangement { + CLASSIC_STYLE, // A simple vertical tiling of parameter-box widgets. + TAB_BOX_STYLE // A horizontal list of tabs, displaying one box at a time. + }; + + // Redisplay the widgets with a different layout style. + + void setArrangement(Arrangement style); + +protected: +private: + Arrangement m_style; // The current layout style. + + // The list of parameter box widgets that are being displayed by this + // widget. + + std::vector<RosegardenParameterBox *> m_parameterBoxes; + + // Create a parallel array of group boxes, to be used when the + // corresponding parameter box widget needs to be enclosed by a + // titled outline. + + std::vector<QVGroupBox *> m_groupBoxes; + + // Move a RosegardenParameterBox widget from one container to another. + + void moveWidget(QWidget *old_container, QWidget *new_container, + RosegardenParameterBox *box); + + QScrollView *m_scrollView; // Holds the m_classic container + QVBox *m_classic; // The container widget for m_style==CLASSIC_STYLE. + KTabWidget *m_tabBox; // The container widget for m_style==TAB_BOX_STYLE. + QWidget *m_active; // The current container widget. + QWidget *m_spacing; +}; + + +} + +#endif diff --git a/src/gui/editors/parameters/RosegardenParameterBox.cpp b/src/gui/editors/parameters/RosegardenParameterBox.cpp new file mode 100644 index 0000000..7d9100c --- /dev/null +++ b/src/gui/editors/parameters/RosegardenParameterBox.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 "RosegardenParameterBox.h" + +#include "RosegardenParameterArea.h" +#include <ktabwidget.h> +#include <qfont.h> +#include <qframe.h> +#include <qscrollview.h> +#include <qstring.h> +#include <qvbox.h> +#include <qwidget.h> +#include <qwidgetstack.h> + + +namespace Rosegarden +{ + +RosegardenParameterBox::RosegardenParameterBox(const QString &shortLabel, + const QString &longLabel, + QWidget *parent, + const char *name) : + QFrame(parent, name), + m_shortLabel(shortLabel), + m_longLabel(longLabel), + m_mode(LANDSCAPE_MODE) +{ + init(); +} + +void RosegardenParameterBox::init() +{ + QFont plainFont; + plainFont.setPointSize(plainFont.pointSize() * 95 / 100); + if (plainFont.pixelSize() > 14) + plainFont.setPixelSize(14); + plainFont.setBold(false); + m_font = plainFont; + + QFont boldFont; + boldFont.setPointSize(int(boldFont.pointSize() * 9.5 / 10.0 + 0.5)); + if (boldFont.pixelSize() > 14) + boldFont.setPixelSize(14); + boldFont.setBold(true); + + setFont(boldFont); +} + +QString RosegardenParameterBox::getShortLabel() const +{ + return m_shortLabel; +} + +QString RosegardenParameterBox::getLongLabel() const +{ + return m_longLabel; +} + +QString RosegardenParameterBox::getPreviousBox(RosegardenParameterArea::Arrangement) const +{ + // No ordering known -- depends on subclasses + return ""; +} + +} +#include "RosegardenParameterBox.moc" diff --git a/src/gui/editors/parameters/RosegardenParameterBox.h b/src/gui/editors/parameters/RosegardenParameterBox.h new file mode 100644 index 0000000..6f17358 --- /dev/null +++ b/src/gui/editors/parameters/RosegardenParameterBox.h @@ -0,0 +1,92 @@ + +/* -*- 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_ROSEGARDENPARAMETERBOX_H_ +#define _RG_ROSEGARDENPARAMETERBOX_H_ + +#include "RosegardenParameterArea.h" +#include <qfont.h> +#include <qframe.h> +#include <qstring.h> +#include <klocale.h> + + +class QWidget; + + +namespace Rosegarden +{ + + + +/** + * A flat QFrame, in which a group of parameters can be laid out. + * Virtual method functions are defined for for requesting a layout + * style, and returning the single-word to use for labelling the + * box. + */ + +class RosegardenParameterBox : public QFrame +{ + Q_OBJECT +public: + RosegardenParameterBox(const QString &shortLabel, // e.g. i18n("Track") + const QString &longLabel, // e.g. i18n("Track Parameters") + QWidget *parent = 0, + const char *name = 0); + + // Ask for a one-word string that can be used to label the widget. + QString getShortLabel() const; + + // Ask for the full label (e.g. short-label "Parameters") + QString getLongLabel() const; + + // Get the short label of the prior parameter box (to establish an ordering) + virtual QString getPreviousBox(RosegardenParameterArea::Arrangement) const; + + virtual void showAdditionalControls(bool) = 0; + +protected: + void init(); + + // List the layout styles that may be requested via a call to setStyle(). + + enum LayoutMode { + LANDSCAPE_MODE, // Optimize the layout for a tall and narrow parent. + PORTRAIT_MODE // Optimize the layout for a short and wide parent. + }; + + void setLayoutMode(LayoutMode mode); + + QFont m_font; + QString m_shortLabel; // The string that containers can use for labelling and identification + QString m_longLabel; // The full title + LayoutMode m_mode; // The current layout mode. +}; + + +} + +#endif diff --git a/src/gui/editors/parameters/SegmentParameterBox.cpp b/src/gui/editors/parameters/SegmentParameterBox.cpp new file mode 100644 index 0000000..c17cbe2 --- /dev/null +++ b/src/gui/editors/parameters/SegmentParameterBox.cpp @@ -0,0 +1,1214 @@ +/* -*- 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 "SegmentParameterBox.h" +#include <qlayout.h> +#include <kapplication.h> + +#include <klocale.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/Colour.h" +#include "base/ColourMap.h" +#include "base/Composition.h" +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "base/BasicQuantizer.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "commands/segment/SegmentChangeQuantizationCommand.h" +#include "commands/segment/SegmentColourCommand.h" +#include "commands/segment/SegmentColourMapCommand.h" +#include "commands/segment/SegmentCommandRepeat.h" +#include "commands/segment/SegmentLabelCommand.h" +#include "document/MultiViewCommandHistory.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/dialogs/PitchPickerDialog.h" +#include "gui/editors/notation/NotationStrings.h" +#include "gui/editors/notation/NotePixmapFactory.h" +#include "gui/general/GUIPalette.h" +#include "gui/widgets/ColourTable.h" +#include "gui/widgets/TristateCheckBox.h" +#include "RosegardenParameterArea.h" +#include "RosegardenParameterBox.h" +#include <kcolordialog.h> +#include <kcombobox.h> +#include <kcommand.h> +#include <kconfig.h> +#include <klineeditdlg.h> +#include <ktabwidget.h> +#include <qbutton.h> +#include <qcheckbox.h> +#include <qcolor.h> +#include <qdialog.h> +#include <qfont.h> +#include <qfontmetrics.h> +#include <qframe.h> +#include <qlabel.h> +#include <qpixmap.h> +#include <qpushbutton.h> +#include <qscrollview.h> +#include <qspinbox.h> +#include <qstring.h> +#include <qtooltip.h> +#include <qvbox.h> +#include <qwidget.h> +#include <qwidgetstack.h> + + +namespace Rosegarden +{ + +SegmentParameterBox::SegmentParameterBox(RosegardenGUIDoc* doc, + QWidget *parent) + : RosegardenParameterBox(i18n("Segment"), + i18n("Segment Parameters"), + parent), + m_highestPlayable(127), + m_lowestPlayable(0), + m_standardQuantizations(BasicQuantizer::getStandardQuantizations()), + m_doc(doc), + m_transposeRange(48) +{ + initBox(); + + m_doc->getComposition().addObserver(this); + + connect(getCommandHistory(), SIGNAL(commandExecuted()), + this, SLOT(update())); +} + +SegmentParameterBox::~SegmentParameterBox() +{ + if (!isCompositionDeleted()) { + m_doc->getComposition().removeObserver(this); + } +} + +void +SegmentParameterBox::initBox() +{ + QFont font(m_font); + + QFontMetrics fontMetrics(font); + // magic numbers: 13 is the height of the menu pixmaps, 10 is just 10 + //int comboHeight = std::max(fontMetrics.height(), 13) + 10; + int width = fontMetrics.width("12345678901234567890"); + + // QFrame *frame = new QFrame(this); + QGridLayout *gridLayout = new QGridLayout(this, 8, 6, 4, 2); + + QLabel *label = new QLabel(i18n("Label"), this); + QLabel *repeatLabel = new QLabel(i18n("Repeat"), this); + QLabel *quantizeLabel = new QLabel(i18n("Quantize"), this); + QLabel *transposeLabel = new QLabel(i18n("Transpose"), this); + QLabel *delayLabel = new QLabel(i18n("Delay"), this); + QLabel *colourLabel = new QLabel(i18n("Color"), this); +// m_autoFadeLabel = new QLabel(i18n("Audio auto-fade"), this); +// m_fadeInLabel = new QLabel(i18n("Fade in"), this); +// m_fadeOutLabel = new QLabel(i18n("Fade out"), this); +// m_rangeLabel = new QLabel(i18n("Range"), this); + + // Label .. + m_label = new QLabel(this); + m_label->setFont(font); + m_label->setFixedWidth(width); + //m_label->setFixedHeight(comboHeight); + m_label->setFrameStyle(QFrame::Panel | QFrame::Sunken); + + // .. and edit button + m_labelButton = new QPushButton(i18n("Edit"), this); + m_labelButton->setFont(font); + // m_labelButton->setFixedWidth(50); + + connect(m_labelButton, SIGNAL(released()), + SLOT(slotEditSegmentLabel())); + + m_repeatValue = new TristateCheckBox(this); + m_repeatValue->setFont(font); + //m_repeatValue->setFixedHeight(comboHeight); + + // handle state changes + connect(m_repeatValue, SIGNAL(pressed()), SLOT(slotRepeatPressed())); + + // non-reversing motif style read-only combo + m_quantizeValue = new KComboBox(this); + m_quantizeValue->setFont(font); + //m_quantizeValue->setFixedHeight(comboHeight); + + // handle quantize changes from drop down + connect(m_quantizeValue, SIGNAL(activated(int)), + SLOT(slotQuantizeSelected(int))); + + // reversing motif style read-write combo + m_transposeValue = new KComboBox(this); + m_transposeValue->setFont(font); + //m_transposeValue->setFixedHeight(comboHeight); + + // handle transpose combo changes + connect(m_transposeValue, SIGNAL(activated(int)), + SLOT(slotTransposeSelected(int))); + + // and text changes + connect(m_transposeValue, SIGNAL(textChanged(const QString&)), + SLOT(slotTransposeTextChanged(const QString&))); + + // reversing motif style read-write combo + m_delayValue = new KComboBox(this); + m_delayValue->setFont(font); + //m_delayValue->setFixedHeight(comboHeight); + + // handle delay combo changes + connect(m_delayValue, SIGNAL(activated(int)), + SLOT(slotDelaySelected(int))); + + // Detect when the document colours are updated + connect(m_doc, SIGNAL(docColoursChanged()), + this, SLOT(slotDocColoursChanged())); + + // handle text changes for delay + connect(m_delayValue, SIGNAL(textChanged(const QString&)), + SLOT(slotDelayTextChanged(const QString &))); + + // set up combo box for colours + m_colourValue = new KComboBox(false, this); + m_colourValue->setFont(font); + //m_colourValue->setFixedHeight(comboHeight); + // m_colourValue->setMaximumWidth(width); + m_colourValue->setSizeLimit(20); + + // handle colour combo changes + connect(m_colourValue, SIGNAL(activated(int)), + SLOT(slotColourSelected(int))); + + // pre-set width of buttons so they don't grow later +// width = fontMetrics.width(i18n("used internally for spacing", "High: ----")); + + // highest playable note + // +// m_highButton = new QPushButton(i18n("High: ---"), this); +// QToolTip::add +// (m_highButton, i18n("Choose the highest suggested playable note, using a staff")); +// m_highButton->setFont(font); +// m_highButton->setMinimumWidth(width); + +// connect(m_highButton, SIGNAL(released()), +// SLOT(slotHighestPressed())); + + // lowest playable note + // +// m_lowButton = new QPushButton(i18n("Low: ----"), this); +// QToolTip::add +// (m_lowButton, i18n("Choose the lowest suggested playable note, using a staff")); +// m_lowButton->setFont(font); +// m_lowButton->setMinimumWidth(width); + +// connect(m_lowButton, SIGNAL(released()), +// SLOT(slotLowestPressed())); + + // Audio autofade enabled + // +// m_autoFadeBox = new QCheckBox(this); +// connect(m_autoFadeBox, SIGNAL(stateChanged(int)), +// this, SLOT(slotAudioFadeChanged(int))); + + // Fade in and out times + // +// m_fadeInSpin = new QSpinBox(this); +// m_fadeInSpin->setMinValue(0); +// m_fadeInSpin->setMaxValue(5000); +// m_fadeInSpin->setSuffix(i18n(" ms")); +// connect(m_fadeInSpin, SIGNAL(valueChanged(int)), +// this, SLOT(slotFadeInChanged(int))); + +// m_fadeOutSpin = new QSpinBox(this); +// m_fadeOutSpin->setMinValue(0); +// m_fadeOutSpin->setMaxValue(5000); +// m_fadeOutSpin->setSuffix(i18n(" ms")); +// connect(m_fadeOutSpin, SIGNAL(valueChanged(int)), +// this, SLOT(slotFadeOutChanged(int))); + + label->setFont(font); + repeatLabel->setFont(font); + quantizeLabel->setFont(font); + transposeLabel->setFont(font); + delayLabel->setFont(font); + colourLabel->setFont(font); +// m_autoFadeLabel->setFont(font); +// m_fadeInLabel->setFont(font); +// m_fadeOutLabel->setFont(font); +// m_rangeLabel->setFont(font); + + int row = 0; + +// gridLayout->addRowSpacing(0, 12); // why?? + + gridLayout->addWidget(label, row, 0); //, AlignRight); + gridLayout->addMultiCellWidget(m_label, row, row, 1, 4); //, AlignLeft); + gridLayout->addWidget(m_labelButton, row, 5); //, AlignLeft); + ++row; + + gridLayout->addWidget(repeatLabel, row, 0); //, AlignRight); + gridLayout->addWidget(m_repeatValue, row, 1); //, AlignLeft); + + gridLayout->addMultiCellWidget(transposeLabel, row, row, 2, 3, AlignRight); + gridLayout->addMultiCellWidget(m_transposeValue, row, row, 4, 5); + ++row; + + gridLayout->addWidget(quantizeLabel, row, 0); //, AlignRight); + gridLayout->addMultiCellWidget(m_quantizeValue, row, row, 1, 2); //, AlignLeft); + + gridLayout->addWidget(delayLabel, row, 3, AlignRight); + gridLayout->addMultiCellWidget(m_delayValue, row, row, 4, 5); + ++row; + + gridLayout->addWidget(colourLabel, row, 0); //, AlignRight); + gridLayout->addMultiCellWidget(m_colourValue, row, row, 1, 5); + ++row; + +// gridLayout->addWidget(m_rangeLabel, row, 0); //, AlignRight); +// gridLayout->addMultiCellWidget(m_lowButton, row, row, 1, 2); +// gridLayout->addMultiCellWidget(m_highButton, row, row, 3, 4); +// ++row; + +// m_autoFadeLabel->hide(); +// m_autoFadeBox->hide(); + /* + gridLayout->addWidget(m_fadeInLabel, 5, 0, AlignRight); + gridLayout->addWidget(m_fadeInSpin, 5, 1); + + gridLayout->addWidget(m_fadeOutLabel, 5, 2, AlignRight); + gridLayout->addWidget(m_fadeOutSpin, 5, 3); + */ + // Configure the empty final row to accomodate any extra vertical space. + + gridLayout->setRowStretch(gridLayout->numRows() - 1, 1); + + // Configure the empty final column to accomodate any extra horizontal + // space. + +// gridLayout->setColStretch(gridLayout->numCols() - 1, 1); + + // populate the quantize combo + // + QPixmap noMap = NotePixmapFactory::toQPixmap(NotePixmapFactory::makeToolbarPixmap("menu-no-note")); + + for (unsigned int i = 0; i < m_standardQuantizations.size(); ++i) { + + timeT time = m_standardQuantizations[i]; + timeT error = 0; + QString label = NotationStrings::makeNoteMenuLabel(time, true, error); + QPixmap pmap = NotePixmapFactory::toQPixmap(NotePixmapFactory::makeNoteMenuPixmap(time, error)); + m_quantizeValue->insertItem(error ? noMap : pmap, label); + } + m_quantizeValue->insertItem(noMap, i18n("Off")); + + // default to last item + m_quantizeValue->setCurrentItem(m_quantizeValue->count() - 1); + + // populate the transpose combo + // + for (int i = -m_transposeRange; i < m_transposeRange + 1; i++) { + m_transposeValue->insertItem(noMap, QString("%1").arg(i)); + if (i == 0) + m_transposeValue->setCurrentItem(m_transposeValue->count() - 1); + } + + m_delays.clear(); + + for (int i = 0; i < 6; i++) { + timeT time = 0; + if (i > 0 && i < 6) { + time = Note(Note::Hemidemisemiquaver).getDuration() << (i - 1); + } else if (i > 5) { + time = Note(Note::Crotchet).getDuration() * (i - 4); + } + + m_delays.push_back(time); + + // check if it's a valid note duration (it will be for the + // time defn above, but if we were basing it on the sequencer + // resolution it might not be) & include a note pixmap if so + // + timeT error = 0; + QString label = NotationStrings::makeNoteMenuLabel(time, true, error); + QPixmap pmap = NotePixmapFactory::toQPixmap(NotePixmapFactory::makeNoteMenuPixmap(time, error)); + m_delayValue->insertItem((error ? noMap : pmap), label); + } + + for (int i = 0; i < 10; i++) { + int rtd = (i < 5 ? ((i + 1) * 10) : ((i - 3) * 50)); + m_realTimeDelays.push_back(rtd); + m_delayValue->insertItem(i18n("%1 ms").arg(rtd)); + } + + // set delay blank initially + m_delayValue->setCurrentItem( -1); + + // populate m_colourValue + slotDocColoursChanged(); + + //!!! disabled until after 1.3 +// m_highButton->hide(); +// m_lowButton->hide(); +// m_rangeLabel->hide(); + ////////////////////////////// + +} + +void +SegmentParameterBox::setDocument(RosegardenGUIDoc* doc) +{ + if (m_doc != 0) + disconnect(m_doc, SIGNAL(docColoursChanged()), + this, SLOT(slotDocColoursChanged())); + + m_doc = doc; + + // Detect when the document colours are updated + connect (m_doc, SIGNAL(docColoursChanged()), + this, SLOT(slotDocColoursChanged())); + + slotDocColoursChanged(); // repopulate combo +} + +void +SegmentParameterBox::useSegment(Segment *segment) +{ + m_segments.clear(); + m_segments.push_back(segment); + populateBoxFromSegments(); +} + +void +SegmentParameterBox::useSegments(const SegmentSelection &segments) +{ + m_segments.clear(); + + m_segments.resize(segments.size()); + std::copy(segments.begin(), segments.end(), m_segments.begin()); + + populateBoxFromSegments(); +} + +void +SegmentParameterBox::slotDocColoursChanged() +{ + RG_DEBUG << "SegmentParameterBox::slotDocColoursChanged()" << endl; + + m_colourValue->clear(); + m_colourList.clear(); + // Populate it from composition.m_segmentColourMap + ColourMap temp = m_doc->getComposition().getSegmentColourMap(); + + unsigned int i = 0; + + for (RCMap::const_iterator it = temp.begin(); it != temp.end(); ++it) { + QString qtrunc(strtoqstr(it->second.second)); + QPixmap colour(15, 15); + colour.fill(GUIPalette::convertColour(it->second.first)); + if (qtrunc == "") { + m_colourValue->insertItem(colour, i18n("Default"), i); + } else { + // truncate name to 15 characters to avoid the combo forcing the + // whole kit and kaboodle too wide + if (qtrunc.length() > 15) + qtrunc = qtrunc.left(12) + "..."; + m_colourValue->insertItem(colour, qtrunc, i); + } + m_colourList[it->first] = i; // maps colour number to menu index + ++i; + } + + m_addColourPos = i; + m_colourValue->insertItem(i18n("Add New Color"), m_addColourPos); + + m_colourValue->setCurrentItem(0); +} + +void SegmentParameterBox::update() +{ + RG_DEBUG << "SegmentParameterBox::update()" << endl; + + populateBoxFromSegments(); +} + +void +SegmentParameterBox::segmentRemoved(const Composition *composition, + Segment *segment) +{ + if (composition == &m_doc->getComposition()) { + + for (std::vector<Segment*>::iterator it = + m_segments.begin(); it != m_segments.end(); ++it) { + + if (*it == segment) { + m_segments.erase(it); + return ; + } + } + } +} + +void +SegmentParameterBox::populateBoxFromSegments() +{ + std::vector<Segment*>::iterator it; + Tristate repeated = NotApplicable; + Tristate quantized = NotApplicable; + Tristate transposed = NotApplicable; + Tristate delayed = NotApplicable; + Tristate diffcolours = NotApplicable; + Tristate highlow = NotApplicable; + unsigned int myCol = 0; + unsigned int myHigh = 127; + unsigned int myLow = 0; + + timeT qntzLevel = 0; + // At the moment we have no negative delay, so we use negative + // values to represent real-time delay in ms + timeT delayLevel = 0; + int transposeLevel = 0; + + if (m_segments.size() == 0) + m_label->setText(""); + else + m_label->setText(strtoqstr(m_segments[0]->getLabel())); + + for (it = m_segments.begin(); it != m_segments.end(); it++) { + // ok, first thing is we know we have at least one segment + if (repeated == NotApplicable) + repeated = None; + if (quantized == NotApplicable) + quantized = None; + if (transposed == NotApplicable) + transposed = None; + if (delayed == NotApplicable) + delayed = None; + if (diffcolours == NotApplicable) + diffcolours = None; + if (highlow == NotApplicable) + highlow = None; + + // Set label to "*" when multiple labels don't match + // + if (strtoqstr((*it)->getLabel()) != m_label->text()) + m_label->setText("*"); + + // Are all, some or none of the Segments repeating? + if ((*it)->isRepeating()) { + if (it == m_segments.begin()) + repeated = All; + else { + if (repeated == None) + repeated = Some; + } + } else { + if (repeated == All) + repeated = Some; + } + + // Quantization + // + if ((*it)->hasQuantization()) { + if (it == m_segments.begin()) { + quantized = All; + qntzLevel = (*it)->getQuantizer()->getUnit(); + } else { + // If quantize levels don't match + if (quantized == None || + (quantized == All && + qntzLevel != + (*it)->getQuantizer()->getUnit())) + quantized = Some; + } + } else { + if (quantized == All) + quantized = Some; + } + + // Transpose + // + if ((*it)->getTranspose() != 0) { + if (it == m_segments.begin()) { + transposed = All; + transposeLevel = (*it)->getTranspose(); + } else { + if (transposed == None || + (transposed == All && + transposeLevel != (*it)->getTranspose())) + transposed = Some; + } + + } else { + if (transposed == All) + transposed = Some; + } + + // Delay + // + timeT myDelay = (*it)->getDelay(); + if (myDelay == 0) { + myDelay = -((*it)->getRealTimeDelay().sec * 1000 + + (*it)->getRealTimeDelay().msec()); + } + + if (myDelay != 0) { + if (it == m_segments.begin()) { + delayed = All; + delayLevel = myDelay; + } else { + if (delayed == None || + (delayed == All && + delayLevel != myDelay)) + delayed = Some; + } + } else { + if (delayed == All) + delayed = Some; + } + + // Colour + + if (it == m_segments.begin()) { + myCol = (*it)->getColourIndex(); + } else { + if (myCol != (*it)->getColourIndex()) + ; + diffcolours = All; + } + + // Highest/Lowest playable + // + if (it == m_segments.begin()) { + myHigh = (*it)->getHighestPlayable(); + myLow = (*it)->getLowestPlayable(); + } else { + if (myHigh != (*it)->getHighestPlayable() || + myLow != (*it)->getLowestPlayable()) { + highlow = All; + } + } + + } + + switch (repeated) { + case All: + m_repeatValue->setChecked(true); + break; + + case Some: + m_repeatValue->setNoChange(); + break; + + case None: + case NotApplicable: + default: + m_repeatValue->setChecked(false); + break; + } + + m_repeatValue->setEnabled(repeated != NotApplicable); + + switch (quantized) { + case All: { + for (unsigned int i = 0; + i < m_standardQuantizations.size(); ++i) { + if (m_standardQuantizations[i] == qntzLevel) { + m_quantizeValue->setCurrentItem(i); + break; + } + } + } + break; + + case Some: + // Set the edit text to an unfeasible blank value meaning "Some" + // + m_quantizeValue->setCurrentItem( -1); + break; + + // Assuming "Off" is always the last field + case None: + default: + m_quantizeValue->setCurrentItem(m_quantizeValue->count() - 1); + break; + } + + m_quantizeValue->setEnabled(quantized != NotApplicable); + + switch (transposed) { + // setCurrentItem works with QStrings + // 2nd arg of "true" means "add if necessary" + case All: + m_transposeValue-> + setCurrentItem(QString("%1").arg(transposeLevel), true); + break; + + case Some: + m_transposeValue->setCurrentItem(QString(""), true); + break; + + case None: + default: + m_transposeValue->setCurrentItem("0"); + break; + } + + m_transposeValue->setEnabled(transposed != NotApplicable); + + m_delayValue->blockSignals(true); + + switch (delayed) { + case All: + if (delayLevel >= 0) { + timeT error = 0; + QString label = NotationStrings::makeNoteMenuLabel(delayLevel, + true, + error); + m_delayValue->setCurrentItem(label, true); + + } else if (delayLevel < 0) { + + m_delayValue->setCurrentItem(i18n("%1 ms").arg( -delayLevel), + true); + } + + break; + + case Some: + m_delayValue->setCurrentItem("", true); + break; + + case None: + default: + m_delayValue->setCurrentItem(0); + break; + } + + m_delayValue->setEnabled(delayed != NotApplicable); + + m_delayValue->blockSignals(false); + + switch (diffcolours) { + case None: + if (m_colourList.find(myCol) != m_colourList.end()) + m_colourValue->setCurrentItem(m_colourList[myCol]); + else + m_colourValue->setCurrentItem(0); + break; + + + case All: + case NotApplicable: + default: + m_colourValue->setCurrentItem(0); + break; + + } + + m_colourValue->setEnabled(diffcolours != NotApplicable); + + //!!! this is all borked up and useless; sort out after 1.3 +/* + switch (highlow) { + case All: + updateHighLow(); + break; + + case Some: + case None: + default: + m_highButton->setText(i18n("High: ---")); + m_lowButton->setText(i18n("Low: ----")); + highlow = NotApplicable; + break; + } + + m_highButton->setEnabled(highlow != NotApplicable); + m_lowButton->setEnabled(highlow != NotApplicable); +*/ + + // Enable or disable the fade in/out params +/* + if (m_segments.size() == 1 && + (*(m_segments.begin()))->getType() == Segment::Audio) { + m_autoFadeBox->blockSignals(true); + m_fadeInSpin->blockSignals(true); + m_fadeOutSpin->blockSignals(true); + + ... !!! No, not setting up autofade widgets. The implementation's too + incomplete to finish for this release. + + (Or for the next one after the one the previous comment referred to.) + + (Or for the one after the one after that. Will we ever get those + working, or should Rich's final legacy simply be quietly disappeared?) + + m_fadeInLabel->show(); + m_fadeInSpin->show(); + m_fadeOutLabel->show(); + m_fadeOutSpin->show(); + + instead: + + m_fadeInLabel->hide(); + m_fadeInSpin->hide(); + m_fadeOutLabel->hide(); + m_fadeOutSpin->hide(); + + m_autoFadeLabel->setEnabled(true); + m_autoFadeBox->setEnabled(true); + m_fadeInLabel->setEnabled(true); + m_fadeInSpin->setEnabled(true); + m_fadeOutLabel->setEnabled(true); + m_fadeOutSpin->setEnabled(true); + + Segment *seg = *(m_segments.begin()); + + int fadeInTime = seg->getFadeInTime().sec * 1000 + + seg->getFadeInTime().msec(); + m_fadeInSpin->setValue(fadeInTime); + + int fadeOutTime = seg->getFadeOutTime().sec * 1000 + + seg->getFadeOutTime().msec(); + m_fadeOutSpin->setValue(fadeOutTime); + + m_autoFadeBox->setChecked(seg->isAutoFading()); + + m_autoFadeBox->blockSignals(false); + m_fadeInSpin->blockSignals(false); + m_fadeOutSpin->blockSignals(false); + } else { + m_autoFadeLabel->setEnabled(false); + m_autoFadeBox->setEnabled(false); + m_fadeInLabel->setEnabled(false); + m_fadeInSpin->setEnabled(false); + m_fadeOutLabel->setEnabled(false); + m_fadeOutSpin->setEnabled(false); + + m_autoFadeLabel->hide(); + m_autoFadeBox->hide(); + m_fadeInLabel->hide(); + m_fadeInSpin->hide(); + m_fadeOutLabel->hide(); + m_fadeOutSpin->hide(); + + m_autoFadeBox->setChecked(false); + m_fadeInSpin->setValue(0); + m_fadeOutSpin->setValue(0); + } +*/ + +} + +void SegmentParameterBox::slotRepeatPressed() +{ + if (m_segments.size() == 0) + return ; + + bool state = false; + + switch (m_repeatValue->state()) { + case QButton::Off: + state = true; + break; + + case QButton::NoChange: + case QButton::On: + default: + state = false; + break; + } + + // update the check box and all current Segments + m_repeatValue->setChecked(state); + + addCommandToHistory(new SegmentCommandRepeat(m_segments, state)); + + // std::vector<Segment*>::iterator it; + + // for (it = m_segments.begin(); it != m_segments.end(); it++) + // (*it)->setRepeating(state); +} + +void +SegmentParameterBox::slotQuantizeSelected(int qLevel) +{ + bool off = (qLevel == m_quantizeValue->count() - 1); + + SegmentChangeQuantizationCommand *command = + new SegmentChangeQuantizationCommand + (off ? 0 : m_standardQuantizations[qLevel]); + + std::vector<Segment*>::iterator it; + for (it = m_segments.begin(); it != m_segments.end(); it++) { + command->addSegment(*it); + } + + addCommandToHistory(command); +} + +void +SegmentParameterBox::slotTransposeTextChanged(const QString &text) +{ + if (text.isEmpty() || m_segments.size() == 0) + return ; + + int transposeValue = text.toInt(); + + // addCommandToHistory(new SegmentCommandChangeTransposeValue(m_segments, + // transposeValue)); + + std::vector<Segment*>::iterator it; + for (it = m_segments.begin(); it != m_segments.end(); it++) { + (*it)->setTranspose(transposeValue); + } + + emit documentModified(); +} + +void +SegmentParameterBox::slotTransposeSelected(int value) +{ + slotTransposeTextChanged(m_transposeValue->text(value)); +} + +void +SegmentParameterBox::slotDelayTimeChanged(timeT delayValue) +{ + // by convention and as a nasty hack, we use negative timeT here + // to represent positive RealTime in ms + + if (delayValue > 0) { + + std::vector<Segment*>::iterator it; + for (it = m_segments.begin(); it != m_segments.end(); it++) { + (*it)->setDelay(delayValue); + (*it)->setRealTimeDelay(RealTime::zeroTime); + } + + } else if (delayValue < 0) { + + std::vector<Segment*>::iterator it; + for (it = m_segments.begin(); it != m_segments.end(); it++) { + (*it)->setDelay(0); + int sec = ( -delayValue) / 1000; + int nsec = (( -delayValue) - 1000 * sec) * 1000000; + (*it)->setRealTimeDelay(RealTime(sec, nsec)); + } + } else { + + std::vector<Segment*>::iterator it; + for (it = m_segments.begin(); it != m_segments.end(); it++) { + (*it)->setDelay(0); + (*it)->setRealTimeDelay(RealTime::zeroTime); + } + } + + emit documentModified(); +} + +void +SegmentParameterBox::slotDelayTextChanged(const QString &text) +{ + if (text.isEmpty() || m_segments.size() == 0) + return ; + + slotDelayTimeChanged( -(text.toInt())); +} + +void +SegmentParameterBox::slotDelaySelected(int value) +{ + if (value < int(m_delays.size())) { + slotDelayTimeChanged(m_delays[value]); + } else { + slotDelayTimeChanged( -(m_realTimeDelays[value - m_delays.size()])); + } +} + +void +SegmentParameterBox::slotColourSelected(int value) +{ + if (value != m_addColourPos) { + unsigned int temp = 0; + + ColourTable::ColourList::const_iterator pos; + for (pos = m_colourList.begin(); pos != m_colourList.end(); ++pos) { + if (pos->second == value) { + temp = pos->first; + break; + } + } + + SegmentSelection segments; + std::vector<Segment*>::iterator it; + + for (it = m_segments.begin(); it != m_segments.end(); ++it) { + segments.insert(*it); + } + + SegmentColourCommand *command = new SegmentColourCommand(segments, temp); + + addCommandToHistory(command); + } else { + ColourMap newMap = m_doc->getComposition().getSegmentColourMap(); + QColor newColour; + bool ok = false; + QString newName = KLineEditDlg::getText(i18n("New Color Name"), i18n("Enter new name"), + i18n("New"), &ok); + if ((ok == true) && (!newName.isEmpty())) { + KColorDialog box(this, "", true); + + int result = box.getColor(newColour); + + if (result == KColorDialog::Accepted) { + Colour newRColour = GUIPalette::convertColour(newColour); + newMap.addItem(newRColour, qstrtostr(newName)); + SegmentColourMapCommand *command = new SegmentColourMapCommand(m_doc, newMap); + addCommandToHistory(command); + slotDocColoursChanged(); + } + } + // Else we don't do anything as they either didn't give a name· + // or didn't give a colour + } + + +} + +void +SegmentParameterBox::updateHighLow() +{ + // Key of C major and NoAccidental means any "black key" notes will be + // written as sharps. + Accidental accidental = Accidentals::NoAccidental; + Rosegarden::Key key = Rosegarden::Key("C major"); + + Pitch highest(m_highestPlayable, accidental); + Pitch lowest(m_lowestPlayable, accidental); + + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + int base = config->readNumEntry("midipitchoctave", -2); + //!!! FIXME this code is broken, and needs to be fixed after the fashion of + //the TPB, but I'm not bothering with that at this time, because they are + //going to be hidden for 1.3 anyway +// m_highButton->setText(QString("&High: %1%2").arg(highest.getNoteName(key)).arg(highest.getOctave(base))); +// m_lowButton->setText(QString("&Low: %1%2").arg(lowest.getNoteName(key)).arg(lowest.getOctave(base))); +} + +void +SegmentParameterBox::slotHighestPressed() +{ + RG_DEBUG << "SegmentParameterBox::slotHighestPressed()" << endl; + + PitchPickerDialog dialog(0, m_highestPlayable, i18n("Highest playable note")); + std::vector<Segment*>::iterator it; + + if (dialog.exec() == QDialog::Accepted) { + m_highestPlayable = dialog.getPitch(); + updateHighLow(); + + for (it = m_segments.begin(); it != m_segments.end(); it++) { + (*it)->setHighestPlayable(m_highestPlayable); + } + + emit documentModified(); + } +} + +void +SegmentParameterBox::slotLowestPressed() +{ + RG_DEBUG << "SegmentParameterBox::slotLowestPressed()" << endl; + + PitchPickerDialog dialog(0, m_lowestPlayable, i18n("Lowest playable note")); + std::vector<Segment*>::iterator it; + + if (dialog.exec() == QDialog::Accepted) { + m_lowestPlayable = dialog.getPitch(); + updateHighLow(); + + for (it = m_segments.begin(); it != m_segments.end(); it++) { + (*it)->setLowestPlayable(m_lowestPlayable); + } + + emit documentModified(); + } +} + +MultiViewCommandHistory* +SegmentParameterBox::getCommandHistory() +{ + return m_doc->getCommandHistory(); +} + +void +SegmentParameterBox::addCommandToHistory(KCommand *command) +{ + m_doc->getCommandHistory()->addCommand(command); +} + +void +SegmentParameterBox::slotEditSegmentLabel() +{ + QString editLabel; + + if (m_segments.size() == 0) + return ; + else if (m_segments.size() == 1) + editLabel = i18n("Modify Segment label"); + else + editLabel = i18n("Modify Segments label"); + + bool ok = false; + + // Remove the asterisk if we're using it + // + QString label = m_label->text(); + if (label == "*") + label = ""; + + QString newLabel = KLineEditDlg::getText(editLabel, + i18n("Enter new label"), + m_label->text(), + &ok, + this); + + if (ok) { + SegmentSelection segments; + std::vector<Segment*>::iterator it; + for (it = m_segments.begin(); it != m_segments.end(); ++it) + segments.insert(*it); + + SegmentLabelCommand *command = new + SegmentLabelCommand(segments, newLabel); + + addCommandToHistory(command); + + // fix #1776915, maybe? + update(); + } +} + +void +SegmentParameterBox::slotAudioFadeChanged(int value) +{ + RG_DEBUG << "SegmentParameterBox::slotAudioFadeChanged - value = " + << value << endl; +/* + if (m_segments.size() == 0) + return ; + + bool state = false; + if (value == QButton::On) + state = true; + + std::vector<Segment*>::iterator it; + for (it = m_segments.begin(); it != m_segments.end(); it++) { + (*it)->setAutoFade(state); + } +*/ +} + +void +SegmentParameterBox::slotFadeInChanged(int value) +{ + RG_DEBUG << "SegmentParameterBox::slotFadeInChanged - value = " + << value << endl; +/* + if (m_segments.size() == 0) + return ; + + if (value == 0 && m_fadeOutSpin->value() == 0) + slotAudioFadeChanged(QButton::Off); + else + slotAudioFadeChanged(QButton::On); + + // Convert from ms + // + RealTime fadeInTime(value / 1000, (value % 1000) * 1000000); + + std::vector<Segment*>::iterator it; + for (it = m_segments.begin(); it != m_segments.end(); it++) { + (*it)->setFadeInTime(fadeInTime); + } + + emit documentModified(); +*/ +} + +void +SegmentParameterBox::slotFadeOutChanged(int value) +{ + RG_DEBUG << "SegmentParameterBox::slotFadeOutChanged - value = " + << value << endl; +/* + if (m_segments.size() == 0) + return ; + + if (value == 0 && m_fadeInSpin->value() == 0) + slotAudioFadeChanged(QButton::Off); + else + slotAudioFadeChanged(QButton::On); + + // Convert from ms + // + RealTime fadeOutTime(value / 1000000, (value % 1000) * 10000000); + + std::vector<Segment*>::iterator it; + for (it = m_segments.begin(); it != m_segments.end(); it++) { + (*it)->setFadeOutTime(fadeOutTime); + } + + emit documentModified(); +*/ +} + +void +SegmentParameterBox::showAdditionalControls(bool showThem) +{ + //!!! disabled until after 1.3 + /* m_highButton->setShown(showThem); + m_lowButton->setShown(showThem); + m_rangeLabel->setShown(showThem); */ +} + +QString +SegmentParameterBox::getPreviousBox(RosegardenParameterArea::Arrangement arrangement) const +{ + if (arrangement == RosegardenParameterArea::CLASSIC_STYLE) { + return ""; + } else { + return i18n("Instrument"); + } +} + +} +#include "SegmentParameterBox.moc" diff --git a/src/gui/editors/parameters/SegmentParameterBox.h b/src/gui/editors/parameters/SegmentParameterBox.h new file mode 100644 index 0000000..a8b0353 --- /dev/null +++ b/src/gui/editors/parameters/SegmentParameterBox.h @@ -0,0 +1,174 @@ + +/* -*- 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_SEGMENTPARAMETERBOX_H_ +#define _RG_SEGMENTPARAMETERBOX_H_ + +#include "base/Composition.h" +#include "base/MidiProgram.h" +#include "gui/widgets/ColourTable.h" +#include "RosegardenParameterArea.h" +#include "RosegardenParameterBox.h" +#include <qstring.h> +#include <vector> +#include "base/Event.h" + + +class QWidget; +class QSpinBox; +class QPushButton; +class QLabel; +class QCheckBox; +class KCommand; +class KComboBox; + + +namespace Rosegarden +{ + +class TristateCheckBox; +class SegmentSelection; +class Segment; +class RosegardenGUIDoc; +class MultiViewCommandHistory; +class Composition; + + +class SegmentParameterBox : public RosegardenParameterBox, + public CompositionObserver +{ +Q_OBJECT + +public: + + typedef enum + { + None, + Some, + All, + NotApplicable // no applicable segments selected + } Tristate; + + SegmentParameterBox(RosegardenGUIDoc *doc, + QWidget *parent=0); + ~SegmentParameterBox(); + + // Use Segments to update GUI parameters + // + void useSegment(Segment *segment); + void useSegments(const SegmentSelection &segments); + + // Command history stuff + MultiViewCommandHistory* getCommandHistory(); + void addCommandToHistory(KCommand *command); + + void setDocument(RosegardenGUIDoc*); + + // CompositionObserver interface + // + virtual void segmentRemoved(const Composition *, + Segment *); + + virtual void showAdditionalControls(bool showThem); + + virtual QString getPreviousBox(RosegardenParameterArea::Arrangement) const; + +public slots: + void slotRepeatPressed(); + void slotQuantizeSelected(int); + + void slotTransposeSelected(int); + void slotTransposeTextChanged(const QString &); + + void slotDelaySelected(int); + void slotDelayTimeChanged(timeT delayValue); + void slotDelayTextChanged(const QString &); + + void slotEditSegmentLabel(); + + void slotColourSelected(int); + void slotDocColoursChanged(); + + void slotAudioFadeChanged(int); + void slotFadeInChanged(int); + void slotFadeOutChanged(int); + + void slotHighestPressed(); + void slotLowestPressed(); + + virtual void update(); + +signals: + void documentModified(); + void canvasModified(); + +protected: + void initBox(); + void populateBoxFromSegments(); + void updateHighLow(); + + QLabel *m_label; +// QLabel *m_rangeLabel; + QPushButton *m_labelButton; +// QPushButton *m_highButton; +// QPushButton *m_lowButton; + TristateCheckBox *m_repeatValue; + KComboBox *m_quantizeValue; + KComboBox *m_transposeValue; + KComboBox *m_delayValue; + KComboBox *m_colourValue; + + // Audio autofade + // +// QLabel *m_autoFadeLabel; +// QCheckBox *m_autoFadeBox; +// QLabel *m_fadeInLabel; +// QSpinBox *m_fadeInSpin; +// QLabel *m_fadeOutLabel; +// QSpinBox *m_fadeOutSpin; + + int m_addColourPos; + + // used to keep track of highest/lowest as there is no associated spinbox + // to query for its value + int m_highestPlayable; + int m_lowestPlayable; + + std::vector<Segment*> m_segments; + std::vector<timeT> m_standardQuantizations; + std::vector<timeT> m_delays; + std::vector<int> m_realTimeDelays; + ColourTable::ColourList m_colourList; + + RosegardenGUIDoc *m_doc; + + MidiByte m_transposeRange; +}; + + + +} + +#endif diff --git a/src/gui/editors/parameters/TrackParameterBox.cpp b/src/gui/editors/parameters/TrackParameterBox.cpp new file mode 100644 index 0000000..fc85346 --- /dev/null +++ b/src/gui/editors/parameters/TrackParameterBox.cpp @@ -0,0 +1,1022 @@ +/* -*- 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. + + This file is Copyright 2006 + Pedro Lopez-Cabanillas <[email protected]> + D. Michael McIntyre <[email protected]> + + 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 "TrackParameterBox.h" +#include <qlayout.h> +#include <kapplication.h> + +#include <klocale.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "gui/general/ClefIndex.h" +#include "document/ConfigGroups.h" +#include "base/AudioPluginInstance.h" +#include "base/Colour.h" +#include "base/ColourMap.h" +#include "base/Composition.h" +#include "base/Device.h" +#include "base/Exception.h" +#include "base/Instrument.h" +#include "base/MidiDevice.h" +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "base/StaffExportTypes.h" +#include "commands/segment/SegmentSyncCommand.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/dialogs/PitchPickerDialog.h" +#include "gui/general/GUIPalette.h" +#include "gui/general/PresetHandlerDialog.h" +#include "gui/widgets/CollapsingFrame.h" +#include "gui/widgets/ColourTable.h" +#include "RosegardenParameterArea.h" +#include "RosegardenParameterBox.h" +#include "sound/PluginIdentifier.h" +#include <kcolordialog.h> +#include <kcombobox.h> +#include <kconfig.h> +#include <klineeditdlg.h> +#include <kmessagebox.h> +#include <ksqueezedtextlabel.h> +#include <ktabwidget.h> +#include <qcolor.h> +#include <qdialog.h> +#include <qfont.h> +#include <qfontmetrics.h> +#include <qframe.h> +#include <qlabel.h> +#include <qpixmap.h> +#include <qpushbutton.h> +#include <qregexp.h> +#include <qscrollview.h> +#include <qstring.h> +#include <qtooltip.h> +#include <qvbox.h> +#include <qwidget.h> +#include <qwidgetstack.h> +#include <qcheckbox.h> + + +namespace Rosegarden +{ + +TrackParameterBox::TrackParameterBox( RosegardenGUIDoc *doc, + QWidget *parent) + : RosegardenParameterBox(i18n("Track"), + i18n("Track Parameters"), + parent), + m_doc(doc), + m_highestPlayable(127), + m_lowestPlayable(0), + m_selectedTrackId( -1) +{ + QFont font(m_font); + QFont title_font(m_font); + QFontMetrics metrics(font); + int width11 = metrics.width("12345678901"); + int width20 = metrics.width("12345678901234567890"); + int width22 = metrics.width("1234567890123456789012"); + int width25 = metrics.width("1234567890123456789012345"); + setFont(m_font); + title_font.setBold(true); + + // Set up default expansions for the collapsing elements + KConfig *config = kapp->config(); + QString groupTemp = config->group(); + config->setGroup("CollapsingFrame"); + bool expanded = config->readBoolEntry("trackparametersplayback", true); + config->writeEntry("trackparametersplayback", expanded); + expanded = config->readBoolEntry("trackparametersrecord", false); + config->writeEntry("trackparametersrecord", expanded); + expanded = config->readBoolEntry("trackparametersdefaults", false); + config->writeEntry("trackparametersdefaults", expanded); + expanded = config->readBoolEntry("trackstaffgroup", false); + config->writeEntry("trackstaffgroup", expanded); + config->setGroup(groupTemp); + + QGridLayout *mainLayout = new QGridLayout(this, 5, 1, 2, 1); + + int row = 0; + + // track label + // + m_trackLabel = new KSqueezedTextLabel(i18n("<untitled>"), this); + m_trackLabel->setAlignment(Qt::AlignCenter); + //mainLayout->addMultiCellWidget(m_trackLabel, 0, 0, 0, 5, AlignCenter); + mainLayout->addWidget(m_trackLabel, 0, 0); + + // playback group + // + CollapsingFrame *cframe = new CollapsingFrame(i18n("Playback parameters"), + this, "trackparametersplayback"); + m_playbackGroup = new QFrame(cframe); + cframe->setWidget(m_playbackGroup); + QGridLayout *groupLayout = new QGridLayout(m_playbackGroup, 3, 3, 3, 2); + + // playback group title + // + row = 0; + + // playback device + // + // row++; + QLabel *devLabel = new QLabel(i18n("Device"), m_playbackGroup); + groupLayout->addWidget(devLabel, row, 0); + m_playDevice = new KComboBox(m_playbackGroup); + m_playDevice->setMinimumWidth(width25); + groupLayout->addMultiCellWidget(m_playDevice, row, row, 1, 2); + + // playback instrument + // + row++; + QLabel *insLabel = new QLabel(i18n("Instrument"), m_playbackGroup); + groupLayout->addMultiCellWidget(insLabel, row, row, 0, 1); + m_instrument = new KComboBox(m_playbackGroup); + m_instrument->setSizeLimit( 16 ); + m_instrument->setMinimumWidth(width22); + groupLayout->addWidget(m_instrument, row, 2); + + groupLayout->setColStretch(groupLayout->numCols() - 1, 1); + + mainLayout->addWidget(cframe, 1, 0); + + // record group + // + cframe = new CollapsingFrame(i18n("Recording filters"), this, + "trackparametersrecord"); + m_recordGroup = new QFrame(cframe); + cframe->setWidget(m_recordGroup); + groupLayout = new QGridLayout(m_recordGroup, 3, 3, 3, 2); + + // recording group title + // + row = 0; + + // recording device + groupLayout->addWidget(new QLabel(i18n("Device"), m_recordGroup), row, 0); + m_recDevice = new KComboBox(m_recordGroup); + m_recDevice->setMinimumWidth(width25); + groupLayout->addMultiCellWidget(m_recDevice, row, row, 1, 2); + + // recording channel + // + row++; + groupLayout->addMultiCellWidget(new QLabel(i18n("Channel"), m_recordGroup), row, row, 0, 1); + m_recChannel = new KComboBox(m_recordGroup); + m_recChannel->setSizeLimit( 17 ); + m_recChannel->setMinimumWidth(width11); + groupLayout->addWidget(m_recChannel, row, 2); + + groupLayout->setColStretch(groupLayout->numCols() - 1, 1); + + mainLayout->addWidget(cframe, 2, 0); + + // staff group + // + cframe = new CollapsingFrame(i18n("Staff export options"), this, + "staffoptions"); + m_staffGroup = new QFrame(cframe); + cframe->setWidget(m_staffGroup); + groupLayout = new QGridLayout(m_staffGroup, 2, 2, 2, 2); + + groupLayout->setColStretch(1, 1); + + row = 0; + + // Notation size (export only) + // + // NOTE: This is the only way to get a \small or \tiny inserted before the + // first note in LilyPond export. Setting the actual staff size on a + // per-staff (rather than per-score) basis is something the author of the + // LilyPond documentation has no idea how to do, so we settle for this, + // which is not as nice, but actually a lot easier to implement. + m_staffGrpLbl = new QLabel(i18n("Notation size:"), m_staffGroup); + groupLayout->addWidget(m_staffGrpLbl, row, 0, AlignLeft); + m_staffSizeCombo = new KComboBox(m_staffGroup); + m_staffSizeCombo->setMinimumWidth(width11); + m_staffSizeCombo->insertItem(i18n("Normal"), StaffTypes::Normal); + m_staffSizeCombo->insertItem(i18n("Small"), StaffTypes::Small); + m_staffSizeCombo->insertItem(i18n("Tiny"), StaffTypes::Tiny); + + groupLayout->addMultiCellWidget(m_staffSizeCombo, row, row, 1, 2); + + // Staff bracketing (export only at the moment, but using this for GUI + // rendering would be nice in the future!) //!!! + row++; + m_grandStaffLbl = new QLabel(i18n("Bracket type:"), m_staffGroup); + groupLayout->addWidget(m_grandStaffLbl, row, 0, AlignLeft); + m_staffBracketCombo = new KComboBox(m_staffGroup); + m_staffBracketCombo->setMinimumWidth(width11); + m_staffBracketCombo->insertItem(i18n("-----"), Brackets::None); + m_staffBracketCombo->insertItem(i18n("[----"), Brackets::SquareOn); + m_staffBracketCombo->insertItem(i18n("----]"), Brackets::SquareOff); + m_staffBracketCombo->insertItem(i18n("[---]"), Brackets::SquareOnOff); + m_staffBracketCombo->insertItem(i18n("{----"), Brackets::CurlyOn); + m_staffBracketCombo->insertItem(i18n("----}"), Brackets::CurlyOff); + m_staffBracketCombo->insertItem(i18n("{[---"), Brackets::CurlySquareOn); + m_staffBracketCombo->insertItem(i18n("---]}"), Brackets::CurlySquareOff); + + groupLayout->addMultiCellWidget(m_staffBracketCombo, row, row, 1, 2); + + mainLayout->addWidget(cframe, 3, 0); + + + // default segment group + // + cframe = new CollapsingFrame(i18n("Create segments with"), this, + "trackparametersdefaults"); + m_defaultsGroup = new QFrame(cframe); + cframe->setWidget(m_defaultsGroup); + groupLayout = new QGridLayout(m_defaultsGroup, 6, 6, 3, 2); + + groupLayout->setColStretch(1, 1); + + row = 0; + + // preset picker + m_psetLbl = new QLabel(i18n("Preset"), m_defaultsGroup); + groupLayout->addWidget(m_psetLbl, row, 0, AlignLeft); + + m_presetLbl = new QLabel(i18n("<none>"), m_defaultsGroup); + m_presetLbl->setFrameStyle(QFrame::Panel | QFrame::Sunken); + m_presetLbl->setFixedWidth(width20); + groupLayout->addMultiCellWidget(m_presetLbl, row, row, 1, 3); + + m_presetButton = new QPushButton(i18n("Load"), m_defaultsGroup); + groupLayout->addMultiCellWidget(m_presetButton, row, row, 4, 5); + + // default clef + // + row++; + m_clefLbl = new QLabel(i18n("Clef"), m_defaultsGroup); + groupLayout->addWidget(m_clefLbl, row, 0, AlignLeft); + m_defClef = new KComboBox(m_defaultsGroup); + m_defClef->setMinimumWidth(width11); + m_defClef->insertItem(i18n("treble"), TrebleClef); + m_defClef->insertItem(i18n("bass"), BassClef); + m_defClef->insertItem(i18n("crotales"), CrotalesClef); + m_defClef->insertItem(i18n("xylophone"), XylophoneClef); + m_defClef->insertItem(i18n("guitar"), GuitarClef); + m_defClef->insertItem(i18n("contrabass"), ContrabassClef); + m_defClef->insertItem(i18n("celesta"), CelestaClef); + m_defClef->insertItem(i18n("old celesta"), OldCelestaClef); + m_defClef->insertItem(i18n("french"), FrenchClef); + m_defClef->insertItem(i18n("soprano"), SopranoClef); + m_defClef->insertItem(i18n("mezzosoprano"), MezzosopranoClef); + m_defClef->insertItem(i18n("alto"), AltoClef); + m_defClef->insertItem(i18n("tenor"), TenorClef); + m_defClef->insertItem(i18n("baritone"), BaritoneClef); + m_defClef->insertItem(i18n("varbaritone"), VarbaritoneClef); + m_defClef->insertItem(i18n("subbass"), SubbassClef); + /* clef types in the datbase that are not yet supported must be ignored for + * now: + m_defClef->insertItem(i18n("two bar"), TwoBarClef); */ + groupLayout->addMultiCellWidget(m_defClef, row, row, 1, 2); + + // default transpose + // + m_transpLbl = new QLabel(i18n("Transpose"), m_defaultsGroup); + groupLayout->addMultiCellWidget(m_transpLbl, row, row, 3, 4, AlignRight); + m_defTranspose = new KComboBox(m_defaultsGroup); + + connect(m_defTranspose, SIGNAL(activated(int)), + SLOT(slotTransposeIndexChanged(int))); + + int transposeRange = 48; + for (int i = -transposeRange; i < transposeRange + 1; i++) { + m_defTranspose->insertItem(QString("%1").arg(i)); + if (i == 0) + m_defTranspose->setCurrentItem(m_defTranspose->count() - 1); + } + + groupLayout->addMultiCellWidget(m_defTranspose, row, row, 5, 5); + + // highest/lowest playable note + // + row++; + m_rangeLbl = new QLabel(i18n("Pitch"), m_defaultsGroup); + groupLayout->addMultiCellWidget(m_rangeLbl, row, row, 0, 0); + + groupLayout->addWidget(new QLabel(i18n("Lowest"), m_defaultsGroup), row, 1, AlignRight); + + m_lowButton = new QPushButton(i18n("---"), m_defaultsGroup); + QToolTip::add + (m_lowButton, i18n("Choose the lowest suggested playable note, using a staff")); + groupLayout->addMultiCellWidget(m_lowButton, row, row, 2, 2); + + groupLayout->addWidget(new QLabel(i18n("Highest"), m_defaultsGroup), row, 3, AlignRight); + + m_highButton = new QPushButton(i18n("---"), m_defaultsGroup); + QToolTip::add + (m_highButton, i18n("Choose the highest suggested playable note, using a staff")); + groupLayout->addMultiCellWidget(m_highButton, row, row, 4, 5); + + updateHighLow(); + + // default color + // + row++; + m_colorLbl = new QLabel(i18n("Color"), m_defaultsGroup); + groupLayout->addWidget(m_colorLbl, row, 0, AlignLeft); + m_defColor = new KComboBox(false, m_defaultsGroup); + m_defColor->setSizeLimit(20); + groupLayout->addMultiCellWidget(m_defColor, row, row, 1, 5); + + // populate combo from doc colors + slotDocColoursChanged(); + + mainLayout->addWidget(cframe, 4, 0); + + + // Configure the empty final row to accomodate any extra vertical space. + // +// mainLayout->setColStretch(mainLayout->numCols() - 1, 1); + mainLayout->setRowStretch(mainLayout->numRows() - 1, 1); + + // Connections + connect( m_playDevice, SIGNAL(activated(int)), + this, SLOT(slotPlaybackDeviceChanged(int))); + + connect( m_instrument, SIGNAL(activated(int)), + this, SLOT(slotInstrumentChanged(int))); + + connect( m_recDevice, SIGNAL(activated(int)), + this, SLOT(slotRecordingDeviceChanged(int))); + + connect( m_recChannel, SIGNAL(activated(int)), + this, SLOT(slotRecordingChannelChanged(int))); + + connect( m_defClef, SIGNAL(activated(int)), + this, SLOT(slotClefChanged(int))); + + // Detect when the document colours are updated + connect(m_doc, SIGNAL(docColoursChanged()), + this, SLOT(slotDocColoursChanged())); + + // handle colour combo changes + connect(m_defColor, SIGNAL(activated(int)), + SLOT(slotColorChanged(int))); + + connect(m_highButton, SIGNAL(released()), + SLOT(slotHighestPressed())); + + connect(m_lowButton, SIGNAL(released()), + SLOT(slotLowestPressed())); + + connect(m_presetButton, SIGNAL(released()), + SLOT(slotPresetPressed())); + + connect(m_staffSizeCombo, SIGNAL(activated(int)), + this, SLOT(slotStaffSizeChanged(int))); + + connect(m_staffBracketCombo, SIGNAL(activated(int)), + this, SLOT(slotStaffBracketChanged(int))); +} + +TrackParameterBox::~TrackParameterBox() +{} + +void + +TrackParameterBox::setDocument( RosegardenGUIDoc *doc ) +{ + if (m_doc != doc) { + RG_DEBUG << "TrackParameterBox::setDocument\n"; + m_doc = doc; + populateDeviceLists(); + } +} + +void +TrackParameterBox::populateDeviceLists() +{ + RG_DEBUG << "TrackParameterBox::populateDeviceLists()\n"; + populatePlaybackDeviceList(); + //populateRecordingDeviceList(); + slotUpdateControls( -1); + m_lastInstrumentType = -1; +} + +void +TrackParameterBox::populatePlaybackDeviceList() +{ + RG_DEBUG << "TrackParameterBox::populatePlaybackDeviceList()\n"; + m_playDevice->clear(); + m_playDeviceIds.clear(); + m_instrument->clear(); + m_instrumentIds.clear(); + m_instrumentNames.clear(); + + Studio &studio = m_doc->getStudio(); + + // Get the list + InstrumentList list = studio.getPresentationInstruments(); + InstrumentList::iterator it; + int currentDevId = -1; + + for (it = list.begin(); it != list.end(); it++) { + + if (! (*it)) + continue; // sanity check + + //QString iname(strtoqstr((*it)->getPresentationName())); + QString iname(strtoqstr((*it)->getName())); + QString pname(strtoqstr((*it)->getProgramName())); + Device *device = (*it)->getDevice(); + DeviceId devId = device->getId(); + + if ((*it)->getType() == Instrument::SoftSynth) { + iname.replace("Synth plugin ", ""); + pname = ""; + AudioPluginInstance *plugin = (*it)->getPlugin + (Instrument::SYNTH_PLUGIN_POSITION); + if (plugin) { + pname = strtoqstr(plugin->getProgram()); + QString identifier = strtoqstr(plugin->getIdentifier()); + if (identifier != "") { + QString type, soName, label; + PluginIdentifier::parseIdentifier + (identifier, type, soName, label); + if (pname == "") { + pname = strtoqstr(plugin->getDistinctiveConfigurationText()); + } + if (pname != "") { + pname = QString("%1: %2").arg(label).arg(pname); + } else { + pname = label; + } + } + } + } + + if (devId != (DeviceId)(currentDevId)) { + currentDevId = int(devId); + QString deviceName = strtoqstr(device->getName()); + m_playDevice->insertItem(deviceName); + m_playDeviceIds.push_back(currentDevId); + } + + if (pname != "") + iname += " (" + pname + ")"; + m_instrumentIds[currentDevId].push_back((*it)->getId()); + m_instrumentNames[currentDevId].append(iname); + + } + + m_playDevice->setCurrentItem( -1); + m_instrument->setCurrentItem( -1); +} + +void +TrackParameterBox::populateRecordingDeviceList() +{ + RG_DEBUG << "TrackParameterBox::populateRecordingDeviceList()\n"; + + if (m_selectedTrackId < 0) + return ; + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(m_selectedTrackId); + if (!trk) + return ; + + Instrument *inst = m_doc->getStudio().getInstrumentById(trk->getInstrument()); + if (!inst) + return ; + + if (m_lastInstrumentType != (char)inst->getInstrumentType()) { + m_lastInstrumentType = (char)inst->getInstrumentType(); + + m_recDevice->clear(); + m_recDeviceIds.clear(); + m_recChannel->clear(); + + if (inst->getInstrumentType() == Instrument::Audio) { + + m_recDeviceIds.push_back(Device::NO_DEVICE); + m_recDevice->insertItem(i18n("Audio")); + m_recChannel->insertItem(i18n("Audio")); + + m_recDevice->setEnabled(false); + m_recChannel->setEnabled(false); + + // hide these for audio instruments + m_defaultsGroup->parentWidget()->setShown(false); + + } else { // InstrumentType::Midi and InstrumentType::SoftSynth + + // show these if not audio instrument + m_defaultsGroup->parentWidget()->setShown(true); + + m_recDeviceIds.push_back(Device::ALL_DEVICES); + m_recDevice->insertItem(i18n("All")); + + DeviceList *devices = m_doc->getStudio().getDevices(); + DeviceListConstIterator it; + for (it = devices->begin(); it != devices->end(); it++) { + MidiDevice *dev = + dynamic_cast<MidiDevice*>(*it); + if (dev) { + if (dev->getDirection() == MidiDevice::Record + && dev->isRecording()) { + QString connection = strtoqstr(dev->getConnection()); + // remove trailing "(duplex)", "(read only)", "(write only)" etc + connection.replace(QRegExp("\\s*\\([^)0-9]+\\)\\s*$"), ""); + m_recDevice->insertItem(connection); + m_recDeviceIds.push_back(dev->getId()); + } + } + } + + m_recChannel->insertItem(i18n("All")); + for (int i = 1; i < 17; ++i) { + m_recChannel->insertItem(QString::number(i)); + } + + m_recDevice->setEnabled(true); + m_recChannel->setEnabled(true); + } + } + + if (inst->getInstrumentType() == Instrument::Audio) { + m_recDevice->setCurrentItem(0); + m_recChannel->setCurrentItem(0); + } else { + m_recDevice->setCurrentItem(0); + m_recChannel->setCurrentItem((int)trk->getMidiInputChannel() + 1); + for (unsigned int i = 0; i < m_recDeviceIds.size(); ++i) { + if (m_recDeviceIds[i] == trk->getMidiInputDevice()) { + m_recDevice->setCurrentItem(i); + break; + } + } + } +} + +void +TrackParameterBox::updateHighLow() +{ + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(comp.getSelectedTrack()); + if (!trk) + return ; + + trk->setHighestPlayable(m_highestPlayable); + trk->setLowestPlayable(m_lowestPlayable); + + Accidental accidental = Accidentals::NoAccidental; + + Pitch highest(m_highestPlayable, accidental); + Pitch lowest(m_lowestPlayable, accidental); + + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + int base = config->readNumEntry("midipitchoctave", -2); + bool useSharps = true; + bool includeOctave = true; + +// m_highButton->setText(i18n("High: %1").arg(highest.getAsString(useSharps, includeOctave, base))); +// m_lowButton->setText(i18n("Low: %1").arg(lowest.getAsString(useSharps, includeOctave, base))); + m_highButton->setText(QString("%1").arg(highest.getAsString(useSharps, includeOctave, base))); + m_lowButton->setText(QString("%1").arg(lowest.getAsString(useSharps, includeOctave, base))); + + m_presetLbl->setEnabled(false); +} + +void +TrackParameterBox::slotUpdateControls(int /*dummy*/) +{ + RG_DEBUG << "TrackParameterBox::slotUpdateControls()\n"; + slotPlaybackDeviceChanged( -1); + slotInstrumentChanged( -1); + + if (m_selectedTrackId < 0) + return ; + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(m_selectedTrackId); + if (!trk) + return ; + + m_defClef->setCurrentItem(trk->getClef()); + m_defTranspose->setCurrentItem(QString("%1").arg(trk->getTranspose()), true); + m_defColor->setCurrentItem(trk->getColor()); + m_highestPlayable = trk->getHighestPlayable(); + m_lowestPlayable = trk->getLowestPlayable(); + updateHighLow(); + // set this down here because updateHighLow just disabled the label + m_presetLbl->setText(trk->getPresetLabel()); + m_presetLbl->setEnabled(true); + + m_staffSizeCombo->setCurrentItem(trk->getStaffSize()); + m_staffBracketCombo->setCurrentItem(trk->getStaffBracket()); +} + +void +TrackParameterBox::slotSelectedTrackChanged() +{ + RG_DEBUG << "TrackParameterBox::slotSelectedTrackChanged()\n"; + Composition &comp = m_doc->getComposition(); + TrackId newTrack = comp.getSelectedTrack(); + if ( newTrack != m_selectedTrackId ) { + m_presetLbl->setEnabled(true); + m_selectedTrackId = newTrack; + slotSelectedTrackNameChanged(); + slotUpdateControls( -1); + } +} + +void +TrackParameterBox::slotSelectedTrackNameChanged() +{ + RG_DEBUG << "TrackParameterBox::sotSelectedTrackNameChanged()\n"; + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(m_selectedTrackId); + QString m_trackName = trk->getLabel(); + if (m_trackName.isEmpty()) + m_trackName = i18n("<untitled>"); + else + m_trackName.truncate(20); + int trackNum = trk->getPosition() + 1; + m_trackLabel->setText(i18n("[ Track %1 - %2 ]").arg(trackNum).arg(m_trackName)); +} + +void +TrackParameterBox::slotPlaybackDeviceChanged(int index) +{ + RG_DEBUG << "TrackParameterBox::slotPlaybackDeviceChanged(" << index << ")\n"; + DeviceId devId; + if (index == -1) { + if (m_selectedTrackId < 0) + return ; + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(m_selectedTrackId); + if (!trk) + return ; + Instrument *inst = m_doc->getStudio().getInstrumentById(trk->getInstrument()); + if (!inst) + return ; + devId = inst->getDevice()->getId(); + int pos = -1; + IdsVector::const_iterator it; + for ( it = m_playDeviceIds.begin(); it != m_playDeviceIds.end(); ++it) { + pos++; + if ((*it) == devId) + break; + } + m_playDevice->setCurrentItem(pos); + } else { + devId = m_playDeviceIds[index]; + } + + m_instrument->clear(); + m_instrument->insertStringList(m_instrumentNames[devId]); + + populateRecordingDeviceList(); + + if (index != -1) { + m_instrument->setCurrentItem(0); + slotInstrumentChanged(0); + } +} + +void +TrackParameterBox::slotInstrumentChanged(int index) +{ + RG_DEBUG << "TrackParameterBox::slotInstrumentChanged(" << index << ")\n"; + DeviceId devId; + Instrument *inst; + if (index == -1) { + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(comp.getSelectedTrack()); + if (!trk) + return ; + inst = m_doc->getStudio().getInstrumentById(trk->getInstrument()); + if (!inst) + return ; + devId = inst->getDevice()->getId(); + + int pos = -1; + IdsVector::const_iterator it; + for ( it = m_instrumentIds[devId].begin(); it != m_instrumentIds[devId].end(); ++it ) { + pos++; + if ((*it) == trk->getInstrument()) + break; + } + m_instrument->setCurrentItem(pos); + } else { + devId = m_playDeviceIds[m_playDevice->currentItem()]; + // set the new selected instrument for the track + int item = 0; + std::map<DeviceId, IdsVector>::const_iterator it; + for ( it = m_instrumentIds.begin(); it != m_instrumentIds.end(); ++it) { + if ( (*it).first == devId ) + break; + item += (*it).second.size(); + } + item += index; + RG_DEBUG << "TrackParameterBox::slotInstrumentChanged() item = " << item << "\n"; + emit instrumentSelected( m_selectedTrackId, item ); + } +} + +void +TrackParameterBox::slotRecordingDeviceChanged(int index) +{ + RG_DEBUG << "TrackParameterBox::slotRecordingDeviceChanged(" << index << ")" << endl; + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(comp.getSelectedTrack()); + if (!trk) + return ; + Instrument *inst = m_doc->getStudio().getInstrumentById(trk->getInstrument()); + if (!inst) + return ; + if (inst->getInstrumentType() == Instrument::Audio) { + //Not implemented yet + } else { + trk->setMidiInputDevice(m_recDeviceIds[index]); + } +} + +void +TrackParameterBox::slotRecordingChannelChanged(int index) +{ + RG_DEBUG << "TrackParameterBox::slotRecordingChannelChanged(" << index << ")" << endl; + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(comp.getSelectedTrack()); + if (!trk) + return ; + Instrument *inst = m_doc->getStudio().getInstrumentById(trk->getInstrument()); + if (!inst) + return ; + if (inst->getInstrumentType() == Instrument::Audio) { + //Not implemented yet + } else { + trk->setMidiInputChannel(index - 1); + } +} + +void +TrackParameterBox::slotInstrumentLabelChanged(InstrumentId id, QString label) +{ + RG_DEBUG << "TrackParameterBox::slotInstrumentLabelChanged(" << id << ") = " << label << "\n"; + populatePlaybackDeviceList(); + slotUpdateControls( -1); +} + +void +TrackParameterBox::showAdditionalControls(bool showThem) +{ + // m_defaultsGroup->parentWidget()->setShown(showThem); +} + +void +TrackParameterBox::slotClefChanged(int clef) +{ + RG_DEBUG << "TrackParameterBox::slotClefChanged(" << clef << ")" << endl; + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(comp.getSelectedTrack()); + trk->setClef(clef); + m_presetLbl->setEnabled(false); +} + +void +TrackParameterBox::slotTransposeChanged(int transpose) +{ + RG_DEBUG << "TrackParameterBox::slotTransposeChanged(" << transpose << ")" << endl; + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(comp.getSelectedTrack()); + trk->setTranspose(transpose); + m_presetLbl->setEnabled(false); +} + +void +TrackParameterBox::slotTransposeIndexChanged(int index) +{ + slotTransposeTextChanged(m_defTranspose->text(index)); +} + +void +TrackParameterBox::slotTransposeTextChanged(QString text) +{ + if (text.isEmpty()) + return ; + int value = text.toInt(); + slotTransposeChanged(value); +} + +void +TrackParameterBox::slotDocColoursChanged() +{ + RG_DEBUG << "TrackParameterBox::slotDocColoursChanged()" << endl; + + m_defColor->clear(); + m_colourList.clear(); + // Populate it from composition.m_segmentColourMap + ColourMap temp = m_doc->getComposition().getSegmentColourMap(); + + unsigned int i = 0; + + for (RCMap::const_iterator it = temp.begin(); it != temp.end(); ++it) { + QString qtrunc(strtoqstr(it->second.second)); + QPixmap colour(15, 15); + colour.fill(GUIPalette::convertColour(it->second.first)); + if (qtrunc == "") { + m_defColor->insertItem(colour, i18n("Default"), i); + } else { + // truncate name to 15 characters to avoid the combo forcing the + // whole kit and kaboodle too wide + if (qtrunc.length() > 15) + qtrunc = qtrunc.left(12) + "..."; + m_defColor->insertItem(colour, qtrunc, i); + } + m_colourList[it->first] = i; // maps colour number to menu index + ++i; + } + + m_addColourPos = i; + m_defColor->insertItem(i18n("Add New Color"), m_addColourPos); + + m_defColor->setCurrentItem(0); +} + +void +TrackParameterBox::slotColorChanged(int index) +{ + RG_DEBUG << "TrackParameterBox::slotColorChanged(" << index << ")" << endl; + + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(comp.getSelectedTrack()); + + trk->setColor(index); + + if (index == m_addColourPos) { + ColourMap newMap = m_doc->getComposition().getSegmentColourMap(); + QColor newColour; + bool ok = false; + QString newName = KLineEditDlg::getText(i18n("New Color Name"), i18n("Enter new name"), + i18n("New"), &ok); + if ((ok == true) && (!newName.isEmpty())) { + KColorDialog box(this, "", true); + + int result = box.getColor(newColour); + + if (result == KColorDialog::Accepted) { + Colour newRColour = GUIPalette::convertColour(newColour); + newMap.addItem(newRColour, qstrtostr(newName)); + slotDocColoursChanged(); + } + } + // Else we don't do anything as they either didn't give a name� + // or didn't give a colour + } +} + +void +TrackParameterBox::slotHighestPressed() +{ + RG_DEBUG << "TrackParameterBox::slotHighestPressed()" << endl; + + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(comp.getSelectedTrack()); + if (!trk) + return ; + + PitchPickerDialog dialog(0, m_highestPlayable, i18n("Highest playable note")); + + if (dialog.exec() == QDialog::Accepted) { + m_highestPlayable = dialog.getPitch(); + updateHighLow(); + } + + m_presetLbl->setEnabled(false); +} + +void +TrackParameterBox::slotLowestPressed() +{ + RG_DEBUG << "TrackParameterBox::slotLowestPressed()" << endl; + + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(comp.getSelectedTrack()); + if (!trk) + return ; + + PitchPickerDialog dialog(0, m_lowestPlayable, i18n("Lowest playable note")); + + if (dialog.exec() == QDialog::Accepted) { + m_lowestPlayable = dialog.getPitch(); + updateHighLow(); + } + + m_presetLbl->setEnabled(false); +} + +void +TrackParameterBox::slotPresetPressed() +{ + RG_DEBUG << "TrackParameterBox::slotPresetPressed()" << endl; + + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(comp.getSelectedTrack()); + if (!trk) + return ; + + PresetHandlerDialog dialog(this); + + try { + if (dialog.exec() == QDialog::Accepted) { + m_presetLbl->setText(dialog.getName()); + trk->setPresetLabel(dialog.getName()); + if (dialog.getConvertAllSegments()) { + SegmentSyncCommand* command = new SegmentSyncCommand( + comp.getSegments(), comp.getSelectedTrack(), + dialog.getTranspose(), dialog.getLowRange(), + dialog.getHighRange(), + clefIndexToClef(dialog.getClef())); + m_doc->getCommandHistory()->addCommand(command); + } + m_defClef->setCurrentItem(dialog.getClef()); + m_defTranspose->setCurrentItem(QString("%1").arg + (dialog.getTranspose()), true); + + m_highestPlayable = dialog.getHighRange(); + m_lowestPlayable = dialog.getLowRange(); + updateHighLow(); + slotClefChanged(dialog.getClef()); + slotTransposeChanged(dialog.getTranspose()); + + // the preceding slots will have set this disabled, so we + // re-enable it until it is subsequently re-disabled by the + // user overriding the preset, calling one of the above slots + // in the normal course + m_presetLbl->setEnabled(true); + } + } catch (Exception e) { + //!!! This should be a more verbose error to pass along the + // row/column of the corruption, but I can't be bothered to work + // that out just at the moment. Hopefully this code will never + // execute anyway. + KMessageBox::sorry(0, i18n("The instrument preset database is corrupt. Check your installation.")); + } + +} + +void +TrackParameterBox::slotStaffSizeChanged(int index) +{ + RG_DEBUG << "TrackParameterBox::sotStaffSizeChanged()" << endl; + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(m_selectedTrackId); + + trk->setStaffSize(index); +} + + +void +TrackParameterBox::slotStaffBracketChanged(int index) +{ + RG_DEBUG << "TrackParameterBox::sotStaffBracketChanged()" << endl; + Composition &comp = m_doc->getComposition(); + Track *trk = comp.getTrackById(m_selectedTrackId); + + trk->setStaffBracket(index); +} + +QString +TrackParameterBox::getPreviousBox(RosegardenParameterArea::Arrangement arrangement) const +{ + if (arrangement == RosegardenParameterArea::CLASSIC_STYLE) { + return i18n("Segment"); + } else { + return ""; + } +} + +} +#include "TrackParameterBox.moc" diff --git a/src/gui/editors/parameters/TrackParameterBox.h b/src/gui/editors/parameters/TrackParameterBox.h new file mode 100644 index 0000000..c5fa0f9 --- /dev/null +++ b/src/gui/editors/parameters/TrackParameterBox.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. + + This file is Copyright 2006 + Pedro Lopez-Cabanillas <[email protected]> + D. Michael McIntyre <[email protected]> + + 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_TRACKPARAMETERBOX_H_ +#define _RG_TRACKPARAMETERBOX_H_ + +#include "base/MidiProgram.h" +#include "base/Track.h" +#include "gui/widgets/ColourTable.h" +#include <map> +#include "RosegardenParameterArea.h" +#include "RosegardenParameterBox.h" +#include <qstring.h> +#include <qcheckbox.h> // #include <QCheckBox> in QT4, thinking ahead +#include <vector> + + +class QWidget; +class QPushButton; +class QLabel; +class QFrame; +class KComboBox; +class QCheckBox; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +class TrackParameterBox : public RosegardenParameterBox +{ +Q_OBJECT + +public: + TrackParameterBox( RosegardenGUIDoc *doc, + QWidget *parent=0); + ~TrackParameterBox(); + + void setDocument( RosegardenGUIDoc *doc ); + void populateDeviceLists(); + virtual void showAdditionalControls(bool showThem); + + virtual QString getPreviousBox(RosegardenParameterArea::Arrangement) const; + +public slots: + void slotSelectedTrackChanged(); + void slotSelectedTrackNameChanged(); + void slotPlaybackDeviceChanged(int index); + void slotInstrumentChanged(int index); + void slotRecordingDeviceChanged(int index); + void slotRecordingChannelChanged(int index); + void slotUpdateControls(int); + void slotInstrumentLabelChanged(InstrumentId id, QString label); + + void slotClefChanged(int clef); + void slotTransposeChanged(int transpose); + void slotTransposeIndexChanged(int index); + void slotTransposeTextChanged(QString text); + void slotDocColoursChanged(); + void slotColorChanged(int index); + void slotHighestPressed(); + void slotLowestPressed(); + void slotPresetPressed(); + + void slotStaffSizeChanged(int index); + void slotStaffBracketChanged(int index); + +signals: + void instrumentSelected(TrackId, int); + +protected: + void populatePlaybackDeviceList(); + void populateRecordingDeviceList(); + void updateHighLow(); + +private: + RosegardenGUIDoc *m_doc; + + KComboBox *m_playDevice; + KComboBox *m_instrument; + KComboBox *m_recDevice; + KComboBox *m_recChannel; + + QPushButton *m_presetButton; + QPushButton *m_highButton; + QPushButton *m_lowButton; + + KComboBox *m_defClef; + KComboBox *m_defColor; + KComboBox *m_defTranspose; + KComboBox *m_staffSizeCombo; + KComboBox *m_staffBracketCombo; + + + int m_addColourPos; + int m_highestPlayable; + int m_lowestPlayable; + ColourTable::ColourList m_colourList; + + QLabel *m_trackLabel; + + typedef std::vector<DeviceId> IdsVector; + + IdsVector m_playDeviceIds; + IdsVector m_recDeviceIds; + + std::map<DeviceId, IdsVector> m_instrumentIds; + std::map<DeviceId, QStringList> m_instrumentNames; + + int m_selectedTrackId; + + char m_lastInstrumentType; + + // Additional elements that may be hidden in vertical stacked mode + //QFrame *m_separator2; + QFrame *m_playbackGroup; + QFrame *m_recordGroup; + QFrame *m_defaultsGroup; + QFrame *m_staffGroup; + QLabel *m_segHeader; + QLabel *m_presetLbl; + QLabel *m_staffGrpLbl; + QLabel *m_grandStaffLbl; + QLabel *m_clefLbl; + QLabel *m_transpLbl; + QLabel *m_colorLbl; + QLabel *m_rangeLbl; + QLabel *m_psetLbl; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/ControlEditorDialog.cpp b/src/gui/editors/segment/ControlEditorDialog.cpp new file mode 100644 index 0000000..3c4cc47 --- /dev/null +++ b/src/gui/editors/segment/ControlEditorDialog.cpp @@ -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. +*/ + + +#include "ControlEditorDialog.h" +#include <qlayout.h> +#include <kapplication.h> + +#include <klocale.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Colour.h" +#include "base/Composition.h" +#include "base/ControlParameter.h" +#include "base/Device.h" +#include "base/Event.h" +#include "base/MidiDevice.h" +#include "base/MidiTypes.h" +#include "base/Studio.h" +#include "commands/studio/AddControlParameterCommand.h" +#include "commands/studio/ModifyControlParameterCommand.h" +#include "commands/studio/RemoveControlParameterCommand.h" +#include "ControlParameterEditDialog.h" +#include "ControlParameterItem.h" +#include "document/MultiViewCommandHistory.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include <kaction.h> +#include <kcommand.h> +#include <klistview.h> +#include <kmainwindow.h> +#include <kstdaccel.h> +#include <kstdaction.h> +#include <qcolor.h> +#include <qdialog.h> +#include <qframe.h> +#include <qlabel.h> +#include <qlistview.h> +#include <qpixmap.h> +#include <qptrlist.h> +#include <qpushbutton.h> +#include <qsizepolicy.h> +#include <qstring.h> +#include <qtooltip.h> +#include <qvbox.h> +#include <qwidget.h> + + +namespace Rosegarden +{ + +const QString notShowing(i18n("<not showing>")); + +ControlEditorDialog::ControlEditorDialog(QWidget *parent, + RosegardenGUIDoc *doc, + DeviceId device): + KMainWindow(parent, "controleditordialog"), + m_studio(&doc->getStudio()), + m_doc(doc), + m_device(device), + m_modified(false) +{ + RG_DEBUG << "ControlEditorDialog::ControlEditorDialog: device is " << m_device << endl; + + QVBox* mainFrame = new QVBox(this); + setCentralWidget(mainFrame); + + setCaption(i18n("Manage Control Events")); + + QString deviceName(i18n("<no device>")); + MidiDevice *md = + dynamic_cast<MidiDevice *>(m_studio->getDevice(m_device)); + if (md) + deviceName = strtoqstr(md->getName()); + + // spacing hack! + new QLabel("", mainFrame); + new QLabel(i18n(" Control Events for %1 (device %2)").arg(deviceName). + arg(device), mainFrame); + new QLabel("", mainFrame); + + m_listView = new KListView(mainFrame); + m_listView->addColumn(i18n("Control Event name ")); + m_listView->addColumn(i18n("Control Event type ")); + m_listView->addColumn(i18n("Control Event value ")); + m_listView->addColumn(i18n("Description ")); + m_listView->addColumn(i18n("Min ")); + m_listView->addColumn(i18n("Max ")); + m_listView->addColumn(i18n("Default ")); + m_listView->addColumn(i18n("Color ")); + m_listView->addColumn(i18n("Position on instrument panel")); + + m_listView->setColumnAlignment(0, Qt::AlignLeft); + + // Align remaining columns centrally + for (int i = 1; i < 9; ++i) + m_listView->setColumnAlignment(i, Qt::AlignHCenter); + + m_listView->restoreLayout(kapp->config(), ControlEditorConfigGroup); + + QFrame* btnBox = new QFrame(mainFrame); + + btnBox->setSizePolicy( + QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed)); + + QHBoxLayout* layout = new QHBoxLayout(btnBox, 4, 10); + + m_addButton = new QPushButton(i18n("Add"), btnBox); + m_deleteButton = new QPushButton(i18n("Delete"), btnBox); + + m_closeButton = new QPushButton(i18n("Close"), btnBox); + + QToolTip::add + (m_addButton, + i18n("Add a Control Parameter to the Studio")); + + QToolTip::add + (m_deleteButton, + i18n("Delete a Control Parameter from the Studio")); + + QToolTip::add + (m_closeButton, + i18n("Close the Control Parameter editor")); + + layout->addStretch(10); + layout->addWidget(m_addButton); + layout->addWidget(m_deleteButton); + layout->addSpacing(30); + + layout->addWidget(m_closeButton); + layout->addSpacing(5); + + connect(m_addButton, SIGNAL(released()), + SLOT(slotAdd())); + + connect(m_deleteButton, SIGNAL(released()), + SLOT(slotDelete())); + + setupActions(); + + m_doc->getCommandHistory()->attachView(actionCollection()); + connect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()), + this, SLOT(slotUpdate())); + + connect(m_listView, SIGNAL(doubleClicked(QListViewItem *)), + SLOT(slotEdit(QListViewItem *))); + + // Highlight all columns - enable extended selection mode + // + m_listView->setAllColumnsShowFocus(true); + m_listView->setSelectionMode(QListView::Extended); + + initDialog(); + + setAutoSaveSettings(ControlEditorConfigGroup, true); +} + +ControlEditorDialog::~ControlEditorDialog() +{ + RG_DEBUG << "\n*** ControlEditorDialog::~ControlEditorDialog\n" << endl; + + m_listView->saveLayout(kapp->config(), ControlEditorConfigGroup); + + if (m_doc) + m_doc->getCommandHistory()->detachView(actionCollection()); +} + +void +ControlEditorDialog::initDialog() +{ + RG_DEBUG << "ControlEditorDialog::initDialog" << endl; + slotUpdate(); +} + +void +ControlEditorDialog::slotUpdate() +{ + RG_DEBUG << "ControlEditorDialog::slotUpdate" << endl; + + //QPtrList<QListViewItem> selection = m_listView->selectedItems(); + + MidiDevice *md = + dynamic_cast<MidiDevice *>(m_studio->getDevice(m_device)); + if (!md) + return ; + + ControlList::const_iterator it = md->beginControllers(); + QListViewItem *item; + int i = 0; + + m_listView->clear(); + + for (; it != md->endControllers(); ++it) { + Composition &comp = m_doc->getComposition(); + + QString colour = + strtoqstr(comp.getGeneralColourMap().getNameByIndex(it->getColourIndex())); + + if (colour == "") + colour = i18n("<default>"); + + QString position = QString("%1").arg(it->getIPBPosition()); + if (position.toInt() == -1) + position = notShowing; + + QString value; + value.sprintf("%d (0x%x)", it->getControllerValue(), + it->getControllerValue()); + + if (it->getType() == PitchBend::EventType) { + item = new ControlParameterItem(i++, + m_listView, + strtoqstr(it->getName()), + strtoqstr(it->getType()), + QString("-"), + strtoqstr(it->getDescription()), + QString("%1").arg(it->getMin()), + QString("%1").arg(it->getMax()), + QString("%1").arg(it->getDefault()), + colour, + position); + } else { + item = new ControlParameterItem(i++, + m_listView, + strtoqstr(it->getName()), + strtoqstr(it->getType()), + value, + strtoqstr(it->getDescription()), + QString("%1").arg(it->getMin()), + QString("%1").arg(it->getMax()), + QString("%1").arg(it->getDefault()), + colour, + position); + } + + + // create and set a colour pixmap + // + QPixmap colourPixmap(16, 16); + Colour c = comp.getGeneralColourMap().getColourByIndex(it->getColourIndex()); + colourPixmap.fill(QColor(c.getRed(), c.getGreen(), c.getBlue())); + item->setPixmap(7, colourPixmap); + + m_listView->insertItem(item); + } + + if (m_listView->childCount() == 0) { + QListViewItem *item = new QListViewItem(m_listView, i18n("<none>")); + m_listView->insertItem(item); + + m_listView->setSelectionMode(QListView::NoSelection); + } else { + m_listView->setSelectionMode(QListView::Extended); + } + + +} + +/* +void +ControlEditorDialog::slotEditCopy() +{ + RG_DEBUG << "ControlEditorDialog::slotEditCopy" << endl; +} + +void +ControlEditorDialog::slotEditPaste() +{ + RG_DEBUG << "ControlEditorDialog::slotEditPaste" << endl; +} +*/ + +void +ControlEditorDialog::slotAdd() +{ + RG_DEBUG << "ControlEditorDialog::slotAdd to device " << m_device << endl; + + AddControlParameterCommand *command = + new AddControlParameterCommand(m_studio, m_device, + ControlParameter()); + + addCommandToHistory(command); +} + +void +ControlEditorDialog::slotDelete() +{ + RG_DEBUG << "ControlEditorDialog::slotDelete" << endl; + + if (!m_listView->currentItem()) + return ; + + ControlParameterItem *item = + dynamic_cast<ControlParameterItem*>(m_listView->currentItem()); + + if (item) { + RemoveControlParameterCommand *command = + new RemoveControlParameterCommand(m_studio, m_device, item->getId()); + + addCommandToHistory(command); + } +} + +void +ControlEditorDialog::slotClose() +{ + RG_DEBUG << "ControlEditorDialog::slotClose" << endl; + + if (m_doc) + m_doc->getCommandHistory()->detachView(actionCollection()); + m_doc = 0; + + close(); +} + +void +ControlEditorDialog::setupActions() +{ + KAction* close = KStdAction::close(this, + SLOT(slotClose()), + actionCollection()); + + m_closeButton->setText(close->text()); + connect(m_closeButton, SIGNAL(released()), this, SLOT(slotClose())); + + // some adjustments + new KToolBarPopupAction(i18n("Und&o"), + "undo", + KStdAccel::key(KStdAccel::Undo), + actionCollection(), + KStdAction::stdName(KStdAction::Undo)); + + new KToolBarPopupAction(i18n("Re&do"), + "redo", + KStdAccel::key(KStdAccel::Redo), + actionCollection(), + KStdAction::stdName(KStdAction::Redo)); + + createGUI("controleditor.rc"); +} + +void +ControlEditorDialog::addCommandToHistory(KCommand *command) +{ + getCommandHistory()->addCommand(command); + setModified(false); +} + +MultiViewCommandHistory* +ControlEditorDialog::getCommandHistory() +{ + return m_doc->getCommandHistory(); +} + +void +ControlEditorDialog::setModified(bool modified) +{ + RG_DEBUG << "ControlEditorDialog::setModified(" << modified << ")" << endl; + + if (modified) {} + else {} + + m_modified = modified; +} + +void +ControlEditorDialog::checkModified() +{ + RG_DEBUG << "ControlEditorDialog::checkModified(" << m_modified << ")" + << endl; + +} + +void +ControlEditorDialog::slotEdit() +{} + +void +ControlEditorDialog::slotEdit(QListViewItem *i) +{ + RG_DEBUG << "ControlEditorDialog::slotEdit" << endl; + + ControlParameterItem *item = + dynamic_cast<ControlParameterItem*>(i); + + MidiDevice *md = + dynamic_cast<MidiDevice *>(m_studio->getDevice(m_device)); + + if (item && md) { + ControlParameterEditDialog dialog + (this, + md->getControlParameter(item->getId()), m_doc); + + if (dialog.exec() == QDialog::Accepted) { + ModifyControlParameterCommand *command = + new ModifyControlParameterCommand(m_studio, + m_device, + dialog.getControl(), + item->getId()); + + addCommandToHistory(command); + } + } +} + +void +ControlEditorDialog::closeEvent(QCloseEvent *e) +{ + emit closing(); + KMainWindow::closeEvent(e); +} + +void +ControlEditorDialog::setDocument(RosegardenGUIDoc *doc) +{ + // reset our pointers + m_doc = doc; + m_studio = &doc->getStudio(); + m_modified = false; + + slotUpdate(); +} + +} +#include "ControlEditorDialog.moc" diff --git a/src/gui/editors/segment/ControlEditorDialog.h b/src/gui/editors/segment/ControlEditorDialog.h new file mode 100644 index 0000000..9270d2c --- /dev/null +++ b/src/gui/editors/segment/ControlEditorDialog.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_CONTROLEDITORDIALOG_H_ +#define _RG_CONTROLEDITORDIALOG_H_ + +#include "base/Device.h" +#include "base/MidiDevice.h" +#include <kmainwindow.h> + + +class QWidget; +class QPushButton; +class QListViewItem; +class QCloseEvent; +class KListView; +class KCommand; + + +namespace Rosegarden +{ + +class Studio; +class RosegardenGUIDoc; +class MultiViewCommandHistory; + + +class ControlEditorDialog : public KMainWindow +{ + Q_OBJECT + +public: + ControlEditorDialog(QWidget *parent, + RosegardenGUIDoc *doc, + DeviceId device); + + ~ControlEditorDialog(); + + void initDialog(); + + void addCommandToHistory(KCommand *command); + MultiViewCommandHistory* getCommandHistory(); + + void setModified(bool value); + void checkModified(); + + // reset the document + void setDocument(RosegardenGUIDoc *doc); + + DeviceId getDevice() { return m_device; } + +public slots: + void slotUpdate(); + +/* + void slotEditCopy(); + void slotEditPaste(); +*/ + + void slotAdd(); + void slotDelete(); + void slotClose(); + + void slotEdit(); + void slotEdit(QListViewItem *); + +signals: + void closing(); + + +protected: + virtual void closeEvent(QCloseEvent *); + + void setupActions(); + + //--------------- Data members --------------------------------- + Studio *m_studio; + RosegardenGUIDoc *m_doc; + DeviceId m_device; + + QPushButton *m_closeButton; + + QPushButton *m_copyButton; + QPushButton *m_pasteButton; + + QPushButton *m_addButton; + QPushButton *m_deleteButton; + + KListView *m_listView; + + bool m_modified; + + ControlList m_clipboard; // local clipboard only + +}; + + +} + +#endif diff --git a/src/gui/editors/segment/ControlParameterEditDialog.cpp b/src/gui/editors/segment/ControlParameterEditDialog.cpp new file mode 100644 index 0000000..bc779f5 --- /dev/null +++ b/src/gui/editors/segment/ControlParameterEditDialog.cpp @@ -0,0 +1,325 @@ +/* -*- 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 "ControlParameterEditDialog.h" +#include <qlayout.h> + +#include <klocale.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Colour.h" +#include "base/ColourMap.h" +#include "base/ControlParameter.h" +#include "base/Event.h" +#include "base/MidiTypes.h" +#include "document/RosegardenGUIDoc.h" +#include <kcombobox.h> +#include <kdialogbase.h> +#include <qcolor.h> +#include <qframe.h> +#include <qgroupbox.h> +#include <qlabel.h> +#include <qlineedit.h> +#include <qpixmap.h> +#include <qspinbox.h> +#include <qstring.h> +#include <qvbox.h> +#include <qwidget.h> + + +namespace Rosegarden +{ + +const QString notShowing(i18n("<not showing>")); + +ControlParameterEditDialog::ControlParameterEditDialog( + QWidget *parent, + ControlParameter *control, + RosegardenGUIDoc *doc): + KDialogBase(parent, 0, true, + i18n("Edit Control Parameter"), Ok | Cancel), + m_doc(doc), + m_control(control) +{ + m_dialogControl = *control; // copy in the ControlParameter + + QVBox *vbox = makeVBoxMainWidget(); + + QGroupBox *groupBox = new QGroupBox + (1, Horizontal, i18n("Control Event Properties"), vbox); + + QFrame *frame = new QFrame(groupBox); + + QGridLayout *layout = new QGridLayout(frame, 4, 3, 10, 5); + + layout->addWidget(new QLabel(i18n("Name:"), frame), 0, 0); + m_nameEdit = new QLineEdit(frame); + layout->addWidget(m_nameEdit, 0, 1); + + layout->addWidget(new QLabel(i18n("Type:"), frame), 1, 0); + m_typeCombo = new KComboBox(frame); + layout->addMultiCellWidget(m_typeCombo, 1, 1, 1, 2); + + layout->addWidget(new QLabel(i18n("Description:"), frame), 2, 0); + m_description = new QLineEdit(frame); + layout->addMultiCellWidget(m_description, 2, 2, 1, 2); + + // hex value alongside decimal value + m_hexValue = new QLabel(frame); + layout->addWidget(m_hexValue, 3, 1); + + layout->addWidget(new QLabel(i18n("Control Event value:"), frame), 3, 0); + m_controllerBox = new QSpinBox(frame); + layout->addWidget(m_controllerBox, 3, 2); + + layout->addWidget(new QLabel(i18n("Minimum value:"), frame), 4, 0); + m_minBox = new QSpinBox(frame); + layout->addMultiCellWidget(m_minBox, 4, 4, 1, 2); + + layout->addWidget(new QLabel(i18n("Maximum value:"), frame), 5, 0); + m_maxBox = new QSpinBox(frame); + layout->addMultiCellWidget(m_maxBox, 5, 5, 1, 2); + + layout->addWidget(new QLabel(i18n("Default value:"), frame), 6, 0); + m_defaultBox = new QSpinBox(frame); + layout->addMultiCellWidget(m_defaultBox, 6, 6, 1, 2); + + layout->addWidget(new QLabel(i18n("Color:"), frame), 7, 0); + m_colourCombo = new KComboBox(frame); + layout->addMultiCellWidget(m_colourCombo, 7, 7, 1, 2); + + layout->addWidget(new QLabel(i18n("Instrument Parameter Box position:"), frame), 8, 0); + m_ipbPosition = new KComboBox(frame); + layout->addMultiCellWidget(m_ipbPosition, 8, 8, 1, 2); + + connect(m_nameEdit, SIGNAL(textChanged(const QString&)), + SLOT(slotNameChanged(const QString&))); + + connect(m_typeCombo, SIGNAL(activated(int)), + SLOT(slotTypeChanged(int))); + + connect(m_description, SIGNAL(textChanged(const QString&)), + SLOT(slotDescriptionChanged(const QString &))); + + connect(m_controllerBox, SIGNAL(valueChanged(int)), + SLOT(slotControllerChanged(int))); + + connect(m_minBox, SIGNAL(valueChanged(int)), + SLOT(slotMinChanged(int))); + + connect(m_maxBox, SIGNAL(valueChanged(int)), + SLOT(slotMaxChanged(int))); + + connect(m_defaultBox, SIGNAL(valueChanged(int)), + SLOT(slotDefaultChanged(int))); + + connect(m_colourCombo, SIGNAL(activated(int)), + SLOT(slotColourChanged(int))); + + connect(m_ipbPosition, SIGNAL(activated(int)), + SLOT(slotIPBPositionChanged(int))); + + //m_nameEdit->selectAll(); + //m_description->selectAll(); + + // set limits + m_controllerBox->setMinValue(0); + m_controllerBox->setMaxValue(127); + + m_minBox->setMinValue(INT_MIN); + m_minBox->setMaxValue(INT_MAX); + + m_maxBox->setMinValue(INT_MIN); + m_maxBox->setMaxValue(INT_MAX); + + m_defaultBox->setMinValue(INT_MIN); + m_defaultBox->setMaxValue(INT_MAX); + + // populate combos + m_typeCombo->insertItem(strtoqstr(Controller::EventType)); + m_typeCombo->insertItem(strtoqstr(PitchBend::EventType)); + /* + m_typeCombo->insertItem(strtoqstr(KeyPressure::EventType)); + m_typeCombo->insertItem(strtoqstr(ChannelPressure::EventType)); + */ + + // Populate colour combo + // + // + ColourMap &colourMap = m_doc->getComposition().getGeneralColourMap(); + RCMap::const_iterator it; + QPixmap colourPixmap(16, 16); + + for (it = colourMap.begin(); it != colourMap.end(); ++it) { + Colour c = it->second.first; + colourPixmap.fill(QColor(c.getRed(), c.getGreen(), c.getBlue())); + m_colourCombo->insertItem(colourPixmap, strtoqstr(it->second.second)); + } + + // Populate IPB position combo + // + m_ipbPosition->insertItem(notShowing); + for (unsigned int i = 0; i < 32; i++) + m_ipbPosition->insertItem(QString("%1").arg(i)); + + if (m_control->getType() == Controller::EventType) + m_typeCombo->setCurrentItem(0); + else if (m_control->getType() == PitchBend::EventType) + m_typeCombo->setCurrentItem(1); + /* + else if (m_control->getType() == KeyPressure::EventType) + m_typeCombo->setCurrentItem(2); + else if (m_control->getType() == ChannelPressure::EventType) + m_typeCombo->setCurrentItem(3); + */ + + populate(); +} + +void +ControlParameterEditDialog::populate() +{ + m_nameEdit->setText(strtoqstr(m_control->getName())); + + m_description->setText(strtoqstr(m_control->getDescription())); + m_controllerBox->setValue(int(m_control->getControllerValue())); + + QString hexValue; + hexValue.sprintf("(0x%x)", m_control->getControllerValue()); + m_hexValue->setText(hexValue); + + m_minBox->setValue(m_control->getMin()); + m_maxBox->setValue(m_control->getMax()); + m_defaultBox->setValue(m_control->getDefault()); + + int pos = 0, setItem = 0; + ColourMap &colourMap = m_doc->getComposition().getGeneralColourMap(); + RCMap::const_iterator it; + for (it = colourMap.begin(); it != colourMap.end(); ++it) + if (m_control->getColourIndex() == it->first) + setItem = pos++; + + m_colourCombo->setCurrentItem(setItem); + + // set combo position + m_ipbPosition->setCurrentItem(m_control->getIPBPosition() + 1); + + // If the type has changed and there are no defaults then we have to + // supply some. + // + if (qstrtostr(m_typeCombo->currentText()) == PitchBend::EventType || + qstrtostr(m_typeCombo->currentText()) == KeyPressure::EventType || + qstrtostr(m_typeCombo->currentText()) == ChannelPressure::EventType) { + m_controllerBox->setEnabled(false); + m_ipbPosition->setEnabled(false); + m_colourCombo->setEnabled(false); + m_hexValue->setEnabled(false); + m_minBox->setEnabled(false); + m_maxBox->setEnabled(false); + m_defaultBox->setEnabled(false); + } else if (qstrtostr(m_typeCombo->currentText()) == Controller::EventType) { + m_controllerBox->setEnabled(true); + m_ipbPosition->setEnabled(true); + m_colourCombo->setEnabled(true); + m_hexValue->setEnabled(true); + m_minBox->setEnabled(true); + m_maxBox->setEnabled(true); + m_defaultBox->setEnabled(true); + } + +} + +void +ControlParameterEditDialog::slotNameChanged(const QString &str) +{ + RG_DEBUG << "ControlParameterEditDialog::slotNameChanged" << endl; + m_dialogControl.setName(qstrtostr(str)); +} + +void +ControlParameterEditDialog::slotTypeChanged(int value) +{ + RG_DEBUG << "ControlParameterEditDialog::slotTypeChanged" << endl; + m_dialogControl.setType(qstrtostr(m_typeCombo->text(value))); + + populate(); +} + +void +ControlParameterEditDialog::slotDescriptionChanged(const QString &str) +{ + RG_DEBUG << "ControlParameterEditDialog::slotDescriptionChanged" << endl; + m_dialogControl.setDescription(qstrtostr(str)); +} + +void +ControlParameterEditDialog::slotControllerChanged(int value) +{ + RG_DEBUG << "ControlParameterEditDialog::slotControllerChanged" << endl; + m_dialogControl.setControllerValue(value); + + // set hex value + QString hexValue; + hexValue.sprintf("(0x%x)", value); + m_hexValue->setText(hexValue); +} + +void +ControlParameterEditDialog::slotMinChanged(int value) +{ + RG_DEBUG << "ControlParameterEditDialog::slotMinChanged" << endl; + m_dialogControl.setMin(value); +} + +void +ControlParameterEditDialog::slotMaxChanged(int value) +{ + RG_DEBUG << "ControlParameterEditDialog::slotMaxChanged" << endl; + m_dialogControl.setMax(value); +} + +void +ControlParameterEditDialog::slotDefaultChanged(int value) +{ + RG_DEBUG << "ControlParameterEditDialog::slotDefaultChanged" << endl; + m_dialogControl.setDefault(value); +} + +void +ControlParameterEditDialog::slotColourChanged(int value) +{ + RG_DEBUG << "ControlParameterEditDialog::slotColourChanged" << endl; + m_dialogControl.setColourIndex(value); +} + +void +ControlParameterEditDialog::slotIPBPositionChanged(int value) +{ + RG_DEBUG << "ControlParameterEditDialog::slotIPBPositionChanged" << endl; + m_dialogControl.setIPBPosition(value - 1); +} + +} +#include "ControlParameterEditDialog.moc" diff --git a/src/gui/editors/segment/ControlParameterEditDialog.h b/src/gui/editors/segment/ControlParameterEditDialog.h new file mode 100644 index 0000000..b9f4606 --- /dev/null +++ b/src/gui/editors/segment/ControlParameterEditDialog.h @@ -0,0 +1,92 @@ + +/* -*- 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_CONTROLPARAMETEREDITDIALOG_H_ +#define _RG_CONTROLPARAMETEREDITDIALOG_H_ + +#include "base/ControlParameter.h" +#include <kdialogbase.h> + + +class QWidget; +class QString; +class QSpinBox; +class QLineEdit; +class QLabel; +class KComboBox; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +class ControlParameterEditDialog : public KDialogBase +{ + Q_OBJECT +public: + ControlParameterEditDialog(QWidget *parent, + ControlParameter *control, + RosegardenGUIDoc *doc); + + ControlParameter& getControl() { return m_dialogControl; } + +public slots: + + void slotNameChanged(const QString &); + void slotTypeChanged(int); + void slotDescriptionChanged(const QString &); + void slotControllerChanged(int); + void slotMinChanged(int); + void slotMaxChanged(int); + void slotDefaultChanged(int); + void slotColourChanged(int); + void slotIPBPositionChanged(int); + +protected: + void populate(); // populate the dialog + + RosegardenGUIDoc *m_doc; + ControlParameter *m_control; + ControlParameter m_dialogControl; + + QLineEdit *m_nameEdit; + KComboBox *m_typeCombo; + QLineEdit *m_description; + QSpinBox *m_controllerBox; + QSpinBox *m_minBox; + QSpinBox *m_maxBox; + QSpinBox *m_defaultBox; + KComboBox *m_colourCombo; + KComboBox *m_ipbPosition; + QLabel *m_hexValue; +}; + + + +} + +#endif diff --git a/src/gui/editors/segment/ControlParameterItem.cpp b/src/gui/editors/segment/ControlParameterItem.cpp new file mode 100644 index 0000000..beb0922 --- /dev/null +++ b/src/gui/editors/segment/ControlParameterItem.cpp @@ -0,0 +1,34 @@ +/* -*- 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 "ControlParameterItem.h" + +#include <qlistview.h> +#include <qstring.h> + + +namespace Rosegarden +{ +} diff --git a/src/gui/editors/segment/ControlParameterItem.h b/src/gui/editors/segment/ControlParameterItem.h new file mode 100644 index 0000000..6746ca2 --- /dev/null +++ b/src/gui/editors/segment/ControlParameterItem.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_CONTROLPARAMETERITEM_H_ +#define _RG_CONTROLPARAMETERITEM_H_ + +#include <qstring.h> +#include <klistview.h> + + +namespace Rosegarden +{ + + +class ControlParameterItem : public KListViewItem +{ +public: + ControlParameterItem(int id, + QListView *parent, + QString str1, + QString str2, + QString str3, + QString str4, + QString str5, + QString str6, + QString str7, + QString str8, + QString str9): + KListViewItem(parent, str1, str2, str3, str4, str5, str6, str7, str8), + m_id(id) { setText(8, str9); } + + int getId() const { return m_id; } + +protected: + + int m_id; + QString m_string9; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/MarkerEditor.cpp b/src/gui/editors/segment/MarkerEditor.cpp new file mode 100644 index 0000000..61caaa7 --- /dev/null +++ b/src/gui/editors/segment/MarkerEditor.cpp @@ -0,0 +1,594 @@ +/* -*- 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 "MarkerEditor.h" +#include "MarkerEditorViewItem.h" +#include <qlayout.h> +#include <kapplication.h> + +#include <klocale.h> +#include <kstddirs.h> +#include <kstdaccel.h> +#include <kconfig.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Composition.h" +#include "base/Marker.h" +#include "base/RealTime.h" +#include "commands/edit/AddMarkerCommand.h" +#include "commands/edit/ModifyMarkerCommand.h" +#include "commands/edit/RemoveMarkerCommand.h" +#include "document/MultiViewCommandHistory.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "gui/dialogs/MarkerModifyDialog.h" +#include <kaction.h> +#include <kcommand.h> +#include <kglobal.h> +#include <klistview.h> +#include <kmainwindow.h> +#include <kstdaccel.h> +#include <kstdaction.h> +#include <qaccel.h> +#include <qdialog.h> +#include <qframe.h> +#include <qgroupbox.h> +#include <qiconset.h> +#include <qlabel.h> +#include <qlistview.h> +#include <qptrlist.h> +#include <qpushbutton.h> +#include <qsizepolicy.h> +#include <qstring.h> +#include <qtooltip.h> +#include <qvbox.h> +#include <qwidget.h> +#include <qcanvas.h> + + +namespace Rosegarden +{ + +MarkerEditor::MarkerEditor(QWidget *parent, + RosegardenGUIDoc *doc): + KMainWindow(parent, "markereditordialog"), + m_doc(doc), + m_modified(false) +{ + QVBox* mainFrame = new QVBox(this); + setCentralWidget(mainFrame); + + setCaption(i18n("Manage Markers")); + + m_listView = new KListView(mainFrame); + m_listView->addColumn(i18n("Marker time ")); + m_listView->addColumn(i18n("Marker text ")); + m_listView->addColumn(i18n("Marker description ")); + + // Align centrally + for (int i = 0; i < 3; ++i) + m_listView->setColumnAlignment(i, Qt::AlignHCenter); + + QGroupBox *posGroup = new QGroupBox(2, Horizontal, + i18n("Pointer position"), mainFrame); + + new QLabel(i18n("Absolute time:"), posGroup); + m_absoluteTime = new QLabel(posGroup); + + new QLabel(i18n("Real time:"), posGroup); + m_realTime = new QLabel(posGroup); + + new QLabel(i18n("In measure:"), posGroup); + m_barTime = new QLabel(posGroup); + + QFrame* btnBox = new QFrame(mainFrame); + + btnBox->setSizePolicy( + QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed)); + + QHBoxLayout* layout = new QHBoxLayout(btnBox, 4, 10); + + m_addButton = new QPushButton(i18n("Add"), btnBox); + m_deleteButton = new QPushButton(i18n("Delete"), btnBox); + m_deleteAllButton = new QPushButton(i18n("Delete All"), btnBox); + + m_closeButton = new QPushButton(i18n("Close"), btnBox); + + QToolTip::add + (m_addButton, + i18n("Add a Marker")); + + QToolTip::add + (m_deleteButton, + i18n("Delete a Marker")); + + QToolTip::add + (m_deleteAllButton, + i18n("Delete All Markers")); + + QToolTip::add + (m_closeButton, + i18n("Close the Marker Editor")); + + layout->addStretch(10); + layout->addWidget(m_addButton); + layout->addWidget(m_deleteButton); + layout->addWidget(m_deleteAllButton); + layout->addSpacing(30); + + layout->addWidget(m_closeButton); + layout->addSpacing(5); + + connect(m_addButton, SIGNAL(released()), + SLOT(slotAdd())); + + connect(m_deleteButton, SIGNAL(released()), + SLOT(slotDelete())); + + connect(m_closeButton, SIGNAL(released()), + SLOT(slotClose())); + + connect(m_deleteAllButton, SIGNAL(released()), + SLOT(slotDeleteAll())); + + setupActions(); + + m_doc->getCommandHistory()->attachView(actionCollection()); + connect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()), + this, SLOT(slotUpdate())); + + connect(m_listView, SIGNAL(doubleClicked(QListViewItem *)), + SLOT(slotEdit(QListViewItem *))); + + connect(m_listView, SIGNAL(pressed(QListViewItem *)), + this, SLOT(slotItemClicked(QListViewItem *))); + + // Highlight all columns - enable extended selection mode + // + m_listView->setAllColumnsShowFocus(true); + m_listView->setSelectionMode(QListView::Extended); + m_listView->setItemsRenameable(true); + + initDialog(); + + setAutoSaveSettings(MarkerEditorConfigGroup, true); + + m_accelerators = new QAccel(this); +} + +void +MarkerEditor::updatePosition() +{ + timeT pos = m_doc->getComposition().getPosition(); + m_absoluteTime->setText(QString("%1").arg(pos)); + + RealTime rT = m_doc->getComposition().getElapsedRealTime(pos); + long hours = rT.sec / (60 * 60); + long mins = rT.sec / 60; + long secs = rT.sec; + long msecs = rT.msec(); + + QString realTime, secsStr; + if (hours) + realTime += QString("%1h ").arg(hours); + if (mins) + realTime += QString("%1m ").arg(mins); + secsStr.sprintf("%ld.%03lds", secs, msecs); + realTime += secsStr; + + // only update if we need to to try and avoid flickering + if (m_realTime->text() != realTime) + m_realTime->setText(realTime); + + QString barTime = + QString("%1").arg(m_doc->getComposition().getBarNumber(pos) + 1); + + // again only update if needed + if (m_barTime->text() != barTime) + m_barTime->setText(barTime); + + /* + // Don't allow us to add another marker if there's already one + // at the current position. + // + if (m_doc->getComposition(). + isMarkerAtPosition(m_doc->getComposition().getPosition())) + m_addButton->setEnabled(false); + else + m_addButton->setEnabled(true); + */ +} + +MarkerEditor::~MarkerEditor() +{ + RG_DEBUG << "MarkerEditor::~MarkerEditor" << endl; + + m_listView->saveLayout(kapp->config(), MarkerEditorConfigGroup); + + if (m_doc) + m_doc->getCommandHistory()->detachView(actionCollection()); +} + +void +MarkerEditor::initDialog() +{ + RG_DEBUG << "MarkerEditor::initDialog" << endl; + slotUpdate(); +} + +void +MarkerEditor::slotUpdate() +{ + RG_DEBUG << "MarkerEditor::slotUpdate" << endl; + + //QPtrList<QListViewItem> selection = m_listView->selectedItems(); + + MarkerEditorViewItem *item; + + m_listView->clear(); + + Composition::markercontainer markers = + m_doc->getComposition().getMarkers(); + + Composition::markerconstiterator it; + + kapp->config()->setGroup(MarkerEditorConfigGroup); + int timeMode = kapp->config()->readNumEntry("timemode", 0); + + for (it = markers.begin(); it != markers.end(); ++it) { + QString timeString = makeTimeString((*it)->getTime(), timeMode); + + item = new + MarkerEditorViewItem(m_listView, + (*it)->getID(), + timeString, + strtoqstr((*it)->getName()), + strtoqstr((*it)->getDescription())); + + // Set this for the MarkerEditor + // + item->setRawTime((*it)->getTime()); + + m_listView->insertItem(item); + } + + if (m_listView->childCount() == 0) { + QListViewItem *item = + new MarkerEditorViewItem(m_listView, 0, i18n("<none>")); + ((MarkerEditorViewItem *)item)->setFake(true); + m_listView->insertItem(item); + + m_listView->setSelectionMode(QListView::NoSelection); + } else { + m_listView->setSelectionMode(QListView::Extended); + } + + updatePosition(); + +} + +void +MarkerEditor::slotDeleteAll() +{ + RG_DEBUG << "MarkerEditor::slotDeleteAll" << endl; + KMacroCommand *command = new KMacroCommand(i18n("Remove all markers")); + + QListViewItem *item = m_listView->firstChild(); + + do { + MarkerEditorViewItem *ei = + dynamic_cast<MarkerEditorViewItem *>(item); + if (!ei || ei->isFake()) + continue; + + RemoveMarkerCommand *rc = + new RemoveMarkerCommand(&m_doc->getComposition(), + ei->getID(), + ei->getRawTime(), + qstrtostr(item->text(1)), + qstrtostr(item->text(2))); + command->addCommand(rc); + } while ((item = item->nextSibling())); + + addCommandToHistory(command); +} + +void +MarkerEditor::slotAdd() +{ + RG_DEBUG << "MarkerEditor::slotAdd" << endl; + + AddMarkerCommand *command = + new AddMarkerCommand(&m_doc->getComposition(), + m_doc->getComposition().getPosition(), + std::string("new marker"), + std::string("no description")); + + addCommandToHistory(command); +} + +void +MarkerEditor::slotDelete() +{ + RG_DEBUG << "MarkerEditor::slotDelete" << endl; + QListViewItem *item = m_listView->currentItem(); + + MarkerEditorViewItem *ei = + dynamic_cast<MarkerEditorViewItem *>(item); + + if (!ei || ei->isFake()) + return ; + + RemoveMarkerCommand *command = + new RemoveMarkerCommand(&m_doc->getComposition(), + ei->getID(), + ei->getRawTime(), + qstrtostr(item->text(1)), + qstrtostr(item->text(2))); + + addCommandToHistory(command); + +} + +void +MarkerEditor::slotClose() +{ + RG_DEBUG << "MarkerEditor::slotClose" << endl; + + if (m_doc) + m_doc->getCommandHistory()->detachView(actionCollection()); + m_doc = 0; + + close(); +} + +void +MarkerEditor::setupActions() +{ + KAction* close = KStdAction::close(this, + SLOT(slotClose()), + actionCollection()); + + m_closeButton->setText(close->text()); + connect(m_closeButton, SIGNAL(released()), this, SLOT(slotClose())); + + // some adjustments + new KToolBarPopupAction(i18n("Und&o"), + "undo", + KStdAccel::key(KStdAccel::Undo), + actionCollection(), + KStdAction::stdName(KStdAction::Undo)); + + new KToolBarPopupAction(i18n("Re&do"), + "redo", + KStdAccel::key(KStdAccel::Redo), + actionCollection(), + KStdAction::stdName(KStdAction::Redo)); + + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + kapp->config()->setGroup(MarkerEditorConfigGroup); + int timeMode = kapp->config()->readNumEntry("timemode", 0); + + KRadioAction *action; + + QCanvasPixmap pixmap(pixmapDir + "/toolbar/time-musical.png"); + QIconSet icon(pixmap); + + action = new KRadioAction(i18n("&Musical Times"), icon, 0, this, + SLOT(slotMusicalTime()), + actionCollection(), "time_musical"); + action->setExclusiveGroup("timeMode"); + if (timeMode == 0) + action->setChecked(true); + + pixmap.load(pixmapDir + "/toolbar/time-real.png"); + icon = QIconSet(pixmap); + + action = new KRadioAction(i18n("&Real Times"), icon, 0, this, + SLOT(slotRealTime()), + actionCollection(), "time_real"); + action->setExclusiveGroup("timeMode"); + if (timeMode == 1) + action->setChecked(true); + + pixmap.load(pixmapDir + "/toolbar/time-raw.png"); + icon = QIconSet(pixmap); + + action = new KRadioAction(i18n("Ra&w Times"), icon, 0, this, + SLOT(slotRawTime()), + actionCollection(), "time_raw"); + action->setExclusiveGroup("timeMode"); + if (timeMode == 2) + action->setChecked(true); + + createGUI("markereditor.rc"); +} + +void +MarkerEditor::addCommandToHistory(KCommand *command) +{ + getCommandHistory()->addCommand(command); + setModified(false); +} + +MultiViewCommandHistory* +MarkerEditor::getCommandHistory() +{ + return m_doc->getCommandHistory(); +} + +void +MarkerEditor::setModified(bool modified) +{ + RG_DEBUG << "MarkerEditor::setModified(" << modified << ")" << endl; + + if (modified) {} + else {} + + m_modified = modified; +} + +void +MarkerEditor::checkModified() +{ + RG_DEBUG << "MarkerEditor::checkModified(" << m_modified << ")" + << endl; + +} + +void +MarkerEditor::slotEdit(QListViewItem *i) +{ + RG_DEBUG << "MarkerEditor::slotEdit" << endl; + + if (m_listView->selectionMode() == QListView::NoSelection) { + // The marker list is empty, so we shouldn't allow editing the + // <none> placeholder + return ; + } + + // Need to get the raw time from the ListViewItem + // + MarkerEditorViewItem *item = + dynamic_cast<MarkerEditorViewItem*>(i); + + if (!item || item->isFake()) + return ; + + MarkerModifyDialog dialog(this, + &m_doc->getComposition(), + item->getRawTime(), + item->text(1), + item->text(2)); + + if (dialog.exec() == QDialog::Accepted) { + ModifyMarkerCommand *command = + new ModifyMarkerCommand(&m_doc->getComposition(), + item->getID(), + dialog.getOriginalTime(), + dialog.getTime(), + qstrtostr(dialog.getName()), + qstrtostr(dialog.getDescription())); + + addCommandToHistory(command); + } + + +} + +void +MarkerEditor::closeEvent(QCloseEvent *e) +{ + emit closing(); + KMainWindow::closeEvent(e); +} + +void +MarkerEditor::setDocument(RosegardenGUIDoc *doc) +{ + // reset our pointers + m_doc = doc; + m_modified = false; + + slotUpdate(); +} + +void +MarkerEditor::slotItemClicked(QListViewItem *item) +{ + RG_DEBUG << "MarkerEditor::slotItemClicked" << endl; + MarkerEditorViewItem *ei = + dynamic_cast<MarkerEditorViewItem *>(item); + + if (ei && !ei->isFake()) { + RG_DEBUG << "MarkerEditor::slotItemClicked - " + << "jump to marker at " << ei->getRawTime() << endl; + + emit jumpToMarker(timeT(ei->getRawTime())); + } +} + +QString +MarkerEditor::makeTimeString(timeT time, int timeMode) +{ + switch (timeMode) { + + case 0: // musical time + { + int bar, beat, fraction, remainder; + m_doc->getComposition().getMusicalTimeForAbsoluteTime + (time, bar, beat, fraction, remainder); + ++bar; + return QString("%1%2%3-%4%5-%6%7-%8%9 ") + .arg(bar / 100) + .arg((bar % 100) / 10) + .arg(bar % 10) + .arg(beat / 10) + .arg(beat % 10) + .arg(fraction / 10) + .arg(fraction % 10) + .arg(remainder / 10) + .arg(remainder % 10); + } + + case 1: // real time + { + RealTime rt = + m_doc->getComposition().getElapsedRealTime(time); + // return QString("%1 ").arg(rt.toString().c_str()); + return QString("%1 ").arg(rt.toText().c_str()); + } + + default: + return QString("%1 ").arg(time); + } +} + +void +MarkerEditor::slotMusicalTime() +{ + kapp->config()->setGroup(MarkerEditorConfigGroup); + kapp->config()->writeEntry("timemode", 0); + slotUpdate(); +} + +void +MarkerEditor::slotRealTime() +{ + kapp->config()->setGroup(MarkerEditorConfigGroup); + kapp->config()->writeEntry("timemode", 1); + slotUpdate(); +} + +void +MarkerEditor::slotRawTime() +{ + kapp->config()->setGroup(MarkerEditorConfigGroup); + kapp->config()->writeEntry("timemode", 2); + slotUpdate(); +} + +} +#include "MarkerEditor.moc" diff --git a/src/gui/editors/segment/MarkerEditor.h b/src/gui/editors/segment/MarkerEditor.h new file mode 100644 index 0000000..d3c9ac7 --- /dev/null +++ b/src/gui/editors/segment/MarkerEditor.h @@ -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. +*/ + +#ifndef _RG_MARKEREDITOR_H_ +#define _RG_MARKEREDITOR_H_ + +#include <kmainwindow.h> +#include <qstring.h> +#include "base/Event.h" + + +class QWidget; +class QPushButton; +class QListViewItem; +class QLabel; +class QCloseEvent; +class QAccel; +class KListView; +class KCommand; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class MultiViewCommandHistory; + + +class MarkerEditor : public KMainWindow +{ + Q_OBJECT + +public: + MarkerEditor(QWidget *parent, + RosegardenGUIDoc *doc); + ~MarkerEditor(); + + void initDialog(); + + void addCommandToHistory(KCommand *command); + MultiViewCommandHistory* getCommandHistory(); + + void setModified(bool value); + void checkModified(); + + // reset the document + void setDocument(RosegardenGUIDoc *doc); + + // update pointer position + void updatePosition(); + + QAccel* getAccelerators() { return m_accelerators; } + +public slots: + void slotUpdate(); + + void slotAdd(); + void slotDelete(); + void slotDeleteAll(); + void slotClose(); + void slotEdit(QListViewItem *); + void slotItemClicked(QListViewItem *); + + void slotMusicalTime(); + void slotRealTime(); + void slotRawTime(); + +signals: + void closing(); + void jumpToMarker(timeT); + +protected: + virtual void closeEvent(QCloseEvent *); + + void setupActions(); + QString makeTimeString(timeT time, int timeMode); + + //--------------- Data members --------------------------------- + RosegardenGUIDoc *m_doc; + + QLabel *m_absoluteTime; + QLabel *m_realTime; + QLabel *m_barTime; + + QPushButton *m_closeButton; + + + QPushButton *m_addButton; + QPushButton *m_deleteButton; + QPushButton *m_deleteAllButton; + + KListView *m_listView; + + bool m_modified; + + QAccel *m_accelerators; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/MarkerEditorViewItem.cpp b/src/gui/editors/segment/MarkerEditorViewItem.cpp new file mode 100644 index 0000000..9ff2bda --- /dev/null +++ b/src/gui/editors/segment/MarkerEditorViewItem.cpp @@ -0,0 +1,51 @@ +/* -*- 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 "MarkerEditorViewItem.h" + +namespace Rosegarden { + +int +MarkerEditorViewItem::compare(QListViewItem * i, int col, bool ascending) const +{ + MarkerEditorViewItem *ei = + dynamic_cast<MarkerEditorViewItem *>(i); + + if (!ei) return KListViewItem::compare(i, col, ascending); + + // Raw time sorting on time column + // + if (col == 0) { + + if (m_rawTime < ei->getRawTime()) return -1; + else if (ei->getRawTime() < m_rawTime) return 1; + else return 0; + + } else { + return KListViewItem::compare(i, col, ascending); + } +} + +} + diff --git a/src/gui/editors/segment/MarkerEditorViewItem.h b/src/gui/editors/segment/MarkerEditorViewItem.h new file mode 100644 index 0000000..010d227 --- /dev/null +++ b/src/gui/editors/segment/MarkerEditorViewItem.h @@ -0,0 +1,70 @@ +/* -*- 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_MARKEREDITORVIEWITEM_H_ +#define _RG_MARKEREDITORVIEWITEM_H_ + +#include <klistview.h> + +#include "base/Event.h" + +namespace Rosegarden { + + +class MarkerEditorViewItem : public KListViewItem +{ +public: + MarkerEditorViewItem(QListView * parent, int id, + QString label1, + QString label2 = QString::null, + QString label3 = QString::null, + QString label4 = QString::null, + QString label5 = QString::null, + QString label6 = QString::null, + QString label7 = QString::null, + QString label8 = QString::null): + KListViewItem(parent, label1, label2, label3, label4, + label5, label6, label7, label8), + m_rawTime(0), m_fake(false), m_id(id) { ; } + + virtual int compare(QListViewItem * i, int col, bool ascending) const; + + void setRawTime(Rosegarden::timeT rawTime) { m_rawTime = rawTime; } + Rosegarden::timeT getRawTime() const { return m_rawTime; } + + void setFake(bool fake) { m_fake = true; } + bool isFake() const { return m_fake; } + + int getID() const { return m_id; } + +protected: + Rosegarden::timeT m_rawTime; + bool m_fake; + int m_id; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/PlayList.cpp b/src/gui/editors/segment/PlayList.cpp new file mode 100644 index 0000000..bfc795c --- /dev/null +++ b/src/gui/editors/segment/PlayList.cpp @@ -0,0 +1,254 @@ +/* -*- 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 "PlayList.h" +#include "PlayListView.h" +#include "PlayListViewItem.h" +#include "document/ConfigGroups.h" +#include <qlayout.h> + +#include <klocale.h> +#include <kconfig.h> +#include <kfiledialog.h> +#include <kglobal.h> +#include <kurl.h> +#include <qframe.h> +#include <qpushbutton.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qstrlist.h> +#include <qvbox.h> +#include <qwidget.h> +#include <qdragobject.h> + + +namespace Rosegarden +{ + +PlayList::PlayList(QWidget *parent, const char *name) + : QVBox(parent, name), + m_listView(new PlayListView(this)), + m_buttonBar(new QFrame(this)), + m_barLayout(new QHBoxLayout(m_buttonBar)), + m_playButton(0), + m_moveUpButton(0), + m_moveDownButton(0), + m_deleteButton(0), + m_clearButton(0) +{ + m_openButton = new QPushButton(m_buttonBar); + m_playButton = new QPushButton(m_buttonBar); + m_moveUpButton = new QPushButton(m_buttonBar); + m_moveDownButton = new QPushButton(m_buttonBar); + m_deleteButton = new QPushButton(m_buttonBar); + m_clearButton = new QPushButton(m_buttonBar); + m_barLayout->addWidget(m_openButton); + m_barLayout->addWidget(m_playButton); + m_barLayout->addWidget(m_moveUpButton); + m_barLayout->addWidget(m_moveDownButton); + m_barLayout->addWidget(m_deleteButton); + m_barLayout->addWidget(m_clearButton); + m_barLayout->addStretch(); + + + m_openButton ->setText(i18n("Add...")); + m_playButton ->setText(i18n("Play")); + m_moveUpButton ->setText(i18n("Move Up")); + m_moveDownButton->setText(i18n("Move Down")); + m_deleteButton ->setText(i18n("Delete")); + m_clearButton ->setText(i18n("Clear")); + + connect(m_openButton, SIGNAL(clicked()), + SLOT(slotOpenFiles())); + + connect(m_playButton, SIGNAL(clicked()), + SLOT(slotPlay())); + + connect(m_deleteButton, SIGNAL(clicked()), + SLOT(slotDeleteCurrent())); + + connect(m_clearButton, SIGNAL(clicked()), + SLOT(slotClear())); + + connect(m_moveUpButton, SIGNAL(clicked()), + SLOT(slotMoveUp())); + + connect(m_moveDownButton, SIGNAL(clicked()), + SLOT(slotMoveDown())); + + connect(m_listView, SIGNAL(currentChanged(QListViewItem*)), + SLOT(slotCurrentItemChanged(QListViewItem*))); + + connect(m_listView, SIGNAL(dropped(QDropEvent*, QListViewItem*)), + SLOT(slotDropped(QDropEvent*, QListViewItem*))); + + restore(); + + enableButtons(0); + +} + +PlayList::~PlayList() +{ + save(); +} + +void PlayList::slotOpenFiles() +{ + KURL::List kurlList = + KFileDialog::getOpenURLs(":ROSEGARDEN", + "audio/x-rosegarden audio/x-midi audio/x-rosegarden21", + this, + i18n("Select one or more Rosegarden files")); + + KURL::List::iterator it; + + for (it = kurlList.begin(); it != kurlList.end(); ++it) { + new PlayListViewItem(m_listView, *it); + } + + enableButtons(m_listView->currentItem()); +} + +void +PlayList::slotDropped(QDropEvent *event, QListViewItem* after) +{ + QStrList uri; + + // see if we can decode a URI.. if not, just ignore it + if (QUriDrag::decode(event, uri)) { + + // okay, we have a URI.. process it + // weed out non-rg files + // + for (QString url = uri.first(); url; url = uri.next()) { + if (url.right(3).lower() == ".rg") + new PlayListViewItem(m_listView, after, KURL(url)); + + } + } + + enableButtons(m_listView->currentItem()); +} + +void PlayList::slotPlay() +{ + PlayListViewItem *currentItem = dynamic_cast<PlayListViewItem*>(m_listView->currentItem()); + + if (currentItem) + emit play(currentItem->getURL().url()); +} + +void PlayList::slotMoveUp() +{ + QListViewItem *currentItem = m_listView->currentItem(); + QListViewItem *previousItem = m_listView->previousSibling(currentItem); + + if (previousItem) + previousItem->moveItem(currentItem); + + enableButtons(currentItem); +} + +void PlayList::slotMoveDown() +{ + QListViewItem *currentItem = m_listView->currentItem(); + QListViewItem *nextItem = currentItem->nextSibling(); + + if (nextItem) + currentItem->moveItem(nextItem); + + enableButtons(currentItem); +} + +void PlayList::slotClear() +{ + m_listView->clear(); + enableButtons(0); +} + +void PlayList::slotDeleteCurrent() +{ + QListViewItem* currentItem = m_listView->currentItem(); + if (currentItem) + delete currentItem; +} + +void PlayList::slotCurrentItemChanged(QListViewItem* currentItem) +{ + enableButtons(currentItem); +} + +void PlayList::enableButtons(QListViewItem* currentItem) +{ + bool enable = (currentItem != 0); + + m_playButton->setEnabled(enable); + m_deleteButton->setEnabled(enable); + + if (currentItem) { + m_moveUpButton->setEnabled(currentItem != m_listView->firstChild()); + m_moveDownButton->setEnabled(currentItem != m_listView->lastItem()); + } else { + m_moveUpButton->setEnabled(false); + m_moveDownButton->setEnabled(false); + } + + m_clearButton->setEnabled(m_listView->childCount() > 0); +} + +void PlayList::save() +{ + QStringList urlList; + PlayListViewItem* item = dynamic_cast<PlayListViewItem*>(getListView()->firstChild()); + + while (item) { + urlList << item->getURL().url(); + item = dynamic_cast<PlayListViewItem*>(item->nextSibling()); + } + + KConfig *kc = KGlobal::config(); + KConfigGroupSaver cs(kc, PlayListConfigGroup); + kc->writeEntry("Playlist Files", urlList); + + getListView()->saveLayout(kc, PlayListConfigGroup); +} + +void PlayList::restore() +{ + KConfig *kc = KGlobal::config(); + getListView()->restoreLayout(kc, PlayListConfigGroup); + + KConfigGroupSaver cs(kc, PlayListConfigGroup); + QStringList urlList = kc->readListEntry("Playlist Files"); + + for (QStringList::Iterator it = urlList.begin(); + it != urlList.end(); ++it) { + new PlayListViewItem(getListView(), KURL(*it)); + } +} + +} +#include "PlayList.moc" diff --git a/src/gui/editors/segment/PlayList.h b/src/gui/editors/segment/PlayList.h new file mode 100644 index 0000000..8e40c8c --- /dev/null +++ b/src/gui/editors/segment/PlayList.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_PLAYLIST_H_ +#define _RG_PLAYLIST_H_ + +#include <qvbox.h> + + +class QWidget; +class QPushButton; +class QListViewItem; +class QHBoxLayout; +class QFrame; +class QDropEvent; + + +namespace Rosegarden +{ + +class PlayListView; + + +class PlayList : public QVBox +{ + Q_OBJECT + +public: + PlayList(QWidget *parent = 0, const char *name = 0); + ~PlayList(); + + PlayListView* getListView() { return m_listView; } + + void enableButtons(QListViewItem*); + + +signals: + void play(QString); + +protected slots: + void slotOpenFiles(); + void slotPlay(); + void slotMoveUp(); + void slotMoveDown(); + void slotDeleteCurrent(); + void slotClear(); + void slotCurrentItemChanged(QListViewItem*); + void slotDropped(QDropEvent*, QListViewItem*); + +protected: + void save(); + void restore(); + + //--------------- Data members --------------------------------- + PlayListView* m_listView; + QFrame* m_buttonBar; + QHBoxLayout* m_barLayout; + + QPushButton* m_openButton; + QPushButton* m_playButton; + QPushButton* m_moveUpButton; + QPushButton* m_moveDownButton; + QPushButton* m_deleteButton; + QPushButton* m_clearButton; +}; + + + +} + +#endif diff --git a/src/gui/editors/segment/PlayListDialog.cpp b/src/gui/editors/segment/PlayListDialog.cpp new file mode 100644 index 0000000..7aa03a5 --- /dev/null +++ b/src/gui/editors/segment/PlayListDialog.cpp @@ -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. +*/ + + +#include "PlayListDialog.h" + +#include "document/ConfigGroups.h" +#include "PlayList.h" +#include <kdialogbase.h> +#include <qstring.h> +#include <qwidget.h> + + +namespace Rosegarden +{ + +PlayListDialog::PlayListDialog(QString caption, + QWidget* parent, const char* name) + : KDialogBase(parent, name, false, caption, + KDialogBase::Close, // standard buttons + KDialogBase::Close, // default button + true), + m_playList(new PlayList(this)) +{ + setWFlags(WDestructiveClose); + setMainWidget(m_playList); + restore(); +} + +void PlayListDialog::save() +{ + saveDialogSize(PlayListConfigGroup); +} + +void PlayListDialog::restore() +{ + setInitialSize(configDialogSize(PlayListConfigGroup)); +} + +void PlayListDialog::closeEvent(QCloseEvent *e) +{ + save(); + emit closing(); + KDialogBase::closeEvent(e); +} + +void PlayListDialog::slotClose() +{ + save(); + emit closing(); + KDialogBase::slotClose(); +} + +} +#include "PlayListDialog.moc" diff --git a/src/gui/editors/segment/PlayListDialog.h b/src/gui/editors/segment/PlayListDialog.h new file mode 100644 index 0000000..51db8ca --- /dev/null +++ b/src/gui/editors/segment/PlayListDialog.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_PLAYLISTDIALOG_H_ +#define _RG_PLAYLISTDIALOG_H_ + +#include <kdialogbase.h> +#include <qstring.h> + + +class QWidget; +class QCloseEvent; + + +namespace Rosegarden +{ + +class PlayList; + + +class PlayListDialog : public KDialogBase +{ + Q_OBJECT + +public: + PlayListDialog(QString caption, QWidget* parent = 0, const char* name = 0); + + PlayList* getPlayList() { return m_playList; } + +public slots: + void slotClose(); + +signals: + void closing(); + +protected: + virtual void closeEvent(QCloseEvent *e); + + void save(); + void restore(); + + //--------------- Data members --------------------------------- + PlayList* m_playList; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/PlayListView.cpp b/src/gui/editors/segment/PlayListView.cpp new file mode 100644 index 0000000..8c17076 --- /dev/null +++ b/src/gui/editors/segment/PlayListView.cpp @@ -0,0 +1,66 @@ +/* -*- 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 "PlayListView.h" + +#include <klocale.h> +#include <qdragobject.h> + +namespace Rosegarden { + +PlayListView::PlayListView(QWidget *parent, const char *name) + : KListView(parent, name) +{ + addColumn(i18n("Title")); + addColumn(i18n("File name")); + + setDragEnabled(true); + setAcceptDrops(true); + setDropVisualizer(true); + + setShowToolTips(true); + setShowSortIndicator(true); + setAllColumnsShowFocus(true); + setItemsMovable(true); + setSorting(-1); +} + +bool PlayListView::acceptDrag(QDropEvent* e) const +{ + return QUriDrag::canDecode(e) || KListView::acceptDrag(e); +} + + +QListViewItem* PlayListView::previousSibling(QListViewItem* item) +{ + QListViewItem* prevSib = firstChild(); + + while(prevSib && prevSib->nextSibling() != item) + prevSib = prevSib->nextSibling(); + + return prevSib; +} + +} + diff --git a/src/gui/editors/segment/PlayListView.h b/src/gui/editors/segment/PlayListView.h new file mode 100644 index 0000000..a18b8e8 --- /dev/null +++ b/src/gui/editors/segment/PlayListView.h @@ -0,0 +1,52 @@ +/* -*- 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_PLAYLISTVIEW_H_ +#define _RG_PLAYLISTVIEW_H_ + +#include <klistview.h> + +namespace Rosegarden { + +class PlayListView : public KListView +{ +public: + PlayListView(QWidget *parent=0, const char *name=0); + + QListViewItem* previousSibling(QListViewItem*); + +protected: +// virtual void dragEnterEvent(QDragEnterEvent *event); +// virtual void dropEvent(QDropEvent*); + +// virtual void dragEnterEvent(QDragEnterEvent*); + virtual bool acceptDrag(QDropEvent*) const; + + +}; + +} + +#endif + diff --git a/src/gui/editors/segment/PlayListViewItem.cpp b/src/gui/editors/segment/PlayListViewItem.cpp new file mode 100644 index 0000000..df04a2e --- /dev/null +++ b/src/gui/editors/segment/PlayListViewItem.cpp @@ -0,0 +1,42 @@ +/* -*- 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 "PlayListViewItem.h" + +namespace Rosegarden { + +PlayListViewItem::PlayListViewItem(KListView* parent, KURL kurl) + : KListViewItem(parent, kurl.fileName(), kurl.prettyURL()), + m_kurl(kurl) +{ +} + +PlayListViewItem::PlayListViewItem(KListView* parent, QListViewItem* after, KURL kurl) + : KListViewItem(parent, after, kurl.fileName(), kurl.prettyURL()), + m_kurl(kurl) +{ +} + +} + diff --git a/src/gui/editors/segment/PlayListViewItem.h b/src/gui/editors/segment/PlayListViewItem.h new file mode 100644 index 0000000..b88de0f --- /dev/null +++ b/src/gui/editors/segment/PlayListViewItem.h @@ -0,0 +1,47 @@ +/* -*- 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_PLAYLISTVIEWITEM_H_ +#define _RG_PLAYLISTVIEWITEM_H_ + +#include <klistview.h> +#include <kurl.h> + +namespace Rosegarden { + +class PlayListViewItem : public KListViewItem +{ +public: + PlayListViewItem(KListView* parent, KURL); + PlayListViewItem(KListView* parent, QListViewItem*, KURL); + + const KURL& getURL() { return m_kurl; } + +protected: + KURL m_kurl; +}; + +} + +#endif diff --git a/src/gui/editors/segment/TrackButtons.cpp b/src/gui/editors/segment/TrackButtons.cpp new file mode 100644 index 0000000..5cf9908 --- /dev/null +++ b/src/gui/editors/segment/TrackButtons.cpp @@ -0,0 +1,1149 @@ +/* -*- 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 "TrackButtons.h" +#include <qlayout.h> + +#include <klocale.h> +#include <kstddirs.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/AudioPluginInstance.h" +#include "base/Composition.h" +#include "base/Device.h" +#include "base/Instrument.h" +#include "base/MidiProgram.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "commands/segment/RenameTrackCommand.h" +#include "document/RosegardenGUIDoc.h" +#include "document/MultiViewCommandHistory.h" +#include "gui/application/RosegardenGUIApp.h" +#include "gui/general/GUIPalette.h" +#include "gui/kdeext/KLedButton.h" +#include "sound/AudioFileManager.h" +#include "sound/PluginIdentifier.h" +#include "TrackLabel.h" +#include "TrackVUMeter.h" +#include <kglobal.h> +#include <kled.h> +#include <kmessagebox.h> +#include <qcursor.h> +#include <qframe.h> +#include <qiconset.h> +#include <qlabel.h> +#include <qobject.h> +#include <qpixmap.h> +#include <qpopupmenu.h> +#include <qsignalmapper.h> +#include <qstring.h> +#include <qtimer.h> +#include <qwidget.h> +#include <qwidgetstack.h> +#include <qtooltip.h> + +namespace Rosegarden +{ + +TrackButtons::TrackButtons(RosegardenGUIDoc* doc, + unsigned int trackCellHeight, + unsigned int trackLabelWidth, + bool showTrackLabels, + int overallHeight, + QWidget* parent, + const char* name, + WFlags f) + : QFrame(parent, name, f), + m_doc(doc), + m_layout(new QVBoxLayout(this)), + m_recordSigMapper(new QSignalMapper(this)), + m_muteSigMapper(new QSignalMapper(this)), + m_clickedSigMapper(new QSignalMapper(this)), + m_instListSigMapper(new QSignalMapper(this)), + m_tracks(doc->getComposition().getNbTracks()), + m_offset(4), + m_cellSize(trackCellHeight), + m_borderGap(1), + m_trackLabelWidth(trackLabelWidth), + m_popupItem(0), + m_lastSelected( -1) +{ + setFrameStyle(Plain); + + // when we create the widget, what are we looking at? + if (showTrackLabels) + m_trackInstrumentLabels = TrackLabel::ShowTrack; + else + m_trackInstrumentLabels = TrackLabel::ShowInstrument; + + // Set the spacing between vertical elements + // + m_layout->setSpacing(m_borderGap); + + // Now draw the buttons and labels and meters + // + makeButtons(); + + m_layout->addStretch(20); + + connect(m_recordSigMapper, SIGNAL(mapped(int)), + this, SLOT(slotToggleRecordTrack(int))); + + connect(m_muteSigMapper, SIGNAL(mapped(int)), + this, SLOT(slotToggleMutedTrack(int))); + + // connect signal mappers + connect(m_instListSigMapper, SIGNAL(mapped(int)), + this, SLOT(slotInstrumentSelection(int))); + + connect(m_clickedSigMapper, SIGNAL(mapped(int)), + this, SIGNAL(trackSelected(int))); + + // // Populate instrument popup menu just once at start-up + // // + // populateInstrumentPopup(); + + // We have to force the height for the moment + // + setMinimumHeight(overallHeight); + +} + +TrackButtons::~TrackButtons() +{} + +void +TrackButtons::makeButtons() +{ + if (!m_doc) + return ; + + // Create a horizontal box for each track + // plus the two buttons + // + unsigned int nbTracks = m_doc->getComposition().getNbTracks(); + + for (unsigned int i = 0; i < nbTracks; ++i) { + Track *track = m_doc->getComposition().getTrackByPosition(i); + + if (track) { + QFrame *trackHBox = makeButton(track->getId()); + + if (trackHBox) { + m_layout->addWidget(trackHBox); + m_trackHBoxes.push_back(trackHBox); + } + } + } + + populateButtons(); +} + +void TrackButtons::setButtonMapping(QObject* obj, TrackId trackId) +{ + m_clickedSigMapper->setMapping(obj, trackId); + m_instListSigMapper->setMapping(obj, trackId); +} + +void +TrackButtons::populateButtons() +{ + Instrument *ins = 0; + Track *track; + + for (unsigned int i = 0; i < m_trackLabels.size(); ++i) { + track = m_doc->getComposition().getTrackByPosition(i); + + if (track) { + ins = m_doc->getStudio().getInstrumentById(track->getInstrument()); + + // Set mute button from track + // + if (track->isMuted()) + m_muteLeds[i]->off(); + else + m_muteLeds[i]->on(); + + // Set record button from track + // + bool recording = + m_doc->getComposition().isTrackRecording(track->getId()); + setRecordTrack(track->getPosition(), recording); + + // reset track tokens + m_trackLabels[i]->setId(track->getId()); + setButtonMapping(m_trackLabels[i], track->getId()); + m_trackLabels[i]->setPosition(i); + } + + if (ins) { + m_trackLabels[i]->getInstrumentLabel()->setText + (strtoqstr(ins->getPresentationName())); + if (ins->sendsProgramChange()) { + m_trackLabels[i]->setAlternativeLabel(strtoqstr(ins->getProgramName())); + } + + } else { + m_trackLabels[i]->getInstrumentLabel()->setText(i18n("<no instrument>")); + } + + m_trackLabels[i]->update(); + } + +} + +std::vector<int> +TrackButtons::mutedTracks() +{ + std::vector<int> mutedTracks; + + for (TrackId i = 0; i < m_tracks; i++) { + if (m_muteLeds[i]->state() == KLed::Off) + mutedTracks.push_back(i); + } + + return mutedTracks; +} + +void +TrackButtons::slotToggleMutedTrack(int mutedTrackPos) +{ + RG_DEBUG << "TrackButtons::slotToggleMutedTrack(" << mutedTrackPos << ")\n"; + + if (mutedTrackPos < 0 || mutedTrackPos > (int)m_tracks ) + return ; + + Track *track = + m_doc->getComposition().getTrackByPosition(mutedTrackPos); + + emit muteButton(track->getId(), !track->isMuted()); // will set the value +} + +void +TrackButtons::removeButtons(unsigned int position) +{ + RG_DEBUG << "TrackButtons::removeButtons - " + << "deleting track button at position " + << position << endl; + + if (position >= m_trackHBoxes.size()) { + RG_DEBUG << "%%%%%%%%% BIG PROBLEM : TrackButtons::removeButtons() was passed a non-existing index\n"; + return ; + } + + std::vector<TrackLabel*>::iterator tit = m_trackLabels.begin(); + tit += position; + m_trackLabels.erase(tit); + + std::vector<TrackVUMeter*>::iterator vit = m_trackMeters.begin(); + vit += position; + m_trackMeters.erase(vit); + + std::vector<KLedButton*>::iterator mit = m_muteLeds.begin(); + mit += position; + m_muteLeds.erase(mit); + + mit = m_recordLeds.begin(); + mit += position; + m_recordLeds.erase(mit); + + delete m_trackHBoxes[position]; // deletes all child widgets (button, led, label...) + + std::vector<QFrame*>::iterator it = m_trackHBoxes.begin(); + it += position; + m_trackHBoxes.erase(it); + +} + +void +TrackButtons::slotUpdateTracks() +{ + Composition &comp = m_doc->getComposition(); + unsigned int newNbTracks = comp.getNbTracks(); + Track *track = 0; + + std::cerr << "TrackButtons::slotUpdateTracks" << std::endl; + + if (newNbTracks < m_tracks) { + for (unsigned int i = m_tracks; i > newNbTracks; --i) + removeButtons(i - 1); + } else if (newNbTracks > m_tracks) { + for (unsigned int i = m_tracks; i < newNbTracks; ++i) { + track = m_doc->getComposition().getTrackByPosition(i); + if (track) { + QFrame *trackHBox = makeButton(track->getId()); + + if (trackHBox) { + trackHBox->show(); + m_layout->insertWidget(i, trackHBox); + m_trackHBoxes.push_back(trackHBox); + } + } else + RG_DEBUG << "TrackButtons::slotUpdateTracks - can't find TrackId for position " << i << endl; + } + } + + // Set height + // + for (unsigned int i = 0; i < m_trackHBoxes.size(); ++i) { + + track = comp.getTrackByPosition(i); + + if (track) { + + int multiple = m_doc->getComposition() + .getMaxContemporaneousSegmentsOnTrack(track->getId()); + if (multiple == 0) multiple = 1; + + // nasty dupe from makeButton + + int buttonGap = 8; + int vuWidth = 20; + int vuSpacing = 2; + + int labelWidth = m_trackLabelWidth - + ((m_cellSize - buttonGap) * 2 + + vuSpacing * 2 + vuWidth); + + m_trackHBoxes[i]->setMinimumSize + (labelWidth, m_cellSize * multiple - m_borderGap); + + m_trackHBoxes[i]->setFixedHeight + (m_cellSize * multiple - m_borderGap); + } + } + + // Renumber all the labels + // + for (unsigned int i = 0; i < m_trackLabels.size(); ++i) { + track = comp.getTrackByPosition(i); + + if (track) { + m_trackLabels[i]->setId(track->getId()); + + QLabel *trackLabel = m_trackLabels[i]->getTrackLabel(); + + if (track->getLabel() == std::string("")) { + Instrument *ins = + m_doc->getStudio().getInstrumentById(track->getInstrument()); + if (ins && ins->getType() == Instrument::Audio) { + trackLabel->setText(i18n("<untitled audio>")); + } else { + trackLabel->setText(i18n("<untitled>")); + } + } else { + trackLabel->setText(strtoqstr(track->getLabel())); + } + + // RG_DEBUG << "TrackButtons::slotUpdateTracks - set button mapping at pos " + // << i << " to track id " << track->getId() << endl; + setButtonMapping(m_trackLabels[i], track->getId()); + } + } + m_tracks = newNbTracks; + + // Set record status and colour + + for (unsigned int i = 0; i < m_trackLabels.size(); ++i) { + + track = comp.getTrackByPosition(i); + + if (track) { + + setRecordTrack(i, comp.isTrackRecording(track->getId())); + + Instrument *ins = + m_doc->getStudio().getInstrumentById(track->getInstrument()); + + if (ins && + ins->getType() == Instrument::Audio) { + m_recordLeds[i]->setColor + (GUIPalette::getColour + (GUIPalette::RecordAudioTrackLED)); + } else { + m_recordLeds[i]->setColor + (GUIPalette::getColour + (GUIPalette::RecordMIDITrackLED)); + } + } + } + + // repopulate the buttons + populateButtons(); +} + +void +TrackButtons::slotToggleRecordTrack(int position) +{ + Composition &comp = m_doc->getComposition(); + Track *track = comp.getTrackByPosition(position); + + bool state = !comp.isTrackRecording(track->getId()); + + Instrument *instrument = m_doc->getStudio().getInstrumentById + (track->getInstrument()); + + bool audio = (instrument && + instrument->getType() == Instrument::Audio); + + if (audio && state) { + try { + m_doc->getAudioFileManager().testAudioPath(); + } catch (AudioFileManager::BadAudioPathException e) { + if (KMessageBox::warningContinueCancel + (this, + i18n("The audio file path does not exist or is not writable.\nPlease set the audio file path to a valid directory in Document Properties before recording audio.\nWould you like to set it now?"), + i18n("Warning"), + i18n("Set audio file path")) == KMessageBox::Continue) { + RosegardenGUIApp::self()->slotOpenAudioPathSettings(); + } + } + } + + // can have any number of audio instruments armed, but only one + // track armed per instrument. + + // Need to copy this container, as we're implicitly modifying it + // through calls to comp.setTrackRecording + + Composition::recordtrackcontainer oldRecordTracks = + comp.getRecordTracks(); + + for (Composition::recordtrackcontainer::const_iterator i = + oldRecordTracks.begin(); + i != oldRecordTracks.end(); ++i) { + + if (!comp.isTrackRecording(*i)) { + // We've already reset this one + continue; + } + + Track *otherTrack = comp.getTrackById(*i); + + if (otherTrack && + otherTrack != track) { + + /* Obsolete code: audio, MIDI and plugin tracks behave the same now. + plcl, 06/2006 - Multitrack MIDI recording + + bool unselect; + + if (audio) { + unselect = (otherTrack->getInstrument() == track->getInstrument()); + } else { + // our track is not an audio track, check that the + // other isn't either + Instrument *otherInstrument = + m_doc->getStudio().getInstrumentById(otherTrack->getInstrument()); + bool otherAudio = (otherInstrument && + otherInstrument->getType() == + Instrument::Audio); + + unselect = !otherAudio; + } + + if (unselect) { */ + + if (otherTrack->getInstrument() == track->getInstrument()) { + // found another record track of the same type (and + // with the same instrument, if audio): unselect that + + //!!! should we tell the user, particularly for the + //audio case? might seem odd otherwise + + int otherPos = otherTrack->getPosition(); + setRecordTrack(otherPos, false); + } + } + } + + setRecordTrack(position, state); + + emit recordButton(track->getId(), state); +} + +void +TrackButtons::setRecordTrack(int position, bool state) +{ + setRecordButton(position, state); + m_doc->getComposition().setTrackRecording + (m_trackLabels[position]->getId(), state); +} + +void +TrackButtons::setRecordButton(int position, bool state) +{ + if (position < 0 || position >= (int)m_tracks) + return ; + + KLedButton* led = m_recordLeds[position]; + + led->setState(state ? KLed::On : KLed::Off); +} + +void +TrackButtons::selectLabel(int position) +{ + if (m_lastSelected >= 0 && m_lastSelected < (int)m_trackLabels.size()) { + m_trackLabels[m_lastSelected]->setSelected(false); + } + + if (position >= 0 && position < (int)m_trackLabels.size()) { + m_trackLabels[position]->setSelected(true); + m_lastSelected = position; + } +} + +std::vector<int> +TrackButtons::getHighlightedTracks() +{ + std::vector<int> retList; + + for (unsigned int i = 0; i < m_trackLabels.size(); ++i) { + if (m_trackLabels[i]->isSelected()) + retList.push_back(i); + } + + return retList; +} + +void +TrackButtons::slotRenameTrack(QString newName, TrackId trackId) +{ + m_doc->getCommandHistory()->addCommand + (new RenameTrackCommand(&m_doc->getComposition(), + trackId, + qstrtostr(newName))); + + changeTrackLabel(trackId, newName); +} + +void +TrackButtons::slotSetTrackMeter(float value, int position) +{ + //Composition &comp = m_doc->getComposition(); + //Studio &studio = m_doc->getStudio(); + //Track *track; + + for (unsigned int i = 0; i < m_trackMeters.size(); ++i) { + if (i == ((unsigned int)position)) { + m_trackMeters[i]->setLevel(value); + return ; + } + } +} + +void +TrackButtons::slotSetMetersByInstrument(float value, + InstrumentId id) +{ + Composition &comp = m_doc->getComposition(); + //Studio &studio = m_doc->getStudio(); + Track *track; + + for (unsigned int i = 0; i < m_trackMeters.size(); ++i) { + track = comp.getTrackByPosition(i); + + if (track != 0 && track->getInstrument() == id) { + m_trackMeters[i]->setLevel(value); + } + } +} + +void +TrackButtons::slotInstrumentSelection(int trackId) +{ + RG_DEBUG << "TrackButtons::slotInstrumentSelection(" << trackId << ")\n"; + + Composition &comp = m_doc->getComposition(); + Studio &studio = m_doc->getStudio(); + + int position = comp.getTrackById(trackId)->getPosition(); + + QString instrumentName = i18n("<no instrument>"); + Track *track = comp.getTrackByPosition(position); + + Instrument *instrument = 0; + if (track != 0) { + instrument = studio.getInstrumentById(track->getInstrument()); + if (instrument) + instrumentName = strtoqstr(instrument->getPresentationName()); + } + + // + // populate this instrument widget + m_trackLabels[position]->getInstrumentLabel()->setText(instrumentName); + + // Ensure the instrument name is shown + m_trackLabels[position]->showLabel(TrackLabel::ShowInstrument); + + // Yes, well as we might've changed the Device name in the + // Device/Bank dialog then we reload the whole menu here. + // + + QPopupMenu instrumentPopup(this); + + populateInstrumentPopup(instrument, &instrumentPopup); + + // Store the popup item position + // + m_popupItem = position; + + instrumentPopup.exec(QCursor::pos()); + + // Restore the label back to what it was showing + m_trackLabels[position]->showLabel(m_trackInstrumentLabels); + + // Do this here as well as in slotInstrumentPopupActivated, so as + // to restore the correct alternative label even if no other + // program was selected from the menu + if (track != 0) { + instrument = studio.getInstrumentById(track->getInstrument()); + if (instrument) { + m_trackLabels[position]->getInstrumentLabel()-> + setText(strtoqstr(instrument->getPresentationName())); + m_trackLabels[position]->clearAlternativeLabel(); + if (instrument->sendsProgramChange()) { + m_trackLabels[position]->setAlternativeLabel + (strtoqstr(instrument->getProgramName())); + } + } + } +} + +void +TrackButtons::populateInstrumentPopup(Instrument *thisTrackInstr, QPopupMenu* instrumentPopup) +{ + static QPixmap connectedPixmap, unconnectedPixmap, + connectedUsedPixmap, unconnectedUsedPixmap, + connectedSelectedPixmap, unconnectedSelectedPixmap; + static bool havePixmaps = false; + + if (!havePixmaps) { + + QString pixmapDir = + KGlobal::dirs()->findResource("appdata", "pixmaps/"); + + connectedPixmap.load + (QString("%1/misc/connected.xpm").arg(pixmapDir)); + connectedUsedPixmap.load + (QString("%1/misc/connected-used.xpm").arg(pixmapDir)); + connectedSelectedPixmap.load + (QString("%1/misc/connected-selected.xpm").arg(pixmapDir)); + unconnectedPixmap.load + (QString("%1/misc/unconnected.xpm").arg(pixmapDir)); + unconnectedUsedPixmap.load + (QString("%1/misc/unconnected-used.xpm").arg(pixmapDir)); + unconnectedSelectedPixmap.load + (QString("%1/misc/unconnected-selected.xpm").arg(pixmapDir)); + + havePixmaps = true; + } + + Composition &comp = m_doc->getComposition(); + Studio &studio = m_doc->getStudio(); + + // clear the popup + instrumentPopup->clear(); + + std::vector<QPopupMenu*> instrumentSubMenus; + + // position index + int i = 0; + + // Get the list + InstrumentList list = studio.getPresentationInstruments(); + InstrumentList::iterator it; + int currentDevId = -1; + bool deviceUsedByAnyone = false; + + for (it = list.begin(); it != list.end(); it++) { + + if (! (*it)) + continue; // sanity check + + QString iname(strtoqstr((*it)->getPresentationName())); + QString pname(strtoqstr((*it)->getProgramName())); + Device *device = (*it)->getDevice(); + DeviceId devId = device->getId(); + bool connected = false; + + if ((*it)->getType() == Instrument::SoftSynth) { + pname = ""; + AudioPluginInstance *plugin = (*it)->getPlugin + (Instrument::SYNTH_PLUGIN_POSITION); + if (plugin) { + pname = strtoqstr(plugin->getProgram()); + QString identifier = strtoqstr(plugin->getIdentifier()); + if (identifier != "") { + connected = true; + QString type, soName, label; + PluginIdentifier::parseIdentifier + (identifier, type, soName, label); + if (pname == "") { + pname = strtoqstr(plugin->getDistinctiveConfigurationText()); + } + if (pname != "") { + pname = QString("%1: %2").arg(label).arg(pname); + } else { + pname = label; + } + } else { + connected = false; + } + } + } else if ((*it)->getType() == Instrument::Audio) { + connected = true; + } else { + connected = (device->getConnection() != ""); + } + + bool instrUsedByMe = false; + bool instrUsedByAnyone = false; + + if (thisTrackInstr && thisTrackInstr->getId() == (*it)->getId()) { + instrUsedByMe = true; + instrUsedByAnyone = true; + } + + if (devId != (DeviceId)(currentDevId)) { + + deviceUsedByAnyone = false; + + if (instrUsedByMe) + deviceUsedByAnyone = true; + else { + for (Composition::trackcontainer::iterator tit = + comp.getTracks().begin(); + tit != comp.getTracks().end(); ++tit) { + + if (tit->second->getInstrument() == (*it)->getId()) { + instrUsedByAnyone = true; + deviceUsedByAnyone = true; + break; + } + + Instrument *instr = + studio.getInstrumentById(tit->second->getInstrument()); + if (instr && (instr->getDevice()->getId() == devId)) { + deviceUsedByAnyone = true; + } + } + } + + QIconSet iconSet + (connected ? + (deviceUsedByAnyone ? + connectedUsedPixmap : connectedPixmap) : + (deviceUsedByAnyone ? + unconnectedUsedPixmap : unconnectedPixmap)); + + currentDevId = int(devId); + + QPopupMenu *subMenu = new QPopupMenu(instrumentPopup); + QString deviceName = strtoqstr(device->getName()); + instrumentPopup->insertItem(iconSet, deviceName, subMenu); + instrumentSubMenus.push_back(subMenu); + + // Connect up the submenu + // + connect(subMenu, SIGNAL(activated(int)), + SLOT(slotInstrumentPopupActivated(int))); + + } else if (!instrUsedByMe) { + + for (Composition::trackcontainer::iterator tit = + comp.getTracks().begin(); + tit != comp.getTracks().end(); ++tit) { + + if (tit->second->getInstrument() == (*it)->getId()) { + instrUsedByAnyone = true; + break; + } + } + } + + QIconSet iconSet + (connected ? + (instrUsedByAnyone ? + instrUsedByMe ? + connectedSelectedPixmap : + connectedUsedPixmap : connectedPixmap) : + (instrUsedByAnyone ? + instrUsedByMe ? + unconnectedSelectedPixmap : + unconnectedUsedPixmap : unconnectedPixmap)); + + if (pname != "") + iname += " (" + pname + ")"; + + instrumentSubMenus[instrumentSubMenus.size() - 1]->insertItem(iconSet, iname, i++); + } + +} + +void +TrackButtons::slotInstrumentPopupActivated(int item) +{ + RG_DEBUG << "TrackButtons::slotInstrumentPopupActivated " << item << endl; + + Composition &comp = m_doc->getComposition(); + Studio &studio = m_doc->getStudio(); + + Instrument *inst = studio.getInstrumentFromList(item); + + RG_DEBUG << "TrackButtons::slotInstrumentPopupActivated: instrument " << inst << endl; + + if (inst != 0) { + Track *track = comp.getTrackByPosition(m_popupItem); + + if (track != 0) { + track->setInstrument(inst->getId()); + + // select instrument + emit instrumentSelected((int)inst->getId()); + + m_trackLabels[m_popupItem]->getInstrumentLabel()-> + setText(strtoqstr(inst->getPresentationName())); + + // reset the alternative label + m_trackLabels[m_popupItem]->clearAlternativeLabel(); + + // Now see if the program is being shown for this instrument + // and if so reset the label + // + if (inst->sendsProgramChange()) + m_trackLabels[m_popupItem]->setAlternativeLabel(strtoqstr(inst->getProgramName())); + + if (inst->getType() == Instrument::Audio) { + m_recordLeds[m_popupItem]->setColor + (GUIPalette::getColour + (GUIPalette::RecordAudioTrackLED)); + } else { + m_recordLeds[m_popupItem]->setColor + (GUIPalette::getColour + (GUIPalette::RecordMIDITrackLED)); + } + } else + RG_DEBUG << "slotInstrumentPopupActivated() - can't find item!\n"; + } else + RG_DEBUG << "slotInstrumentPopupActivated() - can't find item!\n"; + +} + +void +TrackButtons::changeTrackInstrumentLabels(TrackLabel::InstrumentTrackLabels label) +{ + // Set new label + m_trackInstrumentLabels = label; + + // update and reconnect with new value + for (int i = 0; i < (int)m_tracks; i++) { + m_trackLabels[i]->showLabel(label); + } +} + +void +TrackButtons::changeInstrumentLabel(InstrumentId id, QString label) +{ + Composition &comp = m_doc->getComposition(); + Track *track; + + for (int i = 0; i < (int)m_tracks; i++) { + track = comp.getTrackByPosition(i); + + if (track && track->getInstrument() == id) { + + m_trackLabels[i]->setAlternativeLabel(label); + + Instrument *ins = m_doc->getStudio(). + getInstrumentById(track->getInstrument()); + + if (ins && ins->getType() == Instrument::Audio) { + m_recordLeds[i]->setColor + (GUIPalette::getColour + (GUIPalette::RecordAudioTrackLED)); + } else { + m_recordLeds[i]->setColor + (GUIPalette::getColour + (GUIPalette::RecordMIDITrackLED)); + } + } + } +} + +void +TrackButtons::changeTrackLabel(TrackId id, QString label) +{ + Composition &comp = m_doc->getComposition(); + Track *track; + + for (int i = 0; i < (int)m_tracks; i++) { + track = comp.getTrackByPosition(i); + if (track && track->getId() == id) { + if (m_trackLabels[i]->getTrackLabel()->text() != label) { + m_trackLabels[i]->getTrackLabel()->setText(label); + emit widthChanged(); + emit nameChanged(); + } + return ; + } + } +} + +void +TrackButtons::slotSynchroniseWithComposition() +{ + Composition &comp = m_doc->getComposition(); + Studio &studio = m_doc->getStudio(); + Track *track; + + for (int i = 0; i < (int)m_tracks; i++) { + track = comp.getTrackByPosition(i); + + if (track) { + if (track->isMuted()) + m_muteLeds[i]->off(); + else + m_muteLeds[i]->on(); + + Instrument *ins = studio. + getInstrumentById(track->getInstrument()); + + QString instrumentName(i18n("<no instrument>")); + if (ins) + instrumentName = strtoqstr(ins->getPresentationName()); + + m_trackLabels[i]->getInstrumentLabel()->setText(instrumentName); + + setRecordButton(i, comp.isTrackRecording(track->getId())); + + if (ins && ins->getType() == Instrument::Audio) { + m_recordLeds[i]->setColor + (GUIPalette::getColour + (GUIPalette::RecordAudioTrackLED)); + } else { + m_recordLeds[i]->setColor + (GUIPalette::getColour + (GUIPalette::RecordMIDITrackLED)); + } + } + } +} + +void +TrackButtons::slotLabelSelected(int position) +{ + Track *track = + m_doc->getComposition().getTrackByPosition(position); + + if (track) { + emit trackSelected(track->getId()); + } +} + +void +TrackButtons::setMuteButton(TrackId track, bool value) +{ + Track *trackObj = m_doc->getComposition().getTrackById(track); + if (trackObj == 0) + return ; + + int pos = trackObj->getPosition(); + + RG_DEBUG << "TrackButtons::setMuteButton() trackId = " + << track << ", pos = " << pos << endl; + + m_muteLeds[pos]->setState(value ? KLed::Off : KLed::On); +} + +void +TrackButtons::slotTrackInstrumentSelection(TrackId trackId, int item) +{ + RG_DEBUG << "TrackButtons::slotTrackInstrumentSelection(" << trackId << ")\n"; + + Composition &comp = m_doc->getComposition(); + int position = comp.getTrackById(trackId)->getPosition(); + m_popupItem = position; + slotInstrumentPopupActivated( item ); +} + +QFrame* TrackButtons::makeButton(Rosegarden::TrackId trackId) +{ + // The buttonGap sets up the sizes of the buttons + // + static const int buttonGap = 8; + + QFrame *trackHBox = 0; + + KLedButton *mute = 0; + KLedButton *record = 0; + + TrackVUMeter *vuMeter = 0; + TrackLabel *trackLabel = 0; + + int vuWidth = 20; + int vuSpacing = 2; + int multiple = m_doc->getComposition() + .getMaxContemporaneousSegmentsOnTrack(trackId); + if (multiple == 0) multiple = 1; + int labelWidth = m_trackLabelWidth - ( (m_cellSize - buttonGap) * 2 + + vuSpacing * 2 + vuWidth ); + + // Set the label from the Track object on the Composition + // + Rosegarden::Track *track = m_doc->getComposition().getTrackById(trackId); + + if (track == 0) return 0; + + // Create a horizontal box for each track + // + trackHBox = new QFrame(this); + QHBoxLayout *hblayout = new QHBoxLayout(trackHBox); + + trackHBox->setMinimumSize(labelWidth, m_cellSize * multiple - m_borderGap); + trackHBox->setFixedHeight(m_cellSize * multiple - m_borderGap); + + // Try a style for the box + // + trackHBox->setFrameStyle(StyledPanel); + trackHBox->setFrameShape(StyledPanel); + trackHBox->setFrameShadow(Raised); + + // Insert a little gap + hblayout->addSpacing(vuSpacing); + + // Create a VU meter + vuMeter = new TrackVUMeter(trackHBox, + VUMeter::PeakHold, + vuWidth, + buttonGap, + track->getPosition()); + + m_trackMeters.push_back(vuMeter); + + hblayout->addWidget(vuMeter); + + // Create another little gap + hblayout->addSpacing(vuSpacing); + + // + // 'mute' and 'record' leds + // + + mute = new KLedButton(Rosegarden::GUIPalette::getColour + (Rosegarden::GUIPalette::MuteTrackLED), trackHBox); + QToolTip::add(mute, i18n("Mute track")); + hblayout->addWidget(mute); + + record = new KLedButton(Rosegarden::GUIPalette::getColour + (Rosegarden::GUIPalette::RecordMIDITrackLED), trackHBox); + QToolTip::add(record, i18n("Record on this track")); + hblayout->addWidget(record); + + record->setLook(KLed::Sunken); + mute->setLook(KLed::Sunken); + record->off(); + + // Connect them to their sigmappers + connect(record, SIGNAL(stateChanged(bool)), + m_recordSigMapper, SLOT(map())); + connect(mute, SIGNAL(stateChanged(bool)), + m_muteSigMapper, SLOT(map())); + m_recordSigMapper->setMapping(record, track->getPosition()); + m_muteSigMapper->setMapping(mute, track->getPosition()); + + // Store the KLedButton + // + m_muteLeds.push_back(mute); + m_recordLeds.push_back(record); + + // + // Track label + // + trackLabel = new TrackLabel(trackId, track->getPosition(), trackHBox); + hblayout->addWidget(trackLabel); + hblayout->addSpacing(vuSpacing); + + if (track->getLabel() == std::string("")) { + Rosegarden::Instrument *ins = + m_doc->getStudio().getInstrumentById(track->getInstrument()); + if (ins && ins->getType() == Rosegarden::Instrument::Audio) { + trackLabel->getTrackLabel()->setText(i18n("<untitled audio>")); + } else { + trackLabel->getTrackLabel()->setText(i18n("<untitled>")); + } + } + else + trackLabel->getTrackLabel()->setText(strtoqstr(track->getLabel())); + + trackLabel->setFixedSize(labelWidth, m_cellSize - buttonGap); + trackLabel->setFixedHeight(m_cellSize - buttonGap); + trackLabel->setIndent(7); + + connect(trackLabel, SIGNAL(renameTrack(QString, TrackId)), + SLOT(slotRenameTrack(QString, TrackId))); + + // Store the TrackLabel pointer + // + m_trackLabels.push_back(trackLabel); + + // Connect it + setButtonMapping(trackLabel, trackId); + + connect(trackLabel, SIGNAL(changeToInstrumentList()), + m_instListSigMapper, SLOT(map())); + connect(trackLabel, SIGNAL(clicked()), + m_clickedSigMapper, SLOT(map())); + + // + // instrument label + // + Rosegarden::Instrument *ins = + m_doc->getStudio().getInstrumentById(track->getInstrument()); + + QString instrumentName(i18n("<no instrument>")); + if (ins) instrumentName = strtoqstr(ins->getPresentationName()); + + // Set label to program change if it's being sent + // + if (ins != 0 && ins->sendsProgramChange()) + trackLabel->setAlternativeLabel(strtoqstr(ins->getProgramName())); + + trackLabel->showLabel(m_trackInstrumentLabels); + + mute->setFixedSize(m_cellSize - buttonGap, m_cellSize - buttonGap); + record->setFixedSize(m_cellSize - buttonGap, m_cellSize - buttonGap); + + // set the mute button + // + if (track->isMuted()) + mute->off(); + + return trackHBox; +} + +} +#include "TrackButtons.moc" diff --git a/src/gui/editors/segment/TrackButtons.h b/src/gui/editors/segment/TrackButtons.h new file mode 100644 index 0000000..a61601d --- /dev/null +++ b/src/gui/editors/segment/TrackButtons.h @@ -0,0 +1,228 @@ + +/* -*- 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_TRACKBUTTONS_H_ +#define _RG_TRACKBUTTONS_H_ + +#include "base/MidiProgram.h" +#include "base/Track.h" +#include "gui/application/RosegardenGUIApp.h" +#include "TrackLabel.h" +#include <qframe.h> +#include <qstring.h> +#include <vector> + + +class QWidget; +class QVBoxLayout; +class QSignalMapper; +class QPopupMenu; +class QObject; + + +namespace Rosegarden +{ + +class TrackVUMeter; +class RosegardenGUIDoc; +class KLedButton; +class Instrument; + + +class TrackButtons : public QFrame +{ + Q_OBJECT +public: + + TrackButtons(RosegardenGUIDoc* doc, + unsigned int trackCellHeight, + unsigned int trackLabelWidth, + bool showTrackLabels, + int overallHeight, + QWidget* parent = 0, + const char* name = 0, + WFlags f=0); + + ~TrackButtons(); + + /// Return a vector of muted tracks + std::vector<int> mutedTracks(); + + /// Return a vector of highlighted tracks + std::vector<int> getHighlightedTracks(); + + void changeTrackInstrumentLabels(TrackLabel::InstrumentTrackLabels label); + + /** + * Change the instrument label to something else like + * an actual program name rather than a meaningless + * device number and midi channel + */ + void changeInstrumentLabel(InstrumentId id, QString label); + + void changeTrackLabel(TrackId id, QString label); + + // Select a label from outside this class by position + // + void selectLabel(int trackId); + + /* + * Set the mute button down or up + */ + void setMuteButton(TrackId track, bool value); + + /* + * Make this available so that others can set record buttons down + */ + void setRecordTrack(int position, bool value); + + /** + * Precalculate the Instrument popup so we don't have to every + * time it appears + * not protected because also used by the RosegardenGUIApp + * + * @see RosegardenGUIApp#slotPopulateTrackInstrumentPopup() + */ + void populateInstrumentPopup(Instrument *thisTrackInstr, QPopupMenu* instrumentPopup); + +signals: + // to emit what Track has been selected + // + void trackSelected(int); + void instrumentSelected(int); + + void widthChanged(); + + // to tell the notation canvas &c when a name changes + // + void nameChanged(); + + // document modified (mute button) + // + void modified(); + + // A record button has been pressed - if we're setting to an audio + // track we need to tell the sequencer for live monitoring + // purposes. + // + void recordButton(TrackId track, bool state); + + // A mute button has been pressed + // + void muteButton(TrackId track, bool state); + +public slots: + + void slotToggleRecordTrack(int position); + void slotToggleMutedTrack(int mutedTrack); + void slotUpdateTracks(); + void slotRenameTrack(QString newName, TrackId trackId); + void slotSetTrackMeter(float value, int position); + void slotSetMetersByInstrument(float value, InstrumentId id); + + void slotInstrumentSelection(int); + void slotInstrumentPopupActivated(int); + void slotTrackInstrumentSelection(TrackId, int); + + // ensure track buttons match the Composition + // + void slotSynchroniseWithComposition(); + + // Convert a positional selection into a track selection and re-emit + // + void slotLabelSelected(int position); + +protected: + + /** + * Populate the track buttons themselves with Instrument information + */ + void populateButtons(); + + /** + * Remove buttons and clear iterators for a position + */ + void removeButtons(unsigned int position); + + /** + * Set record button - graphically only + */ + void setRecordButton(int position, bool down); + + /** + * buttons, starting at the specified index + */ + void makeButtons(); + + QFrame* makeButton(TrackId trackId); + QString getPresentationName(Instrument *); + + void setButtonMapping(QObject*, TrackId); + + //--------------- Data members --------------------------------- + + RosegardenGUIDoc *m_doc; + + QVBoxLayout *m_layout; + + std::vector<KLedButton *> m_muteLeds; + std::vector<KLedButton *> m_recordLeds; + std::vector<TrackLabel *> m_trackLabels; + std::vector<TrackVUMeter *> m_trackMeters; + std::vector<QFrame *> m_trackHBoxes; + + QSignalMapper *m_recordSigMapper; + QSignalMapper *m_muteSigMapper; + QSignalMapper *m_clickedSigMapper; + QSignalMapper *m_instListSigMapper; + + // Number of tracks on our view + // + unsigned int m_tracks; + + // The pixel offset from the top - just to overcome + // the borders + int m_offset; + + // The height of the cells + // + int m_cellSize; + + // gaps between elements + // + int m_borderGap; + + int m_trackLabelWidth; + int m_popupItem; + + TrackLabel::InstrumentTrackLabels m_trackInstrumentLabels; + int m_lastSelected; +}; + + + +} + +#endif diff --git a/src/gui/editors/segment/TrackEditor.cpp b/src/gui/editors/segment/TrackEditor.cpp new file mode 100644 index 0000000..32c2b02 --- /dev/null +++ b/src/gui/editors/segment/TrackEditor.cpp @@ -0,0 +1,827 @@ +/* -*- 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 "TrackEditor.h" +#include <qlayout.h> +#include <kapplication.h> + +#include <klocale.h> +#include <kconfig.h> +#include <kstddirs.h> +#include "misc/Debug.h" +#include "document/ConfigGroups.h" +#include "gui/application/RosegardenDCOP.h" +#include "gui/seqmanager/SequenceManager.h" +#include "gui/rulers/StandardRuler.h" +#include "base/Composition.h" +#include "base/MidiProgram.h" +#include "base/RealTime.h" +#include "base/RulerScale.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "commands/segment/AddTracksCommand.h" +#include "commands/segment/DeleteTracksCommand.h" +#include "commands/segment/SegmentEraseCommand.h" +#include "commands/segment/SegmentInsertCommand.h" +#include "commands/segment/SegmentRepeatToCopyCommand.h" +#include "segmentcanvas/CompositionModel.h" +#include "segmentcanvas/CompositionModelImpl.h" +#include "segmentcanvas/CompositionView.h" +#include "document/MultiViewCommandHistory.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/application/RosegardenGUIApp.h" +#include "gui/rulers/ChordNameRuler.h" +#include "gui/rulers/TempoRuler.h" +#include "gui/rulers/LoopRuler.h" +#include "gui/widgets/ProgressDialog.h" +#include "gui/widgets/QDeferScrollView.h" +#include "sound/AudioFile.h" +#include "TrackButtons.h" +#include "TrackEditorIface.h" +#include <dcopobject.h> +#include <kcommand.h> +#include <kglobal.h> +#include <kmessagebox.h> +#include <qapplication.h> +#include <qcursor.h> +#include <qfont.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qscrollview.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qstrlist.h> +#include <qwidget.h> +#include <qvalidator.h> +#include <qdragobject.h> +#include <qtextstream.h> + + +namespace Rosegarden +{ + +TrackEditor::TrackEditor(RosegardenGUIDoc* doc, + QWidget* rosegardenguiview, + RulerScale *rulerScale, + bool showTrackLabels, + double initialUnitsPerPixel, + QWidget* parent, const char* name, + WFlags) : + DCOPObject("TrackEditorIface"), + QWidget(parent, name), + m_doc(doc), + m_rulerScale(rulerScale), + m_topStandardRuler(0), + m_bottomStandardRuler(0), + m_trackButtons(0), + m_segmentCanvas(0), + m_trackButtonScroll(0), + m_showTrackLabels(showTrackLabels), + m_canvasWidth(0), + m_compositionRefreshStatusId(doc->getComposition().getNewRefreshStatusId()), + m_playTracking(true), + m_initialUnitsPerPixel(initialUnitsPerPixel) +{ + // accept dnd + setAcceptDrops(true); + + init(rosegardenguiview); + slotReadjustCanvasSize(); +} + +TrackEditor::~TrackEditor() +{ + delete m_chordNameRuler; + delete m_compositionModel; +} + +void +TrackEditor::init(QWidget* rosegardenguiview) +{ + QGridLayout *grid = new QGridLayout(this, 4, 2); + + int trackLabelWidth = 230; + int barButtonsHeight = 25; + + m_chordNameRuler = new ChordNameRuler(m_rulerScale, + m_doc, + 0.0, + 20, + this); + grid->addWidget(m_chordNameRuler, 0, 1); + + m_tempoRuler = new TempoRuler(m_rulerScale, + m_doc, + RosegardenGUIApp::self(), + 0.0, + 24, + true, + this); + + grid->addWidget(m_tempoRuler, 1, 1); + + m_tempoRuler->connectSignals(); + + // + // Top Bar Buttons + // + m_topStandardRuler = new StandardRuler(m_doc, + m_rulerScale, + 0, + barButtonsHeight, + false, + this, "topbarbuttons"); + m_topStandardRuler->connectRulerToDocPointer(m_doc); + + grid->addWidget(m_topStandardRuler, 2, 1); + + // + // Segment Canvas + // + m_compositionModel = new CompositionModelImpl(m_doc->getComposition(), + m_doc->getStudio(), + m_rulerScale, getTrackCellHeight()); + + connect(rosegardenguiview, SIGNAL(instrumentParametersChanged(InstrumentId)), + m_compositionModel, SLOT(slotInstrumentParametersChanged(InstrumentId))); + connect(rosegardenguiview->parent(), SIGNAL(instrumentParametersChanged(InstrumentId)), + m_compositionModel, SLOT(slotInstrumentParametersChanged(InstrumentId))); + + m_segmentCanvas = new CompositionView(m_doc, m_compositionModel, this); + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + if (kapp->config()->readBoolEntry("backgroundtextures", true)) { + QPixmap background; + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + if (background.load(QString("%1/misc/bg-segmentcanvas.xpm"). + arg(pixmapDir))) { + m_segmentCanvas->setBackgroundPixmap(background); + m_segmentCanvas->viewport()->setBackgroundPixmap(background); + } + } + + // + // Bottom Bar Buttons + // + m_bottomStandardRuler = new StandardRuler(m_doc, + m_rulerScale, + 0, + barButtonsHeight, + true, + m_segmentCanvas, "bottombarbuttons"); + m_bottomStandardRuler->connectRulerToDocPointer(m_doc); + + m_segmentCanvas->setBottomFixedWidget(m_bottomStandardRuler); + + grid->addWidget(m_segmentCanvas, 3, 1); + + grid->setColStretch(1, 10); // to make sure the seg canvas doesn't leave a "blank" grey space when + // loading a file which has a low zoom factor + + // Track Buttons + // + // (must be put in a QScrollView) + // + m_trackButtonScroll = new QDeferScrollView(this); + grid->addWidget(m_trackButtonScroll, 3, 0); + + int canvasHeight = getTrackCellHeight() * + std::max(40u, m_doc->getComposition().getNbTracks()); + + m_trackButtons = new TrackButtons(m_doc, + getTrackCellHeight(), + trackLabelWidth, + m_showTrackLabels, + canvasHeight, + m_trackButtonScroll->viewport()); + m_trackButtonScroll->addChild(m_trackButtons); + m_trackButtonScroll->setHScrollBarMode(QScrollView::AlwaysOff); + m_trackButtonScroll->setVScrollBarMode(QScrollView::AlwaysOff); + m_trackButtonScroll->setResizePolicy(QScrollView::AutoOneFit); + m_trackButtonScroll->setBottomMargin(m_bottomStandardRuler->height() + + m_segmentCanvas->horizontalScrollBar()->height()); + + connect(m_trackButtons, SIGNAL(widthChanged()), + this, SLOT(slotTrackButtonsWidthChanged())); + + connect(m_trackButtons, SIGNAL(trackSelected(int)), + rosegardenguiview, SLOT(slotSelectTrackSegments(int))); + + connect(m_trackButtons, SIGNAL(instrumentSelected(int)), + rosegardenguiview, SLOT(slotUpdateInstrumentParameterBox(int))); + + connect(this, SIGNAL(stateChange(QString, bool)), + rosegardenguiview, SIGNAL(stateChange(QString, bool))); + + connect(m_trackButtons, SIGNAL(modified()), + m_doc, SLOT(slotDocumentModified())); + + connect(m_trackButtons, SIGNAL(muteButton(TrackId, bool)), + rosegardenguiview, SLOT(slotSetMuteButton(TrackId, bool))); + + // connect loop rulers' follow-scroll signals + connect(m_topStandardRuler->getLoopRuler(), SIGNAL(startMouseMove(int)), + m_segmentCanvas, SLOT(startAutoScroll(int))); + connect(m_topStandardRuler->getLoopRuler(), SIGNAL(stopMouseMove()), + m_segmentCanvas, SLOT(stopAutoScroll())); + connect(m_bottomStandardRuler->getLoopRuler(), SIGNAL(startMouseMove(int)), + m_segmentCanvas, SLOT(startAutoScroll(int))); + connect(m_bottomStandardRuler->getLoopRuler(), SIGNAL(stopMouseMove()), + m_segmentCanvas, SLOT(stopAutoScroll())); + + connect(m_segmentCanvas, SIGNAL(contentsMoving(int, int)), + this, SLOT(slotCanvasScrolled(int, int))); + + // Synchronize bar buttons' scrollview with segment canvas' scrollbar + // + connect(m_segmentCanvas->verticalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(slotVerticalScrollTrackButtons(int))); + + connect(m_segmentCanvas->verticalScrollBar(), SIGNAL(sliderMoved(int)), + this, SLOT(slotVerticalScrollTrackButtons(int))); + + // scrolling with mouse wheel + connect(m_trackButtonScroll, SIGNAL(gotWheelEvent(QWheelEvent*)), + m_segmentCanvas, SLOT(slotExternalWheelEvent(QWheelEvent*))); + + // Connect horizontal scrollbar + // + connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(valueChanged(int)), + m_topStandardRuler, SLOT(slotScrollHoriz(int))); + connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(sliderMoved(int)), + m_topStandardRuler, SLOT(slotScrollHoriz(int))); + + connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(valueChanged(int)), + m_bottomStandardRuler, SLOT(slotScrollHoriz(int))); + connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(sliderMoved(int)), + m_bottomStandardRuler, SLOT(slotScrollHoriz(int))); + + connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(valueChanged(int)), + m_tempoRuler, SLOT(slotScrollHoriz(int))); + connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(sliderMoved(int)), + m_tempoRuler, SLOT(slotScrollHoriz(int))); + + connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(valueChanged(int)), + m_chordNameRuler, SLOT(slotScrollHoriz(int))); + connect(m_segmentCanvas->horizontalScrollBar(), SIGNAL(sliderMoved(int)), + m_chordNameRuler, SLOT(slotScrollHoriz(int))); + + connect(this, SIGNAL(needUpdate()), m_segmentCanvas, SLOT(slotUpdateSegmentsDrawBuffer())); + + connect(m_segmentCanvas->getModel(), + SIGNAL(selectedSegments(const SegmentSelection &)), + rosegardenguiview, + SLOT(slotSelectedSegments(const SegmentSelection &))); + + connect(m_segmentCanvas, SIGNAL(zoomIn()), + RosegardenGUIApp::self(), SLOT(slotZoomIn())); + connect(m_segmentCanvas, SIGNAL(zoomOut()), + RosegardenGUIApp::self(), SLOT(slotZoomOut())); + + connect(getCommandHistory(), SIGNAL(commandExecuted()), + this, SLOT(update())); + + connect(m_doc, SIGNAL(pointerPositionChanged(timeT)), + this, SLOT(slotSetPointerPosition(timeT))); + + // + // pointer and loop drag signals from top and bottom bar buttons (loop rulers actually) + // + connect(m_topStandardRuler, SIGNAL(dragPointerToPosition(timeT)), + this, SLOT(slotPointerDraggedToPosition(timeT))); + connect(m_bottomStandardRuler, SIGNAL(dragPointerToPosition(timeT)), + this, SLOT(slotPointerDraggedToPosition(timeT))); + + connect(m_topStandardRuler, SIGNAL(dragLoopToPosition(timeT)), + this, SLOT(slotLoopDraggedToPosition(timeT))); + connect(m_bottomStandardRuler, SIGNAL(dragLoopToPosition(timeT)), + this, SLOT(slotLoopDraggedToPosition(timeT))); + + connect(m_doc, SIGNAL(loopChanged(timeT, + timeT)), + this, SLOT(slotSetLoop(timeT, timeT))); +} + +void TrackEditor::slotReadjustCanvasSize() +{ + m_segmentCanvas->slotUpdateSize(); +} + +void TrackEditor::slotTrackButtonsWidthChanged() +{ + // We need to make sure the trackButtons geometry is fully updated + // + ProgressDialog::processEvents(); + + m_trackButtonScroll->setMinimumWidth(m_trackButtons->width()); + m_doc->slotDocumentModified(); +} + +int TrackEditor::getTrackCellHeight() const +{ + int size; + static QFont defaultFont; + + // do some scrabbling around for a reasonable size + // + size = defaultFont.pixelSize(); + + if (size < 8) { + if (QApplication::font(this).pixelSize() < 8) + size = 12; + else + size = QApplication::font(this).pixelSize(); + } + + return size + 12; +} + +bool TrackEditor::isCompositionModified() +{ + return m_doc->getComposition().getRefreshStatus + (m_compositionRefreshStatusId).needsRefresh(); +} + +void TrackEditor::setCompositionModified(bool c) +{ + m_doc->getComposition().getRefreshStatus + (m_compositionRefreshStatusId).setNeedsRefresh(c); +} + +void TrackEditor::updateRulers() +{ + if (getTempoRuler() != 0) + getTempoRuler()->update(); + + if (getChordNameRuler() != 0) + getChordNameRuler()->update(); + + getTopStandardRuler()->update(); + getBottomStandardRuler()->update(); +} + +void TrackEditor::paintEvent(QPaintEvent* e) +{ + if (isCompositionModified()) { + + slotReadjustCanvasSize(); + m_trackButtons->slotUpdateTracks(); + m_segmentCanvas->clearSegmentRectsCache(true); + m_segmentCanvas->updateContents(); + + Composition &composition = m_doc->getComposition(); + + if (composition.getNbSegments() == 0) { + emit stateChange("have_segments", false); // no segments : reverse state + emit stateChange("have_selection", false); // no segments : reverse state + } else { + emit stateChange("have_segments", true); + if (m_segmentCanvas->haveSelection()) + emit stateChange("have_selection", true); + else + emit stateChange("have_selection", false); // no selection : reverse state + } + + setCompositionModified(false); + } + + QWidget::paintEvent(e); +} + +void TrackEditor::slotAddTracks(unsigned int nbNewTracks, + InstrumentId id, + int position) +{ + Composition &comp = m_doc->getComposition(); + + AddTracksCommand* command = new AddTracksCommand(&comp, nbNewTracks, id, + position); + addCommandToHistory(command); + slotReadjustCanvasSize(); +} + +void TrackEditor::slotDeleteTracks(std::vector<TrackId> tracks) +{ + Composition &comp = m_doc->getComposition(); + + DeleteTracksCommand* command = new DeleteTracksCommand(&comp, tracks); + addCommandToHistory(command); +} + +void TrackEditor::addSegment(int track, int time, unsigned int duration) +{ + if (!m_doc) + return ; // sanity check + + SegmentInsertCommand *command = + new SegmentInsertCommand(m_doc, track, time, duration); + + addCommandToHistory(command); +} + +void TrackEditor::slotSegmentOrderChanged(int section, int fromIdx, int toIdx) +{ + RG_DEBUG << QString("TrackEditor::segmentOrderChanged(section : %1, from %2, to %3)") + .arg(section).arg(fromIdx).arg(toIdx) << endl; + + //!!! how do we get here? need to involve a command + emit needUpdate(); +} + +void +TrackEditor::slotCanvasScrolled(int x, int y) +{ + // update the pointer position if the user is dragging it from the loop ruler + if ((m_topStandardRuler && m_topStandardRuler->getLoopRuler() && + m_topStandardRuler->getLoopRuler()->hasActiveMousePress() && + !m_topStandardRuler->getLoopRuler()->getLoopingMode()) || + (m_bottomStandardRuler && m_bottomStandardRuler->getLoopRuler() && + m_bottomStandardRuler->getLoopRuler()->hasActiveMousePress() && + !m_bottomStandardRuler->getLoopRuler()->getLoopingMode())) { + + int mx = m_segmentCanvas->viewport()->mapFromGlobal(QCursor::pos()).x(); + m_segmentCanvas->setPointerPos(x + mx); + + // bad idea, creates a feedback loop + // timeT t = m_segmentCanvas->grid().getRulerScale()->getTimeForX(x + mx); + // slotSetPointerPosition(t); + } +} + +void +TrackEditor::slotSetPointerPosition(timeT position) +{ + SimpleRulerScale *ruler = + dynamic_cast<SimpleRulerScale*>(m_rulerScale); + + if (!ruler) + return ; + + double pos = m_segmentCanvas->grid().getRulerScale()->getXForTime(position); + + int currentPointerPos = m_segmentCanvas->getPointerPos(); + + double distance = pos - currentPointerPos; + if (distance < 0.0) + distance = -distance; + + if (distance >= 1.0) { + + if (m_doc && m_doc->getSequenceManager() && + (m_doc->getSequenceManager()->getTransportStatus() != STOPPED)) { + + if (m_playTracking) { + getSegmentCanvas()->slotScrollHoriz(int(double(position) / ruler->getUnitsPerPixel())); + } + } else if (!getSegmentCanvas()->isAutoScrolling()) { + int newpos = int(double(position) / ruler->getUnitsPerPixel()); + // RG_DEBUG << "TrackEditor::slotSetPointerPosition(" + // << position + // << ") : calling canvas->slotScrollHoriz() " + // << newpos << endl; + getSegmentCanvas()->slotScrollHoriz(newpos); + } + + m_segmentCanvas->setPointerPos(pos); + } + +} + +void +TrackEditor::slotPointerDraggedToPosition(timeT position) +{ + int currentPointerPos = m_segmentCanvas->getPointerPos(); + + double newPosition; + + if (handleAutoScroll(currentPointerPos, position, newPosition)) + m_segmentCanvas->setPointerPos(int(newPosition)); +} + +void +TrackEditor::slotLoopDraggedToPosition(timeT position) +{ + if (m_doc) { + int currentEndLoopPos = m_doc->getComposition().getLoopEnd(); + double dummy; + handleAutoScroll(currentEndLoopPos, position, dummy); + } +} + +bool TrackEditor::handleAutoScroll(int currentPosition, timeT newTimePosition, double &newPosition) +{ + SimpleRulerScale *ruler = + dynamic_cast<SimpleRulerScale*>(m_rulerScale); + + if (!ruler) + return false; + + newPosition = m_segmentCanvas->grid().getRulerScale()->getXForTime(newTimePosition); + + double distance = fabs(newPosition - currentPosition); + + bool moveDetected = distance >= 1.0; + + if (moveDetected) { + + if (m_doc && m_doc->getSequenceManager() && + (m_doc->getSequenceManager()->getTransportStatus() != STOPPED)) { + + if (m_playTracking) { + getSegmentCanvas()->slotScrollHoriz(int(double(newTimePosition) / ruler->getUnitsPerPixel())); + } + } else { + int newpos = int(double(newTimePosition) / ruler->getUnitsPerPixel()); + getSegmentCanvas()->slotScrollHorizSmallSteps(newpos); + getSegmentCanvas()->doAutoScroll(); + } + + } + + return moveDetected; +} + +void +TrackEditor::slotToggleTracking() +{ + m_playTracking = !m_playTracking; +} + +void +TrackEditor::slotSetLoop(timeT start, timeT end) +{ + getTopStandardRuler()->getLoopRuler()->slotSetLoopMarker(start, end); + getBottomStandardRuler()->getLoopRuler()->slotSetLoopMarker(start, end); +} + +MultiViewCommandHistory* +TrackEditor::getCommandHistory() +{ + return m_doc->getCommandHistory(); +} + +void +TrackEditor::addCommandToHistory(KCommand *command) +{ + getCommandHistory()->addCommand(command); +} + +void +TrackEditor::slotScrollToTrack(int track) +{ + // Find the vertical track pos + int newY = track * getTrackCellHeight(); + + RG_DEBUG << "TrackEditor::scrollToTrack(" << track << + ") scrolling to Y " << newY << endl; + + // Scroll the segment view; it will scroll tracks by connected signals + // slotVerticalScrollTrackButtons(newY); + m_segmentCanvas->slotScrollVertSmallSteps(newY); +} + +void +TrackEditor::slotDeleteSelectedSegments() +{ + KMacroCommand *macro = new KMacroCommand("Delete Segments"); + + SegmentSelection segments = + m_segmentCanvas->getSelectedSegments(); + + if (segments.size() == 0) + return ; + + SegmentSelection::iterator it; + + // Clear the selection before erasing the Segments + // the selection points to + // + m_segmentCanvas->getModel()->clearSelected(); + + // Create the compound command + // + for (it = segments.begin(); it != segments.end(); it++) { + macro->addCommand(new SegmentEraseCommand(*it, + &m_doc->getAudioFileManager())); + } + + addCommandToHistory(macro); + +} + +void +TrackEditor::slotTurnRepeatingSegmentToRealCopies() +{ + RG_DEBUG << "TrackEditor::slotTurnRepeatingSegmentToRealCopies" << endl; + + SegmentSelection segments = + m_segmentCanvas->getSelectedSegments(); + + if (segments.size() == 0) + return ; + + QString text; + + if (segments.size() == 1) + text = i18n("Turn Repeating Segment into Real Copies"); + else + text = i18n("Turn Repeating Segments into Real Copies"); + + KMacroCommand *macro = new KMacroCommand(text); + + SegmentSelection::iterator it = segments.begin(); + for (; it != segments.end(); it++) { + if ((*it)->isRepeating()) { + macro->addCommand(new SegmentRepeatToCopyCommand(*it)); + } + } + + addCommandToHistory(macro); + +} + +void +TrackEditor::slotVerticalScrollTrackButtons(int y) +{ + m_trackButtonScroll->setContentsPos(0, y); +} + +void TrackEditor::dragEnterEvent(QDragEnterEvent *event) +{ + event->accept(QUriDrag::canDecode(event) || + QTextDrag::canDecode(event)); +} + +void TrackEditor::dropEvent(QDropEvent* event) +{ + QStrList uri; + QString text; + + int heightAdjust = 0; + //int widthAdjust = 0; + + // Adjust any drop event height position by visible rulers + // + if (m_topStandardRuler && m_topStandardRuler->isVisible()) + heightAdjust += m_topStandardRuler->height(); + + if (m_tempoRuler && m_tempoRuler->isVisible()) + heightAdjust += m_tempoRuler->height(); + + if (m_chordNameRuler && m_chordNameRuler->isVisible()) + heightAdjust += m_chordNameRuler->height(); + + QPoint posInSegmentCanvas = + m_segmentCanvas->viewportToContents + (m_segmentCanvas-> + viewport()->mapFrom(this, event->pos())); + + int trackPos = m_segmentCanvas->grid().getYBin(posInSegmentCanvas.y()); + + timeT time = +// m_segmentCanvas->grid().getRulerScale()-> +// getTimeForX(posInSegmentCanvas.x()); + m_segmentCanvas->grid().snapX(posInSegmentCanvas.x()); + + + if (QUriDrag::decode(event, uri)) { + RG_DEBUG << "TrackEditor::dropEvent() : got URI :" + << uri.first() << endl; + QString uriPath = uri.first(); + + if (uriPath.endsWith(".rg")) { + emit droppedDocument(uriPath); + } else { + + QStrList uris; + QString uri; + if (QUriDrag::decode(event, uris)) uri = uris.first(); +// QUriDrag::decodeLocalFiles(event, files); +// QString filePath = files.first(); + + RG_DEBUG << "TrackEditor::dropEvent() : got URI: " + << uri << endl; + + RG_DEBUG << "TrackEditor::dropEvent() : dropping at track pos = " + << trackPos + << ", time = " + << time + << ", x = " + << event->pos().x() + << ", mapped x = " + << posInSegmentCanvas.x() + << endl; + + Track* track = m_doc->getComposition().getTrackByPosition(trackPos); + if (track) { + QString audioText; + QTextOStream t(&audioText); + + t << uri << "\n"; + t << track->getId() << "\n"; + t << time << "\n"; + + emit droppedNewAudio(audioText); + } + + } + + } else if (QTextDrag::decode(event, text)) { + RG_DEBUG << "TrackEditor::dropEvent() : got text info " << endl; + //<< text << endl; + + if (text.endsWith(".rg")) { + emit droppedDocument(text); + // + // WARNING + // + // DO NOT PERFORM ANY OPERATIONS AFTER THAT + // EMITTING THIS SIGNAL TRIGGERS THE LOADING OF A NEW DOCUMENT + // AND AS A CONSEQUENCE THE DELETION OF THIS TrackEditor OBJECT + // + } else { + + QTextIStream s(&text); + + QString id; + AudioFileId audioFileId; + RealTime startTime, endTime; + + // read the audio info checking for end of stream + s >> id; + s >> audioFileId; + s >> startTime.sec; + s >> startTime.nsec; + s >> endTime.sec; + s >> endTime.nsec; + + if (id == "AudioFileManager") { // only create something if this is data from the right client + + + // Drop this audio segment if we have a valid track number + // (could also check for time limits too) + // + Track* track = m_doc->getComposition().getTrackByPosition(trackPos); + if (track) { + + RG_DEBUG << "TrackEditor::dropEvent() : dropping at track pos = " + << trackPos + << ", time = " + << time + << ", x = " + << event->pos().x() + << ", map = " + << posInSegmentCanvas.x() + << endl; + + QString audioText; + QTextOStream t(&audioText); + t << audioFileId << "\n"; + t << track->getId() << "\n"; + t << time << "\n"; // time on canvas + t << startTime.sec << "\n"; + t << startTime.nsec << "\n"; + t << endTime.sec << "\n"; + t << endTime.nsec << "\n"; + + emit droppedAudio(audioText); + } + + } else { + + KMessageBox::sorry(this, i18n("You can't drop files into Rosegarden from this client. Try using Konqueror instead.")); + + } + + } + + // SEE WARNING ABOVE - DON'T DO ANYTHING, THIS OBJECT MAY NOT + // EXIST AT THIS POINT. + + } +} + +} +#include "TrackEditor.moc" diff --git a/src/gui/editors/segment/TrackEditor.h b/src/gui/editors/segment/TrackEditor.h new file mode 100644 index 0000000..6670a15 --- /dev/null +++ b/src/gui/editors/segment/TrackEditor.h @@ -0,0 +1,248 @@ + +/* -*- 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_TRACKEDITOR_H_ +#define _RG_TRACKEDITOR_H_ + +#include "base/MidiProgram.h" +#include <map> +#include "TrackEditorIface.h" +#include <qstring.h> +#include <qwidget.h> +#include "base/Event.h" +#include "gui/editors/segment/TrackButtons.h" + + +class QPaintEvent; +class QDropEvent; +class QDragEnterEvent; +class KCommand; + + +namespace Rosegarden +{ + +class TrackButtons; +class TempoRuler; +class Segment; +class RulerScale; +class RosegardenGUIDoc; +class QDeferScrollView; +class MultiViewCommandHistory; +class CompositionView; +class CompositionModel; +class ChordNameRuler; +class StandardRuler; + + +/** + * Global widget for segment edition. + * + * Shows a global overview of the composition, and lets the user + * manipulate the segments + * + * @see CompositionView + */ +class TrackEditor : public QWidget, virtual public TrackEditorIface +{ + Q_OBJECT +public: + /** + * Create a new TrackEditor representing the document \a doc + */ + TrackEditor(RosegardenGUIDoc* doc, + QWidget* rosegardenguiview, + RulerScale *rulerScale, + bool showTrackLabels, + double initialUnitsPerPixel = 0, + QWidget* parent = 0, const char* name = 0, + WFlags f=0); + + ~TrackEditor(); + + CompositionView* getSegmentCanvas() { return m_segmentCanvas; } + TempoRuler* getTempoRuler() { return m_tempoRuler; } + ChordNameRuler*getChordNameRuler() { return m_chordNameRuler; } + StandardRuler* getTopStandardRuler() { return m_topStandardRuler; } + StandardRuler* getBottomStandardRuler() { return m_bottomStandardRuler; } + TrackButtons* getTrackButtons() { return m_trackButtons; } + RulerScale* getRulerScale() { return m_rulerScale; } + + int getTrackCellHeight() const; + + /** + * Add a new segment - DCOP interface + */ + virtual void addSegment(int track, int start, unsigned int duration); + + /** + * Manage command history + */ + MultiViewCommandHistory *getCommandHistory(); + void addCommandToHistory(KCommand *command); + + void updateRulers(); + + bool isTracking() const { return m_playTracking; } + +public slots: + + /** + * Scroll the view such that the numbered track is on-screen + */ + void slotScrollToTrack(int trackPosition); + + /** + * Set the position pointer during playback + */ + void slotSetPointerPosition(timeT position); + + /** + * Update the pointer position as it is being dragged along + * This changes how the segment canvas will scroll to follow the pointer + */ + void slotPointerDraggedToPosition(timeT position); + + /** + * Update the loop end position as it is being dragged along + * This changes how the segment canvas will scroll to follow the pointer + */ + void slotLoopDraggedToPosition(timeT position); + + /** + * Act on a canvas scroll event + */ + void slotCanvasScrolled(int, int); + + /** + * Adjust the canvas size to that required for the composition + */ + void slotReadjustCanvasSize(); + + /** + * Show the given loop on the ruler or wherever + */ + void slotSetLoop(timeT start, timeT end); + + /** + * Add given number of tracks + */ + void slotAddTracks(unsigned int nbTracks, InstrumentId id, int position); + + /* + * Delete a given track + */ + void slotDeleteTracks(std::vector<TrackId> tracks); + + void slotDeleteSelectedSegments(); + void slotTurnRepeatingSegmentToRealCopies(); + + void slotToggleTracking(); + +protected slots: + void slotSegmentOrderChanged(int section, int fromIdx, int toIdx); + + void slotTrackButtonsWidthChanged(); + + /// Scroll the track buttons along with the segment canvas + void slotVerticalScrollTrackButtons(int y); + +signals: + /** + * Emitted when the represented data changed and the SegmentCanvas + * needs to update itself + * + * @see SegmentCanvas::update() + */ + void needUpdate(); + + /** + * sent back to RosegardenGUI + */ + void stateChange(QString, bool); + + /** + * A URI to a Rosegarden document was dropped on the canvas + * + * @see RosegardenGUI#slotOpenURL() + */ + void droppedDocument(QString uri); + + /** + * An audio file was dropped from the audio manager dialog + */ + void droppedAudio(QString audioDesc); + + /** + * And audio file was dropped from konqi say and needs to be + * inserted into AudioManagerDialog before adding to the + * composition. + */ + void droppedNewAudio(QString audioDesc); + +protected: + + virtual void dragEnterEvent(QDragEnterEvent *event); + virtual void dropEvent(QDropEvent*); + + virtual void paintEvent(QPaintEvent* e); + + void init(QWidget *); + + bool isCompositionModified(); + void setCompositionModified(bool); + + /// return true if an actual move occurred between current and new position, newPosition contains the horiz. pos corresponding to newTimePosition + bool handleAutoScroll(int currentPosition, timeT newTimePosition, double& newPosition); + + //--------------- Data members --------------------------------- + + RosegardenGUIDoc *m_doc; + RulerScale *m_rulerScale; + TempoRuler *m_tempoRuler; + ChordNameRuler *m_chordNameRuler; + StandardRuler *m_topStandardRuler; + StandardRuler *m_bottomStandardRuler; + TrackButtons *m_trackButtons; + CompositionView *m_segmentCanvas; + CompositionModel *m_compositionModel; + QDeferScrollView *m_trackButtonScroll; + + bool m_showTrackLabels; + unsigned int m_canvasWidth; + unsigned int m_compositionRefreshStatusId; + bool m_playTracking; + + typedef std::map<Segment *, unsigned int> + SegmentRefreshStatusIdMap; + SegmentRefreshStatusIdMap m_segmentsRefreshStatusIds; + + double m_initialUnitsPerPixel; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/TrackEditorIface.cpp b/src/gui/editors/segment/TrackEditorIface.cpp new file mode 100644 index 0000000..8238c1d --- /dev/null +++ b/src/gui/editors/segment/TrackEditorIface.cpp @@ -0,0 +1,33 @@ +/* -*- 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 "TrackEditorIface.h" + +#include <dcopobject.h> + + +namespace Rosegarden +{ +} diff --git a/src/gui/editors/segment/TrackEditorIface.h b/src/gui/editors/segment/TrackEditorIface.h new file mode 100644 index 0000000..1637591 --- /dev/null +++ b/src/gui/editors/segment/TrackEditorIface.h @@ -0,0 +1,55 @@ + +/* -*- 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_TRACKEDITORIFACE_H_ +#define _RG_TRACKEDITORIFACE_H_ + +#include <dcopobject.h> + + + + +namespace Rosegarden +{ + + + +/** + * TrackEditor DCOP Interface + * + * @see TrackEditor + */ +class TrackEditorIface : virtual public DCOPObject +{ + K_DCOP +public: +k_dcop: + virtual void addSegment(int instrument, int start, unsigned int length) = 0; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/TrackHeader.cpp b/src/gui/editors/segment/TrackHeader.cpp new file mode 100644 index 0000000..d7ca6d3 --- /dev/null +++ b/src/gui/editors/segment/TrackHeader.cpp @@ -0,0 +1,64 @@ +/* -*- 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 "TrackHeader.h" + +#include <qheader.h> +#include <qpainter.h> +#include <qrect.h> +#include <qwidget.h> + + +namespace Rosegarden +{ + +TrackHeader::~TrackHeader() +{} + +void +TrackHeader::paintEvent(QPaintEvent *e) +{ + QPainter p( this ); + p.setPen( colorGroup().buttonText() ); + int pos = (orientation() == Horizontal) + ? e->rect().left() + : e->rect().top(); + int id = mapToIndex( sectionAt( pos + offset() ) ); + if ( id < 0 ) + if ( pos > 0 ) + return ; + else + id = 0; + for ( int i = id; i < count(); i++ ) { + QRect r = sRect( i ); + paintSection( &p, i, r ); + if ( orientation() == Horizontal && r. right() >= e->rect().right() || + orientation() == Vertical && r. bottom() >= e->rect().bottom() ) + return ; + } + +} + +} diff --git a/src/gui/editors/segment/TrackHeader.h b/src/gui/editors/segment/TrackHeader.h new file mode 100644 index 0000000..fe404c3 --- /dev/null +++ b/src/gui/editors/segment/TrackHeader.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_TRACKHEADER_H_ +#define _RG_TRACKHEADER_H_ + +#include <qheader.h> + + +class QWidget; +class QPaintEvent; + + +namespace Rosegarden +{ + + + +class TrackHeader : public QHeader +{ + +public: + TrackHeader(int number, + QWidget *parent=0, + const char *name=0 ): + QHeader(number, parent, name) {;} + ~TrackHeader(); + +protected: + virtual void paintEvent(QPaintEvent *pe); +// void paintSection(QPainter * p, int index, QRect fr); +// void paintSectionLabel (QPainter * p, int index, const QRect & fr); +// QRect sRect (int index); + +private: + +}; + + + +} + +#endif diff --git a/src/gui/editors/segment/TrackLabel.cpp b/src/gui/editors/segment/TrackLabel.cpp new file mode 100644 index 0000000..90561d1 --- /dev/null +++ b/src/gui/editors/segment/TrackLabel.cpp @@ -0,0 +1,203 @@ +/* -*- 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 "TrackLabel.h" + +#include <klocale.h> +#include "base/Track.h" +#include <klineeditdlg.h> +#include <qfont.h> +#include <qframe.h> +#include <qlabel.h> +#include <qregexp.h> +#include <qstring.h> +#include <qtimer.h> +#include <qtooltip.h> +#include <qwidget.h> +#include <qwidgetstack.h> +#include <qvalidator.h> + + +namespace Rosegarden +{ + +TrackLabel::TrackLabel(TrackId id, + int position, + QWidget *parent, + const char *name): + QWidgetStack(parent, name), + m_instrumentLabel(new QLabel(this)), + m_trackLabel(new QLabel(this)), + m_id(id), + m_position(position) +{ + QFont font; + font.setPointSize(font.pointSize() * 95 / 100); + if (font.pixelSize() > 14) + font.setPixelSize(14); + font.setBold(false); + m_instrumentLabel->setFont(font); + m_trackLabel->setFont(font); + + addWidget(m_instrumentLabel, ShowInstrument); + addWidget(m_trackLabel, ShowTrack); + raiseWidget(ShowTrack); + + m_instrumentLabel->setFrameShape(QFrame::NoFrame); + m_trackLabel->setFrameShape(QFrame::NoFrame); + + m_pressTimer = new QTimer(this); + + connect(m_pressTimer, SIGNAL(timeout()), + this, SIGNAL(changeToInstrumentList())); + + QToolTip::add + (this, i18n("Click and hold with left mouse button to assign this Track to an Instrument.")); + +} + +TrackLabel::~TrackLabel() +{} + +void TrackLabel::setIndent(int i) +{ + m_instrumentLabel->setIndent(i); + m_trackLabel->setIndent(i); +} + +void TrackLabel::setAlternativeLabel(const QString &label) +{ + // recover saved original + if (label.isEmpty()) { + + if (!m_alternativeLabel.isEmpty()) + m_instrumentLabel->setText(m_alternativeLabel); + + // do nothing if we've got nothing to swap + return ; + } + + // Store the current (first) label + // + if (m_alternativeLabel.isEmpty()) + m_alternativeLabel = m_instrumentLabel->text(); + + // set new label + m_instrumentLabel->setText(label); +} + +void TrackLabel::clearAlternativeLabel() +{ + m_alternativeLabel = ""; +} + +void TrackLabel::showLabel(InstrumentTrackLabels l) +{ + raiseWidget(l); +} + +void +TrackLabel::setSelected(bool on) +{ + if (on) { + m_selected = true; + + m_instrumentLabel->setPaletteBackgroundColor(colorGroup().highlight()); + m_instrumentLabel->setPaletteForegroundColor(colorGroup().highlightedText()); + m_trackLabel->setPaletteBackgroundColor(colorGroup().highlight()); + m_trackLabel->setPaletteForegroundColor(colorGroup().highlightedText()); + + } else { + m_selected = false; + + m_instrumentLabel->setPaletteBackgroundColor(colorGroup().background()); + m_trackLabel->setPaletteBackgroundColor(colorGroup().background()); + m_instrumentLabel->setPaletteForegroundColor(colorGroup().text()); + m_trackLabel->setPaletteForegroundColor(colorGroup().text()); + } + if (visibleWidget()) + visibleWidget()->update(); +} + +void +TrackLabel::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == RightButton) { + + emit clicked(); + emit changeToInstrumentList(); + + } else if (e->button() == LeftButton) { + + // start a timer on this hold + m_pressTimer->start(200, true); // 200ms, single shot + } +} + +void +TrackLabel::mouseReleaseEvent(QMouseEvent *e) +{ + // stop the timer if running + if (m_pressTimer->isActive()) + m_pressTimer->stop(); + + if (e->button() == LeftButton) { + emit clicked(); + } +} + +void +TrackLabel::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() != LeftButton) + return ; + + // Highlight this label alone and cheat using + // the clicked signal + // + emit clicked(); + + // Just in case we've got our timing wrong - reapply + // this label highlight + // + setSelected(true); + + bool ok = false; + + QRegExpValidator validator(QRegExp(".*"), this); // empty is OK + + QString newText = KLineEditDlg::getText(i18n("Change track name"), + i18n("Enter new track name"), + m_trackLabel->text(), + &ok, + this, + &validator); + + if ( ok ) + emit renameTrack(newText, m_id); +} + +} +#include "TrackLabel.moc" diff --git a/src/gui/editors/segment/TrackLabel.h b/src/gui/editors/segment/TrackLabel.h new file mode 100644 index 0000000..e56d0e5 --- /dev/null +++ b/src/gui/editors/segment/TrackLabel.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_TRACKLABEL_H_ +#define _RG_TRACKLABEL_H_ + +#include "base/Track.h" +#include <qstring.h> +#include <qwidgetstack.h> + + +class QWidget; +class QTimer; +class QMouseEvent; +class QLabel; + + +namespace Rosegarden +{ + + + +/** + * Specialises QLabel to create in effect a toggleable and hence + * selectable label/label list. In conjunction with TrackButtons + * provides a framework for Track selection on the TrackCanvas. + */ +class TrackLabel : public QWidgetStack +{ +Q_OBJECT +public: + + enum InstrumentTrackLabels + { + ShowTrack, + ShowInstrument, + ShowBoth + }; + + TrackLabel(TrackId id, + int position, + QWidget *parent, + const char *name=0); + + ~TrackLabel(); + + // QLabel API delegation - applies on both labels + void setIndent(int); + + QLabel* getInstrumentLabel() { return m_instrumentLabel; } + QLabel* getTrackLabel() { return m_trackLabel; } + void setAlternativeLabel(const QString &label); + void clearAlternativeLabel(); + void showLabel(InstrumentTrackLabels); + + // Encapsulates setting the label to highlighted or not + // + void setSelected(bool on); + bool isSelected() const { return m_selected; } + + void setId(TrackId id) { m_id = id; } + TrackId getId() const { return m_id; } + + int getPosition() const { return m_position; } + void setPosition(int position) { m_position = position; } + +signals: + void clicked(); + + // We emit this once we've renamed a track + // + void renameTrack(QString, TrackId); + + void changeToInstrumentList(); + +protected: + + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + + QLabel* getVisibleLabel(); + + //--------------- Data members --------------------------------- + + QLabel *m_instrumentLabel; + QLabel *m_trackLabel; + QString m_alternativeLabel; + + TrackId m_id; + int m_position; + bool m_selected; + + QTimer *m_pressTimer; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/TrackVUMeter.cpp b/src/gui/editors/segment/TrackVUMeter.cpp new file mode 100644 index 0000000..a638ee7 --- /dev/null +++ b/src/gui/editors/segment/TrackVUMeter.cpp @@ -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. +*/ + + +#include "TrackVUMeter.h" + +#include "gui/widgets/VUMeter.h" +#include <qfont.h> +#include <qstring.h> +#include <qwidget.h> + + +namespace Rosegarden +{ + +TrackVUMeter::TrackVUMeter(QWidget *parent, + VUMeterType type, + int width, + int height, + int position, + const char *name): + VUMeter(parent, type, false, false, width, height, VUMeter::Horizontal, name), + m_position(position), m_textHeight(12) +{ + setAlignment(AlignCenter); + + QFont font; + font.setPointSize(font.pointSize() * 95 / 100); + if (font.pointSize() > 14) + font.setPointSize(14); + font.setBold(false); + setFont(font); +} + +void +TrackVUMeter::meterStart() +{ + clear(); + setMinimumHeight(m_originalHeight); + setMaximumHeight(m_originalHeight); + m_active = true; +} + +void +TrackVUMeter::meterStop() +{ + setMinimumHeight(m_textHeight); + setMaximumHeight(m_textHeight); + setText(QString("%1").arg(m_position + 1)); + if (m_active) { + m_active = false; + update(); + } +} + +} diff --git a/src/gui/editors/segment/TrackVUMeter.h b/src/gui/editors/segment/TrackVUMeter.h new file mode 100644 index 0000000..26b8e4e --- /dev/null +++ b/src/gui/editors/segment/TrackVUMeter.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_TRACKVUMETER_H_ +#define _RG_TRACKVUMETER_H_ + +#include "gui/widgets/VUMeter.h" + + +class QWidget; + + +namespace Rosegarden +{ + + + +class TrackVUMeter: public VUMeter +{ +public: + TrackVUMeter(QWidget *parent = 0, + VUMeterType type = Plain, + int width = 0, + int height = 0, + int position = 0, + const char *name = 0); + + int getPosition() const { return m_position; } + +protected: + virtual void meterStart(); + virtual void meterStop(); + +private: + int m_position; + int m_textHeight; + +}; + + +} + +#endif diff --git a/src/gui/editors/segment/TriggerManagerItem.cpp b/src/gui/editors/segment/TriggerManagerItem.cpp new file mode 100644 index 0000000..2e7402d --- /dev/null +++ b/src/gui/editors/segment/TriggerManagerItem.cpp @@ -0,0 +1,60 @@ +/* -*- 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 "TriggerManagerItem.h" + +namespace Rosegarden { + +int +TriggerManagerItem::compare(QListViewItem * i, int col, bool ascending) const +{ + TriggerManagerItem *ei = + dynamic_cast<TriggerManagerItem *>(i); + + if (!ei) return QListViewItem::compare(i, col, ascending); + + // col 0 -> index -- numeric compare + // col 1 -> ID -- numeric compare + // col 2 -> label -- default string compare + // col 3 -> duration -- raw duration compare + // col 4 -> base pitch -- pitch compare + // col 5 -> base velocity -- numeric compare + // col 6 -> usage count -- numeric compare + // + if (col == 2) { + return QListViewItem::compare(i, col, ascending); + } else if (col == 3) { + if (m_rawDuration < ei->getRawDuration()) return -1; + else if (ei->getRawDuration() < m_rawDuration) return 1; + else return 0; + } else if (col == 4) { + if (m_pitch < ei->getPitch()) return -1; + else if (ei->getPitch() < m_pitch) return 1; + else return 0; + } else { + return key(col, ascending).toInt() - i->key(col, ascending).toInt(); + } +} + +} diff --git a/src/gui/editors/segment/TriggerManagerItem.h b/src/gui/editors/segment/TriggerManagerItem.h new file mode 100644 index 0000000..c1eb95a --- /dev/null +++ b/src/gui/editors/segment/TriggerManagerItem.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_TRIGGERMANAGERITEM_H_ +#define _RG_TRIGGERMANAGERITEM_H_ + +#include <klistview.h> + +#include "base/Event.h" +#include "base/TriggerSegment.h" + +namespace Rosegarden { + +class TriggerManagerItem : public QListViewItem +{ +public: + TriggerManagerItem(QListView * parent, QString label1, + QString label2 = QString::null, + QString label3 = QString::null, + QString label4 = QString::null, + QString label5 = QString::null, + QString label6 = QString::null, + QString label7 = QString::null, + QString label8 = QString::null): + QListViewItem(parent, label1, label2, label3, label4, + label5, label6, label7, label8) { ; } + + virtual int compare(QListViewItem * i, int col, bool ascending) const; + + void setRawDuration(timeT raw) { m_rawDuration = raw; } + timeT getRawDuration() const { return m_rawDuration; } + + void setId(TriggerSegmentId id) { m_id = id; } + TriggerSegmentId getId() const { return m_id; } + + void setUsage(int usage) { m_usage = usage; } + int getUsage() const { return m_usage; } + + void setPitch(int pitch) { m_pitch = pitch; } + int getPitch() const { return m_pitch; } + +protected: + timeT m_rawDuration; + TriggerSegmentId m_id; + int m_usage; + int m_pitch; +}; + +} + +#endif diff --git a/src/gui/editors/segment/TriggerSegmentManager.cpp b/src/gui/editors/segment/TriggerSegmentManager.cpp new file mode 100644 index 0000000..3fb1732 --- /dev/null +++ b/src/gui/editors/segment/TriggerSegmentManager.cpp @@ -0,0 +1,576 @@ +/* -*- 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 "TriggerSegmentManager.h" +#include "TriggerManagerItem.h" +#include <qlayout.h> +#include <kapplication.h> + +#include "base/BaseProperties.h" +#include <klocale.h> +#include <kstddirs.h> +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "base/CompositionTimeSliceAdapter.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/TriggerSegment.h" +#include "commands/segment/AddTriggerSegmentCommand.h" +#include "commands/segment/DeleteTriggerSegmentCommand.h" +#include "commands/segment/PasteToTriggerSegmentCommand.h" +#include "document/MultiViewCommandHistory.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "gui/dialogs/TimeDialog.h" +#include "gui/general/MidiPitchLabel.h" +#include <kaction.h> +#include <kcommand.h> +#include <kglobal.h> +#include <klistview.h> +#include <kmainwindow.h> +#include <kmessagebox.h> +#include <kstdaccel.h> +#include <kstdaction.h> +#include <kconfig.h> +#include <qaccel.h> +#include <qdialog.h> +#include <qframe.h> +#include <qiconset.h> +#include <qlistview.h> +#include <qpushbutton.h> +#include <qsizepolicy.h> +#include <qstring.h> +#include <qtooltip.h> +#include <qvbox.h> +#include <qwidget.h> +#include <qcanvas.h> + + +namespace Rosegarden +{ + +TriggerSegmentManager::TriggerSegmentManager(QWidget *parent, + RosegardenGUIDoc *doc): + KMainWindow(parent, "triggereditordialog"), + m_doc(doc), + m_modified(false) +{ + QVBox* mainFrame = new QVBox(this); + setCentralWidget(mainFrame); + + setCaption(i18n("Manage Triggered Segments")); + + m_listView = new KListView(mainFrame); + m_listView->addColumn("Index"); + m_listView->addColumn(i18n("ID")); + m_listView->addColumn(i18n("Label")); + m_listView->addColumn(i18n("Duration")); + m_listView->addColumn(i18n("Base pitch")); + m_listView->addColumn(i18n("Base velocity")); + m_listView->addColumn(i18n("Triggers")); + + // Align centrally + for (int i = 0; i < 2; ++i) + m_listView->setColumnAlignment(i, Qt::AlignHCenter); + + QFrame* btnBox = new QFrame(mainFrame); + + btnBox->setSizePolicy( + QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed)); + + QHBoxLayout* layout = new QHBoxLayout(btnBox, 4, 10); + + m_addButton = new QPushButton(i18n("Add"), btnBox); + m_deleteButton = new QPushButton(i18n("Delete"), btnBox); + m_deleteAllButton = new QPushButton(i18n("Delete All"), btnBox); + + m_closeButton = new QPushButton(i18n("Close"), btnBox); + + QToolTip::add + (m_addButton, + i18n("Add a Triggered Segment")); + + QToolTip::add + (m_deleteButton, + i18n("Delete a Triggered Segment")); + + QToolTip::add + (m_deleteAllButton, + i18n("Delete All Triggered Segments")); + + QToolTip::add + (m_closeButton, + i18n("Close the Triggered Segment Manager")); + + layout->addStretch(10); + layout->addWidget(m_addButton); + layout->addWidget(m_deleteButton); + layout->addWidget(m_deleteAllButton); + layout->addSpacing(30); + + layout->addWidget(m_closeButton); + layout->addSpacing(5); + + connect(m_addButton, SIGNAL(released()), + SLOT(slotAdd())); + + connect(m_deleteButton, SIGNAL(released()), + SLOT(slotDelete())); + + connect(m_closeButton, SIGNAL(released()), + SLOT(slotClose())); + + connect(m_deleteAllButton, SIGNAL(released()), + SLOT(slotDeleteAll())); + + setupActions(); + + m_doc->getCommandHistory()->attachView(actionCollection()); + connect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()), + this, SLOT(slotUpdate())); + + connect(m_listView, SIGNAL(doubleClicked(QListViewItem *)), + SLOT(slotEdit(QListViewItem *))); + + connect(m_listView, SIGNAL(pressed(QListViewItem *)), + this, SLOT(slotItemClicked(QListViewItem *))); + + // Highlight all columns - enable extended selection mode + // + m_listView->setAllColumnsShowFocus(true); + m_listView->setSelectionMode(QListView::Extended); + m_listView->setItemsRenameable(true); + + initDialog(); + + setAutoSaveSettings(TriggerManagerConfigGroup, true); + + m_accelerators = new QAccel(this); +} + +TriggerSegmentManager::~TriggerSegmentManager() +{ + RG_DEBUG << "TriggerSegmentManager::~TriggerSegmentManager" << endl; + + m_listView->saveLayout(kapp->config(), TriggerManagerConfigGroup); + + if (m_doc) + m_doc->getCommandHistory()->detachView(actionCollection()); +} + +void +TriggerSegmentManager::initDialog() +{ + RG_DEBUG << "TriggerSegmentManager::initDialog" << endl; + slotUpdate(); +} + +void +TriggerSegmentManager::slotUpdate() +{ + RG_DEBUG << "TriggerSegmentManager::slotUpdate" << endl; + + TriggerManagerItem *item; + + m_listView->clear(); + + Composition &comp = m_doc->getComposition(); + + const Composition::triggersegmentcontainer &triggers = + comp.getTriggerSegments(); + + Composition::triggersegmentcontainerconstiterator it; + + kapp->config()->setGroup(TriggerManagerConfigGroup); + int timeMode = kapp->config()->readNumEntry("timemode", 0); + + int i = 0; + + for (it = triggers.begin(); it != triggers.end(); ++it) { + + // duration is as of first usage, or 0 + + int uses = 0; + timeT first = 0; + std::set + <int> tracks; + + CompositionTimeSliceAdapter tsa(&m_doc->getComposition()); + for (CompositionTimeSliceAdapter::iterator ci = tsa.begin(); + ci != tsa.end(); ++ci) { + if ((*ci)->has(BaseProperties::TRIGGER_SEGMENT_ID) && + (*ci)->get + <Int>(BaseProperties::TRIGGER_SEGMENT_ID) == (long)(*it)->getId()) { + ++uses; + if (tracks.empty()) { + first = (*ci)->getAbsoluteTime(); + } + tracks.insert(ci.getTrack()); + } + } + + timeT duration = + (*it)->getSegment()->getEndMarkerTime() - + (*it)->getSegment()->getStartTime(); + + QString timeString = makeDurationString + (first, duration, timeMode); + + QString label = strtoqstr((*it)->getSegment()->getLabel()); + if (label == "") + label = i18n("<no label>"); + + QString used = i18n("%1 on 1 track", + "%1 on %n tracks", + tracks.size()).arg(uses); + + QString pitch = QString("%1 (%2)") + .arg(MidiPitchLabel((*it)->getBasePitch()).getQString()) + .arg((*it)->getBasePitch()); + + QString velocity = QString("%1").arg((*it)->getBaseVelocity()); + + item = new TriggerManagerItem + (m_listView, QString("%1").arg(i + 1), QString("%1").arg((*it)->getId()), + label, timeString, pitch, velocity, used); + + item->setRawDuration(duration); + item->setId((*it)->getId()); + item->setUsage(uses); + item->setPitch((*it)->getBasePitch()); + + m_listView->insertItem(item); + ++i; + } + + if (m_listView->childCount() == 0) { + QListViewItem *item = + new TriggerManagerItem(m_listView, i18n("<none>")); + m_listView->insertItem(item); + + m_listView->setSelectionMode(QListView::NoSelection); + } else { + m_listView->setSelectionMode(QListView::Extended); + } +} + +void +TriggerSegmentManager::slotDeleteAll() +{ + if (KMessageBox::warningContinueCancel(this, i18n("This will remove all triggered segments from the whole composition. Are you sure?")) != KMessageBox::Continue) + return ; + + RG_DEBUG << "TriggerSegmentManager::slotDeleteAll" << endl; + KMacroCommand *command = new KMacroCommand(i18n("Remove all triggered segments")); + + QListViewItem *it = m_listView->firstChild(); + + do { + + TriggerManagerItem *item = + dynamic_cast<TriggerManagerItem*>(it); + + if (!item) + continue; + + DeleteTriggerSegmentCommand *c = + new DeleteTriggerSegmentCommand(m_doc, + item->getId()); + command->addCommand(c); + + } while ((it = it->nextSibling())); + + addCommandToHistory(command); +} + +void +TriggerSegmentManager::slotAdd() +{ + TimeDialog dialog(this, i18n("Trigger Segment Duration"), + &m_doc->getComposition(), + 0, 3840, false); + + if (dialog.exec() == QDialog::Accepted) { + addCommandToHistory(new AddTriggerSegmentCommand + (m_doc, dialog.getTime(), 64)); + } +} + +void +TriggerSegmentManager::slotDelete() +{ + RG_DEBUG << "TriggerSegmentManager::slotDelete" << endl; + + TriggerManagerItem *item = + dynamic_cast<TriggerManagerItem*>(m_listView->currentItem()); + + if (!item) + return ; + + if (item->getUsage() > 0) { + if (KMessageBox::warningContinueCancel(this, i18n("This triggered segment is used 1 time in the current composition. Are you sure you want to remove it?", + "This triggered segment is used %n times in the current composition. Are you sure you want to remove it?", item->getUsage())) != KMessageBox::Continue) + return ; + } + + DeleteTriggerSegmentCommand *command = + new DeleteTriggerSegmentCommand(m_doc, item->getId()); + + addCommandToHistory(command); +} + +void +TriggerSegmentManager::slotPasteAsNew() +{ + Clipboard *clipboard = m_doc->getClipboard(); + + if (clipboard->isEmpty()) { + KMessageBox::information(this, i18n("Clipboard is empty")); + return ; + } + + addCommandToHistory(new PasteToTriggerSegmentCommand + (&m_doc->getComposition(), + clipboard, + "", + -1)); +} + +void +TriggerSegmentManager::slotClose() +{ + RG_DEBUG << "TriggerSegmentManager::slotClose" << endl; + + if (m_doc) + m_doc->getCommandHistory()->detachView(actionCollection()); + m_doc = 0; + + close(); +} + +void +TriggerSegmentManager::setupActions() +{ + KAction* close = KStdAction::close(this, + SLOT(slotClose()), + actionCollection()); + + m_closeButton->setText(close->text()); + connect(m_closeButton, SIGNAL(released()), this, SLOT(slotClose())); + + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + + // some adjustments + new KToolBarPopupAction(i18n("Und&o"), + "undo", + KStdAccel::key(KStdAccel::Undo), + actionCollection(), + KStdAction::stdName(KStdAction::Undo)); + + new KToolBarPopupAction(i18n("Re&do"), + "redo", + KStdAccel::key(KStdAccel::Redo), + actionCollection(), + KStdAction::stdName(KStdAction::Redo)); + + new KAction(i18n("Pa&ste as New Triggered Segment"), CTRL + SHIFT + Key_V, this, + SLOT(slotPasteAsNew()), actionCollection(), + "paste_to_trigger_segment"); + + kapp->config()->setGroup(TriggerManagerConfigGroup); + int timeMode = kapp->config()->readNumEntry("timemode", 0); + + KRadioAction *action; + + QCanvasPixmap pixmap(pixmapDir + "/toolbar/time-musical.png"); + QIconSet icon(pixmap); + + action = new KRadioAction(i18n("&Musical Times"), icon, 0, this, + SLOT(slotMusicalTime()), + actionCollection(), "time_musical"); + action->setExclusiveGroup("timeMode"); + if (timeMode == 0) + action->setChecked(true); + + pixmap.load(pixmapDir + "/toolbar/time-real.png"); + icon = QIconSet(pixmap); + + action = new KRadioAction(i18n("&Real Times"), icon, 0, this, + SLOT(slotRealTime()), + actionCollection(), "time_real"); + action->setExclusiveGroup("timeMode"); + if (timeMode == 1) + action->setChecked(true); + + pixmap.load(pixmapDir + "/toolbar/time-raw.png"); + icon = QIconSet(pixmap); + + action = new KRadioAction(i18n("Ra&w Times"), icon, 0, this, + SLOT(slotRawTime()), + actionCollection(), "time_raw"); + action->setExclusiveGroup("timeMode"); + if (timeMode == 2) + action->setChecked(true); + + createGUI("triggermanager.rc"); +} + +void +TriggerSegmentManager::addCommandToHistory(KCommand *command) +{ + getCommandHistory()->addCommand(command); + setModified(false); +} + +MultiViewCommandHistory* +TriggerSegmentManager::getCommandHistory() +{ + return m_doc->getCommandHistory(); +} + +void +TriggerSegmentManager::setModified(bool modified) +{ + RG_DEBUG << "TriggerSegmentManager::setModified(" << modified << ")" << endl; + + m_modified = modified; +} + +void +TriggerSegmentManager::checkModified() +{ + RG_DEBUG << "TriggerSegmentManager::checkModified(" << m_modified << ")" + << endl; + +} + +void +TriggerSegmentManager::slotEdit(QListViewItem *i) +{ + RG_DEBUG << "TriggerSegmentManager::slotEdit" << endl; + + TriggerManagerItem *item = + dynamic_cast<TriggerManagerItem*>(i); + + if (!item) + return ; + + TriggerSegmentId id = item->getId(); + + RG_DEBUG << "id is " << id << endl; + + emit editTriggerSegment(id); +} + +void +TriggerSegmentManager::closeEvent(QCloseEvent *e) +{ + emit closing(); + KMainWindow::closeEvent(e); +} + +void +TriggerSegmentManager::setDocument(RosegardenGUIDoc *doc) +{ + // reset our pointers + m_doc = doc; + m_modified = false; + + slotUpdate(); +} + +void +TriggerSegmentManager::slotItemClicked(QListViewItem *item) +{ + RG_DEBUG << "TriggerSegmentManager::slotItemClicked" << endl; +} + +QString +TriggerSegmentManager::makeDurationString(timeT time, + timeT duration, int timeMode) +{ + //!!! duplication with EventView::makeDurationString -- merge somewhere? + + switch (timeMode) { + + case 0: // musical time + { + int bar, beat, fraction, remainder; + m_doc->getComposition().getMusicalTimeForDuration + (time, duration, bar, beat, fraction, remainder); + return QString("%1%2%3-%4%5-%6%7-%8%9 ") + .arg(bar / 100) + .arg((bar % 100) / 10) + .arg(bar % 10) + .arg(beat / 10) + .arg(beat % 10) + .arg(fraction / 10) + .arg(fraction % 10) + .arg(remainder / 10) + .arg(remainder % 10); + } + + case 1: // real time + { + RealTime rt = + m_doc->getComposition().getRealTimeDifference + (time, time + duration); + // return QString("%1 ").arg(rt.toString().c_str()); + return QString("%1 ").arg(rt.toText().c_str()); + } + + default: + return QString("%1 ").arg(duration); + } +} + +void +TriggerSegmentManager::slotMusicalTime() +{ + kapp->config()->setGroup(TriggerManagerConfigGroup); + kapp->config()->writeEntry("timemode", 0); + slotUpdate(); +} + +void +TriggerSegmentManager::slotRealTime() +{ + kapp->config()->setGroup(TriggerManagerConfigGroup); + kapp->config()->writeEntry("timemode", 1); + slotUpdate(); +} + +void +TriggerSegmentManager::slotRawTime() +{ + kapp->config()->setGroup(TriggerManagerConfigGroup); + kapp->config()->writeEntry("timemode", 2); + slotUpdate(); +} + +} +#include "TriggerSegmentManager.moc" diff --git a/src/gui/editors/segment/TriggerSegmentManager.h b/src/gui/editors/segment/TriggerSegmentManager.h new file mode 100644 index 0000000..2de6488 --- /dev/null +++ b/src/gui/editors/segment/TriggerSegmentManager.h @@ -0,0 +1,116 @@ + +/* -*- 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_TRIGGERSEGMENTMANAGER_H_ +#define _RG_TRIGGERSEGMENTMANAGER_H_ + +#include <kmainwindow.h> +#include <qstring.h> +#include "base/Event.h" + + +class QWidget; +class QPushButton; +class QListViewItem; +class QCloseEvent; +class QAccel; +class KListView; +class KCommand; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class MultiViewCommandHistory; + + +class TriggerSegmentManager : public KMainWindow +{ + Q_OBJECT + +public: + TriggerSegmentManager(QWidget *parent, + RosegardenGUIDoc *doc); + ~TriggerSegmentManager(); + + void initDialog(); + + void addCommandToHistory(KCommand *command); + MultiViewCommandHistory* getCommandHistory(); + + void setModified(bool value); + void checkModified(); + + // reset the document + void setDocument(RosegardenGUIDoc *doc); + + QAccel* getAccelerators() { return m_accelerators; } + +public slots: + void slotUpdate(); + + void slotAdd(); + void slotDelete(); + void slotDeleteAll(); + void slotClose(); + void slotEdit(QListViewItem *); + void slotItemClicked(QListViewItem *); + void slotPasteAsNew(); + + void slotMusicalTime(); + void slotRealTime(); + void slotRawTime(); + +signals: + void editTriggerSegment(int); + void closing(); + +protected: + virtual void closeEvent(QCloseEvent *); + + void setupActions(); + QString makeDurationString(timeT startTime, + timeT duration, int timeMode); + + //--------------- Data members --------------------------------- + RosegardenGUIDoc *m_doc; + + QPushButton *m_closeButton; + QPushButton *m_addButton; + QPushButton *m_deleteButton; + QPushButton *m_deleteAllButton; + + KListView *m_listView; + + bool m_modified; + + QAccel *m_accelerators; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.cpp b/src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.cpp new file mode 100644 index 0000000..1b982dc --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.cpp @@ -0,0 +1,316 @@ +/* -*- 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 "AudioPreviewPainter.h" + +#include "CompositionModelImpl.h" +#include "CompositionColourCache.h" +#include "base/Composition.h" +#include "base/Track.h" +#include "base/AudioLevel.h" +#include "base/Studio.h" + +#include "misc/Debug.h" +#include "document/ConfigGroups.h" + +#include <qimage.h> +#include <qapplication.h> + +#include <kapp.h> +#include <kconfig.h> + +namespace Rosegarden { + +AudioPreviewPainter::AudioPreviewPainter(CompositionModelImpl& model, + CompositionModel::AudioPreviewData* apData, + const Composition &composition, + const Segment* segment) + : m_model(model), + m_apData(apData), + m_composition(composition), + m_segment(segment), + m_rect(model.computeSegmentRect(*(segment))), + m_image(1, 1, 8, 4), + m_defaultCol(CompositionColourCache::getInstance()->SegmentAudioPreview), + m_height(model.grid().getYSnap()/2) +{ + int pixWidth = std::min(m_rect.getBaseWidth(), tileWidth()); + + m_image = QImage(pixWidth, m_rect.height(), 8, 4); + m_image.setAlphaBuffer(true); + + m_penWidth = (std::max(1U, m_rect.getPen().width()) * 2); + m_halfRectHeight = m_model.grid().getYSnap()/2 - m_penWidth / 2 - 2; +} + +int AudioPreviewPainter::tileWidth() +{ + static int tw = -1; + if (tw == -1) tw = QApplication::desktop()->width(); + return tw; +} + +void AudioPreviewPainter::paintPreviewImage() +{ + const std::vector<float>& values = m_apData->getValues(); + + if (values.size() == 0) + return; + + float gain[2] = { 1.0, 1.0 }; + int instrumentChannels = 2; + TrackId trackId = m_segment->getTrack(); + Track *track = m_model.getComposition().getTrackById(trackId); + if (track) { + Instrument *instrument = m_model.getStudio().getInstrumentById(track->getInstrument()); + if (instrument) { + float level = AudioLevel::dB_to_multiplier(instrument->getLevel()); + float pan = instrument->getPan() - 100.0; + gain[0] = level * ((pan > 0.0) ? (1.0 - (pan / 100.0)) : 1.0); + gain[1] = level * ((pan < 0.0) ? ((pan + 100.0) / 100.0) : 1.0); + instrumentChannels = instrument->getAudioChannels(); + } + } + + bool showMinima = m_apData->showsMinima(); + unsigned int channels = m_apData->getChannels(); + if (channels == 0) { + RG_DEBUG << "AudioPreviewPainter::paintPreviewImage : problem with audio file for segment " + << m_segment->getLabel().c_str() << endl; + return; + } + + int samplePoints = values.size() / (channels * (showMinima ? 2 : 1)); + float h1, h2, l1 = 0, l2 = 0; + double sampleScaleFactor = samplePoints / double(m_rect.getBaseWidth()); + m_sliceNb = 0; + + m_image.fill(0); + + int centre = m_image.height() / 2; + + RG_DEBUG << "AudioPreviewPainter::paintPreviewImage width = " << m_rect.getBaseWidth() << ", height = " << m_rect.height() << ", halfRectHeight = " << m_halfRectHeight << endl; + + RG_DEBUG << "AudioPreviewPainter::paintPreviewImage: channels = " << channels << ", gain left = " << gain[0] << ", right = " << gain[1] << endl; + + double audioDuration = double(m_segment->getAudioEndTime().sec) + + double(m_segment->getAudioEndTime().nsec) / 1000000000.0; + + // We need to take each pixel value and map it onto a point within + // the preview. We have samplePoints preview points in a known + // duration of audioDuration. Thus each point spans a real time + // of audioDuration / samplePoints. We need to convert the + // accumulated real time back into musical time, and map this + // proportionately across the segment width. + + RealTime startRT = + m_model.getComposition().getElapsedRealTime(m_segment->getStartTime()); + double startTime = double(startRT.sec) + double(startRT.nsec) / 1000000000.0; + + RealTime endRT = + m_model.getComposition().getElapsedRealTime(m_segment->getEndMarkerTime()); + double endTime = double(endRT.sec) + double(endRT.nsec) / 1000000000.0; + + bool haveTempoChange = false; + + int finalTempoChangeNumber = + m_model.getComposition().getTempoChangeNumberAt + (m_segment->getEndMarkerTime()); + + if ((finalTempoChangeNumber >= 0) && + + (finalTempoChangeNumber > + m_model.getComposition().getTempoChangeNumberAt + (m_segment->getStartTime()))) { + + haveTempoChange = true; + } + + KConfig* config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + + bool meterLevels = (config->readUnsignedNumEntry("audiopreviewstyle", 1) + == 1); + + for (int i = 0; i < m_rect.getBaseWidth(); ++i) { + + // i is the x coordinate within the rectangle. We need to + // calculate the position within the audio preview from which + // to draw the peak for this coordinate. It's possible there + // may be more than one, in which case we need to find the + // peak of all of them. + + int position = 0; + + if (haveTempoChange) { + + // First find the time corresponding to this i. + timeT musicalTime = + m_model.grid().getRulerScale()->getTimeForX(m_rect.x() + i); + RealTime realTime = + m_model.getComposition().getElapsedRealTime(musicalTime); + + double time = double(realTime.sec) + + double(realTime.nsec) / 1000000000.0; + double offset = time - startTime; + + if (endTime > startTime) { + position = offset * m_rect.getBaseWidth() / (endTime - startTime); + position = int(channels * position); + } + + } else { + + position = int(channels * i * sampleScaleFactor); + } + + if (position < 0) continue; + + if (position >= values.size() - channels) { + finalizeCurrentSlice(); + break; + } + + if (channels == 1) { + + h1 = values[position++]; + h2 = h1; + + if (showMinima) { + l1 = values[position++]; + l2 = l1; + } + } else { + + h1 = values[position++]; + if (showMinima) l1 = values[position++]; + + h2 = values[position++]; + if (showMinima) l2 = values[position++]; + + } + + if (instrumentChannels == 1 && channels == 2) { + h1 = h2 = (h1 + h2) / 2; + l1 = l2 = (l1 + l2) / 2; + } + + h1 *= gain[0]; + h2 *= gain[1]; + + l1 *= gain[0]; + l2 *= gain[1]; + + int width = 1; + int pixel; + + // h1 left, h2 right + if (h1 >= 1.0) { h1 = 1.0; pixel = 2; } + else { pixel = 1; } + + int h; + + if (meterLevels) { + h = AudioLevel::multiplier_to_preview(h1, m_height); + } else { + h = h1 * m_height; + } + if (h <= 0) h = 1; + if (h > m_halfRectHeight) h = m_halfRectHeight; + + int rectX = i % tileWidth(); + + for (int py = 0; py < h; ++py) { + m_image.setPixel(rectX, centre - py, pixel); + } + + if (h2 >= 1.0) { h2 = 1.0; pixel = 2; } + else { pixel = 1; } + + if (meterLevels) { + h = AudioLevel::multiplier_to_preview(h2, m_height); + } else { + h = h2 * m_height; + } + if (h < 0) h = 0; + + for (int py = 0; py < h; ++py) { + m_image.setPixel(rectX, centre + py, pixel); + } + + if (((i+1) % tileWidth()) == 0 || i == (m_rect.getBaseWidth() - 1)) { + finalizeCurrentSlice(); + } + } + +/* Auto-fade not yet implemented. + + if (m_segment->isAutoFading()) { + + Composition &comp = m_model.getComposition(); + + int audioFadeInEnd = int( + m_model.grid().getRulerScale()->getXForTime(comp. + getElapsedTimeForRealTime(m_segment->getFadeInTime()) + + m_segment->getStartTime()) - + m_model.grid().getRulerScale()->getXForTime(m_segment->getStartTime())); + + m_p.setPen(Qt::blue); + m_p.drawRect(0, m_apData->getSegmentRect().height() - 1, audioFadeInEnd, 1); + m_pb.drawRect(0, m_apData->getSegmentRect().height() - 1, audioFadeInEnd, 1); + } + + m_p.end(); + m_pb.end(); +*/ +} + +void AudioPreviewPainter::finalizeCurrentSlice() +{ +// RG_DEBUG << "AudioPreviewPainter::finalizeCurrentSlice : copying pixmap to image at " << m_sliceNb * tileWidth() << endl; + + // transparent background + m_image.setColor(0, qRgba(255, 255, 255, 0)); + + // foreground from computeSegmentPreviewColor + QColor c = m_model.computeSegmentPreviewColor(m_segment); + QRgb rgba = qRgba(c.red(), c.green(), c.blue(), 255); + m_image.setColor(1, rgba); + + // red for clipping + m_image.setColor(2, qRgba(255, 0, 0, 255)); + + m_previewPixmaps.push_back(m_image.copy()); + + m_image.fill(0); + + ++m_sliceNb; +} + +PixmapArray AudioPreviewPainter::getPreviewImage() +{ + return m_previewPixmaps; +} + +} diff --git a/src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.h b/src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.h new file mode 100644 index 0000000..b3c1cac --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.h @@ -0,0 +1,79 @@ +/* -*- 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_AUDIOPREVIEWPAINTER_H_ +#define _RG_AUDIOPREVIEWPAINTER_H_ + +#include "CompositionModel.h" + +#include <qpainter.h> +#include <qcolor.h> + +namespace Rosegarden { + +class CompositionModelImpl; +class Composition; +class Segment; +class CompositionRect; + +class AudioPreviewPainter { +public: + AudioPreviewPainter(CompositionModelImpl& model, + CompositionModel::AudioPreviewData* apData, + const Composition &composition, + const Segment* segment); + + void paintPreviewImage(); + PixmapArray getPreviewImage(); + const CompositionRect& getSegmentRect() { return m_rect; } + + static int tileWidth(); + +protected: + void finalizeCurrentSlice(); + + //--------------- Data members --------------------------------- + CompositionModelImpl& m_model; + CompositionModel::AudioPreviewData* m_apData; + const Composition &m_composition; + const Segment* m_segment; + CompositionRect m_rect; + + QImage m_image; + PixmapArray m_previewPixmaps; + + QPainter m_p; + QPainter m_pb; + QColor m_defaultCol; + int m_penWidth; + int m_height; + int m_halfRectHeight; + int m_sliceNb; + +}; + +} + +#endif + diff --git a/src/gui/editors/segment/segmentcanvas/AudioPreviewThread.cpp b/src/gui/editors/segment/segmentcanvas/AudioPreviewThread.cpp new file mode 100644 index 0000000..ae64134 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/AudioPreviewThread.cpp @@ -0,0 +1,267 @@ +/* -*- 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 "AudioPreviewThread.h" + +#include "base/RealTime.h" +#include "sound/AudioFileManager.h" +#include "sound/PeakFileManager.h" +#include <qapplication.h> +#include <qevent.h> +#include <qmutex.h> +#include <qobject.h> +#include <qthread.h> + + +namespace Rosegarden +{ + +AudioPreviewThread::AudioPreviewThread(AudioFileManager *manager) : + m_manager(manager), + m_nextToken(0), + m_exiting(false), + m_emptyQueueListener(0) +{} + +void +AudioPreviewThread::run() +{ + bool emptyQueueSignalled = false; + +#ifdef DEBUG_AUDIO_PREVIEW_THREAD + + std::cerr << "AudioPreviewThread::run entering\n"; +#endif + + while (!m_exiting) { + + if (m_queue.empty()) { + if (m_emptyQueueListener && !emptyQueueSignalled) { + QApplication::postEvent(m_emptyQueueListener, + new QCustomEvent(AudioPreviewQueueEmpty, 0)); + emptyQueueSignalled = true; + } + + usleep(300000); + } else { + process(); + } + } + +#ifdef DEBUG_AUDIO_PREVIEW_THREAD + std::cerr << "AudioPreviewThread::run exiting\n"; +#endif +} + +void +AudioPreviewThread::finish() +{ + m_exiting = true; +} + +bool +AudioPreviewThread::process() +{ +#ifdef DEBUG_AUDIO_PREVIEW_THREAD + std::cerr << "AudioPreviewThread::process()\n"; +#endif + + if (!m_queue.empty()) { + + int failed = 0; + int inQueue = 0; + //int count = 0; + + m_mutex.lock(); + + // process 1st request and leave + inQueue = m_queue.size(); + RequestQueue::iterator i = m_queue.begin(); + + // i->first is width, which we use only to provide an ordering to + // ensure we do smaller previews first. We don't use it here. + + RequestRec &rec = i->second; + int token = rec.first; + Request req = rec.second; + m_mutex.unlock(); + + std::vector<float> results; + + try { +#ifdef DEBUG_AUDIO_PREVIEW_THREAD + std::cerr << "AudioPreviewThread::process() file id " << req.audioFileId << std::endl; +#endif + + // Requires thread-safe AudioFileManager::getPreview + results = m_manager->getPreview(req.audioFileId, + req.audioStartTime, + req.audioEndTime, + req.width, + req.showMinima); + } catch (AudioFileManager::BadAudioPathException e) { + +#ifdef DEBUG_AUDIO_PREVIEW_THREAD + std::cerr << "AudioPreviewThread::process: failed to update preview for audio file " << req.audioFileId << ": bad audio path: " << e.getMessage() << std::endl; +#endif + + // OK, we hope this just means we're still recording -- so + // leave this one in the queue + ++failed; + + } catch (PeakFileManager::BadPeakFileException e) { + +#ifdef DEBUG_AUDIO_PREVIEW_THREAD + std::cerr << "AudioPreviewThread::process: failed to update preview for audio file " << req.audioFileId << ": bad peak file: " << e.getMessage() << std::endl; +#endif + + // As above + ++failed; + } + + m_mutex.lock(); + + // We need to check that the token is still in the queue + // (i.e. hasn't been cancelled). Otherwise we shouldn't notify + + bool found = false; + for (RequestQueue::iterator i = m_queue.begin(); i != m_queue.end(); ++i) { + if (i->second.first == token) { + found = true; + m_queue.erase(i); + break; + } + } + + if (found) { + unsigned int channels = + m_manager->getAudioFile(req.audioFileId)->getChannels(); + m_results[token] = ResultsPair(channels, results); + QObject *notify = req.notify; + QApplication::postEvent + (notify, + new QCustomEvent(AudioPreviewReady, (void *)token)); + } + + m_mutex.unlock(); + + if (failed > 0 && failed == inQueue) { +#ifdef DEBUG_AUDIO_PREVIEW_THREAD + std::cerr << "AudioPreviewThread::process() - return true\n"; +#endif + + return true; // delay and try again + } + } + +#ifdef DEBUG_AUDIO_PREVIEW_THREAD + std::cerr << "AudioPreviewThread::process() - return false\n"; +#endif + + return false; +} + +int +AudioPreviewThread::requestPreview(const Request &request) +{ + m_mutex.lock(); + +#ifdef DEBUG_AUDIO_PREVIEW_THREAD + + std::cerr << "AudioPreviewThread::requestPreview for file id " << request.audioFileId << ", start " << request.audioStartTime << ", end " << request.audioEndTime << ", width " << request.width << ", notify " << request.notify << std::endl; +#endif + /*!!! + for (RequestQueue::iterator i = m_queue.begin(); i != m_queue.end(); ++i) { + if (i->second.second.notify == request.notify) { + m_queue.erase(i); + break; + } + } + */ + int token = m_nextToken; + m_queue.insert(RequestQueue::value_type(request.width, + RequestRec(token, request))); + ++m_nextToken; + m_mutex.unlock(); + + // if (!running()) start(); + +#ifdef DEBUG_AUDIO_PREVIEW_THREAD + + std::cerr << "AudioPreviewThread::requestPreview : thread running : " << running() + << " - thread finished : " << finished() << std::endl; + + std::cerr << "AudioPreviewThread::requestPreview - token = " << token << std::endl; +#endif + + return token; +} + +void +AudioPreviewThread::cancelPreview(int token) +{ + m_mutex.lock(); + +#ifdef DEBUG_AUDIO_PREVIEW_THREAD + + std::cerr << "AudioPreviewThread::cancelPreview for token " << token << std::endl; +#endif + + for (RequestQueue::iterator i = m_queue.begin(); i != m_queue.end(); ++i) { + if (i->second.first == token) { + m_queue.erase(i); + break; + } + } + + m_mutex.unlock(); +} + +void +AudioPreviewThread::getPreview(int token, unsigned int &channels, + std::vector<float> &values) +{ + m_mutex.lock(); + + values.clear(); + if (m_results.find(token) == m_results.end()) { + channels = 0; + m_mutex.unlock(); + return ; + } + + channels = m_results[token].first; + values = m_results[token].second; + m_results.erase(m_results.find(token)); + + m_mutex.unlock(); + + return ; +} + +const QEvent::Type AudioPreviewThread::AudioPreviewReady = QEvent::Type(QEvent::User + 1); +const QEvent::Type AudioPreviewThread::AudioPreviewQueueEmpty = QEvent::Type(QEvent::User + 2); + +} diff --git a/src/gui/editors/segment/segmentcanvas/AudioPreviewThread.h b/src/gui/editors/segment/segmentcanvas/AudioPreviewThread.h new file mode 100644 index 0000000..ae3ac81 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/AudioPreviewThread.h @@ -0,0 +1,99 @@ + +/* -*- 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_AUDIOPREVIEWTHREAD_H_ +#define _RG_AUDIOPREVIEWTHREAD_H_ + +#include "base/RealTime.h" +#include <map> +#include <qevent.h> +#include <qmutex.h> +#include <qthread.h> +#include <utility> +#include <vector> + + +class QObject; + + +namespace Rosegarden +{ + +class AudioFileManager; + + +class AudioPreviewThread : public QThread +{ +public: + AudioPreviewThread(AudioFileManager *manager); + + virtual void run(); + virtual void finish(); + + struct Request { + int audioFileId; + RealTime audioStartTime; + RealTime audioEndTime; + int width; + bool showMinima; + QObject *notify; + }; + + virtual int requestPreview(const Request &request); + virtual void cancelPreview(int token); + virtual void getPreview(int token, unsigned int &channels, + std::vector<float> &values); + + void setEmptyQueueListener(QObject* o) { m_emptyQueueListener = o; } + + static const QEvent::Type AudioPreviewReady; + static const QEvent::Type AudioPreviewQueueEmpty; + + +protected: + virtual bool process(); + + + AudioFileManager *m_manager; + int m_nextToken; + bool m_exiting; + + QObject* m_emptyQueueListener; + + typedef std::pair<int, Request> RequestRec; + typedef std::multimap<int, RequestRec> RequestQueue; + RequestQueue m_queue; + + typedef std::pair<unsigned int, std::vector<float> > ResultsPair; + typedef std::map<int, ResultsPair> ResultsQueue; + ResultsQueue m_results; + + QMutex m_mutex; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.cpp b/src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.cpp new file mode 100644 index 0000000..76497b9 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.cpp @@ -0,0 +1,149 @@ +/* -*- 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 "AudioPreviewUpdater.h" + +#include "misc/Debug.h" +#include "AudioPreviewThread.h" +#include "base/Composition.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "CompositionModelImpl.h" +#include <qevent.h> +#include <qobject.h> +#include <qrect.h> + + +namespace Rosegarden +{ + +static int apuExtantCount = 0; + +AudioPreviewUpdater::AudioPreviewUpdater(AudioPreviewThread &thread, + const Composition& c, const Segment* s, + const QRect& r, + CompositionModelImpl* parent) + : QObject(parent), + m_thread(thread), + m_composition(c), + m_segment(s), + m_rect(r), + m_showMinima(false), + m_channels(0), + m_previewToken( -1) +{ + ++apuExtantCount; + RG_DEBUG << "AudioPreviewUpdater::AudioPreviewUpdater " << this << " (now " << apuExtantCount << " extant)" << endl; +} + +AudioPreviewUpdater::~AudioPreviewUpdater() +{ + --apuExtantCount; + RG_DEBUG << "AudioPreviewUpdater::~AudioPreviewUpdater on " << this << " ( token " << m_previewToken << ") (now " << apuExtantCount << " extant)" << endl; + if (m_previewToken >= 0) + m_thread.cancelPreview(m_previewToken); +} + +void AudioPreviewUpdater::update() +{ + // Get sample start and end times and work out duration + // + RealTime audioStartTime = m_segment->getAudioStartTime(); + RealTime audioEndTime = audioStartTime + + m_composition.getElapsedRealTime(m_segment->getEndMarkerTime()) - + m_composition.getElapsedRealTime(m_segment->getStartTime()) ; + + RG_DEBUG << "AudioPreviewUpdater(" << this << ")::update() - for file id " + << m_segment->getAudioFileId() << " requesting values - thread running : " + << m_thread.running() << " - thread finished : " << m_thread.finished() << endl; + + AudioPreviewThread::Request request; + request.audioFileId = m_segment->getAudioFileId(); + request.audioStartTime = audioStartTime; + request.audioEndTime = audioEndTime; + request.width = m_rect.width(); + request.showMinima = m_showMinima; + request.notify = this; + if (m_previewToken >= 0) + m_thread.cancelPreview(m_previewToken); + m_previewToken = m_thread.requestPreview(request); + if (!m_thread.running()) + m_thread.start(); +} + +void AudioPreviewUpdater::cancel() +{ + if (m_previewToken >= 0) + m_thread.cancelPreview(m_previewToken); + m_previewToken = -1; +} + +bool AudioPreviewUpdater::event(QEvent *e) +{ + RG_DEBUG << "AudioPreviewUpdater(" << this << ")::event (" << e << ")" << endl; + + if (e->type() == AudioPreviewThread::AudioPreviewReady) { + QCustomEvent *ev = dynamic_cast<QCustomEvent *>(e); + if (ev) { + intptr_t token = (intptr_t)ev->data(); + m_channels = 0; // to be filled as getPreview return value + + RG_DEBUG << "AudioPreviewUpdater::token " << token << ", my token " << m_previewToken << endl; + + if (m_previewToken >= 0 && token >= m_previewToken) { + + m_previewToken = -1; + m_thread.getPreview(token, m_channels, m_values); + + if (m_channels == 0) { + RG_DEBUG << "AudioPreviewUpdater: failed to find preview!\n"; + } else { + + RG_DEBUG << "AudioPreviewUpdater: got correct preview (" << m_channels + << " channels, " << m_values.size() << " samples)\n"; + } + + emit audioPreviewComplete(this); + + } else { + + // this one is out of date already + std::vector<float> tmp; + unsigned int tmpChannels; + m_thread.getPreview(token, tmpChannels, tmp); + + RG_DEBUG << "AudioPreviewUpdater: got obsolete preview (" << tmpChannels + << " channels, " << tmp.size() << " samples)\n"; + } + + return true; + } + } + + return QObject::event(e); +} + +} +#include "AudioPreviewUpdater.moc" diff --git a/src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.h b/src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.h new file mode 100644 index 0000000..ffc97c9 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.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_AUDIOPREVIEWUPDATER_H_ +#define _RG_AUDIOPREVIEWUPDATER_H_ + +#include <qobject.h> +#include <qrect.h> +#include <vector> + + +class QEvent; + + +namespace Rosegarden +{ + +class Segment; +class CompositionModelImpl; +class Composition; +class AudioPreviewThread; + + +class AudioPreviewUpdater : public QObject +{ + Q_OBJECT + +public: + AudioPreviewUpdater(AudioPreviewThread &thread, + const Composition &composition, + const Segment *segment, + const QRect &displayExtent, + CompositionModelImpl *parent); + ~AudioPreviewUpdater(); + + void update(); + void cancel(); + + QRect getDisplayExtent() const { return m_rect; } + void setDisplayExtent(const QRect &rect) { m_rect = rect; } + + const Segment *getSegment() const { return m_segment; } + + const std::vector<float> &getComputedValues(unsigned int &channels) const + { channels = m_channels; return m_values; } + +signals: + void audioPreviewComplete(AudioPreviewUpdater*); + +protected: + virtual bool event(QEvent*); + + AudioPreviewThread& m_thread; + + const Composition& m_composition; + const Segment* m_segment; + QRect m_rect; + bool m_showMinima; + unsigned int m_channels; + std::vector<float> m_values; + + intptr_t m_previewToken; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/CompositionColourCache.cpp b/src/gui/editors/segment/segmentcanvas/CompositionColourCache.cpp new file mode 100644 index 0000000..b36d6e0 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionColourCache.cpp @@ -0,0 +1,62 @@ +/* -*- 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 "CompositionColourCache.h" + +#include "gui/general/GUIPalette.h" +#include <qcolor.h> + + +namespace Rosegarden +{ + +void CompositionColourCache::init() +{ + SegmentCanvas = GUIPalette::getColour(GUIPalette::SegmentCanvas); + SegmentAudioPreview = GUIPalette::getColour(GUIPalette::SegmentAudioPreview); + SegmentInternalPreview = GUIPalette::getColour(GUIPalette::SegmentInternalPreview); + SegmentLabel = GUIPalette::getColour(GUIPalette::SegmentLabel); + SegmentBorder = GUIPalette::getColour(GUIPalette::SegmentBorder); + RepeatSegmentBorder = GUIPalette::getColour(GUIPalette::RepeatSegmentBorder); + RecordingSegmentBorder = GUIPalette::getColour(GUIPalette::RecordingSegmentBorder); + RecordingAudioSegmentBlock = GUIPalette::getColour(GUIPalette::RecordingAudioSegmentBlock); + RecordingInternalSegmentBlock = GUIPalette::getColour(GUIPalette::RecordingInternalSegmentBlock); + RotaryFloatBackground = GUIPalette::getColour(GUIPalette::RotaryFloatBackground); + RotaryFloatForeground = GUIPalette::getColour(GUIPalette::RotaryFloatForeground); + +} + +CompositionColourCache* CompositionColourCache::getInstance() +{ + if (!m_instance) { + m_instance = new CompositionColourCache(); + } + + return m_instance; +} + +CompositionColourCache* CompositionColourCache::m_instance = 0; + +} diff --git a/src/gui/editors/segment/segmentcanvas/CompositionColourCache.h b/src/gui/editors/segment/segmentcanvas/CompositionColourCache.h new file mode 100644 index 0000000..32d4719 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionColourCache.h @@ -0,0 +1,69 @@ + +/* -*- 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_COMPOSITIONCOLOURCACHE_H_ +#define _RG_COMPOSITIONCOLOURCACHE_H_ + +#include <qcolor.h> + + + + +namespace Rosegarden +{ + + + +class CompositionColourCache +{ +public: + static CompositionColourCache* getInstance(); + + void init(); + + QColor SegmentCanvas; + QColor SegmentAudioPreview; + QColor SegmentInternalPreview; + QColor SegmentLabel; + QColor SegmentBorder; + QColor RepeatSegmentBorder; + QColor RecordingSegmentBorder; + QColor RecordingAudioSegmentBlock; + QColor RecordingInternalSegmentBlock; + QColor Pointer; + QColor MovementGuide; + QColor RotaryFloatBackground; + QColor RotaryFloatForeground; + +protected: + CompositionColourCache() { init(); } + static CompositionColourCache* m_instance; +}; + + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/CompositionItem.cpp b/src/gui/editors/segment/segmentcanvas/CompositionItem.cpp new file mode 100644 index 0000000..798178a --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionItem.cpp @@ -0,0 +1,34 @@ +/* -*- 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 "CompositionItem.h" + +#include <qobject.h> +#include <qrect.h> + + +namespace Rosegarden +{ +} diff --git a/src/gui/editors/segment/segmentcanvas/CompositionItem.h b/src/gui/editors/segment/segmentcanvas/CompositionItem.h new file mode 100644 index 0000000..b5e749b --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionItem.h @@ -0,0 +1,67 @@ + +/* -*- 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_COMPOSITIONITEM_H_ +#define _RG_COMPOSITIONITEM_H_ + +#include <qguardedptr.h> +#include <qobject.h> +#include <qrect.h> + + +namespace Rosegarden +{ + +class _CompositionItem : public QObject { +public: + virtual bool isRepeating() const = 0; + virtual QRect rect() const = 0; + virtual void moveBy(int x, int y) = 0; + virtual void moveTo(int x, int y) = 0; + virtual void setX(int x) = 0; + virtual void setY(int y) = 0; + virtual void setZ(unsigned int z) = 0; + virtual int x() = 0; + virtual int y() = 0; + virtual unsigned int z() = 0; + virtual void setWidth(int w) = 0; + + // used by itemcontainer + virtual long hashKey() = 0; + + QRect savedRect() const { return m_savedRect; } + void saveRect() const { m_savedRect = rect(); } + +protected: + mutable QRect m_savedRect; +}; + +typedef QGuardedPtr<_CompositionItem> CompositionItem; +bool operator<(const CompositionItem&, const CompositionItem&); + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/CompositionItemHelper.cpp b/src/gui/editors/segment/segmentcanvas/CompositionItemHelper.cpp new file mode 100644 index 0000000..e1705cd --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionItemHelper.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 <cmath> + +#include "CompositionItemHelper.h" + +#include "base/Segment.h" +#include "base/SnapGrid.h" +#include "misc/Debug.h" +#include "CompositionModel.h" +#include "CompositionItemImpl.h" +#include <qcolor.h> +#include <qpoint.h> +#include <qrect.h> + +namespace Rosegarden +{ + +timeT CompositionItemHelper::getStartTime(const CompositionItem& item, const Rosegarden::SnapGrid& grid) +{ + timeT t = 0; + + if (item) { + // t = std::max(grid.snapX(item->rect().x()), 0L); - wrong, we can have negative start times, + // and if we do this we 'crop' segments when they are moved before the start of the composition + t = grid.snapX(item->rect().x()); + +// RG_DEBUG << "CompositionItemHelper::getStartTime(): item is repeating : " << item->isRepeating() +// << " - startTime = " << t +// << endl; + } + + return t; +} + +timeT CompositionItemHelper::getEndTime(const CompositionItem& item, const Rosegarden::SnapGrid& grid) +{ + timeT t = 0; + + if (item) { + QRect itemRect = item->rect(); + + t = std::max(grid.snapX(itemRect.x() + itemRect.width()), 0L); + +// RG_DEBUG << "CompositionItemHelper::getEndTime() : rect width = " +// << itemRect.width() +// << " - item is repeating : " << item->isRepeating() +// << " - endTime = " << t +// << endl; + + } + + return t; +} + +void CompositionItemHelper::setStartTime(CompositionItem& item, timeT time, + const Rosegarden::SnapGrid& grid) +{ + if (item) { + int x = int(nearbyint(grid.getRulerScale()->getXForTime(time))); + + RG_DEBUG << "CompositionItemHelper::setStartTime() time = " << time + << " -> x = " << x << endl; + + int curX = item->rect().x(); + item->setX(x); + if (item->isRepeating()) { + int deltaX = curX - x; + CompositionRect& sr = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)item)->getCompRect(); + int curW = sr.getBaseWidth(); + sr.setBaseWidth(curW + deltaX); + } + + } + +} + +void CompositionItemHelper::setEndTime(CompositionItem& item, timeT time, + const Rosegarden::SnapGrid& grid) +{ + if (item) { + int x = int(nearbyint(grid.getRulerScale()->getXForTime(time))); + QRect r = item->rect(); + QPoint topRight = r.topRight(); + topRight.setX(x); + r.setTopRight(topRight); + item->setWidth(r.width()); + + if (item->isRepeating()) { + CompositionRect& sr = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)item)->getCompRect(); + sr.setBaseWidth(r.width()); + } + } +} + +int CompositionItemHelper::getTrackPos(const CompositionItem& item, const Rosegarden::SnapGrid& grid) +{ + return grid.getYBin(item->rect().y()); +} + +Rosegarden::Segment* CompositionItemHelper::getSegment(CompositionItem item) +{ + return (dynamic_cast<CompositionItemImpl*>((_CompositionItem*)item))->getSegment(); +} + +CompositionItem CompositionItemHelper::makeCompositionItem(Rosegarden::Segment* segment) +{ + return CompositionItem(new CompositionItemImpl(*segment, QRect())); +} + +CompositionItem CompositionItemHelper::findSiblingCompositionItem(const CompositionModel::itemcontainer& items, + const CompositionItem& referenceItem) +{ + CompositionModel::itemcontainer::const_iterator it; + Rosegarden::Segment* currentSegment = CompositionItemHelper::getSegment(referenceItem); + + for (it = items.begin(); it != items.end(); it++) { + CompositionItem item = *it; + Rosegarden::Segment* segment = CompositionItemHelper::getSegment(item); + if (segment == currentSegment) { + return item; + } + } + + return referenceItem; +} + +} diff --git a/src/gui/editors/segment/segmentcanvas/CompositionItemHelper.h b/src/gui/editors/segment/segmentcanvas/CompositionItemHelper.h new file mode 100644 index 0000000..1b3ad95 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionItemHelper.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_COMPOSITIONITEMHELPER_H_ +#define _RG_COMPOSITIONITEMHELPER_H_ + +#include "CompositionModel.h" +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class SnapGrid; +class Segment; + + +class CompositionItemHelper { +public: + static timeT getStartTime(const CompositionItem&, const SnapGrid&); + static timeT getEndTime(const CompositionItem&, const SnapGrid&); + static int getTrackPos(const CompositionItem&, const SnapGrid&); + static void setStartTime(CompositionItem&, timeT, const SnapGrid&); + static void setEndTime(CompositionItem&, timeT, const SnapGrid&); + static Segment* getSegment(CompositionItem); + static CompositionItem makeCompositionItem(Segment*); + /** + * return the CompositionItem in the model which references the same segment as referenceItem + */ + static CompositionItem findSiblingCompositionItem(const CompositionModel::itemcontainer& items, const CompositionItem& referenceItem); + +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/CompositionItemImpl.cpp b/src/gui/editors/segment/segmentcanvas/CompositionItemImpl.cpp new file mode 100644 index 0000000..5508ad2 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionItemImpl.cpp @@ -0,0 +1,67 @@ +/* -*- 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 "CompositionItemImpl.h" + +#include "misc/Debug.h" +#include "base/Segment.h" +#include "CompositionRect.h" +#include <qbrush.h> +#include <qcolor.h> +#include <qpen.h> +#include <qpoint.h> +#include <qrect.h> +#include <qsize.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +CompositionItemImpl::CompositionItemImpl(Segment& s, const CompositionRect& rect) + : m_segment(s), + m_rect(rect), + m_z(0) +{} + +QRect CompositionItemImpl::rect() const +{ + QRect res = m_rect; + if (m_rect.isRepeating()) { + CompositionRect::repeatmarks repeatMarks = m_rect.getRepeatMarks(); + int neww = m_rect.getBaseWidth(); + + // RG_DEBUG << "CompositionItemImpl::rect() - width = " + // << m_rect.width() << " - base w = " << neww << endl; + res.setWidth(neww); + } else { + // RG_DEBUG << "CompositionItemImpl::rect() m_rect not repeating\n"; + } + + + return res; +} + +} diff --git a/src/gui/editors/segment/segmentcanvas/CompositionItemImpl.h b/src/gui/editors/segment/segmentcanvas/CompositionItemImpl.h new file mode 100644 index 0000000..b5b3ef7 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionItemImpl.h @@ -0,0 +1,74 @@ + +/* -*- 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_COMPOSITIONITEMIMPL_H_ +#define _RG_COMPOSITIONITEMIMPL_H_ + +#include "CompositionRect.h" +#include "CompositionItem.h" +#include <qrect.h> + + + + +namespace Rosegarden +{ + +class Segment; + + +class CompositionItemImpl : public _CompositionItem { +public: + CompositionItemImpl(Segment& s, const CompositionRect&); + virtual bool isRepeating() const { return m_rect.isRepeating(); } + virtual QRect rect() const; + virtual void moveBy(int x, int y) { m_rect.moveBy(x, y); } + virtual void moveTo(int x, int y) { m_rect.setRect(x, y, m_rect.width(), m_rect.height()); } + virtual void setX(int x) { m_rect.setX(x); } + virtual void setY(int y) { m_rect.setY(y); } + virtual void setZ(unsigned int z) { m_z = z; } + virtual int x() { return m_rect.x(); } + virtual int y() { return m_rect.y(); } + virtual unsigned int z() { return m_z; } + virtual void setWidth(int w) { m_rect.setWidth(w); } + // use segment address as hash key + virtual long hashKey() { return (long)getSegment(); } + + Segment* getSegment() { return &m_segment; } + const Segment* getSegment() const { return &m_segment; } + CompositionRect& getCompRect() { return m_rect; } + +protected: + + //--------------- Data members --------------------------------- + Segment& m_segment; + CompositionRect m_rect; + unsigned int m_z; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/CompositionModel.cpp b/src/gui/editors/segment/segmentcanvas/CompositionModel.cpp new file mode 100644 index 0000000..9701c8a --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionModel.cpp @@ -0,0 +1,43 @@ +/* -*- 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 "CompositionModel.h" + +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/Segment.h" +#include "CompositionItemHelper.h" + + +namespace Rosegarden +{ + +bool CompositionModel::CompositionItemCompare::operator()(const CompositionItem &c1, const CompositionItem &c2) const +{ + return CompositionItemHelper::getSegment(c1) < CompositionItemHelper::getSegment(c2); +} + +} +#include "CompositionModel.moc" diff --git a/src/gui/editors/segment/segmentcanvas/CompositionModel.h b/src/gui/editors/segment/segmentcanvas/CompositionModel.h new file mode 100644 index 0000000..beafc2e --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionModel.h @@ -0,0 +1,179 @@ + +/* -*- 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_COMPOSITIONMODEL_H_ +#define _RG_COMPOSITIONMODEL_H_ + +#include "base/Composition.h" +#include "base/Segment.h" +#include <set> +#include <qcolor.h> +#include <qobject.h> +#include <qimage.h> +#include <qpoint.h> +#include <qrect.h> +#include <utility> +#include <vector> +#include "base/Event.h" +#include "CompositionRect.h" +#include "CompositionItem.h" + + +class RectRanges; +class CompositionItem; +class AudioPreviewDrawData; + + +namespace Rosegarden +{ + +class SnapGrid; +typedef std::vector<QImage> PixmapArray; + + +class CompositionModel : public QObject, public CompositionObserver, public SegmentObserver +{ + Q_OBJECT +public: + + struct CompositionItemCompare { + bool operator()(const CompositionItem &c1, const CompositionItem &c2) const; + }; + + typedef std::vector<QRect> rectlist; + typedef std::vector<int> heightlist; + typedef std::vector<CompositionRect> rectcontainer; + typedef std::set<CompositionItem, CompositionItemCompare> itemcontainer; + + struct AudioPreviewDrawDataItem { + AudioPreviewDrawDataItem(PixmapArray p, QPoint bp, QRect r) : + pixmap(p), basePoint(bp), rect(r), resizeOffset(0) {}; + PixmapArray pixmap; + QPoint basePoint; + QRect rect; + + // when showing a segment that is being resized from the + // beginning, this contains the offset between the current + // rect of the segment and the resized one + int resizeOffset; + }; + + typedef std::vector<AudioPreviewDrawDataItem> AudioPreviewDrawData; + + struct RectRange { + std::pair<rectlist::iterator, rectlist::iterator> range; + QPoint basePoint; + QColor color; + }; + + typedef std::vector<RectRange> RectRanges; + + class AudioPreviewData { + public: + AudioPreviewData(bool showMinima, unsigned int channels) : m_showMinima(showMinima), m_channels(channels) {}; + // ~AudioPreviewData(); + + bool showsMinima() { return m_showMinima; } + void setShowMinima(bool s) { m_showMinima = s; } + + unsigned int getChannels() { return m_channels; } + void setChannels(unsigned int c) { m_channels = c; } + + const std::vector<float> &getValues() const { return m_values; } + void setValues(const std::vector<float>&v) { m_values = v; } + + QRect getSegmentRect() { return m_segmentRect; } + void setSegmentRect(const QRect& r) { m_segmentRect = r; } + + protected: + std::vector<float> m_values; + bool m_showMinima; + unsigned int m_channels; + QRect m_segmentRect; + + private: + // no copy ctor + AudioPreviewData(const AudioPreviewData&); + }; + + + virtual ~CompositionModel() {}; + + virtual unsigned int getNbRows() = 0; + virtual const rectcontainer& getRectanglesIn(const QRect& rect, + RectRanges* notationRects, AudioPreviewDrawData* audioRects) = 0; + + virtual heightlist getTrackDividersIn(const QRect& rect) = 0; + + virtual itemcontainer getItemsAt (const QPoint&) = 0; + virtual timeT getRepeatTimeAt (const QPoint&, const CompositionItem&) = 0; + + virtual SnapGrid& grid() = 0; + + virtual void setPointerPos(int xPos) = 0; + virtual void setSelected(const CompositionItem&, bool selected = true) = 0; + virtual bool isSelected(const CompositionItem&) const = 0; + virtual void setSelected(const itemcontainer&) = 0; + virtual void clearSelected() = 0; + virtual bool haveSelection() const = 0; + virtual bool haveMultipleSelection() const = 0; + virtual void signalSelection() = 0; + virtual void setSelectionRect(const QRect&) = 0; + virtual void finalizeSelectionRect() = 0; + virtual QRect getSelectionContentsRect() = 0; + virtual void signalContentChange() = 0; + + virtual void addRecordingItem(const CompositionItem&) = 0; + virtual void removeRecordingItem(const CompositionItem&) = 0; + virtual void clearRecordingItems() = 0; + virtual bool haveRecordingItems() = 0; + + enum ChangeType { ChangeMove, ChangeResizeFromStart, ChangeResizeFromEnd }; + + virtual void startChange(const CompositionItem&, ChangeType change) = 0; + virtual void startChangeSelection(ChangeType change) = 0; + virtual itemcontainer& getChangingItems() = 0; + virtual void endChange() = 0; + virtual ChangeType getChangeType() = 0; + + virtual void setLength(int width) = 0; + virtual int getLength() = 0; + +signals: + void needContentUpdate(); + void needContentUpdate(const QRect&); + void needArtifactsUpdate(); + +protected: + CompositionItem* m_currentCompositionItem; +}; + +class AudioPreviewThread; +class AudioPreviewUpdater; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp b/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp new file mode 100644 index 0000000..39deb2e --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp @@ -0,0 +1,1328 @@ +/* -*- 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 <algorithm> +#include "CompositionModelImpl.h" + +#include "base/BaseProperties.h" +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "AudioPreviewThread.h" +#include "AudioPreviewUpdater.h" +#include "base/Composition.h" +#include "base/Event.h" +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "base/RulerScale.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/SnapGrid.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "CompositionItemHelper.h" +#include "CompositionItemImpl.h" +#include "CompositionModel.h" +#include "CompositionRect.h" +#include "CompositionColourCache.h" +#include "AudioPreviewPainter.h" +#include "gui/general/GUIPalette.h" +#include "SegmentOrderer.h" +#include <qbrush.h> +#include <qcolor.h> +#include <qpen.h> +#include <qpoint.h> +#include <qrect.h> +#include <qregexp.h> +#include <qsize.h> +#include <qstring.h> + + + +namespace Rosegarden +{ + +CompositionModelImpl::CompositionModelImpl(Composition& compo, + Studio& studio, + RulerScale *rulerScale, + int vStep) + : m_composition(compo), + m_studio(studio), + m_grid(rulerScale, vStep), + m_pointerTimePos(0), + m_audioPreviewThread(0) +{ + m_notationPreviewDataCache.setAutoDelete(true); + m_audioPreviewDataCache.setAutoDelete(true); + m_composition.addObserver(this); + + setTrackHeights(); + + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + (*i)->addObserver(this); + } +} + +CompositionModelImpl::~CompositionModelImpl() +{ + RG_DEBUG << "CompositionModelImpl::~CompositionModelImpl()" << endl; + + if (!isCompositionDeleted()) { + + m_composition.removeObserver(this); + + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + (*i)->removeObserver(this); + } + } + + RG_DEBUG << "CompositionModelImpl::~CompositionModelImpl(): removal from Segment & Composition observers OK" << endl; + + if (m_audioPreviewThread) { + while (!m_audioPreviewUpdaterMap.empty()) { + // Cause any running previews to be cancelled + delete m_audioPreviewUpdaterMap.begin()->second; + m_audioPreviewUpdaterMap.erase(m_audioPreviewUpdaterMap.begin()); + } + } +} + +struct RectCompare { + bool operator()(const QRect &r1, const QRect &r2) const { + return r1.x() < r2.x(); + } +}; + +void CompositionModelImpl::makeNotationPreviewRects(RectRanges* npRects, QPoint basePoint, + const Segment* segment, const QRect& clipRect) +{ + + rectlist* cachedNPData = getNotationPreviewData(segment); + + if (cachedNPData->empty()) + return ; + + rectlist::iterator npEnd = cachedNPData->end(); + + rectlist::iterator npi = std::lower_bound(cachedNPData->begin(), npEnd, clipRect, RectCompare()); + + if (npi == npEnd) + return ; + + if (npi != cachedNPData->begin()) + --npi; + + RectRange interval; + + interval.range.first = npi; + + int segEndX = int(nearbyint(m_grid.getRulerScale()->getXForTime(segment->getEndMarkerTime()))); + int xLim = std::min(clipRect.topRight().x(), segEndX); + + // RG_DEBUG << "CompositionModelImpl::makeNotationPreviewRects : basePoint.x : " + // << basePoint.x() << endl; + + // move iterator forward + // + while (npi->x() < xLim && npi != npEnd) + ++npi; + + interval.range.second = npi; + interval.basePoint.setX(0); + interval.basePoint.setY(basePoint.y()); + interval.color = computeSegmentPreviewColor(segment); + + npRects->push_back(interval); +} + +void CompositionModelImpl::makeNotationPreviewRectsMovingSegment(RectRanges* npRects, QPoint basePoint, + const Segment* segment, const QRect& currentSR) +{ + CompositionRect unmovedSR = computeSegmentRect(*segment); + + rectlist* cachedNPData = getNotationPreviewData(segment); + + if (cachedNPData->empty()) + return ; + + rectlist::iterator npEnd = cachedNPData->end(), + npBegin = cachedNPData->begin(); + + rectlist::iterator npi; + + if (getChangeType() == ChangeResizeFromStart) + npi = std::lower_bound(npBegin, npEnd, currentSR, RectCompare()); + else + npi = std::lower_bound(npBegin, npEnd, unmovedSR, RectCompare()); + + if (npi == npEnd) + return ; + + if (npi != npBegin && getChangeType() != ChangeResizeFromStart) { + --npi; + } + + RectRange interval; + + interval.range.first = npi; + + int xLim = getChangeType() == ChangeMove ? unmovedSR.topRight().x() : currentSR.topRight().x(); + + // RG_DEBUG << "CompositionModelImpl::makeNotationPreviewRectsMovingSegment : basePoint.x : " + // << basePoint.x() << endl; + + // move iterator forward + // + while (npi->x() < xLim && npi != npEnd) + ++npi; + + interval.range.second = npi; + interval.basePoint.setY(basePoint.y()); + + if (getChangeType() == ChangeMove) + interval.basePoint.setX(basePoint.x() - unmovedSR.x()); + else + interval.basePoint.setX(0); + + interval.color = computeSegmentPreviewColor(segment); + + npRects->push_back(interval); +} + +void CompositionModelImpl::makeAudioPreviewRects(AudioPreviewDrawData* apRects, const Segment* segment, + const CompositionRect& segRect, const QRect& clipRect) +{ + Profiler profiler("CompositionModelImpl::makeAudioPreviewRects", true); + RG_DEBUG << "CompositionModelImpl::makeAudioPreviewRects - segRect = " << segRect << endl; + + PixmapArray previewImage = getAudioPreviewPixmap(segment); + + QPoint basePoint = segRect.topLeft(); + + AudioPreviewDrawDataItem previewItem(previewImage, basePoint, segRect); + + if (getChangeType() == ChangeResizeFromStart) { + CompositionRect originalRect = computeSegmentRect(*segment); + previewItem.resizeOffset = segRect.x() - originalRect.x(); + } + + apRects->push_back(previewItem); +} + +void CompositionModelImpl::computeRepeatMarks(CompositionItem& item) +{ + Segment* s = CompositionItemHelper::getSegment(item); + CompositionRect& sr = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)item)->getCompRect(); + computeRepeatMarks(sr, s); +} + +void CompositionModelImpl::computeRepeatMarks(CompositionRect& sr, const Segment* s) +{ + if (s->isRepeating()) { + + timeT startTime = s->getStartTime(); + timeT endTime = s->getEndMarkerTime(); + timeT repeatInterval = endTime - startTime; + + if (repeatInterval <= 0) { + // std::cerr << "WARNING: CompositionModelImpl::computeRepeatMarks: Segment at " << startTime << " has repeatInterval " << repeatInterval << std::endl; + // std::cerr << kdBacktrace() << std::endl; + return ; + } + + timeT repeatStart = endTime; + timeT repeatEnd = s->getRepeatEndTime(); + sr.setWidth(int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(startTime, + repeatEnd - startTime)))); + + CompositionRect::repeatmarks repeatMarks; + + // RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : repeatStart = " + // << repeatStart << " - repeatEnd = " << repeatEnd << endl; + + for (timeT repeatMark = repeatStart; repeatMark < repeatEnd; repeatMark += repeatInterval) { + int mark = int(nearbyint(m_grid.getRulerScale()->getXForTime(repeatMark))); + // RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : mark at " << mark << endl; + repeatMarks.push_back(mark); + } + sr.setRepeatMarks(repeatMarks); + if (repeatMarks.size() > 0) + sr.setBaseWidth(repeatMarks[0] - sr.x()); + else { + // RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : no repeat marks\n"; + sr.setBaseWidth(sr.width()); + } + + // RG_DEBUG << "CompositionModelImpl::computeRepeatMarks : s = " + // << s << " base width = " << sr.getBaseWidth() + // << " - nb repeat marks = " << repeatMarks.size() << endl; + + } +} + +void CompositionModelImpl::setAudioPreviewThread(AudioPreviewThread *thread) +{ + // std::cerr << "\nCompositionModelImpl::setAudioPreviewThread()\n" << std::endl; + + while (!m_audioPreviewUpdaterMap.empty()) { + // Cause any running previews to be cancelled + delete m_audioPreviewUpdaterMap.begin()->second; + m_audioPreviewUpdaterMap.erase(m_audioPreviewUpdaterMap.begin()); + } + + m_audioPreviewThread = thread; +} + +void CompositionModelImpl::clearPreviewCache() +{ + RG_DEBUG << "CompositionModelImpl::clearPreviewCache\n"; + + m_notationPreviewDataCache.clear(); + m_audioPreviewDataCache.clear(); + m_audioSegmentPreviewMap.clear(); + + for (AudioPreviewUpdaterMap::iterator i = m_audioPreviewUpdaterMap.begin(); + i != m_audioPreviewUpdaterMap.end(); ++i) { + i->second->cancel(); + } + + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + if ((*i)->getType() == Segment::Audio) { + // This will create the audio preview updater. The + // preview won't be calculated and cached until the + // updater completes and calls back. + updatePreviewCacheForAudioSegment((*i), 0); + } + } +} + +void CompositionModelImpl::updatePreviewCacheForNotationSegment(const Segment* segment, rectlist* npData) +{ + npData->clear(); + + int segStartX = int(nearbyint(m_grid.getRulerScale()->getXForTime(segment->getStartTime()))); + + bool isPercussion = false; + Track *track = m_composition.getTrackById(segment->getTrack()); + if (track) { + InstrumentId iid = track->getInstrument(); + Instrument *instrument = m_studio.getInstrumentById(iid); + if (instrument && instrument->isPercussion()) isPercussion = true; + } + + for (Segment::iterator i = segment->begin(); + i != segment->end(); ++i) { + + long pitch = 0; + if (!(*i)->isa(Note::EventType) || + !(*i)->get<Int>(BaseProperties::PITCH, pitch)) { + continue; + } + + timeT eventStart = (*i)->getAbsoluteTime(); + timeT eventEnd = eventStart + (*i)->getDuration(); + // if (eventEnd > segment->getEndMarkerTime()) { + // eventEnd = segment->getEndMarkerTime(); + // } + + int x = int(nearbyint(m_grid.getRulerScale()->getXForTime(eventStart))); + int width = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(eventStart, + eventEnd - eventStart))); + + if (x <= segStartX) { + ++x; + if (width > 1) + --width; + } + if (width > 1) + --width; + if (width < 1) + ++width; + + double y0 = 0; + double y1 = m_grid.getYSnap(); + double y = y1 + ((y0 - y1) * (pitch - 16)) / 96; + + int height = 2; + + if (isPercussion) { + height = 3; + if (width > 2) width = 2; + } + + if (y < y0) + y = y0; + if (y > y1 - height + 1) + y = y1 - height + 1; + + QRect r(x, (int)y, width, height); + + // RG_DEBUG << "CompositionModelImpl::updatePreviewCacheForNotationSegment() : npData = " + // << npData << ", preview rect = " + // << r << endl; + npData->push_back(r); + } + +} + +QColor CompositionModelImpl::computeSegmentPreviewColor(const Segment* segment) +{ + // compute the preview color so it's as visible as possible over the segment's color + QColor segColor = GUIPalette::convertColour(m_composition.getSegmentColourMap().getColourByIndex(segment->getColourIndex())); + int h, s, v; + segColor.hsv(&h, &s, &v); + + // colors with saturation lower than value should be pastel tints, and + // they get a value of 0; yellow and green hues close to the dead center + // point for that hue were taking a value of 255 with the (s < v) + // formula, so I added an extra hack to force hues in those two narrow + // ranges toward black. Black always looks good, while white washes out + // badly against intense yellow, and doesn't look very good against + // intense green either... hacky, but this produces pleasant results against + // every bizarre extreme of color I could cook up to throw at it, plus + // (the real reason for all this convoluted fiddling, it does all that while keeping + // white against bright reds and blues, which looks better than black) + if ( ((((h > 57) && (h < 66)) || ((h > 93) && (h < 131))) && (s > 127) && (v > 127) ) || + (s < v) ) { + v = 0; + } else { + v = 255; + } + s = 31; + h += 180; + + segColor.setHsv(h, s, v); + + return segColor; +} + +void CompositionModelImpl::updatePreviewCacheForAudioSegment(const Segment* segment, AudioPreviewData* apData) +{ + if (m_audioPreviewThread) { + // std::cerr << "CompositionModelImpl::updatePreviewCacheForAudioSegment() - new audio preview started" << std::endl; + + CompositionRect segRect = computeSegmentRect(*segment); + segRect.setWidth(segRect.getBaseWidth()); // don't use repeating area + segRect.moveTopLeft(QPoint(0, 0)); + + if (apData) + apData->setSegmentRect(segRect); + + if (m_audioPreviewUpdaterMap.find(segment) == + m_audioPreviewUpdaterMap.end()) { + + AudioPreviewUpdater *updater = new AudioPreviewUpdater + (*m_audioPreviewThread, m_composition, segment, segRect, this); + + connect(updater, SIGNAL(audioPreviewComplete(AudioPreviewUpdater*)), + this, SLOT(slotAudioPreviewComplete(AudioPreviewUpdater*))); + + m_audioPreviewUpdaterMap[segment] = updater; + + } else { + + m_audioPreviewUpdaterMap[segment]->setDisplayExtent(segRect); + } + + m_audioPreviewUpdaterMap[segment]->update(); + + } else { + RG_DEBUG << "CompositionModelImpl::updatePreviewCacheForAudioSegment() - no audio preview thread set\n"; + } +} + +void CompositionModelImpl::slotAudioPreviewComplete(AudioPreviewUpdater* apu) +{ + RG_DEBUG << "CompositionModelImpl::slotAudioPreviewComplete()\n"; + + AudioPreviewData *apData = getAudioPreviewData(apu->getSegment()); + QRect updateRect; + + if (apData) { + RG_DEBUG << "CompositionModelImpl::slotAudioPreviewComplete(" << apu << "): apData contains " << apData->getValues().size() << " values already" << endl; + unsigned int channels = 0; + const std::vector<float> &values = apu->getComputedValues(channels); + if (channels > 0) { + RG_DEBUG << "CompositionModelImpl::slotAudioPreviewComplete: set " + << values.size() << " samples on " << channels << " channels\n"; + apData->setChannels(channels); + apData->setValues(values); + updateRect = postProcessAudioPreview(apData, apu->getSegment()); + } + } + + if (!updateRect.isEmpty()) + emit needContentUpdate(updateRect); +} + +QRect CompositionModelImpl::postProcessAudioPreview(AudioPreviewData* apData, const Segment* segment) +{ + // RG_DEBUG << "CompositionModelImpl::postProcessAudioPreview()\n"; + + AudioPreviewPainter previewPainter(*this, apData, m_composition, segment); + previewPainter.paintPreviewImage(); + + m_audioSegmentPreviewMap[segment] = previewPainter.getPreviewImage(); + + return previewPainter.getSegmentRect(); +} + +void CompositionModelImpl::slotInstrumentParametersChanged(InstrumentId id) +{ + std::cerr << "CompositionModelImpl::slotInstrumentParametersChanged()\n"; + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + Segment* s = *i; + TrackId trackId = s->getTrack(); + Track *track = getComposition().getTrackById(trackId); + + // We need to update the cache for audio segments, because the + // instrument playback level is reflected in the audio + // preview. And we need to update it for midi segments, + // because the preview style differs depending on whether the + // segment is on a percussion instrument or not + + if (track && track->getInstrument() == id) { + removePreviewCache(s); + emit needContentUpdate(computeSegmentRect(*s)); + } + } +} + +void CompositionModelImpl::slotAudioFileFinalized(Segment* s) +{ + // RG_DEBUG << "CompositionModelImpl::slotAudioFileFinalized()\n"; + removePreviewCache(s); +} + +PixmapArray CompositionModelImpl::getAudioPreviewPixmap(const Segment* s) +{ + getAudioPreviewData(s); + return m_audioSegmentPreviewMap[s]; +} + +void CompositionModelImpl::eventAdded(const Segment *s, Event *) +{ + // RG_DEBUG << "CompositionModelImpl::eventAdded()\n"; + removePreviewCache(s); + emit needContentUpdate(computeSegmentRect(*s)); +} + +void CompositionModelImpl::eventRemoved(const Segment *s, Event *) +{ + // RG_DEBUG << "CompositionModelImpl::eventRemoved" << endl; + removePreviewCache(s); + emit needContentUpdate(computeSegmentRect(*s)); +} + +void CompositionModelImpl::appearanceChanged(const Segment *s) +{ + // RG_DEBUG << "CompositionModelImpl::appearanceChanged" << endl; + clearInCache(s, true); + emit needContentUpdate(computeSegmentRect(*s)); +} + +void CompositionModelImpl::endMarkerTimeChanged(const Segment *s, bool shorten) +{ + // RG_DEBUG << "CompositionModelImpl::endMarkerTimeChanged(" << shorten << ")" << endl; + clearInCache(s, true); + if (shorten) { + emit needContentUpdate(); // no longer know former segment dimension + } else { + emit needContentUpdate(computeSegmentRect(*s)); + } +} + +void CompositionModelImpl::makePreviewCache(const Segment *s) +{ + if (s->getType() == Segment::Internal) { + makeNotationPreviewDataCache(s); + } else { + makeAudioPreviewDataCache(s); + } +} + +void CompositionModelImpl::removePreviewCache(const Segment *s) +{ + if (s->getType() == Segment::Internal) { + m_notationPreviewDataCache.remove(const_cast<Segment*>(s)); + } else { + m_audioPreviewDataCache.remove(const_cast<Segment*>(s)); + m_audioSegmentPreviewMap.erase(s); + } + +} + +void CompositionModelImpl::segmentAdded(const Composition *, Segment *s) +{ + std::cerr << "CompositionModelImpl::segmentAdded: segment " << s << " on track " << s->getTrack() << ": calling setTrackHeights" << std::endl; + setTrackHeights(s); + + makePreviewCache(s); + s->addObserver(this); + emit needContentUpdate(); +} + +void CompositionModelImpl::segmentRemoved(const Composition *, Segment *s) +{ + setTrackHeights(); + + QRect r = computeSegmentRect(*s); + + m_selectedSegments.erase(s); + + clearInCache(s, true); + s->removeObserver(this); + m_recordingSegments.erase(s); // this could be a recording segment + emit needContentUpdate(r); +} + +void CompositionModelImpl::segmentTrackChanged(const Composition *, Segment *s, TrackId tid) +{ + std::cerr << "CompositionModelImpl::segmentTrackChanged: segment " << s << " on track " << tid << ", calling setTrackHeights" << std::endl; + + // we don't call setTrackHeights(s), because some of the tracks + // above s may have changed height as well (if s was moved off one + // of them) + if (setTrackHeights()) { + std::cerr << "... changed, updating" << std::endl; + emit needContentUpdate(); + } +} + +void CompositionModelImpl::segmentStartChanged(const Composition *, Segment *s, timeT) +{ +// std::cerr << "CompositionModelImpl::segmentStartChanged: segment " << s << " on track " << s->getTrack() << ": calling setTrackHeights" << std::endl; + if (setTrackHeights(s)) emit needContentUpdate(); +} + +void CompositionModelImpl::segmentEndMarkerChanged(const Composition *, Segment *s, bool) +{ +// std::cerr << "CompositionModelImpl::segmentEndMarkerChanged: segment " << s << " on track " << s->getTrack() << ": calling setTrackHeights" << std::endl; + if (setTrackHeights(s)) { +// std::cerr << "... changed, updating" << std::endl; + emit needContentUpdate(); + } +} + +void CompositionModelImpl::segmentRepeatChanged(const Composition *, Segment *s, bool) +{ + clearInCache(s); + setTrackHeights(s); + emit needContentUpdate(); +} + +void CompositionModelImpl::endMarkerTimeChanged(const Composition *, bool) +{ + emit needSizeUpdate(); +} + +void CompositionModelImpl::setSelectionRect(const QRect& r) +{ + m_selectionRect = r.normalize(); + + m_previousTmpSelectedSegments = m_tmpSelectedSegments; + m_tmpSelectedSegments.clear(); + + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + QRect updateRect = m_selectionRect; + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + Segment* s = *i; + CompositionRect sr = computeSegmentRect(*s); + if (sr.intersects(m_selectionRect)) { + m_tmpSelectedSegments.insert(s); + updateRect |= sr; + } + } + + updateRect = updateRect.normalize(); + + if (!updateRect.isNull() && !m_previousSelectionUpdateRect.isNull()) { + + if (m_tmpSelectedSegments != m_previousTmpSelectedSegments) + emit needContentUpdate(updateRect | m_previousSelectionUpdateRect); + + emit needArtifactsUpdate(); + } + + + m_previousSelectionUpdateRect = updateRect; + +} + +void CompositionModelImpl::finalizeSelectionRect() +{ + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + Segment* s = *i; + CompositionRect sr = computeSegmentRect(*s); + if (sr.intersects(m_selectionRect)) { + setSelected(s); + } + } + + m_previousSelectionUpdateRect = m_selectionRect = QRect(); + m_tmpSelectedSegments.clear(); +} + +QRect CompositionModelImpl::getSelectionContentsRect() +{ + QRect selectionRect; + + SegmentSelection sel = getSelectedSegments(); + for (SegmentSelection::iterator i = sel.begin(); + i != sel.end(); ++i) { + + Segment* s = *i; + CompositionRect sr = computeSegmentRect(*s); + selectionRect |= sr; + } + + return selectionRect; +} + +void CompositionModelImpl::addRecordingItem(const CompositionItem& item) +{ + m_recordingSegments.insert(CompositionItemHelper::getSegment(item)); + emit needContentUpdate(); + + RG_DEBUG << "CompositionModelImpl::addRecordingItem: now have " + << m_recordingSegments.size() << " recording items\n"; +} + +void CompositionModelImpl::removeRecordingItem(const CompositionItem &item) +{ + Segment* s = CompositionItemHelper::getSegment(item); + + m_recordingSegments.erase(s); + clearInCache(s, true); + + emit needContentUpdate(); + + RG_DEBUG << "CompositionModelImpl::removeRecordingItem: now have " + << m_recordingSegments.size() << " recording items\n"; +} + +void CompositionModelImpl::clearRecordingItems() +{ + for (recordingsegmentset::iterator i = m_recordingSegments.begin(); + i != m_recordingSegments.end(); ++i) + clearInCache(*i, true); + + m_recordingSegments.clear(); + + emit needContentUpdate(); + RG_DEBUG << "CompositionModelImpl::clearRecordingItem\n"; +} + +bool CompositionModelImpl::isMoving(const Segment* sm) const +{ + itemcontainer::const_iterator movEnd = m_changingItems.end(); + + for (itemcontainer::const_iterator i = m_changingItems.begin(); i != movEnd; ++i) { + const CompositionItemImpl* ci = dynamic_cast<const CompositionItemImpl*>((_CompositionItem*)(*i)); + const Segment* s = ci->getSegment(); + if (sm == s) + return true; + } + + return false; +} + +bool CompositionModelImpl::isRecording(const Segment* s) const +{ + return m_recordingSegments.find(const_cast<Segment*>(s)) != m_recordingSegments.end(); +} + +CompositionModel::itemcontainer CompositionModelImpl::getItemsAt(const QPoint& point) +{ + itemcontainer res; + + const Composition::segmentcontainer& segments = m_composition.getSegments(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segments.end(); ++i) { + + Segment* s = *i; + + CompositionRect sr = computeSegmentRect(*s); + if (sr.contains(point)) { + // RG_DEBUG << "CompositionModelImpl::getItemsAt() adding " << sr << " for segment " << s << endl; + CompositionItem item(new CompositionItemImpl(*s, sr)); + unsigned int z = computeZForSegment(s); + // RG_DEBUG << "CompositionModelImpl::getItemsAt() z = " << z << endl; + item->setZ(z); + res.insert(item); + } else { + // RG_DEBUG << "CompositionModelImpl::getItemsAt() skiping " << sr << endl; + } + + } + + if (res.size() == 1) { // only one segment under click point + Segment* s = CompositionItemHelper::getSegment(*(res.begin())); + m_segmentOrderer.segmentClicked(s); + } + + return res; +} + +void CompositionModelImpl::setPointerPos(int xPos) +{ + m_pointerTimePos = grid().getRulerScale()->getTimeForX(xPos); + + for (recordingsegmentset::iterator i = m_recordingSegments.begin(); + i != m_recordingSegments.end(); ++i) { + emit needContentUpdate(computeSegmentRect(**i)); + } +} + +void CompositionModelImpl::setSelected(const CompositionItem& item, bool selected) +{ + const CompositionItemImpl* itemImpl = dynamic_cast<const CompositionItemImpl*>((_CompositionItem*)item); + if (itemImpl) { + Segment* segment = const_cast<Segment*>(itemImpl->getSegment()); + setSelected(segment, selected); + } +} + +void CompositionModelImpl::setSelected(const itemcontainer& items) +{ + for (itemcontainer::const_iterator i = items.begin(); i != items.end(); ++i) { + setSelected(*i); + } +} + +void CompositionModelImpl::setSelected(const Segment* segment, bool selected) +{ + RG_DEBUG << "CompositionModelImpl::setSelected " << segment << " - " << selected << endl; + if (selected) { + if (!isSelected(segment)) + m_selectedSegments.insert(const_cast<Segment*>(segment)); + } else { + SegmentSelection::iterator i = m_selectedSegments.find(const_cast<Segment*>(segment)); + if (i != m_selectedSegments.end()) + m_selectedSegments.erase(i); + } + emit needContentUpdate(); +} + +void CompositionModelImpl::signalSelection() +{ + // RG_DEBUG << "CompositionModelImpl::signalSelection()\n"; + emit selectedSegments(getSelectedSegments()); +} + +void CompositionModelImpl::signalContentChange() +{ + // RG_DEBUG << "CompositionModelImpl::signalContentChange" << endl; + emit needContentUpdate(); +} + +void CompositionModelImpl::clearSelected() +{ + RG_DEBUG << "CompositionModelImpl::clearSelected" << endl; + m_selectedSegments.clear(); + emit needContentUpdate(); +} + +bool CompositionModelImpl::isSelected(const CompositionItem& ci) const +{ + const CompositionItemImpl* itemImpl = dynamic_cast<const CompositionItemImpl*>((_CompositionItem*)ci); + return itemImpl ? isSelected(itemImpl->getSegment()) : 0; +} + +bool CompositionModelImpl::isSelected(const Segment* s) const +{ + return m_selectedSegments.find(const_cast<Segment*>(s)) != m_selectedSegments.end(); +} + +bool CompositionModelImpl::isTmpSelected(const Segment* s) const +{ + return m_tmpSelectedSegments.find(const_cast<Segment*>(s)) != m_tmpSelectedSegments.end(); +} + +bool CompositionModelImpl::wasTmpSelected(const Segment* s) const +{ + return m_previousTmpSelectedSegments.find(const_cast<Segment*>(s)) != m_previousTmpSelectedSegments.end(); +} + +void CompositionModelImpl::startChange(const CompositionItem& item, CompositionModel::ChangeType change) +{ + m_changeType = change; + + itemcontainer::iterator i = m_changingItems.find(item); + + // if an "identical" composition item has already been inserted, drop this one + if (i != m_changingItems.end()) { + RG_DEBUG << "CompositionModelImpl::startChange : item already in\n"; + m_itemGC.push_back(item); + } else { + item->saveRect(); + m_changingItems.insert(item); + } +} + +void CompositionModelImpl::startChangeSelection(CompositionModel::ChangeType change) +{ + SegmentSelection::iterator i = m_selectedSegments.begin(); + for (; i != m_selectedSegments.end(); ++i) { + Segment* s = *i; + CompositionRect sr = computeSegmentRect(*s); + startChange(CompositionItem(new CompositionItemImpl(*s, sr)), change); + } + +} + +void CompositionModelImpl::endChange() +{ + for (itemcontainer::const_iterator i = m_changingItems.begin(); i != m_changingItems.end(); ++i) { + delete *i; + } + + m_changingItems.clear(); + + for (itemgc::iterator i = m_itemGC.begin(); i != m_itemGC.end(); ++i) { + delete *i; + } + m_itemGC.clear(); + RG_DEBUG << "CompositionModelImpl::endChange\n"; + emit needContentUpdate(); +} + +void CompositionModelImpl::setLength(int width) +{ + timeT endMarker = m_grid.snapX(width); + m_composition.setEndMarker(endMarker); +} + +int CompositionModelImpl::getLength() +{ + timeT endMarker = m_composition.getEndMarker(); + int w = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(0, endMarker))); + return w; +} + +timeT CompositionModelImpl::getRepeatTimeAt(const QPoint& p, const CompositionItem& cItem) +{ + // timeT timeAtClick = m_grid.getRulerScale()->getTimeForX(p.x()); + + CompositionItemImpl* itemImpl = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)cItem); + + const Segment* s = itemImpl->getSegment(); + + timeT startTime = s->getStartTime(); + timeT endTime = s->getEndMarkerTime(); + timeT repeatInterval = endTime - startTime; + + int rWidth = int(nearbyint(m_grid.getRulerScale()->getXForTime(repeatInterval))); + + int count = (p.x() - int(itemImpl->rect().x())) / rWidth; + RG_DEBUG << "CompositionModelImpl::getRepeatTimeAt() : count = " << count << endl; + + return count != 0 ? startTime + (count * (s->getEndMarkerTime() - s->getStartTime())) : 0; +} + +bool CompositionModelImpl::setTrackHeights(Segment *s) +{ + bool heightsChanged = false; + +// std::cerr << "CompositionModelImpl::setTrackHeights" << std::endl; + + for (Composition::trackcontainer::const_iterator i = + m_composition.getTracks().begin(); + i != m_composition.getTracks().end(); ++i) { + + if (s && i->first != s->getTrack()) continue; + + int max = m_composition.getMaxContemporaneousSegmentsOnTrack(i->first); + if (max == 0) max = 1; + +// std::cerr << "for track " << i->first << ": height = " << max << ", old height = " << m_trackHeights[i->first] << std::endl; + + if (max != m_trackHeights[i->first]) { + heightsChanged = true; + m_trackHeights[i->first] = max; + } + + m_grid.setBinHeightMultiple(i->second->getPosition(), max); + } + + if (heightsChanged) { +// std::cerr << "CompositionModelImpl::setTrackHeights: heights have changed" << std::endl; + for (Composition::segmentcontainer::iterator i = m_composition.begin(); + i != m_composition.end(); ++i) { + computeSegmentRect(**i); + } + } + + return heightsChanged; +} + +QPoint CompositionModelImpl::computeSegmentOrigin(const Segment& s) +{ + // Profiler profiler("CompositionModelImpl::computeSegmentOrigin", true); + + int trackPosition = m_composition.getTrackPositionById(s.getTrack()); + timeT startTime = s.getStartTime(); + + QPoint res; + + res.setX(int(nearbyint(m_grid.getRulerScale()->getXForTime(startTime)))); + + res.setY(m_grid.getYBinCoordinate(trackPosition) + + m_composition.getSegmentVoiceIndex(&s) * + m_grid.getYSnap() + 1); + + return res; +} + +bool CompositionModelImpl::isCachedRectCurrent(const Segment& s, const CompositionRect& r, QPoint cachedSegmentOrigin, timeT cachedSegmentEndTime) +{ + return s.isRepeating() == r.isRepeating() && + ((cachedSegmentOrigin.x() != r.x() && s.getEndMarkerTime() != cachedSegmentEndTime) || + (cachedSegmentOrigin.x() == r.x() && s.getEndMarkerTime() == cachedSegmentEndTime)); +} + +void CompositionModelImpl::clearInCache(const Segment* s, bool clearPreview) +{ + if (s) { + m_segmentRectMap.erase(s); + m_segmentEndTimeMap.erase(s); + if (clearPreview) + removePreviewCache(s); + } else { // clear the whole cache + m_segmentRectMap.clear(); + m_segmentEndTimeMap.clear(); + if (clearPreview) + clearPreviewCache(); + } +} + +void CompositionModelImpl::putInCache(const Segment*s, const CompositionRect& cr) +{ + m_segmentRectMap[s] = cr; + m_segmentEndTimeMap[s] = s->getEndMarkerTime(); +} + +CompositionRect CompositionModelImpl::computeSegmentRect(const Segment& s, bool computeZ) +{ + // Profiler profiler("CompositionModelImpl::computeSegmentRect", true); + + QPoint origin = computeSegmentOrigin(s); + + bool isRecordingSegment = isRecording(&s); + + if (!isRecordingSegment) { + timeT endTime = 0; + + CompositionRect cachedCR = getFromCache(&s, endTime); + // don't cache repeating segments - it's just hopeless, because the segment's rect may have to be recomputed + // in other cases than just when the segment itself is moved, + // for instance if another segment is moved over it + if (!s.isRepeating() && cachedCR.isValid() && isCachedRectCurrent(s, cachedCR, origin, endTime)) { + // RG_DEBUG << "CompositionModelImpl::computeSegmentRect() : using cache for seg " + // << &s << " - cached rect repeating = " << cachedCR.isRepeating() << " - base width = " + // << cachedCR.getBaseWidth() << endl; + + bool xChanged = origin.x() != cachedCR.x(); + bool yChanged = origin.y() != cachedCR.y(); + + cachedCR.moveTopLeft(origin); + + if (s.isRepeating() && (xChanged || yChanged)) { // update repeat marks + + // this doesn't work in the general case (if there's another segment on the same track for instance), + // it's better to simply recompute all the marks + // CompositionRect::repeatmarks repeatMarks = cachedCR.getRepeatMarks(); + // for(unsigned int i = 0; i < repeatMarks.size(); ++i) { + // repeatMarks[i] += deltaX; + // } + // cachedCR.setRepeatMarks(repeatMarks); + computeRepeatMarks(cachedCR, &s); + } + putInCache(&s, cachedCR); + return cachedCR; + } + } + + timeT startTime = s.getStartTime(); + timeT endTime = isRecordingSegment ? m_pointerTimePos /*s.getEndTime()*/ : s.getEndMarkerTime(); + + + int h = m_grid.getYSnap() - 2; + int w; + + RG_DEBUG << "CompositionModelImpl::computeSegmentRect: x " << origin.x() << ", y " << origin.y() << " startTime " << startTime << ", endTime " << endTime << endl; + + if (s.isRepeating()) { + timeT repeatStart = endTime; + timeT repeatEnd = s.getRepeatEndTime(); + w = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(startTime, + repeatEnd - startTime))); + // RG_DEBUG << "CompositionModelImpl::computeSegmentRect : s is repeating - repeatStart = " + // << repeatStart << " - repeatEnd : " << repeatEnd + // << " w = " << w << endl; + } else { + w = int(nearbyint(m_grid.getRulerScale()->getWidthForDuration(startTime, endTime - startTime))); + // RG_DEBUG << "CompositionModelImpl::computeSegmentRect : s is NOT repeating" + // << " w = " << w << " (x for time at start is " << m_grid.getRulerScale()->getXForTime(startTime) << ", end is " << m_grid.getRulerScale()->getXForTime(endTime) << ")" << endl; + } + + CompositionRect cr(origin, QSize(w, h)); + QString label = strtoqstr(s.getLabel()); + if (s.getType() == Segment::Audio) { + static QRegExp re1("( *\\([^)]*\\))*$"); // (inserted) (copied) (etc) + static QRegExp re2("\\.[^.]+$"); // filename suffix + label.replace(re1, "").replace(re2, ""); + } + cr.setLabel(label); + + if (s.isRepeating()) { + computeRepeatMarks(cr, &s); + } else { + cr.setBaseWidth(cr.width()); + } + + putInCache(&s, cr); + + return cr; +} + +unsigned int CompositionModelImpl::computeZForSegment(const Rosegarden::Segment* s) +{ + return m_segmentOrderer.getZForSegment(s); +} + +const CompositionRect& CompositionModelImpl::getFromCache(const Rosegarden::Segment* s, timeT& endTime) +{ + endTime = m_segmentEndTimeMap[s]; + return m_segmentRectMap[s]; +} + +unsigned int CompositionModelImpl::getNbRows() +{ + return m_composition.getNbTracks(); +} + +const CompositionModel::rectcontainer& CompositionModelImpl::getRectanglesIn(const QRect& rect, + RectRanges* npData, + AudioPreviewDrawData* apData) +{ + // Profiler profiler("CompositionModelImpl::getRectanglesIn", true); + + m_res.clear(); + + // RG_DEBUG << "CompositionModelImpl::getRectanglesIn: ruler scale is " + // << (dynamic_cast<SimpleRulerScale *>(m_grid.getRulerScale()))->getUnitsPerPixel() << endl; + + const Composition::segmentcontainer& segments = m_composition.getSegments(); + Composition::segmentcontainer::iterator segEnd = segments.end(); + + for (Composition::segmentcontainer::iterator i = segments.begin(); + i != segEnd; ++i) { + + // RG_DEBUG << "CompositionModelImpl::getRectanglesIn: Composition contains segment " << *i << " (" << (*i)->getStartTime() << "->" << (*i)->getEndTime() << ")"<< endl; + + Segment* s = *i; + + if (isMoving(s)) + continue; + + CompositionRect sr = computeSegmentRect(*s); + // RG_DEBUG << "CompositionModelImpl::getRectanglesIn: seg rect = " << sr << endl; + + if (sr.intersects(rect)) { + bool tmpSelected = isTmpSelected(s), + pTmpSelected = wasTmpSelected(s); + +// RG_DEBUG << "CompositionModelImpl::getRectanglesIn: segment " << s +// << " selected : " << isSelected(s) << " - tmpSelected : " << isTmpSelected(s) << endl; + + if (isSelected(s) || isTmpSelected(s) || sr.intersects(m_selectionRect)) { + sr.setSelected(true); + } + + if (pTmpSelected != tmpSelected) + sr.setNeedsFullUpdate(true); + + bool isAudio = (s && s->getType() == Segment::Audio); + + if (!isRecording(s)) { + QColor brushColor = GUIPalette::convertColour(m_composition. + getSegmentColourMap().getColourByIndex(s->getColourIndex())); + sr.setBrush(brushColor); + sr.setPen(CompositionColourCache::getInstance()->SegmentBorder); + } else { + // border is the same for both audio and MIDI + sr.setPen(CompositionColourCache::getInstance()->RecordingSegmentBorder); + // audio color + if (isAudio) { + sr.setBrush(CompositionColourCache::getInstance()->RecordingAudioSegmentBlock); + // MIDI/default color + } else { + sr.setBrush(CompositionColourCache::getInstance()->RecordingInternalSegmentBlock); + } + } + + // Notation preview data + if (npData && s->getType() == Segment::Internal) { + makeNotationPreviewRects(npData, QPoint(0, sr.y()), s, rect); + // Audio preview data + } else if (apData && s->getType() == Segment::Audio) { + makeAudioPreviewRects(apData, s, sr, rect); + } + + m_res.push_back(sr); + } else { + // RG_DEBUG << "CompositionModelImpl::getRectanglesIn: - segment out of rect\n"; + } + + } + + // changing items + + itemcontainer::iterator movEnd = m_changingItems.end(); + for (itemcontainer::iterator i = m_changingItems.begin(); i != movEnd; ++i) { + CompositionRect sr((*i)->rect()); + if (sr.intersects(rect)) { + Segment* s = CompositionItemHelper::getSegment(*i); + sr.setSelected(true); + QColor brushColor = GUIPalette::convertColour(m_composition.getSegmentColourMap().getColourByIndex(s->getColourIndex())); + sr.setBrush(brushColor); + + sr.setPen(CompositionColourCache::getInstance()->SegmentBorder); + + // Notation preview data + if (npData && s->getType() == Segment::Internal) { + makeNotationPreviewRectsMovingSegment(npData, sr.topLeft(), s, sr); + // Audio preview data + } else if (apData && s->getType() == Segment::Audio) { + makeAudioPreviewRects(apData, s, sr, rect); + } + + m_res.push_back(sr); + } + } + + return m_res; +} + +CompositionModel::heightlist +CompositionModelImpl::getTrackDividersIn(const QRect& rect) +{ + int top = m_grid.getYBin(rect.y()); + int bottom = m_grid.getYBin(rect.y() + rect.height()); + +// std::cerr << "CompositionModelImpl::getTrackDividersIn: rect " +// << rect.x() << ", " << rect.y() << ", " +// << rect.width() << "x" << rect.height() << ", top = " << top +// << ", bottom = " << bottom << std::endl; + + CompositionModel::heightlist list; + + for (int pos = top; pos <= bottom; ++pos) { + int divider = m_grid.getYBinCoordinate(pos); + list.push_back(divider); +// std::cerr << "divider at " << divider << std::endl; + } + + return list; +} + +CompositionModel::rectlist* CompositionModelImpl::getNotationPreviewData(const Segment* s) +{ + rectlist* npData = m_notationPreviewDataCache[const_cast<Segment*>(s)]; + + if (!npData) { + npData = makeNotationPreviewDataCache(s); + } + + return npData; +} + +CompositionModel::AudioPreviewData* CompositionModelImpl::getAudioPreviewData(const Segment* s) +{ + // Profiler profiler("CompositionModelImpl::getAudioPreviewData", true); + RG_DEBUG << "CompositionModelImpl::getAudioPreviewData\n"; + + AudioPreviewData* apData = m_audioPreviewDataCache[const_cast<Segment*>(s)]; + + if (!apData) { + apData = makeAudioPreviewDataCache(s); + } + + RG_DEBUG << "CompositionModelImpl::getAudioPreviewData returning\n"; + return apData; +} + +CompositionModel::rectlist* CompositionModelImpl::makeNotationPreviewDataCache(const Segment *s) +{ + rectlist* npData = new rectlist(); + updatePreviewCacheForNotationSegment(s, npData); + m_notationPreviewDataCache.insert(const_cast<Segment*>(s), npData); + return npData; +} + +CompositionModel::AudioPreviewData* CompositionModelImpl::makeAudioPreviewDataCache(const Segment *s) +{ + RG_DEBUG << "CompositionModelImpl::makeAudioPreviewDataCache(" << s << ")" << endl; + + AudioPreviewData* apData = new AudioPreviewData(false, 0); // 0 channels -> empty + updatePreviewCacheForAudioSegment(s, apData); + m_audioPreviewDataCache.insert(const_cast<Segment*>(s), apData); + return apData; +} + +} +#include "CompositionModelImpl.moc" diff --git a/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.h b/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.h new file mode 100644 index 0000000..6e1c9d6 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionModelImpl.h @@ -0,0 +1,239 @@ + +/* -*- 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_COMPOSITIONMODELIMPL_H_ +#define _RG_COMPOSITIONMODELIMPL_H_ + +#include "base/Selection.h" +#include "base/SnapGrid.h" +#include "CompositionModel.h" +#include "CompositionRect.h" +#include <map> +#include "SegmentOrderer.h" +#include <set> +#include <qcolor.h> +#include <qpoint.h> +#include <qptrdict.h> +#include <qrect.h> +#include <vector> +#include "base/Event.h" + + +class RectRanges; +class CompositionItem; +class AudioPreviewDrawData; +class AudioPreviewData; + + +namespace Rosegarden +{ + +class Studio; +class Segment; +class RulerScale; +class Event; +class Composition; +class AudioPreviewUpdater; +class AudioPreviewThread; + + +class CompositionModelImpl : public CompositionModel +{ + Q_OBJECT +public: + + CompositionModelImpl(Composition& compo, + Studio& studio, + RulerScale *rulerScale, + int vStep); + + virtual ~CompositionModelImpl(); + + virtual unsigned int getNbRows(); + virtual const rectcontainer& getRectanglesIn(const QRect& rect, + RectRanges* notationRects, AudioPreviewDrawData* audioRects); + virtual heightlist getTrackDividersIn(const QRect& rect); + virtual itemcontainer getItemsAt (const QPoint&); + virtual timeT getRepeatTimeAt (const QPoint&, const CompositionItem&); + + virtual SnapGrid& grid() { return m_grid; } + + virtual void setPointerPos(int xPos); + virtual void setSelected(const CompositionItem&, bool selected = true); + virtual bool isSelected(const CompositionItem&) const; + virtual void setSelected(const itemcontainer&); + virtual void clearSelected(); + virtual bool haveSelection() const { return !m_selectedSegments.empty(); } + virtual bool haveMultipleSelection() const { return m_selectedSegments.size() > 1; } + virtual void signalSelection(); + virtual void setSelectionRect(const QRect&); + virtual void finalizeSelectionRect(); + virtual QRect getSelectionContentsRect(); + virtual void signalContentChange(); + + virtual void addRecordingItem(const CompositionItem&); + virtual void removeRecordingItem(const CompositionItem &); + virtual void clearRecordingItems(); + virtual bool haveRecordingItems() { return m_recordingSegments.size() > 0; } + + virtual void startChange(const CompositionItem&, ChangeType change); + virtual void startChangeSelection(ChangeType change); + virtual itemcontainer& getChangingItems() { return m_changingItems; } + virtual void endChange(); + virtual ChangeType getChangeType() { return m_changeType; } + + virtual void setLength(int width); + virtual int getLength(); + + void setAudioPreviewThread(AudioPreviewThread *thread); + AudioPreviewThread* getAudioPreviewThread() { return m_audioPreviewThread; } + + void clearPreviewCache(); + void clearSegmentRectsCache(bool clearPreviews = false) { clearInCache(0, clearPreviews); } + + rectlist* makeNotationPreviewDataCache(const Segment *s); + AudioPreviewData* makeAudioPreviewDataCache(const Segment *s); + + CompositionRect computeSegmentRect(const Segment&, bool computeZ = false); + QColor computeSegmentPreviewColor(const Segment*); + QPoint computeSegmentOrigin(const Segment&); + void computeRepeatMarks(CompositionItem&); + + SegmentSelection getSelectedSegments() { return m_selectedSegments; } + Composition& getComposition() { return m_composition; } + Studio& getStudio() { return m_studio; } + + + // CompositionObserver + virtual void segmentAdded(const Composition *, Segment *); + virtual void segmentRemoved(const Composition *, Segment *); + virtual void segmentRepeatChanged(const Composition *, Segment *, bool); + virtual void segmentStartChanged(const Composition *, Segment *, timeT); + virtual void segmentEndMarkerChanged(const Composition *, Segment *, bool); + virtual void segmentTrackChanged(const Composition *, Segment *, TrackId); + virtual void endMarkerTimeChanged(const Composition *, bool /*shorten*/); + + // SegmentObserver + virtual void eventAdded(const Segment *, Event *); + virtual void eventRemoved(const Segment *, Event *); + virtual void appearanceChanged(const Segment *); + virtual void endMarkerTimeChanged(const Segment *, bool /*shorten*/); + virtual void segmentDeleted(const Segment*) { /* nothing to do - handled by CompositionObserver::segmentRemoved() */ }; + +signals: + void selectedSegments(const SegmentSelection &); + void needSizeUpdate(); + +public slots: + void slotAudioFileFinalized(Segment*); + void slotInstrumentParametersChanged(InstrumentId); + +protected slots: + void slotAudioPreviewComplete(AudioPreviewUpdater*); + +protected: + bool setTrackHeights(Segment *changed = 0); // true if something changed + + void setSelected(const Segment*, bool selected = true); + bool isSelected(const Segment*) const; + bool isTmpSelected(const Segment*) const; + bool wasTmpSelected(const Segment*) const; + bool isMoving(const Segment*) const; + bool isRecording(const Segment*) const; + + void computeRepeatMarks(CompositionRect& sr, const Segment* s); + unsigned int computeZForSegment(const Segment* s); + + // segment preview stuff + + void updatePreviewCacheForNotationSegment(const Segment* s, rectlist*); + void updatePreviewCacheForAudioSegment(const Segment* s, AudioPreviewData*); + rectlist* getNotationPreviewData(const Segment* s); + AudioPreviewData* getAudioPreviewData(const Segment* s); + PixmapArray getAudioPreviewPixmap(const Segment* s); + QRect postProcessAudioPreview(AudioPreviewData*, const Segment*); + + void makePreviewCache(const Segment* s); + void removePreviewCache(const Segment* s); + void makeNotationPreviewRects(RectRanges* npData, QPoint basePoint, const Segment*, const QRect&); + void makeNotationPreviewRectsMovingSegment(RectRanges* npData, QPoint basePoint, const Segment*, + const QRect&); + void makeAudioPreviewRects(AudioPreviewDrawData* apRects, const Segment*, + const CompositionRect& segRect, const QRect& clipRect); + + void clearInCache(const Segment*, bool clearPreviewCache = false); + void putInCache(const Segment*, const CompositionRect&); + const CompositionRect& getFromCache(const Segment*, timeT& endTime); + bool isCachedRectCurrent(const Segment& s, const CompositionRect& r, + QPoint segmentOrigin, timeT segmentEndTime); + + //--------------- Data members --------------------------------- + Composition& m_composition; + Studio& m_studio; + SnapGrid m_grid; + SegmentSelection m_selectedSegments; + SegmentSelection m_tmpSelectedSegments; + SegmentSelection m_previousTmpSelectedSegments; + + timeT m_pointerTimePos; + + typedef std::set<Segment *> recordingsegmentset; + recordingsegmentset m_recordingSegments; + + typedef std::vector<CompositionItem> itemgc; + + AudioPreviewThread* m_audioPreviewThread; + + typedef QPtrDict<rectlist> NotationPreviewDataCache; + typedef QPtrDict<AudioPreviewData> AudioPreviewDataCache; + + NotationPreviewDataCache m_notationPreviewDataCache; + AudioPreviewDataCache m_audioPreviewDataCache; + + rectcontainer m_res; + itemcontainer m_changingItems; + ChangeType m_changeType; + itemgc m_itemGC; + + QRect m_selectionRect; + QRect m_previousSelectionUpdateRect; + + std::map<const Segment*, CompositionRect> m_segmentRectMap; + std::map<const Segment*, timeT> m_segmentEndTimeMap; + std::map<const Segment*, PixmapArray> m_audioSegmentPreviewMap; + std::map<TrackId, int> m_trackHeights; + + typedef std::map<const Segment*, AudioPreviewUpdater *> + AudioPreviewUpdaterMap; + AudioPreviewUpdaterMap m_audioPreviewUpdaterMap; + + SegmentOrderer m_segmentOrderer; +}; + + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/CompositionRect.cpp b/src/gui/editors/segment/segmentcanvas/CompositionRect.cpp new file mode 100644 index 0000000..9e34d71 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionRect.cpp @@ -0,0 +1,42 @@ +/* -*- 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 "CompositionRect.h" +#include "base/ColourMap.h" + +#include <qbrush.h> +#include <qcolor.h> +#include <qpen.h> +#include <qpoint.h> +#include <qrect.h> +#include <qsize.h> +#include <qstring.h> + + +namespace Rosegarden +{ + const QColor CompositionRect::DefaultPenColor = Qt::black; + const QColor CompositionRect::DefaultBrushColor = QColor(COLOUR_DEF_R, COLOUR_DEF_G, COLOUR_DEF_B); +} diff --git a/src/gui/editors/segment/segmentcanvas/CompositionRect.h b/src/gui/editors/segment/segmentcanvas/CompositionRect.h new file mode 100644 index 0000000..3c3d2b6 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionRect.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_COMPOSITIONRECT_H_ +#define _RG_COMPOSITIONRECT_H_ + +#include <qbrush.h> +#include <qcolor.h> +#include <qpen.h> +#include <qrect.h> +#include <qstring.h> +#include <qvaluevector.h> + + +class QSize; +class QPoint; + + +namespace Rosegarden +{ + +class CompositionRect : public QRect +{ +public: + typedef QValueVector<int> repeatmarks; + + friend bool operator<(const CompositionRect&, const CompositionRect&); + + CompositionRect() : QRect(), m_selected(false), + m_needUpdate(false), m_brush(DefaultBrushColor), m_pen(DefaultPenColor) {}; + CompositionRect(const QRect& r) : QRect(r), m_resized(false), m_selected(false), + m_needUpdate(false), m_brush(DefaultBrushColor), m_pen(DefaultPenColor), m_z(0) {}; + CompositionRect(const QPoint & topLeft, const QPoint & bottomRight) + : QRect(topLeft, bottomRight), m_resized(false), m_selected(false), + m_needUpdate(false), m_brush(DefaultBrushColor), m_pen(DefaultPenColor), m_z(0) {}; + CompositionRect(const QPoint & topLeft, const QSize & size) + : QRect(topLeft, size), m_resized(false), m_selected(false), + m_needUpdate(false), m_brush(DefaultBrushColor), m_pen(DefaultPenColor), m_z(0) {}; + CompositionRect(int left, int top, int width, int height) + : QRect(left, top, width, height), m_resized(false), m_selected(false), + m_needUpdate(false), m_brush(DefaultBrushColor), m_pen(DefaultPenColor), m_z(0) {}; + + void setResized(bool s) { m_resized = s; } + bool isResized() const { return m_resized; } + void setSelected(bool s) { m_selected = s; } + bool isSelected() const { return m_selected; } + bool needsFullUpdate() const { return m_needUpdate; } + void setNeedsFullUpdate(bool s) { m_needUpdate = s; } + + void setZ(int z) { m_z = z; } + int z() const { return m_z; } + + // brush, pen draw info + void setBrush(QBrush b) { m_brush = b; } + QBrush getBrush() const { return m_brush; } + void setPen(QPen b) { m_pen = b; } + QPen getPen() const { return m_pen; } + + // repeating segments + void setRepeatMarks(const repeatmarks& rm) { m_repeatMarks = rm; } + const repeatmarks& getRepeatMarks() const { return m_repeatMarks; } + bool isRepeating() const { return m_repeatMarks.size() > 0; } + int getBaseWidth() const { return m_baseWidth; } + void setBaseWidth(int bw) { m_baseWidth = bw; } + QString getLabel() const { return m_label; } + void setLabel(QString l) { m_label = l; } + + static const QColor DefaultPenColor; + static const QColor DefaultBrushColor; + +protected: + bool m_resized; + bool m_selected; + bool m_needUpdate; + QBrush m_brush; + QPen m_pen; + repeatmarks m_repeatMarks; + int m_baseWidth; + QString m_label; + int m_z; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/CompositionView.cpp b/src/gui/editors/segment/segmentcanvas/CompositionView.cpp new file mode 100644 index 0000000..8e83a6b --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionView.cpp @@ -0,0 +1,1591 @@ +/* -*- 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 "CompositionView.h" + +#include "misc/Debug.h" +#include "AudioPreviewThread.h" +#include "base/RulerScale.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/SnapGrid.h" +#include "CompositionColourCache.h" +#include "CompositionItemHelper.h" +#include "CompositionItemImpl.h" +#include "CompositionModel.h" +#include "CompositionModelImpl.h" +#include "CompositionRect.h" +#include "AudioPreviewPainter.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "gui/general/GUIPalette.h" +#include "gui/general/RosegardenCanvasView.h" +#include "gui/general/RosegardenScrollView.h" +#include "SegmentSelector.h" +#include "SegmentToolBox.h" +#include "SegmentTool.h" +#include <kmessagebox.h> +#include <qbrush.h> +#include <qcolor.h> +#include <qevent.h> +#include <qfont.h> +#include <qfontmetrics.h> +#include <qmemarray.h> +#include <qpainter.h> +#include <qpen.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qrect.h> +#include <qscrollbar.h> +#include <qscrollview.h> +#include <qsize.h> +#include <qstring.h> +#include <qwidget.h> +#include <kapplication.h> +#include <kconfig.h> +#include <algorithm> + + +namespace Rosegarden +{ + +class PreviewRect : public QRect { +public: + PreviewRect(int left, int top, int width, int height) : + QRect(left, top, width, height) {}; + + PreviewRect(const QRect& r) : + QRect(r) {}; + + const QColor& getColor() const { return m_color; } + void setColor(QColor c) { m_color = c; } + +protected: + QColor m_color; +}; + +CompositionView::CompositionView(RosegardenGUIDoc* doc, + CompositionModel* model, + QWidget * parent, const char * name, WFlags f) +#if KDE_VERSION >= KDE_MAKE_VERSION(3,2,0) + : RosegardenScrollView(parent, name, f | WNoAutoErase | WStaticContents), +#else + : + RosegardenScrollView(parent, name, f | WRepaintNoErase | WResizeNoErase | WStaticContents), +#endif + m_model(model), + m_currentItem(0), + m_tool(0), + m_toolBox(0), + m_showPreviews(false), + m_showSegmentLabels(true), + m_fineGrain(false), + m_pencilOverExisting(false), + m_minWidth(m_model->getLength()), + m_stepSize(0), + m_rectFill(0xF0, 0xF0, 0xF0), + m_selectedRectFill(0x00, 0x00, 0xF0), + m_pointerPos(0), + m_pointerColor(GUIPalette::getColour(GUIPalette::Pointer)), + m_pointerWidth(4), + m_pointerPen(QPen(m_pointerColor, m_pointerWidth)), + m_tmpRect(QRect(QPoint(0, 0), QPoint( -1, -1))), + m_tmpRectFill(CompositionRect::DefaultBrushColor), + m_trackDividerColor(GUIPalette::getColour(GUIPalette::TrackDivider)), + m_drawGuides(false), + m_guideColor(GUIPalette::getColour(GUIPalette::MovementGuide)), + m_topGuidePos(0), + m_foreGuidePos(0), + m_drawSelectionRect(false), + m_drawTextFloat(false), + m_segmentsDrawBuffer(visibleWidth(), visibleHeight()), + m_artifactsDrawBuffer(visibleWidth(), visibleHeight()), + m_segmentsDrawBufferRefresh(0, 0, visibleWidth(), visibleHeight()), + m_artifactsDrawBufferRefresh(0, 0, visibleWidth(), visibleHeight()), + m_lastBufferRefreshX(0), + m_lastBufferRefreshY(0), + m_lastPointerRefreshX(0), + m_contextHelpShown(false) +{ + if (doc) { + m_toolBox = new SegmentToolBox(this, doc); + + connect(m_toolBox, SIGNAL(showContextHelp(const QString &)), + this, SLOT(slotToolHelpChanged(const QString &))); + } + + setDragAutoScroll(true); + setBackgroundMode(NoBackground); + viewport()->setBackgroundMode(NoBackground); + viewport()->setPaletteBackgroundColor(GUIPalette::getColour(GUIPalette::SegmentCanvas)); + + slotUpdateSize(); + + QScrollBar* hsb = horizontalScrollBar(); + + // dynamically adjust content size when scrolling past current composition's end + connect(hsb, SIGNAL(nextLine()), + this, SLOT(scrollRight())); + connect(hsb, SIGNAL(prevLine()), + this, SLOT(scrollLeft())); + + // connect(this, SIGNAL(contentsMoving(int, int)), + // this, SLOT(slotAllDrawBuffersNeedRefresh())); + + // connect(this, SIGNAL(contentsMoving(int, int)), + // this, SLOT(slotContentsMoving(int, int))); + + connect(model, SIGNAL(needContentUpdate()), + this, SLOT(slotUpdateSegmentsDrawBuffer())); + connect(model, SIGNAL(needContentUpdate(const QRect&)), + this, SLOT(slotUpdateSegmentsDrawBuffer(const QRect&))); + connect(model, SIGNAL(needArtifactsUpdate()), + this, SLOT(slotArtifactsDrawBufferNeedsRefresh())); + connect(model, SIGNAL(needSizeUpdate()), + this, SLOT(slotUpdateSize())); + + if (doc) { + connect(doc, SIGNAL(docColoursChanged()), + this, SLOT(slotRefreshColourCache())); + + // recording-related signals + connect(doc, SIGNAL(newMIDIRecordingSegment(Segment*)), + this, SLOT(slotNewMIDIRecordingSegment(Segment*))); + connect(doc, SIGNAL(newAudioRecordingSegment(Segment*)), + this, SLOT(slotNewAudioRecordingSegment(Segment*))); + // connect(doc, SIGNAL(recordMIDISegmentUpdated(Segment*, timeT)), + // this, SLOT(slotRecordMIDISegmentUpdated(Segment*, timeT))); + connect(doc, SIGNAL(stoppedAudioRecording()), + this, SLOT(slotStoppedRecording())); + connect(doc, SIGNAL(stoppedMIDIRecording()), + this, SLOT(slotStoppedRecording())); + connect(doc, SIGNAL(audioFileFinalized(Segment*)), + getModel(), SLOT(slotAudioFileFinalized(Segment*))); + } + + CompositionModelImpl* cmi = dynamic_cast<CompositionModelImpl*>(model); + if (cmi) { + cmi->setAudioPreviewThread(&doc->getAudioPreviewThread()); + } + + if (doc) { + doc->getAudioPreviewThread().setEmptyQueueListener(this); + } + + m_segmentsDrawBuffer.setOptimization(QPixmap::BestOptim); + m_artifactsDrawBuffer.setOptimization(QPixmap::BestOptim); + + viewport()->setMouseTracking(true); +} + +void CompositionView::endAudioPreviewGeneration() +{ + CompositionModelImpl* cmi = dynamic_cast<CompositionModelImpl*>(m_model); + if (cmi) { + cmi->setAudioPreviewThread(0); + } +} + +void CompositionView::setBackgroundPixmap(const QPixmap &m) +{ + m_backgroundPixmap = m; + // viewport()->setErasePixmap(m_backgroundPixmap); +} + +void CompositionView::initStepSize() +{ + QScrollBar* hsb = horizontalScrollBar(); + m_stepSize = hsb->lineStep(); +} + +void CompositionView::slotUpdateSize() +{ + int vStep = getModel()->grid().getYSnap(); + int height = std::max(getModel()->getNbRows() * vStep, (unsigned)visibleHeight()); + + RulerScale *ruler = grid().getRulerScale(); + + int minWidth = sizeHint().width(); + int computedWidth = int(nearbyint(ruler->getTotalWidth())); + + int width = std::max(computedWidth, minWidth); + + resizeContents(width, height); +} + +void CompositionView::scrollRight() +{ + RG_DEBUG << "CompositionView::scrollRight()\n"; + if (m_stepSize == 0) + initStepSize(); + + if (horizontalScrollBar()->value() == horizontalScrollBar()->maxValue()) { + + resizeContents(contentsWidth() + m_stepSize, contentsHeight()); + setContentsPos(contentsX() + m_stepSize, contentsY()); + getModel()->setLength(contentsWidth()); + } + +} + +void CompositionView::scrollLeft() +{ + RG_DEBUG << "CompositionView::scrollLeft()\n"; + if (m_stepSize == 0) + initStepSize(); + + int cWidth = contentsWidth(); + + if (horizontalScrollBar()->value() < cWidth && cWidth > m_minWidth) { + resizeContents(cWidth - m_stepSize, contentsHeight()); + getModel()->setLength(contentsWidth()); + } + +} + +void CompositionView::setSelectionRectPos(const QPoint& pos) +{ + m_selectionRect.setRect(pos.x(), pos.y(), 0, 0); + getModel()->setSelectionRect(m_selectionRect); +} + +void CompositionView::setSelectionRectSize(int w, int h) +{ + m_selectionRect.setSize(QSize(w, h)); + getModel()->setSelectionRect(m_selectionRect); +} + +void CompositionView::setDrawSelectionRect(bool d) +{ + if (m_drawSelectionRect != d) { + m_drawSelectionRect = d; + slotArtifactsDrawBufferNeedsRefresh(); + slotUpdateSegmentsDrawBuffer(m_selectionRect); + } +} + +void CompositionView::clearSegmentRectsCache(bool clearPreviews) +{ + dynamic_cast<CompositionModelImpl*>(getModel())->clearSegmentRectsCache(clearPreviews); +} + +SegmentSelection +CompositionView::getSelectedSegments() +{ + return (dynamic_cast<CompositionModelImpl*>(m_model))->getSelectedSegments(); +} + +void CompositionView::updateSelectionContents() +{ + if (!haveSelection()) + return ; + + + QRect selectionRect = getModel()->getSelectionContentsRect(); + updateContents(selectionRect); +} + +void CompositionView::slotContentsMoving(int x, int y) +{ + // qDebug("contents moving : x=%d", x); +} + +void CompositionView::slotSetTool(const QString& toolName) +{ + RG_DEBUG << "CompositionView::slotSetTool(" << toolName << ")" + << this << "\n"; + + if (m_tool) + m_tool->stow(); + + m_toolContextHelp = ""; + + m_tool = m_toolBox->getTool(toolName); + + if (m_tool) + m_tool->ready(); + else { + KMessageBox::error(0, QString("CompositionView::slotSetTool() : unknown tool name %1").arg(toolName)); + } +} + +void CompositionView::slotSelectSegments(const SegmentSelection &segments) +{ + RG_DEBUG << "CompositionView::slotSelectSegments\n"; + + static QRect dummy; + + getModel()->clearSelected(); + + for (SegmentSelection::iterator i = segments.begin(); i != segments.end(); ++i) { + getModel()->setSelected(CompositionItem(new CompositionItemImpl(**i, dummy))); + } + slotUpdateSegmentsDrawBuffer(); +} + +SegmentSelector* +CompositionView::getSegmentSelectorTool() +{ + return dynamic_cast<SegmentSelector*>(getToolBox()->getTool(SegmentSelector::ToolName)); +} + +void CompositionView::slotSetSelectAdd(bool value) +{ + SegmentSelector* selTool = getSegmentSelectorTool(); + + if (!selTool) + return ; + + selTool->setSegmentAdd(value); +} + +void CompositionView::slotSetSelectCopy(bool value) +{ + SegmentSelector* selTool = getSegmentSelectorTool(); + + if (!selTool) + return ; + + selTool->setSegmentCopy(value); +} + +void CompositionView::slotShowSplitLine(int x, int y) +{ + m_splitLinePos.setX(x); + m_splitLinePos.setY(y); +} + +void CompositionView::slotHideSplitLine() +{ + m_splitLinePos.setX( -1); + m_splitLinePos.setY( -1); +} + +void CompositionView::slotExternalWheelEvent(QWheelEvent* e) +{ + e->accept(); + wheelEvent(e); +} + +CompositionItem CompositionView::getFirstItemAt(QPoint pos) +{ + CompositionModel::itemcontainer items = getModel()->getItemsAt(pos); + + if (items.size()) { + // find topmost item + CompositionItem res = *(items.begin()); + + unsigned int maxZ = res->z(); + + CompositionModel::itemcontainer::iterator maxZItemPos = items.begin(); + + for (CompositionModel::itemcontainer::iterator i = items.begin(); + i != items.end(); ++i) { + CompositionItem ic = *i; + if (ic->z() > maxZ) { + RG_DEBUG << k_funcinfo << "found new topmost at z=" << ic->z() << endl; + res = ic; + maxZ = ic->z(); + maxZItemPos = i; + } + } + + // get rid of the rest; + items.erase(maxZItemPos); + for (CompositionModel::itemcontainer::iterator i = items.begin(); + i != items.end(); ++i) + delete *i; + + return res; + } else { + RG_DEBUG << k_funcinfo << "no item under cursor\n"; + } + + + return CompositionItem(); +} + +void CompositionView::setSnapGrain(bool fine) +{ + if (m_fineGrain) { + grid().setSnapTime(SnapGrid::NoSnap); + } else { + grid().setSnapTime(fine ? SnapGrid::SnapToBeat : SnapGrid::SnapToBar); + } +} + +void CompositionView::slotUpdateSegmentsDrawBuffer() +{ + // RG_DEBUG << "CompositionView::slotUpdateSegmentsDrawBuffer()\n"; + slotAllDrawBuffersNeedRefresh(); + updateContents(); +} + +void CompositionView::slotUpdateSegmentsDrawBuffer(const QRect& rect) +{ + // RG_DEBUG << "CompositionView::slotUpdateSegmentsDrawBuffer() rect " + // << rect << " - valid : " << rect.isValid() << endl; + + slotAllDrawBuffersNeedRefresh(rect); + + if (rect.isValid()) { + updateContents(rect); + } else { + updateContents(); + } +} + +void CompositionView::slotRefreshColourCache() +{ + CompositionColourCache::getInstance()->init(); + clearSegmentRectsCache(); + slotUpdateSegmentsDrawBuffer(); +} + +void CompositionView::slotNewMIDIRecordingSegment(Segment* s) +{ + getModel()->addRecordingItem(CompositionItemHelper::makeCompositionItem(s)); +} + +void CompositionView::slotNewAudioRecordingSegment(Segment* s) +{ + getModel()->addRecordingItem(CompositionItemHelper::makeCompositionItem(s)); +} + +void CompositionView::slotStoppedRecording() +{ + getModel()->clearRecordingItems(); +} + +void CompositionView::resizeEvent(QResizeEvent* e) +{ + QScrollView::resizeEvent(e); + slotUpdateSize(); + + int w = std::max(m_segmentsDrawBuffer.width(), visibleWidth()); + int h = std::max(m_segmentsDrawBuffer.height(), visibleHeight()); + + m_segmentsDrawBuffer.resize(w, h); + m_artifactsDrawBuffer.resize(w, h); + slotAllDrawBuffersNeedRefresh(); + // RG_DEBUG << "CompositionView::resizeEvent() : drawBuffer size = " << m_segmentsDrawBuffer.size() << endl; +} + +void CompositionView::viewportPaintEvent(QPaintEvent* e) +{ + QMemArray<QRect> rects = e->region().rects(); + + for (unsigned int i = 0; i < rects.size(); ++i) { + viewportPaintRect(rects[i]); + } +} + +void CompositionView::viewportPaintRect(QRect r) +{ + QRect updateRect = r; + + r &= viewport()->rect(); + r.moveBy(contentsX(), contentsY()); + + // RG_DEBUG << "CompositionView::viewportPaintRect() r = " << r + // << " - moveBy " << contentsX() << "," << contentsY() << " - updateRect = " << updateRect + // << " - refresh " << m_segmentsDrawBufferRefresh << " artrefresh " << m_artifactsDrawBufferRefresh << endl; + + + bool scroll = false; + bool changed = checkScrollAndRefreshDrawBuffer(r, scroll); + + if (changed || m_artifactsDrawBufferRefresh.isValid()) { + + // r was modified by checkScrollAndRefreshDrawBuffer + QRect copyRect(r | m_artifactsDrawBufferRefresh); + copyRect.moveBy( -contentsX(), -contentsY()); + + // RG_DEBUG << "copying from segment to artifacts buffer: " << copyRect << endl; + + bitBlt(&m_artifactsDrawBuffer, + copyRect.x(), copyRect.y(), + &m_segmentsDrawBuffer, + copyRect.x(), copyRect.y(), copyRect.width(), copyRect.height()); + m_artifactsDrawBufferRefresh |= r; + } + + if (m_artifactsDrawBufferRefresh.isValid()) { + refreshArtifactsDrawBuffer(m_artifactsDrawBufferRefresh); + m_artifactsDrawBufferRefresh = QRect(); + } + + if (scroll) { + bitBlt(viewport(), 0, 0, + &m_artifactsDrawBuffer, 0, 0, + m_artifactsDrawBuffer.width(), m_artifactsDrawBuffer.height()); + } else { + bitBlt(viewport(), updateRect.x(), updateRect.y(), + &m_artifactsDrawBuffer, updateRect.x(), updateRect.y(), + updateRect.width(), updateRect.height()); + } + + // DEBUG + + // QPainter pdebug(viewport()); + // static QPen framePen(Qt::red, 1); + // pdebug.setPen(framePen); + // pdebug.drawRect(updateRect); + +} + +bool CompositionView::checkScrollAndRefreshDrawBuffer(QRect &rect, bool& scroll) +{ + bool all = false; + QRect refreshRect = m_segmentsDrawBufferRefresh; + + int w = visibleWidth(), h = visibleHeight(); + int cx = contentsX(), cy = contentsY(); + + scroll = (cx != m_lastBufferRefreshX || cy != m_lastBufferRefreshY); + + if (scroll) { + + // RG_DEBUG << "checkScrollAndRefreshDrawBuffer: scrolling by (" + // << cx - m_lastBufferRefreshX << "," << cy - m_lastBufferRefreshY << ")" << endl; + + if (refreshRect.isValid()) { + + // If we've scrolled and there was an existing refresh + // rect, we can't be sure whether the refresh rect + // predated or postdated the internal update of scroll + // location. Cut our losses and refresh everything. + + refreshRect.setRect(cx, cy, w, h); + + } else { + + // No existing refresh rect: we only need to handle the + // scroll + + if (cx != m_lastBufferRefreshX) { + + int dx = m_lastBufferRefreshX - cx; + + if (dx > -w && dx < w) { + + QPainter cp(&m_segmentsDrawBuffer); + cp.drawPixmap(dx, 0, m_segmentsDrawBuffer); + + if (dx < 0) { + refreshRect |= QRect(cx + w + dx, cy, -dx, h); + } else { + refreshRect |= QRect(cx, cy, dx, h); + } + + } else { + + refreshRect.setRect(cx, cy, w, h); + all = true; + } + } + + if (cy != m_lastBufferRefreshY && !all) { + + int dy = m_lastBufferRefreshY - cy; + + if (dy > -h && dy < h) { + + QPainter cp(&m_segmentsDrawBuffer); + cp.drawPixmap(0, dy, m_segmentsDrawBuffer); + + if (dy < 0) { + refreshRect |= QRect(cx, cy + h + dy, w, -dy); + } else { + refreshRect |= QRect(cx, cy, w, dy); + } + + } else { + + refreshRect.setRect(cx, cy, w, h); + all = true; + } + } + } + } + + bool needRefresh = false; + + if (refreshRect.isValid()) { + needRefresh = true; + } + + if (needRefresh) + refreshSegmentsDrawBuffer(refreshRect); + + m_segmentsDrawBufferRefresh = QRect(); + m_lastBufferRefreshX = cx; + m_lastBufferRefreshY = cy; + + rect |= refreshRect; + if (scroll) + rect.setRect(cx, cy, w, h); + return needRefresh; +} + +void CompositionView::refreshSegmentsDrawBuffer(const QRect& rect) +{ + // Profiler profiler("CompositionView::refreshDrawBuffer", true); + // RG_DEBUG << "CompositionView::refreshSegmentsDrawBuffer() r = " + // << rect << endl; + + QPainter p(&m_segmentsDrawBuffer, viewport()); + p.translate( -contentsX(), -contentsY()); + + if (!m_backgroundPixmap.isNull()) { + QPoint pp(rect.x() % m_backgroundPixmap.height(), rect.y() % m_backgroundPixmap.width()); + p.drawTiledPixmap(rect, m_backgroundPixmap, pp); + } else { + p.eraseRect(rect); + } + + drawArea(&p, rect); + + // DEBUG - show what's updated + // QPen framePen(Qt::red, 1); + // p.setPen(framePen); + // p.drawRect(rect); + + // m_segmentsDrawBufferNeedsRefresh = false; +} + +void CompositionView::refreshArtifactsDrawBuffer(const QRect& rect) +{ + // RG_DEBUG << "CompositionView::refreshArtifactsDrawBuffer() r = " + // << rect << endl; + + QPainter p; + p.begin(&m_artifactsDrawBuffer, viewport()); + p.translate( -contentsX(), -contentsY()); + // QRect r(contentsX(), contentsY(), m_artifactsDrawBuffer.width(), m_artifactsDrawBuffer.height()); + drawAreaArtifacts(&p, rect); + p.end(); + + // m_artifactsDrawBufferNeedsRefresh = false; +} + +void CompositionView::drawArea(QPainter *p, const QRect& clipRect) +{ + // Profiler profiler("CompositionView::drawArea", true); + + // RG_DEBUG << "CompositionView::drawArea() clipRect = " << clipRect << endl; + + // + // Fetch track dividing lines + // + CompositionModel::heightlist lineHeights = getModel()->getTrackDividersIn(clipRect); + + if (!lineHeights.empty()) { + + p->save(); + QColor light = m_trackDividerColor.light(); + p->setPen(light); + + for (CompositionModel::heightlist::const_iterator hi = lineHeights.begin(); + hi != lineHeights.end(); ++hi) { + int y = *hi; + if (y-1 >= clipRect.y()) { + p->drawLine(clipRect.x(), y-1, + clipRect.x() + clipRect.width() - 1, y-1); + } + if (y >= clipRect.y()) { + p->drawLine(clipRect.x(), y, + clipRect.x() + clipRect.width() - 1, y); + } + } + + p->setPen(m_trackDividerColor); + + for (CompositionModel::heightlist::const_iterator hi = lineHeights.begin(); + hi != lineHeights.end(); ++hi) { + int y = *hi; + if (y-2 >= clipRect.y()) { + p->drawLine(clipRect.x(), y-2, + clipRect.x() + clipRect.width() - 1, y-2); + } + if (y+1 >= clipRect.y()) { + p->drawLine(clipRect.x(), y+1, + clipRect.x() + clipRect.width() - 1, y+1); + } + } + + p->restore(); + } + + CompositionModel::AudioPreviewDrawData* audioPreviewData = 0; + CompositionModel::RectRanges* notationPreviewData = 0; + + // + // Fetch previews + // + if (m_showPreviews) { + notationPreviewData = &m_notationPreviewRects; + m_notationPreviewRects.clear(); + audioPreviewData = &m_audioPreviewRects; + m_audioPreviewRects.clear(); + } + + // + // Fetch segment rectangles to draw + // + const CompositionModel::rectcontainer& rects = getModel()->getRectanglesIn(clipRect, + notationPreviewData, audioPreviewData); + CompositionModel::rectcontainer::const_iterator i = rects.begin(); + CompositionModel::rectcontainer::const_iterator end = rects.end(); + + // + // Draw Segment Rectangles + // + p->save(); + for (; i != end; ++i) { + p->setBrush(i->getBrush()); + p->setPen(i->getPen()); + + // RG_DEBUG << "CompositionView::drawArea : draw comp rect " << *i << endl; + drawCompRect(*i, p, clipRect); + } + + p->restore(); + + if (rects.size() > 1) { + // RG_DEBUG << "CompositionView::drawArea : drawing intersections\n"; + drawIntersections(rects, p, clipRect); + } + + // + // Previews + // + if (m_showPreviews) { + p->save(); + + // draw audio previews + // + drawAreaAudioPreviews(p, clipRect); + + // draw notation previews + // + CompositionModel::RectRanges::const_iterator npi = m_notationPreviewRects.begin(); + CompositionModel::RectRanges::const_iterator npEnd = m_notationPreviewRects.end(); + + for (; npi != npEnd; ++npi) { + CompositionModel::RectRange interval = *npi; + p->save(); + p->translate(interval.basePoint.x(), interval.basePoint.y()); + // RG_DEBUG << "CompositionView::drawArea : translating to x = " << interval.basePoint.x() << endl; + for (; interval.range.first != interval.range.second; ++interval.range.first) { + + const PreviewRect& pr = *(interval.range.first); + QColor defaultCol = CompositionColourCache::getInstance()->SegmentInternalPreview; + QColor col = interval.color.isValid() ? interval.color : defaultCol; + p->setBrush(col); + p->setPen(col); + // RG_DEBUG << "CompositionView::drawArea : drawing preview rect at x = " << pr.x() << endl; + p->drawRect(pr); + } + p->restore(); + } + + p->restore(); + } + + // + // Draw segment labels (they must be drawn over the preview rects) + // + if (m_showSegmentLabels) { + for (i = rects.begin(); i != end; ++i) { + drawCompRectLabel(*i, p, clipRect); + } + } + + // drawAreaArtifacts(p, clipRect); + +} + +void CompositionView::drawAreaAudioPreviews(QPainter * p, const QRect& clipRect) +{ + CompositionModel::AudioPreviewDrawData::const_iterator api = m_audioPreviewRects.begin(); + CompositionModel::AudioPreviewDrawData::const_iterator apEnd = m_audioPreviewRects.end(); + QRect rectToFill, // rect to fill on canvas + localRect; // the rect of the tile to draw on the canvas + QPoint basePoint, // origin of segment rect + drawBasePoint; // origin of rect to fill on canvas + QRect r; + for (; api != apEnd; ++api) { + rectToFill = api->rect; + basePoint = api->basePoint; + rectToFill.moveTopLeft(basePoint); + rectToFill &= clipRect; + r = rectToFill; + drawBasePoint = rectToFill.topLeft(); + rectToFill.moveBy( -basePoint.x(), -basePoint.y()); + int firstPixmapIdx = (r.x() - basePoint.x()) / AudioPreviewPainter::tileWidth(); + if (firstPixmapIdx >= api->pixmap.size()) { + // RG_DEBUG << "CompositionView::drawAreaAudioPreviews : WARNING - miscomputed pixmap array : r.x = " + // << r.x() << " - basePoint.x = " << basePoint.x() << " - firstPixmapIdx = " << firstPixmapIdx + // << endl; + continue; + } + int x = 0, idx = firstPixmapIdx; + // RG_DEBUG << "CompositionView::drawAreaAudioPreviews : clipRect = " << clipRect + // << " - firstPixmapIdx = " << firstPixmapIdx << endl; + while (x < clipRect.width()) { + int pixmapRectXOffset = idx * AudioPreviewPainter::tileWidth(); + localRect.setRect(basePoint.x() + pixmapRectXOffset, basePoint.y(), + AudioPreviewPainter::tileWidth(), api->rect.height()); + // RG_DEBUG << "CompositionView::drawAreaAudioPreviews : initial localRect = " + // << localRect << endl; + localRect &= r; + if (idx == firstPixmapIdx && api->resizeOffset != 0) { + // this segment is being resized from start, clip beginning of preview + localRect.moveBy(api->resizeOffset, 0); + } + + // RG_DEBUG << "CompositionView::drawAreaAudioPreviews : localRect & clipRect = " + // << localRect << endl; + if (localRect.isEmpty()) { + // RG_DEBUG << "CompositionView::drawAreaAudioPreviews : localRect & clipRect is empty\n"; + break; + } + localRect.moveBy( -(basePoint.x() + pixmapRectXOffset), -basePoint.y()); + + // RG_DEBUG << "CompositionView::drawAreaAudioPreviews : drawing pixmap " + // << idx << " at " << drawBasePoint << " - localRect = " << localRect + // << " - preResizeOrigin : " << api->preResizeOrigin << endl; + + p->drawImage(drawBasePoint, api->pixmap[idx], localRect, + Qt::ColorOnly | Qt::ThresholdDither | Qt::AvoidDither); + + ++idx; + if (idx >= api->pixmap.size()) + break; + drawBasePoint.setX(drawBasePoint.x() + localRect.width()); + x += localRect.width(); + } + } +} + +void CompositionView::drawAreaArtifacts(QPainter * p, const QRect& clipRect) +{ + // + // Playback Pointer + // + drawPointer(p, clipRect); + + // + // Tmp rect (rect displayed while drawing a new segment) + // + if (m_tmpRect.isValid() && m_tmpRect.intersects(clipRect)) { + p->setBrush(m_tmpRectFill); + p->setPen(CompositionColourCache::getInstance()->SegmentBorder); + drawRect(m_tmpRect, p, clipRect); + } + + // + // Tool guides (crosshairs) + // + if (m_drawGuides) + drawGuides(p, clipRect); + + // + // Selection Rect + // + if (m_drawSelectionRect) { + drawRect(m_selectionRect, p, clipRect, false, 0, false); + } + + // + // Floating Text + // + if (m_drawTextFloat) + drawTextFloat(p, clipRect); + + // + // Split line + // + if (m_splitLinePos.x() > 0 && clipRect.contains(m_splitLinePos)) { + p->save(); + p->setPen(m_guideColor); + p->drawLine(m_splitLinePos.x(), m_splitLinePos.y(), + m_splitLinePos.x(), m_splitLinePos.y() + getModel()->grid().getYSnap()); + p->restore(); + } +} + +void CompositionView::drawGuides(QPainter * p, const QRect& /*clipRect*/) +{ + // no need to check for clipping, these guides are meant to follow the mouse cursor + QPoint guideOrig(m_topGuidePos, m_foreGuidePos); + + p->save(); + p->setPen(m_guideColor); + p->drawLine(guideOrig.x(), 0, guideOrig.x(), contentsHeight()); + p->drawLine(0, guideOrig.y(), contentsWidth(), guideOrig.y()); + p->restore(); +} + +void CompositionView::drawCompRect(const CompositionRect& r, QPainter *p, const QRect& clipRect, + int intersectLvl, bool fill) +{ + p->save(); + + QBrush brush = r.getBrush(); + + if (r.isRepeating()) { + QColor brushColor = brush.color(); + brush.setColor(brushColor.light(150)); + } + + p->setBrush(brush); + p->setPen(r.getPen()); + drawRect(r, p, clipRect, r.isSelected(), intersectLvl, fill); + + if (r.isRepeating()) { + + CompositionRect::repeatmarks repeatMarks = r.getRepeatMarks(); + + // RG_DEBUG << "CompositionView::drawCompRect() : drawing repeating rect " << r + // << " nb repeat marks = " << repeatMarks.size() << endl; + + // draw 'start' rectangle with original brush + // + QRect startRect = r; + startRect.setWidth(repeatMarks[0] - r.x()); + p->setBrush(r.getBrush()); + drawRect(startRect, p, clipRect, r.isSelected(), intersectLvl, fill); + + + // now draw the 'repeat' marks + // + p->setPen(CompositionColourCache::getInstance()->RepeatSegmentBorder); + int penWidth = std::max(r.getPen().width(), 1u); + + for (unsigned int i = 0; i < repeatMarks.size(); ++i) { + int pos = repeatMarks[i]; + if (pos > clipRect.right()) + break; + + if (pos >= clipRect.left()) { + QPoint p1(pos, r.y() + penWidth), + p2(pos, r.y() + r.height() - penWidth - 1); + + // RG_DEBUG << "CompositionView::drawCompRect() : drawing repeat mark at " + // << p1 << "-" << p2 << endl; + p->drawLine(p1, p2); + } + + } + + } + + p->restore(); +} + +void CompositionView::drawCompRectLabel(const CompositionRect& r, QPainter *p, const QRect& clipRect) +{ + // draw segment label + // +#ifdef NOT_DEFINED + if (!r.getLabel().isEmpty() /* && !r.isSelected() */) + { + p->save(); + p->setPen(GUIPalette::getColour(GUIPalette::SegmentLabel)); + p->setBrush(white); + QRect textRect(r); + textRect.setX(textRect.x() + 3); + QString label = " " + r.getLabel() + " "; + QRect textBoundingRect = p->boundingRect(textRect, Qt::AlignLeft | Qt::AlignVCenter, label); + p->drawRect(textBoundingRect & r); + p->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, label); + p->restore(); + } +#else + if (!r.getLabel().isEmpty()) { + + p->save(); + + QFont font; + font.setPixelSize(r.height() / 2.2); + font.setWeight(QFont::Bold); + font.setItalic(false); + p->setFont(font); + + QRect labelRect = QRect + (r.x(), + r.y() + ((r.height() - p->fontMetrics().height()) / 2) + 1, + r.width(), + p->fontMetrics().height()); + + int x = labelRect.x() + p->fontMetrics().width('x'); + int y = labelRect.y(); + + QBrush brush = r.getBrush(); + QColor surroundColour = brush.color().light(110); + + int h, s, v; + surroundColour.hsv(&h, &s, &v); + if (v < 150) + surroundColour.setHsv(h, s, 225); + p->setPen(surroundColour); + + for (int i = 0; i < 9; ++i) { + + if (i == 4) + continue; + + int wx = x, wy = y; + + if (i < 3) + --wx; + if (i > 5) + ++wx; + if (i % 3 == 0) + --wy; + if (i % 3 == 2) + ++wy; + + labelRect.setX(wx); + labelRect.setY(wy); + + p->drawText(labelRect, + Qt::AlignLeft | Qt::AlignTop, + r.getLabel()); + } + + labelRect.setX(x); + labelRect.setY(y); + + p->setPen(GUIPalette::getColour + (GUIPalette::SegmentLabel)); + p->drawText(labelRect, + Qt::AlignLeft | Qt::AlignVCenter, r.getLabel()); + p->restore(); + } +#endif +} + +void CompositionView::drawRect(const QRect& r, QPainter *p, const QRect& clipRect, + bool isSelected, int intersectLvl, bool fill) +{ + // RG_DEBUG << "CompositionView::drawRect : intersectLvl = " << intersectLvl + // << " - brush col = " << p->brush().color() << endl; + + // RG_DEBUG << "CompositionView::drawRect " << r << " - xformed : " << p->xForm(r) + // << " - contents x = " << contentsX() << ", contents y = " << contentsY() << endl; + + p->save(); + + QRect rect = r; + + if (fill) { + if (isSelected) { + QColor fillColor = p->brush().color(); + fillColor = fillColor.dark(200); + QBrush b = p->brush(); + b.setColor(fillColor); + p->setBrush(b); + // RG_DEBUG << "CompositionView::drawRect : selected color : " << fillColor << endl; + } + + if (intersectLvl > 0) { + QColor fillColor = p->brush().color(); + fillColor = fillColor.dark((intersectLvl) * 105); + QBrush b = p->brush(); + b.setColor(fillColor); + p->setBrush(b); + // RG_DEBUG << "CompositionView::drawRect : intersected color : " << fillColor << " isSelected : " << isSelected << endl; + } + } else { + p->setBrush(Qt::NoBrush); + } + + // Paint using the small coordinates... + QRect intersection = rect.intersect(clipRect); + + if (clipRect.contains(rect)) { + p->drawRect(rect); + } else { + // draw only what's necessary + if (!intersection.isEmpty() && fill) + p->fillRect(intersection, p->brush()); + + int rectTopY = rect.y(); + + if (rectTopY >= clipRect.y() && + rectTopY <= (clipRect.y() + clipRect.height())) { + // to prevent overflow, in case the original rect is too wide + // the line would be drawn "backwards" + p->drawLine(intersection.topLeft(), intersection.topRight()); + } + + int rectBottomY = rect.y() + rect.height(); + if (rectBottomY >= clipRect.y() && + rectBottomY <= (clipRect.y() + clipRect.height())) + // to prevent overflow, in case the original rect is too wide + // the line would be drawn "backwards" + p->drawLine(intersection.bottomLeft(), intersection.bottomRight()); + + int rectLeftX = rect.x(); + if (rectLeftX >= clipRect.x() && + rectLeftX <= (clipRect.x() + clipRect.width())) + p->drawLine(rect.topLeft(), rect.bottomLeft()); + + unsigned int rectRightX = rect.x() + rect.width(); // make sure we don't overflow + if (rectRightX >= unsigned(clipRect.x()) && + rectRightX <= unsigned(clipRect.x() + clipRect.width())) + p->drawLine(rect.topRight(), rect.bottomRight()); + + } + + p->restore(); +} + +QColor CompositionView::mixBrushes(QBrush a, QBrush b) +{ + QColor ac = a.color(), bc = b.color(); + + int aR = ac.red(), aG = ac.green(), aB = ac.blue(), + bR = bc.red(), bG = bc.green(), bB = ac.blue(); + + ac.setRgb((aR + bR) / 2, (aG + bG) / 2, (aB + bB) / 2); + + return ac; +} + +void CompositionView::drawIntersections(const CompositionModel::rectcontainer& rects, + QPainter * p, const QRect& clipRect) +{ + if (! (rects.size() > 1)) + return ; + + CompositionModel::rectcontainer intersections; + + CompositionModel::rectcontainer::const_iterator i = rects.begin(), + j = rects.begin(); + + for (; j != rects.end(); ++j) { + + CompositionRect testRect = *j; + i = j; + ++i; // set i to pos after j + + if (i == rects.end()) + break; + + for (; i != rects.end(); ++i) { + CompositionRect ri = testRect.intersect(*i); + if (!ri.isEmpty()) { + CompositionModel::rectcontainer::iterator t = std::find(intersections.begin(), + intersections.end(), ri); + if (t == intersections.end()) { + ri.setBrush(mixBrushes(testRect.getBrush(), i->getBrush())); + ri.setSelected(testRect.isSelected() || i->isSelected()); + intersections.push_back(ri); + } + + } + } + } + + // + // draw this level of intersections then compute and draw further ones + // + int intersectionLvl = 1; + + while (!intersections.empty()) { + + for (CompositionModel::rectcontainer::iterator intIter = intersections.begin(); + intIter != intersections.end(); ++intIter) { + CompositionRect r = *intIter; + drawCompRect(r, p, clipRect, intersectionLvl); + } + + if (intersections.size() > 10) + break; // put a limit on how many intersections we can compute and draw - this grows exponentially + + ++intersectionLvl; + + CompositionModel::rectcontainer intersections2; + + CompositionModel::rectcontainer::iterator i = intersections.begin(), + j = intersections.begin(); + + for (; j != intersections.end(); ++j) { + + CompositionRect testRect = *j; + i = j; + ++i; // set i to pos after j + + if (i == intersections.end()) + break; + + for (; i != intersections.end(); ++i) { + CompositionRect ri = testRect.intersect(*i); + if (!ri.isEmpty() && ri != *i) { + CompositionModel::rectcontainer::iterator t = std::find(intersections2.begin(), + intersections2.end(), ri); + if (t == intersections2.end()) + ri.setBrush(mixBrushes(testRect.getBrush(), i->getBrush())); + intersections2.push_back(ri); + } + } + } + + intersections = intersections2; + } + +} + +void CompositionView::drawPointer(QPainter *p, const QRect& clipRect) +{ + // RG_DEBUG << "CompositionView::drawPointer: clipRect " + // << clipRect.x() << "," << clipRect.y() << " " << clipRect.width() + // << "x" << clipRect.height() << " pointer pos is " << m_pointerPos << endl; + + if (m_pointerPos >= clipRect.x() && m_pointerPos <= (clipRect.x() + clipRect.width())) { + p->save(); + p->setPen(m_pointerPen); + p->drawLine(m_pointerPos, clipRect.y(), m_pointerPos, clipRect.y() + clipRect.height()); + p->restore(); + } + +} + +void CompositionView::drawTextFloat(QPainter *p, const QRect& clipRect) +{ + QFontMetrics metrics(p->fontMetrics()); + + QRect bound = p->boundingRect(0, 0, 300, metrics.height() + 6, AlignAuto, m_textFloatText); + + p->save(); + + bound.setLeft(bound.left() - 2); + bound.setRight(bound.right() + 2); + bound.setTop(bound.top() - 2); + bound.setBottom(bound.bottom() + 2); + + QPoint pos(m_textFloatPos); + if (pos.y() < 0 && getModel()) { + if (pos.y() + bound.height() < 0) { + pos.setY(pos.y() + getModel()->grid().getYSnap() * 3); + } else { + pos.setY(pos.y() + getModel()->grid().getYSnap() * 2); + } + } + + bound.moveTopLeft(pos); + + if (bound.intersects(clipRect)) { + + p->setBrush(CompositionColourCache::getInstance()->RotaryFloatBackground); + + drawRect(bound, p, clipRect, false, 0, true); + + p->setPen(CompositionColourCache::getInstance()->RotaryFloatForeground); + + p->drawText(pos.x() + 2, pos.y() + 3 + metrics.ascent(), m_textFloatText); + + } + + p->restore(); +} + +bool CompositionView::event(QEvent* e) +{ + if (e->type() == AudioPreviewThread::AudioPreviewQueueEmpty) { + RG_DEBUG << "CompositionView::event - AudioPreviewQueueEmpty\n"; + slotSegmentsDrawBufferNeedsRefresh(); + viewport()->update(); + return true; + } + + return RosegardenScrollView::event(e); +} + +void CompositionView::enterEvent(QEvent *e) +{ + kapp->config()->setGroup(GeneralOptionsConfigGroup); + if (!kapp->config()->readBoolEntry("toolcontexthelp", true)) return; + + emit showContextHelp(m_toolContextHelp); + m_contextHelpShown = true; +} + +void CompositionView::leaveEvent(QEvent *e) +{ + emit showContextHelp(""); + m_contextHelpShown = false; +} + +void CompositionView::slotToolHelpChanged(const QString &text) +{ + if (m_toolContextHelp == text) return; + m_toolContextHelp = text; + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + if (!kapp->config()->readBoolEntry("toolcontexthelp", true)) return; + + if (m_contextHelpShown) emit showContextHelp(text); +} + +void CompositionView::contentsMousePressEvent(QMouseEvent* e) +{ + Qt::ButtonState bs = e->state(); + slotSetSelectCopy((bs & Qt::ControlButton) != 0); + slotSetSelectAdd((bs & Qt::ShiftButton) != 0); + slotSetFineGrain((bs & Qt::ShiftButton) != 0); + slotSetPencilOverExisting((bs & Qt::AltButton + Qt::ControlButton) != 0); + + switch (e->button()) { + case LeftButton: + case MidButton: + startAutoScroll(); + + if (m_tool) + m_tool->handleMouseButtonPress(e); + else + RG_DEBUG << "CompositionView::contentsMousePressEvent() :" + << this << " no tool\n"; + break; + case RightButton: + if (m_tool) + m_tool->handleRightButtonPress(e); + else + RG_DEBUG << "CompositionView::contentsMousePressEvent() :" + << this << " no tool\n"; + break; + default: + break; + } +} + +void CompositionView::contentsMouseReleaseEvent(QMouseEvent* e) +{ + RG_DEBUG << "CompositionView::contentsMouseReleaseEvent()\n"; + + stopAutoScroll(); + + if (!m_tool) + return ; + + if (e->button() == LeftButton || + e->button() == MidButton ) + m_tool->handleMouseButtonRelease(e); +} + +void CompositionView::contentsMouseDoubleClickEvent(QMouseEvent* e) +{ + m_currentItem = getFirstItemAt(e->pos()); + + if (!m_currentItem) { + RG_DEBUG << "CompositionView::contentsMouseDoubleClickEvent - no currentItem\n"; + RulerScale *ruler = grid().getRulerScale(); + if (ruler) emit setPointerPosition(ruler->getTimeForX(e->pos().x())); + return ; + } + + RG_DEBUG << "CompositionView::contentsMouseDoubleClickEvent - have currentItem\n"; + + CompositionItemImpl* itemImpl = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)m_currentItem); + + if (m_currentItem->isRepeating()) { + timeT time = getModel()->getRepeatTimeAt(e->pos(), m_currentItem); + + RG_DEBUG << "editRepeat at time " << time << endl; + if (time > 0) + emit editRepeat(itemImpl->getSegment(), time); + else + emit editSegment(itemImpl->getSegment()); + + } else { + + emit editSegment(itemImpl->getSegment()); + } +} + +void CompositionView::contentsMouseMoveEvent(QMouseEvent* e) +{ + if (!m_tool) + return ; + + Qt::ButtonState bs = e->state(); + slotSetFineGrain((bs & Qt::ShiftButton) != 0); + slotSetPencilOverExisting((bs & Qt::AltButton) != 0); + + int follow = m_tool->handleMouseMove(e); + setScrollDirectionConstraint(follow); + + if (follow != RosegardenCanvasView::NoFollow) { + doAutoScroll(); + + if (follow & RosegardenCanvasView::FollowHorizontal) { + slotScrollHorizSmallSteps(e->pos().x()); + + // enlarge composition if needed + if (horizontalScrollBar()->value() == horizontalScrollBar()->maxValue()) { + resizeContents(contentsWidth() + m_stepSize, contentsHeight()); + setContentsPos(contentsX() + m_stepSize, contentsY()); + getModel()->setLength(contentsWidth()); + slotUpdateSize(); + } + } + + if (follow & RosegardenCanvasView::FollowVertical) + slotScrollVertSmallSteps(e->pos().y()); + } +} + +void CompositionView::releaseCurrentItem() +{ + m_currentItem = CompositionItem(); +} + +void CompositionView::setPointerPos(int pos) +{ + // RG_DEBUG << "CompositionView::setPointerPos(" << pos << ")\n"; + int oldPos = m_pointerPos; + if (oldPos == pos) + return ; + + m_pointerPos = pos; + getModel()->setPointerPos(pos); + + // automagically grow contents width if pointer position goes beyond right end + // + if (pos >= (contentsWidth() - m_stepSize)) { + resizeContents(pos + m_stepSize, contentsHeight()); + // grow composition too, if needed (it may not be the case if + if (getModel()->getLength() < contentsWidth()) + getModel()->setLength(contentsWidth()); + } + + + // interesting -- isAutoScrolling() never seems to return true? + // RG_DEBUG << "CompositionView::setPointerPos(" << pos << "), isAutoScrolling " << isAutoScrolling() << ", contentsX " << contentsX() << ", m_lastPointerRefreshX " << m_lastPointerRefreshX << ", contentsHeight " << contentsHeight() << endl; + + if (contentsX() != m_lastPointerRefreshX) { + m_lastPointerRefreshX = contentsX(); + // We'll need to shift the whole canvas anyway, so + slotArtifactsDrawBufferNeedsRefresh(); + return ; + } + + int deltaW = abs(m_pointerPos - oldPos); + + if (deltaW <= m_pointerPen.width() * 2) { // use one rect instead of two separate ones + + QRect updateRect + (std::min(m_pointerPos, oldPos) - m_pointerPen.width(), 0, + deltaW + m_pointerPen.width() * 2, contentsHeight()); + + slotArtifactsDrawBufferNeedsRefresh(updateRect); + + } else { + + slotArtifactsDrawBufferNeedsRefresh + (QRect(m_pointerPos - m_pointerPen.width(), 0, + m_pointerPen.width() * 2, contentsHeight())); + + slotArtifactsDrawBufferNeedsRefresh + (QRect(oldPos - m_pointerPen.width(), 0, + m_pointerPen.width() * 2, contentsHeight())); + } +} + +void CompositionView::setGuidesPos(int x, int y) +{ + m_topGuidePos = x; + m_foreGuidePos = y; + slotArtifactsDrawBufferNeedsRefresh(); +} + +void CompositionView::setGuidesPos(const QPoint& p) +{ + m_topGuidePos = p.x(); + m_foreGuidePos = p.y(); + slotArtifactsDrawBufferNeedsRefresh(); +} + +void CompositionView::setDrawGuides(bool d) +{ + m_drawGuides = d; + slotArtifactsDrawBufferNeedsRefresh(); +} + +void CompositionView::setTmpRect(const QRect& r) +{ + setTmpRect(r, m_tmpRectFill); +} + +void CompositionView::setTmpRect(const QRect& r, const QColor &c) +{ + QRect pRect = m_tmpRect; + m_tmpRect = r; + m_tmpRectFill = c; + slotUpdateSegmentsDrawBuffer(m_tmpRect | pRect); +} + +void CompositionView::setTextFloat(int x, int y, const QString &text) +{ + m_textFloatPos.setX(x); + m_textFloatPos.setY(y); + m_textFloatText = text; + m_drawTextFloat = true; + slotArtifactsDrawBufferNeedsRefresh(); + + // most of the time when the floating text is drawn + // we want to update a larger part of the view + // so don't update here + // QRect r = fontMetrics().boundingRect(x, y, 300, 40, AlignAuto, m_textFloatText); + // slotUpdateSegmentsDrawBuffer(r); + + + // rgapp->slotSetStatusMessage(text); +} + +void CompositionView::slotSetFineGrain(bool value) +{ + m_fineGrain = value; +} + +void CompositionView::slotSetPencilOverExisting(bool value) +{ + m_pencilOverExisting = value; +} + +void +CompositionView::slotTextFloatTimeout() +{ + hideTextFloat(); + slotArtifactsDrawBufferNeedsRefresh(); + // rgapp->slotSetStatusMessage(QString::null); +} + +} +#include "CompositionView.moc" diff --git a/src/gui/editors/segment/segmentcanvas/CompositionView.h b/src/gui/editors/segment/segmentcanvas/CompositionView.h new file mode 100644 index 0000000..ff0d440 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/CompositionView.h @@ -0,0 +1,366 @@ + +/* -*- 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_COMPOSITIONVIEW_H_ +#define _RG_COMPOSITIONVIEW_H_ + +#include "base/Selection.h" +#include "CompositionModel.h" +#include "CompositionItem.h" +#include "gui/general/RosegardenScrollView.h" +#include <qbrush.h> +#include <qcolor.h> +#include <qpen.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qrect.h> +#include <qstring.h> +#include "base/Event.h" + + +class QWidget; +class QWheelEvent; +class QResizeEvent; +class QPaintEvent; +class QPainter; +class QMouseEvent; +class QEvent; + + +namespace Rosegarden +{ + +class SnapGrid; +class SegmentToolBox; +class SegmentTool; +class SegmentSelector; +class Segment; +class RosegardenGUIDoc; +class CompositionRect; + + +class CompositionView : public RosegardenScrollView +{ + Q_OBJECT +public: + CompositionView(RosegardenGUIDoc*, CompositionModel*, + QWidget * parent=0, const char* name=0, WFlags f=0); + + void setPointerPos(int pos); + int getPointerPos() { return m_pointerPos; } + + void setGuidesPos(int x, int y); + void setGuidesPos(const QPoint& p); + void setDrawGuides(bool d); + + QRect getSelectionRect() const { return m_selectionRect; } + void setSelectionRectPos(const QPoint& pos); + void setSelectionRectSize(int w, int h); + void setDrawSelectionRect(bool d); + + SnapGrid& grid() { return m_model->grid(); } + + CompositionItem getFirstItemAt(QPoint pos); + + SegmentToolBox* getToolBox() { return m_toolBox; } + + CompositionModel* getModel() { return m_model; } + + void setTmpRect(const QRect& r); + void setTmpRect(const QRect& r, const QColor &c); + const QRect& getTmpRect() const { return m_tmpRect; } + + /** + * Set the snap resolution of the grid to something suitable. + * + * fineTool indicates whether the current tool is a fine-grain sort + * (such as the resize or move tools) or a coarse one (such as the + * segment creation pencil). If the user is requesting extra-fine + * resolution (through the setFineGrain method) that will also be + * taken into account. + */ + void setSnapGrain(bool fine); + + /** + * Find out whether the user is requesting extra-fine resolution + * (e.g. by holding Shift key). This is seldom necessary -- most + * client code will only need to query the snap grid that is + * adjusted appropriately by the view when interactions take + * place. + */ + bool isFineGrain() const { return m_fineGrain; } + + /** + * Find out whether the user is requesting to draw over an existing segment + * with the pencil, by holding the Ctrl key. This is used by the segment + * pencil to decide whether to abort or not if a user attempts to draw over + * an existing segment, and this is all necessary in order to avoid breaking + * the double-click-to-open behavior. + */ + bool pencilOverExisting() const { return m_pencilOverExisting; } + + /** + * Set whether the segment items contain previews or not + */ + void setShowPreviews(bool previews) { m_showPreviews = previews; } + + /** + * Return whether the segment items contain previews or not + */ + bool isShowingPreviews() { return m_showPreviews; } + + /** + * clear all seg rect cache + */ + void clearSegmentRectsCache(bool clearPreviews = false); + + /// Return the selected Segments if we're currently using a "Selector" + SegmentSelection getSelectedSegments(); + + bool haveSelection() const { return m_model->haveSelection(); } + + void updateSelectionContents(); + + /** + * Set and hide a text float on this canvas - it can contain + * anything and can be left to timeout or you can hide it + * explicitly. + * + */ + void setTextFloat(int x, int y, const QString &text); + void hideTextFloat() { m_drawTextFloat = false; } + + void setShowSegmentLabels(bool b) { m_showSegmentLabels = b; } + + void setBackgroundPixmap(const QPixmap &m); + + void endAudioPreviewGeneration(); + +public slots: + void scrollRight(); + void scrollLeft(); + void slotContentsMoving(int x, int y); + + /// Set the current segment editing tool + void slotSetTool(const QString& toolName); + + // This method only operates if we're of the "Selector" + // tool type - it's called from the View to enable it + // to automatically set the selection of Segments (say + // by Track). + // + void slotSelectSegments(const SegmentSelection &segment); + + // These are sent from the top level app when it gets key + // depresses relating to selection add (usually SHIFT) and + // selection copy (usually CONTROL) + // + void slotSetSelectAdd(bool value); + void slotSetSelectCopy(bool value); + + void slotSetFineGrain(bool value); + void slotSetPencilOverExisting(bool value); + + // Show and hige the splitting line on a Segment + // + void slotShowSplitLine(int x, int y); + void slotHideSplitLine(); + + void slotExternalWheelEvent(QWheelEvent*); + + // TextFloat timer + void slotTextFloatTimeout(); + + void slotUpdateSegmentsDrawBuffer(); + void slotUpdateSegmentsDrawBuffer(const QRect&); + + void slotRefreshColourCache(); + + void slotNewMIDIRecordingSegment(Segment*); + void slotNewAudioRecordingSegment(Segment*); + // no longer used, see RosegardenGUIDoc::insertRecordedMidi +// void slotRecordMIDISegmentUpdated(Segment*, timeT updatedFrom); + void slotStoppedRecording(); + + void slotUpdateSize(); + +signals: + void editSegment(Segment*); // use default editor + void editSegmentNotation(Segment*); + void editSegmentMatrix(Segment*); + void editSegmentAudio(Segment*); + void editSegmentEventList(Segment*); + void audioSegmentAutoSplit(Segment*); + void editRepeat(Segment*, timeT); + + void setPointerPosition(timeT); + + void showContextHelp(const QString &); + +protected: + virtual bool event(QEvent *); + + virtual void contentsMousePressEvent(QMouseEvent*); + virtual void contentsMouseReleaseEvent(QMouseEvent*); + virtual void contentsMouseDoubleClickEvent(QMouseEvent*); + virtual void contentsMouseMoveEvent(QMouseEvent*); + + virtual void viewportPaintEvent(QPaintEvent*); + virtual void resizeEvent(QResizeEvent*); + + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); + + virtual void viewportPaintRect(QRect); + + /** + * if something changed, returns true and sets rect accordingly + * sets 'scroll' if some scrolling occurred + */ + bool checkScrollAndRefreshDrawBuffer(QRect &, bool& scroll); + void refreshSegmentsDrawBuffer(const QRect&); + void refreshArtifactsDrawBuffer(const QRect&); + void drawArea(QPainter * p, const QRect& rect); + void drawAreaAudioPreviews(QPainter * p, const QRect& rect); + void drawAreaArtifacts(QPainter * p, const QRect& rect); + void drawRect(const QRect& rect, QPainter * p, const QRect& clipRect, + bool isSelected = false, int intersectLvl = 0, bool fill = true); + void drawCompRect(const CompositionRect& r, QPainter *p, const QRect& clipRect, + int intersectLvl = 0, bool fill = true); + void drawCompRectLabel(const CompositionRect& r, QPainter *p, const QRect& clipRect); + void drawIntersections(const CompositionModel::rectcontainer&, QPainter * p, const QRect& clipRect); + + void drawPointer(QPainter * p, const QRect& clipRect); + void drawGuides(QPainter * p, const QRect& clipRect); + void drawTextFloat(QPainter * p, const QRect& clipRect); + + void initStepSize(); + void releaseCurrentItem(); + + static QColor mixBrushes(QBrush a, QBrush b); + + SegmentSelector* getSegmentSelectorTool(); + +protected slots: + void slotSegmentsDrawBufferNeedsRefresh() { + m_segmentsDrawBufferRefresh = + QRect(contentsX(), contentsY(), visibleWidth(), visibleHeight()); + } + + void slotSegmentsDrawBufferNeedsRefresh(QRect r) { + m_segmentsDrawBufferRefresh |= + (QRect(contentsX(), contentsY(), visibleWidth(), visibleHeight()) + & r); + } + + void slotArtifactsDrawBufferNeedsRefresh() { + m_artifactsDrawBufferRefresh = + QRect(contentsX(), contentsY(), visibleWidth(), visibleHeight()); + updateContents(); + } + + void slotArtifactsDrawBufferNeedsRefresh(QRect r) { + m_artifactsDrawBufferRefresh |= + (QRect(contentsX(), contentsY(), visibleWidth(), visibleHeight()) + & r); + updateContents(r); + } + + void slotAllDrawBuffersNeedRefresh() { + slotSegmentsDrawBufferNeedsRefresh(); + slotArtifactsDrawBufferNeedsRefresh(); + } + + void slotAllDrawBuffersNeedRefresh(QRect r) { + slotSegmentsDrawBufferNeedsRefresh(r); + slotArtifactsDrawBufferNeedsRefresh(r); + } + + void slotToolHelpChanged(const QString &); + +protected: + + //--------------- Data members --------------------------------- + + CompositionModel* m_model; + CompositionItem m_currentItem; + + SegmentTool* m_tool; + SegmentToolBox* m_toolBox; + + bool m_showPreviews; + bool m_showSegmentLabels; + bool m_fineGrain; + bool m_pencilOverExisting; + + int m_minWidth; + + int m_stepSize; + QColor m_rectFill; + QColor m_selectedRectFill; + + int m_pointerPos; + QColor m_pointerColor; + int m_pointerWidth; + QPen m_pointerPen; + + QRect m_tmpRect; + QColor m_tmpRectFill; + QPoint m_splitLinePos; + + QColor m_trackDividerColor; + + bool m_drawGuides; + QColor m_guideColor; + int m_topGuidePos; + int m_foreGuidePos; + + bool m_drawSelectionRect; + QRect m_selectionRect; + + bool m_drawTextFloat; + QString m_textFloatText; + QPoint m_textFloatPos; + + QPixmap m_segmentsDrawBuffer; + QPixmap m_artifactsDrawBuffer; + QRect m_segmentsDrawBufferRefresh; + QRect m_artifactsDrawBufferRefresh; + int m_lastBufferRefreshX; + int m_lastBufferRefreshY; + int m_lastPointerRefreshX; + QPixmap m_backgroundPixmap; + + QString m_toolContextHelp; + bool m_contextHelpShown; + + mutable CompositionModel::AudioPreviewDrawData m_audioPreviewRects; + mutable CompositionModel::RectRanges m_notationPreviewRects; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/PreviewRect.cpp b/src/gui/editors/segment/segmentcanvas/PreviewRect.cpp new file mode 100644 index 0000000..fa09644 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/PreviewRect.cpp @@ -0,0 +1,34 @@ +/* -*- 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 "PreviewRect.h" + +#include <qcolor.h> +#include <qrect.h> + + +namespace Rosegarden +{ +} diff --git a/src/gui/editors/segment/segmentcanvas/PreviewRect.h b/src/gui/editors/segment/segmentcanvas/PreviewRect.h new file mode 100644 index 0000000..59f3113 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/PreviewRect.h @@ -0,0 +1,62 @@ + +/* -*- 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_PREVIEWRECT_H_ +#define _RG_PREVIEWRECT_H_ + +#include <qcolor.h> +#include <qrect.h> +#include <vector> + + + + +namespace Rosegarden +{ + + + +class PreviewRect : public QRect { +public: + PreviewRect(int left, int top, int width, int height) : + QRect(left, top, width, height) {}; + + PreviewRect(const QRect& r) : + QRect(r) {}; + + const QColor& getColor() const { return m_color; } + void setColor(QColor c) { m_color = c; } + +protected: + QColor m_color; +}; + +typedef std::vector<QImage> PixmapArray; + + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/SegmentEraser.cpp b/src/gui/editors/segment/segmentcanvas/SegmentEraser.cpp new file mode 100644 index 0000000..3d1e26f --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentEraser.cpp @@ -0,0 +1,88 @@ +/* -*- 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 "SegmentEraser.h" + +#include "misc/Debug.h" +#include "commands/segment/SegmentEraseCommand.h" +#include "CompositionView.h" +#include "CompositionItemImpl.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/general/BaseTool.h" +#include "gui/general/RosegardenCanvasView.h" +#include "SegmentTool.h" +#include <kcommand.h> +#include <qpoint.h> +#include <qstring.h> +#include <klocale.h> + + +namespace Rosegarden +{ + +SegmentEraser::SegmentEraser(CompositionView *c, RosegardenGUIDoc *d) + : SegmentTool(c, d) +{ + RG_DEBUG << "SegmentEraser()\n"; +} + +void SegmentEraser::ready() +{ + m_canvas->viewport()->setCursor(Qt::pointingHandCursor); + setBasicContextHelp(); +} + +void SegmentEraser::handleMouseButtonPress(QMouseEvent *e) +{ + setCurrentItem(m_canvas->getFirstItemAt(e->pos())); +} + +void SegmentEraser::handleMouseButtonRelease(QMouseEvent*) +{ + if (m_currentItem) { + // no need to test the result, we know it's good (see handleMouseButtonPress) + CompositionItemImpl* item = dynamic_cast<CompositionItemImpl*>((_CompositionItem*)m_currentItem); + + addCommandToHistory(new SegmentEraseCommand(item->getSegment())); + } + + setCurrentItem(CompositionItem()); +} + +int SegmentEraser::handleMouseMove(QMouseEvent*) +{ + setBasicContextHelp(); + return RosegardenCanvasView::NoFollow; +} + +void SegmentEraser::setBasicContextHelp() +{ + setContextHelp(i18n("Click on a segment to delete it")); +} + +const QString SegmentEraser::ToolName = "segmenteraser"; + +} +#include "SegmentEraser.moc" diff --git a/src/gui/editors/segment/segmentcanvas/SegmentEraser.h b/src/gui/editors/segment/segmentcanvas/SegmentEraser.h new file mode 100644 index 0000000..f428c28 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentEraser.h @@ -0,0 +1,67 @@ + +/* -*- 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_SEGMENTERASER_H_ +#define _RG_SEGMENTERASER_H_ + +#include "SegmentTool.h" +#include <qstring.h> + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class CompositionView; + + +class SegmentEraser : public SegmentTool +{ + Q_OBJECT + + friend class SegmentToolBox; + +public: + + virtual void ready(); + + virtual void handleMouseButtonPress(QMouseEvent*); + virtual void handleMouseButtonRelease(QMouseEvent*); + virtual int handleMouseMove(QMouseEvent*); + + static const QString ToolName; + +protected: + SegmentEraser(CompositionView*, RosegardenGUIDoc*); + void setBasicContextHelp(); +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/SegmentItemPreview.cpp b/src/gui/editors/segment/segmentcanvas/SegmentItemPreview.cpp new file mode 100644 index 0000000..f0c4598 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentItemPreview.cpp @@ -0,0 +1,37 @@ +/* -*- 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 "SegmentItemPreview.h" + +#include "base/RulerScale.h" +#include "base/Segment.h" +#include <qpainter.h> +#include <qrect.h> +#include <qwmatrix.h> + + +namespace Rosegarden +{ +} diff --git a/src/gui/editors/segment/segmentcanvas/SegmentItemPreview.h b/src/gui/editors/segment/segmentcanvas/SegmentItemPreview.h new file mode 100644 index 0000000..e190a5c --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentItemPreview.h @@ -0,0 +1,91 @@ + +/* -*- 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_SEGMENTITEMPREVIEW_H_ +#define _RG_SEGMENTITEMPREVIEW_H_ + +#include <qrect.h> + + +class QWMatrix; +class QPainter; + + +namespace Rosegarden +{ + +class Segment; +class RulerScale; + + +////////////////////////////////////////////////////////////////////// +class SegmentItemPreview +{ +public: + SegmentItemPreview(Segment& parent, + RulerScale* scale); + virtual ~SegmentItemPreview(); + + enum PreviewState { + PreviewChanged, + PreviewCalculating, + PreviewCurrent + }; + + virtual void drawShape(QPainter&) = 0; + + PreviewState getPreviewState() const { return m_previewState; } + + /** + * Sets whether the preview shape shown in the segment needs + * to be refreshed + */ + void setPreviewCurrent(bool c) + { m_previewState = (c ? PreviewCurrent : PreviewChanged); } + + /** + * Clears out the preview entirely so that it will be regenerated + * next time + */ + virtual void clearPreview() = 0; + + QRect rect(); + +protected: + virtual void updatePreview(const QWMatrix &matrix) = 0; + + //--------------- Data members --------------------------------- + + Segment *m_segment; + RulerScale *m_rulerScale; + + PreviewState m_previewState; +}; + + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/SegmentJoiner.cpp b/src/gui/editors/segment/segmentcanvas/SegmentJoiner.cpp new file mode 100644 index 0000000..5129202 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentJoiner.cpp @@ -0,0 +1,73 @@ +/* -*- 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 "SegmentJoiner.h" + +#include "misc/Debug.h" +#include "CompositionView.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/general/BaseTool.h" +#include "gui/general/RosegardenCanvasView.h" +#include "SegmentTool.h" +#include <kcommand.h> +#include <qpoint.h> +#include <qstring.h> +#include <klocale.h> + + +namespace Rosegarden +{ + +SegmentJoiner::SegmentJoiner(CompositionView *c, RosegardenGUIDoc *d) + : SegmentTool(c, d) +{ + RG_DEBUG << "SegmentJoiner() - not implemented\n"; +} + +SegmentJoiner::~SegmentJoiner() +{} + +void +SegmentJoiner::handleMouseButtonPress(QMouseEvent*) +{} + +void +SegmentJoiner::handleMouseButtonRelease(QMouseEvent*) +{} + +int +SegmentJoiner::handleMouseMove(QMouseEvent*) +{ + return RosegardenCanvasView::NoFollow; +} + +void +SegmentJoiner::contentsMouseDoubleClickEvent(QMouseEvent*) +{} + +const QString SegmentJoiner::ToolName = "segmentjoiner"; + +} +#include "SegmentJoiner.moc" diff --git a/src/gui/editors/segment/segmentcanvas/SegmentJoiner.h b/src/gui/editors/segment/segmentcanvas/SegmentJoiner.h new file mode 100644 index 0000000..2c83a26 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentJoiner.h @@ -0,0 +1,70 @@ + +/* -*- 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_SEGMENTJOINER_H_ +#define _RG_SEGMENTJOINER_H_ + +#include "SegmentTool.h" +#include <qstring.h> + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class CompositionView; + + +class SegmentJoiner : public SegmentTool +{ + Q_OBJECT + + friend class SegmentToolBox; + +public: + + virtual ~SegmentJoiner(); + + virtual void handleMouseButtonPress(QMouseEvent*); + virtual void handleMouseButtonRelease(QMouseEvent*); + virtual int handleMouseMove(QMouseEvent*); + + // don't do double clicks + virtual void contentsMouseDoubleClickEvent(QMouseEvent*); + + static const QString ToolName; + +protected: + SegmentJoiner(CompositionView*, RosegardenGUIDoc*); +}; + + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/SegmentMover.cpp b/src/gui/editors/segment/segmentcanvas/SegmentMover.cpp new file mode 100644 index 0000000..a3d2a59 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentMover.cpp @@ -0,0 +1,348 @@ +/* -*- 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 "SegmentMover.h" + +#include "base/Event.h" +#include <klocale.h> +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/RealTime.h" +#include "base/Track.h" +#include "base/SnapGrid.h" +#include "commands/segment/SegmentReconfigureCommand.h" +#include "CompositionItemHelper.h" +#include "CompositionModel.h" +#include "CompositionView.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/general/BaseTool.h" +#include "gui/general/RosegardenCanvasView.h" +#include "SegmentTool.h" +#include "SegmentToolBox.h" +#include "SegmentSelector.h" +#include <kcommand.h> +#include <qcursor.h> +#include <qevent.h> +#include <qpoint.h> +#include <qrect.h> +#include <qstring.h> +#include <klocale.h> + + +namespace Rosegarden +{ + +SegmentMover::SegmentMover(CompositionView *c, RosegardenGUIDoc *d) + : SegmentTool(c, d) +{ + RG_DEBUG << "SegmentMover()\n"; +} + +void SegmentMover::ready() +{ + m_canvas->viewport()->setCursor(Qt::sizeAllCursor); + connect(m_canvas, SIGNAL(contentsMoving (int, int)), + this, SLOT(slotCanvasScrolled(int, int))); + setBasicContextHelp(); +} + +void SegmentMover::stow() +{ + disconnect(m_canvas, SIGNAL(contentsMoving (int, int)), + this, SLOT(slotCanvasScrolled(int, int))); +} + +void SegmentMover::slotCanvasScrolled(int newX, int newY) +{ + QMouseEvent tmpEvent(QEvent::MouseMove, + m_canvas->viewport()->mapFromGlobal(QCursor::pos()) + QPoint(newX, newY), + Qt::NoButton, Qt::NoButton); + handleMouseMove(&tmpEvent); +} + +void SegmentMover::handleMouseButtonPress(QMouseEvent *e) +{ + CompositionItem item = m_canvas->getFirstItemAt(e->pos()); + SegmentSelector* selector = dynamic_cast<SegmentSelector*> + (getToolBox()->getTool("segmentselector")); + + // #1027303: Segment move issue + // Clear selection if we're clicking on an item that's not in it + // and we're not in add mode + + if (selector && item && + !m_canvas->getModel()->isSelected(item) && !selector->isSegmentAdding()) { + m_canvas->getModel()->clearSelected(); + m_canvas->getModel()->signalSelection(); + m_canvas->updateContents(); + } + + if (item) { + + setCurrentItem(item); + m_clickPoint = e->pos(); + Segment* s = CompositionItemHelper::getSegment(m_currentItem); + + int x = int(m_canvas->grid().getRulerScale()->getXForTime(s->getStartTime())); + int y = int(m_canvas->grid().getYBinCoordinate(s->getTrack())); + + m_canvas->setGuidesPos(x, y); + m_canvas->setDrawGuides(true); + + if (m_canvas->getModel()->haveSelection()) { + RG_DEBUG << "SegmentMover::handleMouseButtonPress() : haveSelection\n"; + // startChange on all selected segments + m_canvas->getModel()->startChangeSelection(CompositionModel::ChangeMove); + + + CompositionModel::itemcontainer& changingItems = m_canvas->getModel()->getChangingItems(); + // set m_currentItem to its "sibling" among selected (now moving) items + setCurrentItem(CompositionItemHelper::findSiblingCompositionItem(changingItems, m_currentItem)); + + } else { + RG_DEBUG << "SegmentMover::handleMouseButtonPress() : no selection\n"; + m_canvas->getModel()->startChange(item, CompositionModel::ChangeMove); + } + + m_canvas->updateContents(); + + m_passedInertiaEdge = false; + + } else { + + // check for addmode - clear the selection if not + RG_DEBUG << "SegmentMover::handleMouseButtonPress() : clear selection\n"; + m_canvas->getModel()->clearSelected(); + m_canvas->getModel()->signalSelection(); + m_canvas->updateContents(); + } + +} + +void SegmentMover::handleMouseButtonRelease(QMouseEvent *e) +{ + Composition &comp = m_doc->getComposition(); + + int startDragTrackPos = m_canvas->grid().getYBin(m_clickPoint.y()); + int currentTrackPos = m_canvas->grid().getYBin(e->pos().y()); + int trackDiff = currentTrackPos - startDragTrackPos; + + if (m_currentItem) { + + if (changeMade()) { + + CompositionModel::itemcontainer& changingItems = m_canvas->getModel()->getChangingItems(); + + SegmentReconfigureCommand *command = + new SegmentReconfigureCommand + (changingItems.size() == 1 ? i18n("Move Segment") : i18n("Move Segments")); + + + CompositionModel::itemcontainer::iterator it; + + for (it = changingItems.begin(); + it != changingItems.end(); + it++) { + + CompositionItem item = *it; + + Segment* segment = CompositionItemHelper::getSegment(item); + + TrackId origTrackId = segment->getTrack(); + int trackPos = comp.getTrackPositionById(origTrackId); + trackPos += trackDiff; + + if (trackPos < 0) { + trackPos = 0; + } else if (trackPos >= comp.getNbTracks()) { + trackPos = comp.getNbTracks() - 1; + } + + Track *newTrack = comp.getTrackByPosition(trackPos); + int newTrackId = origTrackId; + if (newTrack) newTrackId = newTrack->getId(); + + timeT newStartTime = CompositionItemHelper::getStartTime(item, m_canvas->grid()); + + // We absolutely don't want to snap the end time + // to the grid. We want it to remain exactly the same + // as it was, but relative to the new start time. + timeT newEndTime = newStartTime + segment->getEndMarkerTime() + - segment->getStartTime(); + + command->addSegment(segment, + newStartTime, + newEndTime, + newTrackId); + } + + addCommandToHistory(command); + } + + m_canvas->hideTextFloat(); + m_canvas->setDrawGuides(false); + m_canvas->getModel()->endChange(); + m_canvas->slotUpdateSegmentsDrawBuffer(); + + } + + setChangeMade(false); + m_currentItem = CompositionItem(); + + setBasicContextHelp(); +} + +int SegmentMover::handleMouseMove(QMouseEvent *e) +{ + m_canvas->setSnapGrain(true); + + Composition &comp = m_doc->getComposition(); + + if (!m_currentItem) { + setBasicContextHelp(); + return RosegardenCanvasView::NoFollow; + } + + if (!m_canvas->isFineGrain()) { + setContextHelp(i18n("Hold Shift to avoid snapping to beat grid")); + } else { + clearContextHelp(); + } + + CompositionModel::itemcontainer& changingItems = m_canvas->getModel()->getChangingItems(); + + // RG_DEBUG << "SegmentMover::handleMouseMove : nb changingItems = " + // << changingItems.size() << endl; + + CompositionModel::itemcontainer::iterator it; + int guideX = 0; + int guideY = 0; + QRect updateRect; + + for (it = changingItems.begin(); + it != changingItems.end(); + it++) { + // it->second->showRepeatRect(false); + + int dx = e->pos().x() - m_clickPoint.x(), + dy = e->pos().y() - m_clickPoint.y(); + + const int inertiaDistance = m_canvas->grid().getYSnap() / 3; + if (!m_passedInertiaEdge && + (dx < inertiaDistance && dx > -inertiaDistance) && + (dy < inertiaDistance && dy > -inertiaDistance)) { + return RosegardenCanvasView::NoFollow; + } else { + m_passedInertiaEdge = true; + } + + timeT newStartTime = m_canvas->grid().snapX((*it)->savedRect().x() + dx); + + int newX = int(m_canvas->grid().getRulerScale()->getXForTime(newStartTime)); + + int startDragTrackPos = m_canvas->grid().getYBin(m_clickPoint.y()); + int currentTrackPos = m_canvas->grid().getYBin(e->pos().y()); + int trackDiff = currentTrackPos - startDragTrackPos; + int trackPos = m_canvas->grid().getYBin((*it)->savedRect().y()); + +// std::cerr << "segment " << *it << ": mouse started at track " << startDragTrackPos << ", is now at " << currentTrackPos << ", trackPos from " << trackPos << " to "; + + trackPos += trackDiff; + +// std::cerr << trackPos << std::endl; + + if (trackPos < 0) { + trackPos = 0; + } else if (trackPos >= comp.getNbTracks()) { + trackPos = comp.getNbTracks() - 1; + } +/*!!! + int newY = m_canvas->grid().snapY((*it)->savedRect().y() + dy); + // Make sure we don't set a non-existing track + if (newY < 0) { + newY = 0; + } + int trackPos = m_canvas->grid().getYBin(newY); + + // RG_DEBUG << "SegmentMover::handleMouseMove: orig y " + // << (*it)->savedRect().y() + // << ", dy " << dy << ", newY " << newY + // << ", track " << track << endl; + + // Make sure we don't set a non-existing track (c'td) + // TODO: make this suck less. Either the tool should + // not allow it in the first place, or we automatically + // create new tracks - might make undo very tricky though + // + if (trackPos >= comp.getNbTracks()) + trackPos = comp.getNbTracks() - 1; +*/ + int newY = m_canvas->grid().getYBinCoordinate(trackPos); + + // RG_DEBUG << "SegmentMover::handleMouseMove: moving to " + // << newX << "," << newY << endl; + + updateRect |= (*it)->rect(); + (*it)->moveTo(newX, newY); + updateRect |= (*it)->rect(); + setChangeMade(true); + } + + if (changeMade()) + m_canvas->getModel()->signalContentChange(); + + guideX = m_currentItem->rect().x(); + guideY = m_currentItem->rect().y(); + + m_canvas->setGuidesPos(guideX, guideY); + + timeT currentItemStartTime = m_canvas->grid().snapX(m_currentItem->rect().x()); + + RealTime time = comp.getElapsedRealTime(currentItemStartTime); + QString ms; + ms.sprintf("%03d", time.msec()); + + int bar, beat, fraction, remainder; + comp.getMusicalTimeForAbsoluteTime(currentItemStartTime, bar, beat, fraction, remainder); + + QString posString = QString("%1.%2s (%3, %4, %5)") + .arg(time.sec).arg(ms) + .arg(bar + 1).arg(beat).arg(fraction); + + m_canvas->setTextFloat(guideX + 10, guideY - 30, posString); + m_canvas->updateContents(); + + return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical; +} + +void SegmentMover::setBasicContextHelp() +{ + setContextHelp(i18n("Click and drag to move a segment")); +} + +const QString SegmentMover::ToolName = "segmentmover"; + +} +#include "SegmentMover.moc" diff --git a/src/gui/editors/segment/segmentcanvas/SegmentMover.h b/src/gui/editors/segment/segmentcanvas/SegmentMover.h new file mode 100644 index 0000000..776189e --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentMover.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_SEGMENTMOVER_H_ +#define _RG_SEGMENTMOVER_H_ + +#include "SegmentTool.h" +#include <qpoint.h> +#include <qstring.h> + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class CompositionView; + + +class SegmentMover : public SegmentTool +{ + Q_OBJECT + + friend class SegmentToolBox; + +public: + + virtual void ready(); + virtual void stow(); + + virtual void handleMouseButtonPress(QMouseEvent*); + virtual void handleMouseButtonRelease(QMouseEvent*); + virtual int handleMouseMove(QMouseEvent*); + + static const QString ToolName; + +protected slots: + void slotCanvasScrolled(int newX, int newY); + +protected: + SegmentMover(CompositionView*, RosegardenGUIDoc*); + + void setBasicContextHelp(); + + //--------------- Data members --------------------------------- + + QPoint m_clickPoint; + bool m_passedInertiaEdge; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/SegmentOrderer.cpp b/src/gui/editors/segment/segmentcanvas/SegmentOrderer.cpp new file mode 100644 index 0000000..4262eb9 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentOrderer.cpp @@ -0,0 +1,48 @@ +/* -*- 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 "SegmentOrderer.h" + +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/Segment.h" +#include <klocale.h> + + +namespace Rosegarden +{ + +void SegmentOrderer::segmentClicked(const Segment* s) +{ + m_segmentZs[s] = ++m_currentMaxZ; + RG_DEBUG << "SegmentOrderer::segmentClicked() s = " << s << " - max Z = " << m_currentMaxZ << endl; +} + +unsigned int SegmentOrderer::getZForSegment(const Rosegarden::Segment* s) +{ + return m_segmentZs[s]; +} + +} diff --git a/src/gui/editors/segment/segmentcanvas/SegmentOrderer.h b/src/gui/editors/segment/segmentcanvas/SegmentOrderer.h new file mode 100644 index 0000000..f4b3d9a --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentOrderer.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_SEGMENTORDERER_H_ +#define _RG_SEGMENTORDERER_H_ + +#include "base/Composition.h" +#include <map> + + + + +namespace Rosegarden +{ + +class Segment; + + +class SegmentOrderer : public CompositionObserver { +public: + SegmentOrderer() : m_currentMaxZ(0) {}; + + unsigned int getZForSegment(const Segment*); + + void segmentClicked(const Segment *); + +protected: + + //--------------- Data members --------------------------------- + std::map<const Segment*, unsigned int> m_segmentZs; + unsigned int m_currentMaxZ; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/SegmentPencil.cpp b/src/gui/editors/segment/segmentcanvas/SegmentPencil.cpp new file mode 100644 index 0000000..68ca020 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentPencil.cpp @@ -0,0 +1,295 @@ +/* -*- 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 "SegmentPencil.h" + +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "gui/general/ClefIndex.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SnapGrid.h" +#include "base/Track.h" +#include "commands/segment/SegmentInsertCommand.h" +#include "CompositionItemHelper.h" +#include "CompositionView.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/general/BaseTool.h" +#include "gui/general/GUIPalette.h" +#include "gui/general/RosegardenCanvasView.h" +#include "SegmentTool.h" +#include <kcommand.h> +#include <klocale.h> +#include <qcursor.h> +#include <qevent.h> +#include <qpoint.h> +#include <qrect.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +SegmentPencil::SegmentPencil(CompositionView *c, RosegardenGUIDoc *d) + : SegmentTool(c, d), + m_newRect(false), + m_track(0), + m_startTime(0), + m_endTime(0) +{ + RG_DEBUG << "SegmentPencil()\n"; +} + +void SegmentPencil::ready() +{ + m_canvas->viewport()->setCursor(Qt::ibeamCursor); + connect(m_canvas, SIGNAL(contentsMoving (int, int)), + this, SLOT(slotCanvasScrolled(int, int))); + setContextHelpFor(QPoint(0, 0)); +} + +void SegmentPencil::stow() +{ + disconnect(m_canvas, SIGNAL(contentsMoving (int, int)), + this, SLOT(slotCanvasScrolled(int, int))); +} + +void SegmentPencil::slotCanvasScrolled(int newX, int newY) +{ + QMouseEvent tmpEvent(QEvent::MouseMove, + m_canvas->viewport()->mapFromGlobal(QCursor::pos()) + QPoint(newX, newY), + Qt::NoButton, Qt::NoButton); + handleMouseMove(&tmpEvent); +} + +void SegmentPencil::handleMouseButtonPress(QMouseEvent *e) +{ + if (e->button() == RightButton) + return; + + // is user holding Ctrl+Alt? (ugly, but we are running short on available + // modifiers; Alt is grabbed by the window manager, and right clicking, my + // (dmm) original idea, is grabbed by the context menu, so let's see how + // this goes over + bool pencilAnyway = (m_canvas->pencilOverExisting()); + + m_newRect = false; + + // Check if mouse click was on a rect + // + CompositionItem item = m_canvas->getFirstItemAt(e->pos()); + + // If user clicked a rect, and pencilAnyway is false, then there's nothing + // left to do here + if (item) { + delete item; + if (!pencilAnyway) return ; + } + + // make new item + // + m_canvas->setSnapGrain(false); + + int trackPosition = m_canvas->grid().getYBin(e->pos().y()); + + // Don't do anything if the user clicked beyond the track buttons + // + if (trackPosition >= m_doc->getComposition().getNbTracks()) + return ; + + Track *t = m_doc->getComposition().getTrackByPosition(trackPosition); + if (!t) + return ; + + TrackId trackId = t->getId(); + + timeT time = int(nearbyint(m_canvas->grid().snapX(e->pos().x(), SnapGrid::SnapLeft))); + timeT duration = int(nearbyint(m_canvas->grid().getSnapTime(double(e->pos().x())))); + if (duration == 0) + duration = Note(Note::Shortest).getDuration(); + + int multiple = m_doc->getComposition() + .getMaxContemporaneousSegmentsOnTrack(trackId); + if (multiple < 1) multiple = 1; + + QRect tmpRect; + tmpRect.setX(int(nearbyint(m_canvas->grid().getRulerScale()->getXForTime(time)))); + tmpRect.setY(m_canvas->grid().getYBinCoordinate(trackPosition) + 1); + tmpRect.setHeight(m_canvas->grid().getYSnap() * multiple - 2); + tmpRect.setWidth(int(nearbyint(m_canvas->grid().getRulerScale()->getWidthForDuration(time, duration)))); + + m_canvas->setTmpRect(tmpRect, + GUIPalette::convertColour + (m_doc->getComposition().getSegmentColourMap(). + getColourByIndex(t->getColor()))); + + m_newRect = true; + m_origPos = e->pos(); + + m_canvas->updateContents(tmpRect); +} + +void SegmentPencil::handleMouseButtonRelease(QMouseEvent* e) +{ + if (e->button() == RightButton) + return ; + + setContextHelpFor(e->pos()); + + if (m_newRect) { + + QRect tmpRect = m_canvas->getTmpRect(); + + int trackPosition = m_canvas->grid().getYBin(tmpRect.y()); + Track *track = m_doc->getComposition().getTrackByPosition(trackPosition); + timeT startTime = int(nearbyint(m_canvas->grid().getRulerScale()->getTimeForX(tmpRect.x()))), + endTime = int(nearbyint(m_canvas->grid().getRulerScale()->getTimeForX(tmpRect.x() + tmpRect.width()))); + + // RG_DEBUG << "SegmentPencil::handleMouseButtonRelease() : new segment with track id " + // << track->getId() << endl; + + SegmentInsertCommand *command = + new SegmentInsertCommand(m_doc, track->getId(), + startTime, endTime); + + m_newRect = false; + + addCommandToHistory(command); + + // add the SegmentItem by hand, instead of allowing the usual + // update mechanism to spot it. This way we can select the + // segment as we add it; otherwise we'd have no way to know + // that the segment was created by this tool rather than by + // e.g. a simple file load + + Segment *segment = command->getSegment(); + + // add a clef to the start of the segment (tracks initialize to a + // default of 0 for this property, so treble will be the default if it + // is not specified elsewhere) + segment->insert(clefIndexToClef(track->getClef()).getAsEvent + (segment->getStartTime())); + segment->setTranspose(track->getTranspose()); + segment->setColourIndex(track->getColor()); + segment->setLowestPlayable(track->getLowestPlayable()); + segment->setHighestPlayable(track->getHighestPlayable()); + + std::string label = qstrtostr(track->getPresetLabel()); + if (label != "") { + segment->setLabel(qstrtostr(track->getPresetLabel())); + } + + CompositionItem item = CompositionItemHelper::makeCompositionItem(segment); + m_canvas->getModel()->clearSelected(); + m_canvas->getModel()->setSelected(item); + m_canvas->getModel()->signalSelection(); + m_canvas->setTmpRect(QRect()); + m_canvas->slotUpdateSegmentsDrawBuffer(); + + } else { + + m_newRect = false; + } +} + +int SegmentPencil::handleMouseMove(QMouseEvent *e) +{ + if (!m_newRect) { + setContextHelpFor(e->pos()); + return RosegardenCanvasView::NoFollow; + } + + if (!m_canvas->isFineGrain()) { + setContextHelp(i18n("Hold Shift to avoid snapping to bar lines")); + } else { + clearContextHelp(); + } + + QRect tmpRect = m_canvas->getTmpRect(); + QRect oldTmpRect = tmpRect; + + m_canvas->setSnapGrain(false); + + SnapGrid::SnapDirection direction = SnapGrid::SnapRight; + if (e->pos().x() <= m_origPos.x()) + direction = SnapGrid::SnapLeft; + + timeT snap = int(nearbyint(m_canvas->grid().getSnapTime(double(e->pos().x())))); + if (snap == 0) + snap = Note(Note::Shortest).getDuration(); + + timeT time = int(nearbyint(m_canvas->grid().snapX(e->pos().x(), direction))); + + timeT startTime = int(nearbyint(m_canvas->grid().getRulerScale()->getTimeForX(tmpRect.x()))); + timeT endTime = int(nearbyint(m_canvas->grid().getRulerScale()->getTimeForX(tmpRect.x() + tmpRect.width()))); + + if (direction == SnapGrid::SnapRight) { + + if (time >= startTime) { + if ((time - startTime) < snap) { + time = startTime + snap; + } + } else { + if ((startTime - time) < snap) { + time = startTime - snap; + } + } + + int w = int(nearbyint(m_canvas->grid().getRulerScale()->getWidthForDuration(startTime, time - startTime))); + tmpRect.setWidth(w); + + } else { // SnapGrid::SnapLeft + + // time += std::max(endTime - startTime, timeT(0)); + tmpRect.setX(int(m_canvas->grid().getRulerScale()->getXForTime(time))); + + } + + m_canvas->setTmpRect(tmpRect); + return RosegardenCanvasView::FollowHorizontal; +} + +void SegmentPencil::setContextHelpFor(QPoint p) +{ + int trackPosition = m_canvas->grid().getYBin(p.y()); + + if (trackPosition < m_doc->getComposition().getNbTracks()) { + Track *t = m_doc->getComposition().getTrackByPosition(trackPosition); + if (t) { + InstrumentId id = t->getInstrument(); + if (id >= AudioInstrumentBase && id < MidiInstrumentBase) { + setContextHelp(i18n("Record or drop audio here")); + return; + } + } + } + + setContextHelp(i18n("Click and drag to draw an empty segment. Control+Alt click and drag to draw in overlap mode.")); +} + +const QString SegmentPencil::ToolName = "segmentpencil"; + +} +#include "SegmentPencil.moc" diff --git a/src/gui/editors/segment/segmentcanvas/SegmentPencil.h b/src/gui/editors/segment/segmentcanvas/SegmentPencil.h new file mode 100644 index 0000000..8b55917 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentPencil.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_SEGMENTPENCIL_H_ +#define _RG_SEGMENTPENCIL_H_ + +#include "base/Track.h" +#include "SegmentTool.h" +#include <qstring.h> +#include "base/Event.h" + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class CompositionView; + + +////////////////////////////// + +class SegmentPencil : public SegmentTool +{ + Q_OBJECT + + friend class SegmentToolBox; + friend class SegmentSelector; + +public: + + virtual void ready(); + virtual void stow(); + + virtual void handleMouseButtonPress(QMouseEvent*); + virtual void handleMouseButtonRelease(QMouseEvent*); + virtual int handleMouseMove(QMouseEvent*); + + static const QString ToolName; + +protected slots: + void slotCanvasScrolled(int newX, int newY); + +protected: + SegmentPencil(CompositionView*, RosegardenGUIDoc*); + void setContextHelpFor(QPoint p); + + //--------------- Data members --------------------------------- + + bool m_newRect; + TrackId m_track; + timeT m_startTime; + timeT m_endTime; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/SegmentResizer.cpp b/src/gui/editors/segment/segmentcanvas/SegmentResizer.cpp new file mode 100644 index 0000000..6ae7433 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentResizer.cpp @@ -0,0 +1,393 @@ +/* -*- 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 "SegmentResizer.h" + +#include "base/Event.h" +#include <klocale.h> +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Track.h" +#include "base/SnapGrid.h" +#include "commands/segment/AudioSegmentResizeFromStartCommand.h" +#include "commands/segment/AudioSegmentRescaleCommand.h" +#include "commands/segment/SegmentRescaleCommand.h" +#include "commands/segment/SegmentReconfigureCommand.h" +#include "commands/segment/SegmentResizeFromStartCommand.h" +#include "CompositionItemHelper.h" +#include "CompositionModel.h" +#include "CompositionView.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/general/BaseTool.h" +#include "gui/application/RosegardenGUIApp.h" +#include "gui/general/RosegardenCanvasView.h" +#include "gui/widgets/ProgressDialog.h" +#include "SegmentTool.h" +#include <kcommand.h> +#include <kmessagebox.h> +#include <qcursor.h> +#include <qevent.h> +#include <qpoint.h> +#include <qrect.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +SegmentResizer::SegmentResizer(CompositionView *c, RosegardenGUIDoc *d, + int edgeThreshold) + : SegmentTool(c, d), + m_edgeThreshold(edgeThreshold) +{ + RG_DEBUG << "SegmentResizer()\n"; +} + +void SegmentResizer::ready() +{ + m_canvas->viewport()->setCursor(Qt::sizeHorCursor); + connect(m_canvas, SIGNAL(contentsMoving (int, int)), + this, SLOT(slotCanvasScrolled(int, int))); + setBasicContextHelp(false); +} + +void SegmentResizer::stow() +{ + disconnect(m_canvas, SIGNAL(contentsMoving (int, int)), + this, SLOT(slotCanvasScrolled(int, int))); +} + +void SegmentResizer::slotCanvasScrolled(int newX, int newY) +{ + QMouseEvent tmpEvent(QEvent::MouseMove, + m_canvas->viewport()->mapFromGlobal(QCursor::pos()) + QPoint(newX, newY), + Qt::NoButton, Qt::NoButton); + handleMouseMove(&tmpEvent); +} + +void SegmentResizer::handleMouseButtonPress(QMouseEvent *e) +{ + RG_DEBUG << "SegmentResizer::handleMouseButtonPress" << endl; + m_canvas->getModel()->clearSelected(); + + CompositionItem item = m_canvas->getFirstItemAt(e->pos()); + + if (item) { + RG_DEBUG << "SegmentResizer::handleMouseButtonPress - got item" << endl; + setCurrentItem(item); + + // Are we resizing from start or end? + if (item->rect().x() + item->rect().width() / 2 > e->pos().x()) { + m_resizeStart = true; + } else { + m_resizeStart = false; + } + + m_canvas->getModel()->startChange(item, m_resizeStart ? CompositionModel::ChangeResizeFromStart : CompositionModel::ChangeResizeFromEnd); + + } +} + +void SegmentResizer::handleMouseButtonRelease(QMouseEvent *e) +{ + RG_DEBUG << "SegmentResizer::handleMouseButtonRelease" << endl; + + bool rescale = (e->state() & Qt::ControlButton); + + if (m_currentItem) { + + Segment* segment = CompositionItemHelper::getSegment(m_currentItem); + + // We only want to snap the end that we were actually resizing. + + timeT oldStartTime, oldEndTime; + + oldStartTime = segment->getStartTime(); + oldEndTime = segment->getEndMarkerTime(); + + timeT newStartTime, newEndTime; + + if (m_resizeStart) { + newStartTime = CompositionItemHelper::getStartTime + (m_currentItem, m_canvas->grid()); + newEndTime = oldEndTime; + } else { + newEndTime = CompositionItemHelper::getEndTime + (m_currentItem, m_canvas->grid()); + newStartTime = oldStartTime; + } + + if (changeMade()) { + + if (newStartTime > newEndTime) std::swap(newStartTime, newEndTime); + + if (rescale) { + + if (segment->getType() == Segment::Audio) { + + try { + m_doc->getAudioFileManager().testAudioPath(); + } catch (AudioFileManager::BadAudioPathException) { + if (KMessageBox::warningContinueCancel + (0, + i18n("The audio file path does not exist or is not writable.\nYou must set the audio file path to a valid directory in Document Properties before rescaling an audio file.\nWould you like to set it now?"), + i18n("Warning"), + i18n("Set audio file path")) == KMessageBox::Continue) { + RosegardenGUIApp::self()->slotOpenAudioPathSettings(); + } + } + + float ratio = float(newEndTime - newStartTime) / + float(oldEndTime - oldStartTime); + + AudioSegmentRescaleCommand *command = + new AudioSegmentRescaleCommand(m_doc, segment, ratio, + newStartTime, newEndTime); + + ProgressDialog progressDlg + (i18n("Rescaling audio file..."), 100, 0); + progressDlg.setAutoClose(false); + progressDlg.setAutoReset(false); + progressDlg.show(); + command->connectProgressDialog(&progressDlg); + + addCommandToHistory(command); + + progressDlg.setLabel(i18n("Generating audio preview...")); + command->disconnectProgressDialog(&progressDlg); + connect(&m_doc->getAudioFileManager(), SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + connect(&progressDlg, SIGNAL(cancelClicked()), + &m_doc->getAudioFileManager(), SLOT(slotStopPreview())); + + int fid = command->getNewAudioFileId(); + if (fid >= 0) { + RosegardenGUIApp::self()->slotAddAudioFile(fid); + m_doc->getAudioFileManager().generatePreview(fid); + } + + } else { + + SegmentRescaleCommand *command = + new SegmentRescaleCommand(segment, + newEndTime - newStartTime, + oldEndTime - oldStartTime, + newStartTime); + addCommandToHistory(command); + } + } else { + + if (m_resizeStart) { + + if (segment->getType() == Segment::Audio) { + addCommandToHistory(new AudioSegmentResizeFromStartCommand + (segment, newStartTime)); + } else { + addCommandToHistory(new SegmentResizeFromStartCommand + (segment, newStartTime)); + } + + } else { + + SegmentReconfigureCommand *command = + new SegmentReconfigureCommand("Resize Segment"); + + int trackPos = CompositionItemHelper::getTrackPos + (m_currentItem, m_canvas->grid()); + + Composition &comp = m_doc->getComposition(); + Track *track = comp.getTrackByPosition(trackPos); + + command->addSegment(segment, + newStartTime, + newEndTime, + track->getId()); + addCommandToHistory(command); + } + } + } + } + + m_canvas->getModel()->endChange(); + m_canvas->updateContents(); + setChangeMade(false); + m_currentItem = CompositionItem(); + setBasicContextHelp(); +} + +int SegmentResizer::handleMouseMove(QMouseEvent *e) +{ + // RG_DEBUG << "SegmentResizer::handleMouseMove" << endl; + + bool rescale = (e->state() & Qt::ControlButton); + + if (!m_currentItem) { + setBasicContextHelp(rescale); + return RosegardenCanvasView::NoFollow; + } + + if (rescale) { + if (!m_canvas->isFineGrain()) { + setContextHelp(i18n("Hold Shift to avoid snapping to beat grid")); + } else { + clearContextHelp(); + } + } else { + if (!m_canvas->isFineGrain()) { + setContextHelp(i18n("Hold Shift to avoid snapping to beat grid; hold Ctrl as well to rescale contents")); + } else { + setContextHelp("Hold Ctrl to rescale contents"); + } + } + + Segment* segment = CompositionItemHelper::getSegment(m_currentItem); + + // Don't allow Audio segments to resize yet + // + /*!!! + if (segment->getType() == Segment::Audio) + { + m_currentItem = CompositionItem(); + KMessageBox::information(m_canvas, + i18n("You can't yet resize an audio segment!")); + return RosegardenCanvasView::NoFollow; + } + */ + + QRect oldRect = m_currentItem->rect(); + + m_canvas->setSnapGrain(true); + + timeT time = m_canvas->grid().snapX(e->pos().x()); + timeT snap = m_canvas->grid().getSnapTime(double(e->pos().x())); + if (snap == 0) + snap = Note(Note::Shortest).getDuration(); + + // We only want to snap the end that we were actually resizing. + + timeT itemStartTime, itemEndTime; + + if (m_resizeStart) { + itemStartTime = CompositionItemHelper::getStartTime + (m_currentItem, m_canvas->grid()); + itemEndTime = segment->getEndMarkerTime(); + } else { + itemEndTime = CompositionItemHelper::getEndTime + (m_currentItem, m_canvas->grid()); + itemStartTime = segment->getStartTime(); + } + + timeT duration = 0; + + if (m_resizeStart) { + + duration = itemEndTime - time; + // RG_DEBUG << "SegmentResizer::handleMouseMove() resize start : duration = " + // << duration << " - snap = " << snap + // << " - itemEndTime : " << itemEndTime + // << " - time : " << time + // << endl; + + timeT newStartTime = 0; + + if ((duration > 0 && duration < snap) || + (duration < 0 && duration > -snap)) { + + newStartTime = itemEndTime - (duration < 0 ? -snap : snap); + + } else { + + newStartTime = itemEndTime - duration; + + } + + CompositionItemHelper::setStartTime(m_currentItem, + newStartTime, + m_canvas->grid()); + } else { // resize end + + duration = time - itemStartTime; + + timeT newEndTime = 0; + + // RG_DEBUG << "SegmentResizer::handleMouseMove() resize end : duration = " + // << duration << " - snap = " << snap + // << " - itemEndTime : " << itemEndTime + // << " - time : " << time + // << endl; + + if ((duration > 0 && duration < snap) || + (duration < 0 && duration > -snap)) { + + newEndTime = (duration < 0 ? -snap : snap) + itemStartTime; + + } else { + + newEndTime = duration + itemStartTime; + + } + + CompositionItemHelper::setEndTime(m_currentItem, + newEndTime, + m_canvas->grid()); + } + + if (duration != 0) + setChangeMade(true); + + m_canvas->slotUpdateSegmentsDrawBuffer(m_currentItem->rect() | oldRect); + + return RosegardenCanvasView::FollowHorizontal; +} + +bool SegmentResizer::cursorIsCloseEnoughToEdge(const CompositionItem& p, const QPoint &coord, + int edgeThreshold, bool &start) +{ + if (abs(p->rect().x() + p->rect().width() - coord.x()) < edgeThreshold) { + start = false; + return true; + } else if (abs(p->rect().x() - coord.x()) < edgeThreshold) { + start = true; + return true; + } else { + return false; + } +} + +void SegmentResizer::setBasicContextHelp(bool ctrlPressed) +{ + if (ctrlPressed) { + setContextHelp(i18n("Click and drag to resize a segment; hold Ctrl as well to rescale its contents")); + } else { + setContextHelp(i18n("Click and drag to rescale segment")); + } +} + +const QString SegmentResizer::ToolName = "segmentresizer"; + +} +#include "SegmentResizer.moc" diff --git a/src/gui/editors/segment/segmentcanvas/SegmentResizer.h b/src/gui/editors/segment/segmentcanvas/SegmentResizer.h new file mode 100644 index 0000000..9d54573 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentResizer.h @@ -0,0 +1,87 @@ + +/* -*- 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_SEGMENTRESIZER_H_ +#define _RG_SEGMENTRESIZER_H_ + +#include "SegmentTool.h" +#include <qstring.h> + + +class QPoint; +class QMouseEvent; +class CompositionItem; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class CompositionView; + + +/** + * Segment Resizer tool. Allows resizing only at the end of the segment part + */ +class SegmentResizer : public SegmentTool +{ + Q_OBJECT + + friend class SegmentToolBox; + friend class SegmentSelector; + +public: + + virtual void ready(); + virtual void stow(); + + virtual void handleMouseButtonPress(QMouseEvent*); + virtual void handleMouseButtonRelease(QMouseEvent*); + virtual int handleMouseMove(QMouseEvent*); + + static bool cursorIsCloseEnoughToEdge(const CompositionItem&, const QPoint&, int, bool &); + + void setEdgeThreshold(int e) { m_edgeThreshold = e; } + int getEdgeThreshold() { return m_edgeThreshold; } + + static const QString ToolName; + +protected slots: + void slotCanvasScrolled(int newX, int newY); + +protected: + SegmentResizer(CompositionView*, RosegardenGUIDoc*, int edgeThreshold = 10); + void setBasicContextHelp(bool ctrlPressed = false); + + //--------------- Data members --------------------------------- + + int m_edgeThreshold; + bool m_resizeStart; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/SegmentSelector.cpp b/src/gui/editors/segment/segmentcanvas/SegmentSelector.cpp new file mode 100644 index 0000000..35ec639 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentSelector.cpp @@ -0,0 +1,532 @@ +/* -*- 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 "SegmentSelector.h" + +#include "base/Event.h" +#include <klocale.h> +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/RealTime.h" +#include "base/SnapGrid.h" +#include "base/Selection.h" +#include "base/Track.h" +#include "commands/segment/SegmentQuickCopyCommand.h" +#include "commands/segment/SegmentReconfigureCommand.h" +#include "CompositionItemHelper.h" +#include "CompositionModel.h" +#include "CompositionView.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "gui/general/BaseTool.h" +#include "gui/general/RosegardenCanvasView.h" +#include "SegmentPencil.h" +#include "SegmentResizer.h" +#include "SegmentTool.h" +#include "SegmentToolBox.h" +#include <kapplication.h> +#include <kconfig.h> +#include <qcursor.h> +#include <qevent.h> +#include <qpoint.h> +#include <qrect.h> +#include <qstring.h> + + +namespace Rosegarden +{ + +SegmentSelector::SegmentSelector(CompositionView *c, RosegardenGUIDoc *d) + : SegmentTool(c, d), + m_segmentAddMode(false), + m_segmentCopyMode(false), + m_segmentQuickCopyDone(false), + m_buttonPressed(false), + m_selectionMoveStarted(false), + m_dispatchTool(0) +{ + RG_DEBUG << "SegmentSelector()\n"; +} + +SegmentSelector::~SegmentSelector() +{} + +void SegmentSelector::ready() +{ + m_canvas->viewport()->setCursor(Qt::arrowCursor); + connect(m_canvas, SIGNAL(contentsMoving (int, int)), + this, SLOT(slotCanvasScrolled(int, int))); + setContextHelp(i18n("Click and drag to select segments")); +} + +void SegmentSelector::stow() +{} + +void SegmentSelector::slotCanvasScrolled(int newX, int newY) +{ + QMouseEvent tmpEvent(QEvent::MouseMove, + m_canvas->viewport()->mapFromGlobal(QCursor::pos()) + QPoint(newX, newY), + Qt::NoButton, Qt::NoButton); + handleMouseMove(&tmpEvent); +} + +void +SegmentSelector::handleMouseButtonPress(QMouseEvent *e) +{ + RG_DEBUG << "SegmentSelector::handleMouseButtonPress\n"; + m_buttonPressed = true; + + CompositionItem item = m_canvas->getFirstItemAt(e->pos()); + + // If we're in segmentAddMode or not clicking on an item then we don't + // clear the selection vector. If we're clicking on an item and it's + // not in the selection - then also clear the selection. + // + if ((!m_segmentAddMode && !item) || + (!m_segmentAddMode && !(m_canvas->getModel()->isSelected(item)))) { + m_canvas->getModel()->clearSelected(); + } + + if (item) { + + // Fifteen percent of the width of the SegmentItem, up to 10px + // + int threshold = int(float(item->rect().width()) * 0.15); + if (threshold == 0) threshold = 1; + if (threshold > 10) threshold = 10; + + bool start = false; + + // Resize if we're dragging from the edge, provided we aren't + // in segment-add mode with at least one segment already + // selected -- as we aren't able to resize multiple segments + // at once, we should assume the segment-add aspect takes + // priority + + if ((!m_segmentAddMode || + !m_canvas->getModel()->haveSelection()) && + SegmentResizer::cursorIsCloseEnoughToEdge(item, e->pos(), threshold, start)) { + + SegmentResizer* resizer = + dynamic_cast<SegmentResizer*>(getToolBox()->getTool(SegmentResizer::ToolName)); + + resizer->setEdgeThreshold(threshold); + + // For the moment we only allow resizing of a single segment + // at a time. + // + m_canvas->getModel()->clearSelected(); + + m_dispatchTool = resizer; + + m_dispatchTool->ready(); // set mouse cursor + m_dispatchTool->handleMouseButtonPress(e); + return ; + } + + bool selecting = true; + + if (m_segmentAddMode && m_canvas->getModel()->isSelected(item)) { + selecting = false; + } else { + // put the segment in 'move' mode only if it's being selected + m_canvas->getModel()->startChange(item, CompositionModel::ChangeMove); + } + + m_canvas->getModel()->setSelected(item, selecting); + + // Moving + // + // RG_DEBUG << "SegmentSelector::handleMouseButtonPress - m_currentItem = " << item << endl; + m_currentItem = item; + m_clickPoint = e->pos(); + + int guideX = item->rect().x(); + int guideY = item->rect().y(); + + m_canvas->setGuidesPos(guideX, guideY); + + m_canvas->setDrawGuides(true); + + } else { + + // Add on middle button or ctrl+left - bounding box on rest + // + if (e->button() == MidButton || + (e->button() == LeftButton && (e->state() & Qt::ControlButton))) { + + m_dispatchTool = getToolBox()->getTool(SegmentPencil::ToolName); + + if (m_dispatchTool) { + m_dispatchTool->ready(); // set mouse cursor + m_dispatchTool->handleMouseButtonPress(e); + } + + return ; + + } else { + + m_canvas->setSelectionRectPos(e->pos()); + m_canvas->setDrawSelectionRect(true); + if (!m_segmentAddMode) + m_canvas->getModel()->clearSelected(); + + } + } + + // Tell the RosegardenGUIView that we've selected some new Segments - + // when the list is empty we're just unselecting. + // + m_canvas->getModel()->signalSelection(); + + m_passedInertiaEdge = false; +} + +void +SegmentSelector::handleMouseButtonRelease(QMouseEvent *e) +{ + m_buttonPressed = false; + + // Hide guides and stuff + // + m_canvas->setDrawGuides(false); + m_canvas->hideTextFloat(); + + if (m_dispatchTool) { + m_dispatchTool->handleMouseButtonRelease(e); + m_dispatchTool = 0; + m_canvas->viewport()->setCursor(Qt::arrowCursor); + return ; + } + + int startDragTrackPos = m_canvas->grid().getYBin(m_clickPoint.y()); + int currentTrackPos = m_canvas->grid().getYBin(e->pos().y()); + int trackDiff = currentTrackPos - startDragTrackPos; + + if (!m_currentItem) { + m_canvas->setDrawSelectionRect(false); + m_canvas->getModel()->finalizeSelectionRect(); + m_canvas->getModel()->signalSelection(); + return ; + } + + m_canvas->viewport()->setCursor(Qt::arrowCursor); + + Composition &comp = m_doc->getComposition(); + + if (m_canvas->getModel()->isSelected(m_currentItem)) { + + CompositionModel::itemcontainer& changingItems = m_canvas->getModel()->getChangingItems(); + CompositionModel::itemcontainer::iterator it; + + if (changeMade()) { + + SegmentReconfigureCommand *command = + new SegmentReconfigureCommand + (m_selectedItems.size() == 1 ? i18n("Move Segment") : + i18n("Move Segments")); + + for (it = changingItems.begin(); + it != changingItems.end(); + it++) { + + CompositionItem item = *it; + + Segment* segment = CompositionItemHelper::getSegment(item); + + TrackId origTrackId = segment->getTrack(); + int trackPos = comp.getTrackPositionById(origTrackId); + trackPos += trackDiff; + + if (trackPos < 0) { + trackPos = 0; + } else if (trackPos >= comp.getNbTracks()) { + trackPos = comp.getNbTracks() - 1; + } + + Track *newTrack = comp.getTrackByPosition(trackPos); + int newTrackId = origTrackId; + if (newTrack) newTrackId = newTrack->getId(); + + timeT itemStartTime = CompositionItemHelper::getStartTime + (item, m_canvas->grid()); + + // We absolutely don't want to snap the end time to + // the grid. We want it to remain exactly the same as + // it was, but relative to the new start time. + timeT itemEndTime = itemStartTime + segment->getEndMarkerTime() + - segment->getStartTime(); + +// std::cerr << "releasing segment " << segment << ": mouse started at track " << startDragTrackPos << ", is now at " << currentTrackPos << ", diff is " << trackDiff << ", moving from track pos " << comp.getTrackPositionById(origTrackId) << " to " << trackPos << ", id " << origTrackId << " to " << newTrackId << std::endl; + + command->addSegment(segment, + itemStartTime, + itemEndTime, + newTrackId); + } + + addCommandToHistory(command); + } + + m_canvas->getModel()->endChange(); + m_canvas->slotUpdateSegmentsDrawBuffer(); + } + + // if we've just finished a quick copy then drop the Z level back + if (m_segmentQuickCopyDone) { + m_segmentQuickCopyDone = false; + // m_currentItem->setZ(2); // see SegmentItem::setSelected --?? + } + + setChangeMade(false); + + m_selectionMoveStarted = false; + + m_currentItem = CompositionItem(); + + setContextHelpFor(e->pos()); +} + +int +SegmentSelector::handleMouseMove(QMouseEvent *e) +{ + if (!m_buttonPressed) { + setContextHelpFor(e->pos(), (e->state() & Qt::ControlButton)); + return RosegardenCanvasView::NoFollow; + } + + if (m_dispatchTool) { + return m_dispatchTool->handleMouseMove(e); + } + + Composition &comp = m_doc->getComposition(); + + if (!m_currentItem) { + + // RG_DEBUG << "SegmentSelector::handleMouseMove: no current item\n"; + + // do a bounding box + QRect selectionRect = m_canvas->getSelectionRect(); + + m_canvas->setDrawSelectionRect(true); + + // same as for notation view + int w = int(e->pos().x() - selectionRect.x()); + int h = int(e->pos().y() - selectionRect.y()); + if (w > 0) + ++w; + else + --w; + if (h > 0) + ++h; + else + --h; + + // Translate these points + // + m_canvas->setSelectionRectSize(w, h); + + m_canvas->getModel()->signalSelection(); + return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical; + } + + m_canvas->viewport()->setCursor(Qt::sizeAllCursor); + + if (m_segmentCopyMode && !m_segmentQuickCopyDone) { + KMacroCommand *mcommand = new KMacroCommand + (SegmentQuickCopyCommand::getGlobalName()); + + SegmentSelection selectedItems = m_canvas->getSelectedSegments(); + SegmentSelection::iterator it; + for (it = selectedItems.begin(); + it != selectedItems.end(); + it++) { + SegmentQuickCopyCommand *command = + new SegmentQuickCopyCommand(*it); + + mcommand->addCommand(command); + } + + addCommandToHistory(mcommand); + + // generate SegmentItem + // + m_canvas->updateContents(); + m_segmentQuickCopyDone = true; + } + + m_canvas->setSnapGrain(true); + + int startDragTrackPos = m_canvas->grid().getYBin(m_clickPoint.y()); + int currentTrackPos = m_canvas->grid().getYBin(e->pos().y()); + int trackDiff = currentTrackPos - startDragTrackPos; + + if (m_canvas->getModel()->isSelected(m_currentItem)) { + + if (!m_canvas->isFineGrain()) { + setContextHelp(i18n("Hold Shift to avoid snapping to beat grid")); + } else { + clearContextHelp(); + } + + // RG_DEBUG << "SegmentSelector::handleMouseMove: current item is selected\n"; + + if (!m_selectionMoveStarted) { // start move on selected items only once + m_canvas->getModel()->startChangeSelection(CompositionModel::ChangeMove); + m_selectionMoveStarted = true; + } + + CompositionModel::itemcontainer& changingItems = m_canvas->getModel()->getChangingItems(); + setCurrentItem(CompositionItemHelper::findSiblingCompositionItem(changingItems, m_currentItem)); + + CompositionModel::itemcontainer::iterator it; + int guideX = 0; + int guideY = 0; + + for (it = changingItems.begin(); + it != changingItems.end(); + ++it) { + + // RG_DEBUG << "SegmentSelector::handleMouseMove() : movingItem at " + // << (*it)->rect().x() << "," << (*it)->rect().y() << endl; + + int dx = e->pos().x() - m_clickPoint.x(), + dy = e->pos().y() - m_clickPoint.y(); + + const int inertiaDistance = m_canvas->grid().getYSnap() / 3; + if (!m_passedInertiaEdge && + (dx < inertiaDistance && dx > -inertiaDistance) && + (dy < inertiaDistance && dy > -inertiaDistance)) { + return RosegardenCanvasView::NoFollow; + } else { + m_passedInertiaEdge = true; + } + + timeT newStartTime = m_canvas->grid().snapX((*it)->savedRect().x() + dx); + + int newX = int(m_canvas->grid().getRulerScale()->getXForTime(newStartTime)); + + int trackPos = m_canvas->grid().getYBin((*it)->savedRect().y()); + +// std::cerr << "segment " << *it << ": mouse started at track " << startDragTrackPos << ", is now at " << currentTrackPos << ", trackPos from " << trackPos << " to "; + + trackPos += trackDiff; + +// std::cerr << trackPos << std::endl; + + if (trackPos < 0) { + trackPos = 0; + } else if (trackPos >= comp.getNbTracks()) { + trackPos = comp.getNbTracks() - 1; + } + + int newY = m_canvas->grid().getYBinCoordinate(trackPos); + + (*it)->moveTo(newX, newY); + setChangeMade(true); + } + + if (changeMade()) + m_canvas->getModel()->signalContentChange(); + + guideX = m_currentItem->rect().x(); + guideY = m_currentItem->rect().y(); + + m_canvas->setGuidesPos(guideX, guideY); + + timeT currentItemStartTime = m_canvas->grid().snapX(m_currentItem->rect().x()); + + RealTime time = comp.getElapsedRealTime(currentItemStartTime); + QString ms; + ms.sprintf("%03d", time.msec()); + + int bar, beat, fraction, remainder; + comp.getMusicalTimeForAbsoluteTime(currentItemStartTime, bar, beat, fraction, remainder); + + QString posString = QString("%1.%2s (%3, %4, %5)") + .arg(time.sec).arg(ms) + .arg(bar + 1).arg(beat).arg(fraction); + + m_canvas->setTextFloat(guideX + 10, guideY - 30, posString); + m_canvas->updateContents(); + + } else { + // RG_DEBUG << "SegmentSelector::handleMouseMove: current item not selected\n"; + } + + return RosegardenCanvasView::FollowHorizontal | RosegardenCanvasView::FollowVertical; +} + +void SegmentSelector::setContextHelpFor(QPoint p, bool ctrlPressed) +{ + kapp->config()->setGroup(GeneralOptionsConfigGroup); + if (!kapp->config()->readBoolEntry("toolcontexthelp", true)) return; + + CompositionItem item = m_canvas->getFirstItemAt(p); + + if (!item) { + setContextHelp(i18n("Click and drag to select segments; middle-click and drag to draw an empty segment")); + + } else { + + // Same logic as in handleMouseButtonPress to establish + // whether we'd be moving or resizing + + int threshold = int(float(item->rect().width()) * 0.15); + if (threshold == 0) threshold = 1; + if (threshold > 10) threshold = 10; + bool start = false; + + if ((!m_segmentAddMode || + !m_canvas->getModel()->haveSelection()) && + SegmentResizer::cursorIsCloseEnoughToEdge(item, p, + threshold, start)) { + if (!ctrlPressed) { + setContextHelp(i18n("Click and drag to resize a segment; hold Ctrl as well to rescale its contents")); + } else { + setContextHelp(i18n("Click and drag to rescale segment")); + } + } else { + if (m_canvas->getModel()->haveMultipleSelection()) { + if (!ctrlPressed) { + setContextHelp(i18n("Click and drag to move segments; hold Ctrl as well to copy them")); + } else { + setContextHelp(i18n("Click and drag to copy segments")); + } + } else { + if (!ctrlPressed) { + setContextHelp(i18n("Click and drag to move segment; hold Ctrl as well to copy it; double-click to edit")); + } else { + setContextHelp(i18n("Click and drag to copy segment")); + } + } + } + } +} + +const QString SegmentSelector::ToolName = "segmentselector"; + +} +#include "SegmentSelector.moc" diff --git a/src/gui/editors/segment/segmentcanvas/SegmentSelector.h b/src/gui/editors/segment/segmentcanvas/SegmentSelector.h new file mode 100644 index 0000000..a6d8d9c --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentSelector.h @@ -0,0 +1,109 @@ + +/* -*- 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_SEGMENTSELECTOR_H_ +#define _RG_SEGMENTSELECTOR_H_ + +#include "SegmentTool.h" +#include <qpoint.h> +#include <qstring.h> + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class CompositionView; + + +class SegmentSelector : public SegmentTool +{ + Q_OBJECT + + friend class SegmentToolBox; + friend class SegmentTool; + +public: + + virtual ~SegmentSelector(); + + virtual void ready(); + virtual void stow(); + + virtual void handleMouseButtonPress(QMouseEvent*); + virtual void handleMouseButtonRelease(QMouseEvent*); + virtual int handleMouseMove(QMouseEvent*); + + // These two alter the behaviour of the selection mode + // + // - SegmentAdd (usually when SHIFT is held down) allows + // multiple selections of Segments. + // + // - SegmentCopy (usually CONTROL) allows draw and drop + // copying of Segments - it's a quick shortcut + // + void setSegmentAdd(const bool &value) { m_segmentAddMode = value; } + void setSegmentCopy(const bool &value) { m_segmentCopyMode = value; } + + bool isSegmentAdding() const { return m_segmentAddMode; } + bool isSegmentCopying() const { return m_segmentCopyMode; } + + // Return the SegmentItem list for other tools to use + // + SegmentItemList* getSegmentItemList() { return &m_selectedItems; } + + static const QString ToolName; + +protected slots: + void slotCanvasScrolled(int newX, int newY); + +protected: + SegmentSelector(CompositionView*, RosegardenGUIDoc*); + + void setContextHelpFor(QPoint p, bool ctrlPressed = false); + + //--------------- Data members --------------------------------- + + SegmentItemList m_selectedItems; + + bool m_segmentAddMode; + bool m_segmentCopyMode; + QPoint m_clickPoint; + bool m_segmentQuickCopyDone; + bool m_passedInertiaEdge; + bool m_buttonPressed; + bool m_selectionMoveStarted; + + SegmentTool *m_dispatchTool; +}; + + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/SegmentSplitter.cpp b/src/gui/editors/segment/segmentcanvas/SegmentSplitter.cpp new file mode 100644 index 0000000..4fd48c3 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentSplitter.cpp @@ -0,0 +1,175 @@ +/* -*- 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 "SegmentSplitter.h" + +#include "misc/Debug.h" +#include "base/Segment.h" +#include "base/SnapGrid.h" +#include "commands/segment/AudioSegmentSplitCommand.h" +#include "commands/segment/SegmentSplitCommand.h" +#include "CompositionItemHelper.h" +#include "CompositionView.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/general/BaseTool.h" +#include "gui/general/RosegardenCanvasView.h" +#include "SegmentTool.h" +#include <kcommand.h> +#include <qpoint.h> +#include <qrect.h> +#include <qstring.h> +#include <klocale.h> + + +namespace Rosegarden +{ + +SegmentSplitter::SegmentSplitter(CompositionView *c, RosegardenGUIDoc *d) + : SegmentTool(c, d), + m_prevX(0), + m_prevY(0) +{ + RG_DEBUG << "SegmentSplitter()\n"; +} + +SegmentSplitter::~SegmentSplitter() +{} + +void SegmentSplitter::ready() +{ + m_canvas->viewport()->setCursor(Qt::splitHCursor); + setBasicContextHelp(); +} + +void +SegmentSplitter::handleMouseButtonPress(QMouseEvent *e) +{ + // Remove cursor and replace with line on a SegmentItem + // at where the cut will be made + CompositionItem item = m_canvas->getFirstItemAt(e->pos()); + + if (item) { + m_canvas->viewport()->setCursor(Qt::blankCursor); + m_prevX = item->rect().x(); + m_prevX = item->rect().y(); + drawSplitLine(e); + delete item; + } + +} + +void +SegmentSplitter::handleMouseButtonRelease(QMouseEvent *e) +{ + setBasicContextHelp(); + + CompositionItem item = m_canvas->getFirstItemAt(e->pos()); + + if (item) { + m_canvas->setSnapGrain(true); + Segment* segment = CompositionItemHelper::getSegment(item); + + if (segment->getType() == Segment::Audio) { + AudioSegmentSplitCommand *command = + new AudioSegmentSplitCommand(segment, m_canvas->grid().snapX(e->pos().x())); + addCommandToHistory(command); + } else { + SegmentSplitCommand *command = + new SegmentSplitCommand(segment, m_canvas->grid().snapX(e->pos().x())); + addCommandToHistory(command); + } + + m_canvas->updateContents(item->rect()); + delete item; + } + + // Reinstate the cursor + m_canvas->viewport()->setCursor(Qt::splitHCursor); + m_canvas->slotHideSplitLine(); +} + +int +SegmentSplitter::handleMouseMove(QMouseEvent *e) +{ + setBasicContextHelp(); + + CompositionItem item = m_canvas->getFirstItemAt(e->pos()); + + if (item) { +// m_canvas->viewport()->setCursor(Qt::blankCursor); + drawSplitLine(e); + delete item; + return RosegardenCanvasView::FollowHorizontal; + } else { + m_canvas->viewport()->setCursor(Qt::splitHCursor); + m_canvas->slotHideSplitLine(); + return RosegardenCanvasView::NoFollow; + } +} + +void +SegmentSplitter::drawSplitLine(QMouseEvent *e) +{ + m_canvas->setSnapGrain(true); + + // Turn the real X into a snapped X + // + timeT xT = m_canvas->grid().snapX(e->pos().x()); + int x = (int)(m_canvas->grid().getRulerScale()->getXForTime(xT)); + + // Need to watch y doesn't leak over the edges of the + // current Segment. + // + int y = m_canvas->grid().snapY(e->pos().y()); + + m_canvas->slotShowSplitLine(x, y); + + QRect updateRect(std::max(0, std::min(x, m_prevX) - 5), y, + std::max(m_prevX, x) + 5, m_prevY + m_canvas->grid().getYSnap()); + m_canvas->updateContents(updateRect); + m_prevX = x; + m_prevY = y; +} + +void +SegmentSplitter::contentsMouseDoubleClickEvent(QMouseEvent*) +{ + // DO NOTHING +} + +void +SegmentSplitter::setBasicContextHelp() +{ + if (!m_canvas->isFineGrain()) { + setContextHelp(i18n("Click on a segment to split it in two; hold Shift to avoid snapping to beat grid")); + } else { + setContextHelp(i18n("Click on a segment to split it in two")); + } +} + +const QString SegmentSplitter::ToolName = "segmentsplitter"; + +} +#include "SegmentSplitter.moc" diff --git a/src/gui/editors/segment/segmentcanvas/SegmentSplitter.h b/src/gui/editors/segment/segmentcanvas/SegmentSplitter.h new file mode 100644 index 0000000..06201ec --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentSplitter.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_SEGMENTSPLITTER_H_ +#define _RG_SEGMENTSPLITTER_H_ + +#include "SegmentTool.h" +#include <qstring.h> +#include "base/Event.h" + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class Segment; +class RosegardenGUIDoc; +class CompositionView; + + +class SegmentSplitter : public SegmentTool +{ + Q_OBJECT + + friend class SegmentToolBox; + +public: + + virtual ~SegmentSplitter(); + + virtual void ready(); + + virtual void handleMouseButtonPress(QMouseEvent*); + virtual void handleMouseButtonRelease(QMouseEvent*); + virtual int handleMouseMove(QMouseEvent*); + + // don't do double clicks + virtual void contentsMouseDoubleClickEvent(QMouseEvent*); + + static const QString ToolName; + +protected: + SegmentSplitter(CompositionView*, RosegardenGUIDoc*); + + void setBasicContextHelp(); + + void drawSplitLine(QMouseEvent*); + void splitSegment(Segment *segment, + timeT &splitTime); + + //--------------- Data members --------------------------------- + int m_prevX; + int m_prevY; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/SegmentTool.cpp b/src/gui/editors/segment/segmentcanvas/SegmentTool.cpp new file mode 100644 index 0000000..9bd7e69 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentTool.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 "SegmentTool.h" + +#include "misc/Debug.h" +#include "CompositionView.h" +#include "document/RosegardenGUIDoc.h" +#include "document/MultiViewCommandHistory.h" +#include "gui/application/RosegardenGUIApp.h" +#include "gui/general/BaseTool.h" +#include "SegmentToolBox.h" +#include <kcommand.h> +#include <kmainwindow.h> +#include <qpoint.h> +#include <qpopupmenu.h> + + +namespace Rosegarden +{ + +SegmentTool::SegmentTool(CompositionView* canvas, RosegardenGUIDoc *doc) + : BaseTool("segment_tool_menu", dynamic_cast<KMainWindow*>(doc->parent())->factory(), canvas), + m_canvas(canvas), + m_doc(doc), + m_changeMade(false) +{} + +SegmentTool::~SegmentTool() +{} + + +void SegmentTool::ready() +{ + m_canvas->viewport()->setCursor(Qt::arrowCursor); +} + +void +SegmentTool::handleRightButtonPress(QMouseEvent *e) +{ + if (m_currentItem) // mouse button is pressed for some tool + return ; + + RG_DEBUG << "SegmentTool::handleRightButtonPress()\n"; + + setCurrentItem(m_canvas->getFirstItemAt(e->pos())); + + if (m_currentItem) { + if (!m_canvas->getModel()->isSelected(m_currentItem)) { + + m_canvas->getModel()->clearSelected(); + m_canvas->getModel()->setSelected(m_currentItem); + m_canvas->getModel()->signalSelection(); + } + } + + showMenu(); + setCurrentItem(CompositionItem()); +} + +void +SegmentTool::createMenu() +{ + RG_DEBUG << "SegmentTool::createMenu()\n"; + + RosegardenGUIApp *app = + dynamic_cast<RosegardenGUIApp*>(m_doc->parent()); + + if (app) { + m_menu = static_cast<QPopupMenu*> + //(app->factory()->container("segment_tool_menu", app)); + (m_parentFactory->container("segment_tool_menu", app)); + + if (!m_menu) { + RG_DEBUG << "SegmentTool::createMenu() failed\n"; + } + } else { + RG_DEBUG << "SegmentTool::createMenu() failed: !app\n"; + } +} + +void +SegmentTool::addCommandToHistory(KCommand *command) +{ + m_doc->getCommandHistory()->addCommand(command); +} + +SegmentToolBox* SegmentTool::getToolBox() +{ + return m_canvas->getToolBox(); +} + +} diff --git a/src/gui/editors/segment/segmentcanvas/SegmentTool.h b/src/gui/editors/segment/segmentcanvas/SegmentTool.h new file mode 100644 index 0000000..90ed961 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentTool.h @@ -0,0 +1,105 @@ + +/* -*- 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_SEGMENTTOOL_H_ +#define _RG_SEGMENTTOOL_H_ + +#include "gui/general/BaseTool.h" +#include "CompositionItem.h" +#include <qpoint.h> +#include <utility> +#include <vector> + + +class QMouseEvent; +class KCommand; + + +namespace Rosegarden +{ + +class SegmentToolBox; +class RosegardenGUIDoc; +class CompositionView; + + +////////////////////////////////////////////////////////////////////// + +class SegmentToolBox; +class SegmentSelector; + +// Allow the tools to share the Selector tool's selection +// through these. +// +typedef std::pair<QPoint, CompositionItem> SegmentItemPair; +typedef std::vector<SegmentItemPair> SegmentItemList; + +class SegmentTool : public BaseTool +{ +public: + friend class SegmentToolBox; + + virtual ~SegmentTool(); + + /** + * Is called by the parent View (EditView or SegmentCanvas) when + * the tool is set as current. + * Add any setup here + */ + virtual void ready(); + + virtual void handleRightButtonPress(QMouseEvent*); + virtual void handleMouseButtonPress(QMouseEvent*) = 0; + virtual void handleMouseButtonRelease(QMouseEvent*) = 0; + virtual int handleMouseMove(QMouseEvent*) = 0; + + void addCommandToHistory(KCommand *command); + +protected: + SegmentTool(CompositionView*, RosegardenGUIDoc *doc); + + virtual void createMenu(); + virtual bool hasMenu() { return true; } + + void setCurrentItem(CompositionItem item) { if (item != m_currentItem) { delete m_currentItem; m_currentItem = item; } } + + SegmentToolBox* getToolBox(); + + bool changeMade() { return m_changeMade; } + void setChangeMade(bool c) { m_changeMade = c; } + + //--------------- Data members --------------------------------- + + CompositionView* m_canvas; + CompositionItem m_currentItem; + RosegardenGUIDoc* m_doc; + QPoint m_origPos; + bool m_changeMade; +}; + + +} + +#endif diff --git a/src/gui/editors/segment/segmentcanvas/SegmentToolBox.cpp b/src/gui/editors/segment/segmentcanvas/SegmentToolBox.cpp new file mode 100644 index 0000000..cb0dec9 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentToolBox.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 "SegmentToolBox.h" + +#include "CompositionView.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/general/BaseToolBox.h" +#include "SegmentTool.h" +#include "SegmentSelector.h" +#include "SegmentEraser.h" +#include "SegmentJoiner.h" +#include "SegmentMover.h" +#include "SegmentPencil.h" +#include "SegmentResizer.h" +#include "SegmentSplitter.h" +#include <qstring.h> +#include <kmessagebox.h> + +namespace Rosegarden +{ + +SegmentToolBox::SegmentToolBox(CompositionView* parent, RosegardenGUIDoc* doc) + : BaseToolBox(parent), + m_canvas(parent), + m_doc(doc) +{} + +SegmentTool* SegmentToolBox::createTool(const QString& toolName) +{ + SegmentTool* tool = 0; + + QString toolNamelc = toolName.lower(); + + if (toolNamelc == SegmentPencil::ToolName) + + tool = new SegmentPencil(m_canvas, m_doc); + + else if (toolNamelc == SegmentEraser::ToolName) + + tool = new SegmentEraser(m_canvas, m_doc); + + else if (toolNamelc == SegmentMover::ToolName) + + tool = new SegmentMover(m_canvas, m_doc); + + else if (toolNamelc == SegmentResizer::ToolName) + + tool = new SegmentResizer(m_canvas, m_doc); + + else if (toolNamelc == SegmentSelector::ToolName) + + tool = new SegmentSelector(m_canvas, m_doc); + + else if (toolNamelc == SegmentSplitter::ToolName) + + tool = new SegmentSplitter(m_canvas, m_doc); + + else if (toolNamelc == SegmentJoiner::ToolName) + + tool = new SegmentJoiner(m_canvas, m_doc); + + else { + KMessageBox::error(0, QString("SegmentToolBox::createTool : unrecognised toolname %1 (%2)") + .arg(toolName).arg(toolNamelc)); + return 0; + } + + m_tools.insert(toolName, tool); + + return tool; +} + +SegmentTool* SegmentToolBox::getTool(const QString& toolName) +{ + return dynamic_cast<SegmentTool*>(BaseToolBox::getTool(toolName)); +} + +} +#include "SegmentToolBox.moc" diff --git a/src/gui/editors/segment/segmentcanvas/SegmentToolBox.h b/src/gui/editors/segment/segmentcanvas/SegmentToolBox.h new file mode 100644 index 0000000..7a46fb6 --- /dev/null +++ b/src/gui/editors/segment/segmentcanvas/SegmentToolBox.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_SEGMENTTOOLBOX_H_ +#define _RG_SEGMENTTOOLBOX_H_ + +#include "gui/general/BaseToolBox.h" +#include "SegmentTool.h" + + +class QString; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class CompositionView; + + +class SegmentToolBox : public BaseToolBox +{ + Q_OBJECT +public: + SegmentToolBox(CompositionView* parent, RosegardenGUIDoc*); + + virtual SegmentTool* getTool(const QString& toolName); + +protected: + virtual SegmentTool* createTool(const QString& toolName); + + //--------------- Data members --------------------------------- + + CompositionView* m_canvas; + RosegardenGUIDoc* m_doc; +}; + + +} + +#endif diff --git a/src/gui/editors/tempo/TempoListItem.cpp b/src/gui/editors/tempo/TempoListItem.cpp new file mode 100644 index 0000000..d088b5b --- /dev/null +++ b/src/gui/editors/tempo/TempoListItem.cpp @@ -0,0 +1,52 @@ +/* -*- 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 "TempoListItem.h" + +namespace Rosegarden { + +int +TempoListItem::compare(QListViewItem *i, int col, bool ascending) const +{ + TempoListItem *ti = dynamic_cast<TempoListItem *>(i); + if (!ti) return QListViewItem::compare(i, col, ascending); + + if (col == 0) { // time + if (m_time == ti->m_time) { + return int(m_type) - int(ti->m_type); + } else { + return int(m_time - ti->m_time); + } + } else if (col == 1) { // type + if (m_type == ti->m_type) { + return int(m_time - ti->m_time); + } else { + return int(m_type) - int(ti->m_type); + } + } else { + return key(col, ascending).compare(i->key(col, ascending)); + } +} + +} diff --git a/src/gui/editors/tempo/TempoListItem.h b/src/gui/editors/tempo/TempoListItem.h new file mode 100644 index 0000000..143edde --- /dev/null +++ b/src/gui/editors/tempo/TempoListItem.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_TEMPOLISTITEM_H_ +#define _RG_TEMPOLISTITEM_H_ + +#include <klistview.h> + +#include "base/Event.h" + +namespace Rosegarden { + +class Composition; + +class TempoListItem : public KListViewItem +{ +public: + enum Type { TimeSignature, Tempo }; + + TempoListItem(Composition *composition, + Type type, + timeT time, + int index, + KListView *parent, + QString label1, + QString label2, + QString label3, + QString label4 = QString::null) : + KListViewItem(parent, label1, label2, label3, label4), + m_composition(composition), + m_type(type), + m_time(time), + m_index(index) { } + + Rosegarden::Composition *getComposition() { return m_composition; } + Type getType() const { return m_type; } + Rosegarden::timeT getTime() const { return m_time; } + int getIndex() const { return m_index; } + + virtual int compare(QListViewItem *i, int col, bool ascending) const; + +protected: + Rosegarden::Composition *m_composition; + Type m_type; + Rosegarden::timeT m_time; + int m_index; +}; + +} + +#endif diff --git a/src/gui/editors/tempo/TempoView.cpp b/src/gui/editors/tempo/TempoView.cpp new file mode 100644 index 0000000..6ff6c1d --- /dev/null +++ b/src/gui/editors/tempo/TempoView.cpp @@ -0,0 +1,839 @@ +/* -*- 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 "TempoView.h" + +#include <klocale.h> +#include <kstddirs.h> +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "commands/segment/AddTempoChangeCommand.h" +#include "commands/segment/AddTimeSignatureAndNormalizeCommand.h" +#include "commands/segment/AddTimeSignatureCommand.h" +#include "commands/segment/RemoveTempoChangeCommand.h" +#include "commands/segment/RemoveTimeSignatureCommand.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "gui/dialogs/TempoDialog.h" +#include "gui/dialogs/TimeSignatureDialog.h" +#include "gui/general/EditViewBase.h" +#include "gui/kdeext/KTmpStatusMsg.h" +#include "TempoListItem.h" +#include <kaction.h> +#include <kglobal.h> +#include <kconfig.h> +#include <klistview.h> +#include <kxmlguiclient.h> +#include <qbuttongroup.h> +#include <qcheckbox.h> +#include <qdialog.h> +#include <qiconset.h> +#include <qlistview.h> +#include <qpixmap.h> +#include <qptrlist.h> +#include <qsize.h> +#include <qstring.h> +#include <qlayout.h> +#include <qcanvas.h> +#include <kstatusbar.h> + + +namespace Rosegarden +{ + +int +TempoView::m_lastSetFilter = -1; + + +TempoView::TempoView(RosegardenGUIDoc *doc, QWidget *parent, timeT openTime): + EditViewBase(doc, std::vector<Segment *>(), 2, parent, "tempoview"), + m_filter(Tempo | TimeSignature), + m_ignoreUpdates(true) +{ + if (m_lastSetFilter < 0) + m_lastSetFilter = m_filter; + else + m_filter = m_lastSetFilter; + + initStatusBar(); + setupActions(); + + // define some note filtering buttons in a group + // + m_filterGroup = + new QButtonGroup(1, Horizontal, i18n("Filter"), getCentralWidget()); + + m_tempoCheckBox = new QCheckBox(i18n("Tempo"), m_filterGroup); + m_timeSigCheckBox = new QCheckBox(i18n("Time Signature"), m_filterGroup); + m_grid->addWidget(m_filterGroup, 2, 0); + + // Connect up + // + connect(m_filterGroup, SIGNAL(released(int)), + SLOT(slotModifyFilter(int))); + + m_list = new KListView(getCentralWidget()); + m_list->setItemsRenameable(true); + + m_grid->addWidget(m_list, 2, 1); + + updateViewCaption(); + + doc->getComposition().addObserver(this); + + // Connect double clicker + // + connect(m_list, SIGNAL(doubleClicked(QListViewItem*)), + SLOT(slotPopupEditor(QListViewItem*))); + + m_list->setAllColumnsShowFocus(true); + m_list->setSelectionMode(QListView::Extended); + + m_list->addColumn(i18n("Time ")); + m_list->addColumn(i18n("Type ")); + m_list->addColumn(i18n("Value ")); + m_list->addColumn(i18n("Properties ")); + + for (int col = 0; col < m_list->columns(); ++col) + m_list->setRenameable(col, true); + + readOptions(); + setButtonsToFilter(); + applyLayout(); + + makeInitialSelection(openTime); + + m_ignoreUpdates = false; + setOutOfCtor(); +} + +TempoView::~TempoView() +{ + if (!getDocument()->isBeingDestroyed() && !isCompositionDeleted()) { + getDocument()->getComposition().removeObserver(this); + } +} + +void +TempoView::closeEvent(QCloseEvent *e) +{ + emit closing(); + EditViewBase::closeEvent(e); +} + +void +TempoView::tempoChanged(const Composition *comp) +{ + if (m_ignoreUpdates) + return ; + if (comp == &getDocument()->getComposition()) { + applyLayout(); + } +} + +void +TempoView::timeSignatureChanged(const Composition *comp) +{ + if (m_ignoreUpdates) + return ; + if (comp == &getDocument()->getComposition()) { + applyLayout(); + } +} + +bool +TempoView::applyLayout(int /*staffNo*/) +{ + // If no selection has already been set then we copy what's + // already set and try to replicate this after the rebuild + // of the view. This code borrowed from EventView. + // + if (m_listSelection.size() == 0) { + QPtrList<QListViewItem> selection = m_list->selectedItems(); + + if (selection.count()) { + QPtrListIterator<QListViewItem> it(selection); + QListViewItem *listItem; + + while ((listItem = it.current()) != 0) { + m_listSelection.push_back(m_list->itemIndex(*it)); + ++it; + } + } + } + + // Ok, recreate list + // + m_list->clear(); + + Composition *comp = &getDocument()->getComposition(); + + m_config->setGroup(TempoViewConfigGroup); + int timeMode = m_config->readNumEntry("timemode", 0); + + if (m_filter & TimeSignature) { + for (int i = 0; i < comp->getTimeSignatureCount(); ++i) { + + std::pair<timeT, Rosegarden::TimeSignature> sig = + comp->getTimeSignatureChange(i); + + QString properties; + if (sig.second.isHidden()) { + if (sig.second.isCommon()) + properties = i18n("Common, hidden"); + else + properties = i18n("Hidden"); + } else { + if (sig.second.isCommon()) + properties = i18n("Common"); + } + + QString timeString = makeTimeString(sig.first, timeMode); + + new TempoListItem(comp, TempoListItem::TimeSignature, + sig.first, i, m_list, timeString, + i18n("Time Signature "), + QString("%1/%2 ").arg(sig.second.getNumerator()). + arg(sig.second.getDenominator()), + properties); + } + } + + if (m_filter & Tempo) { + for (int i = 0; i < comp->getTempoChangeCount(); ++i) { + + std::pair<timeT, tempoT> tempo = + comp->getTempoChange(i); + + QString desc; + + //!!! imprecise -- better to work from tempoT directly + + float qpm = comp->getTempoQpm(tempo.second); + int qpmUnits = int(qpm + 0.001); + int qpmTenths = int((qpm - qpmUnits) * 10 + 0.001); + int qpmHundredths = int((qpm - qpmUnits - qpmTenths / 10.0) * 100 + 0.001); + + Rosegarden::TimeSignature sig = comp->getTimeSignatureAt(tempo.first); + if (sig.getBeatDuration() == + Note(Note::Crotchet).getDuration()) { + desc = i18n("%1.%2%3"). + arg(qpmUnits).arg(qpmTenths).arg(qpmHundredths); + } else { + float bpm = (qpm * + Note(Note::Crotchet).getDuration()) / + sig.getBeatDuration(); + int bpmUnits = int(bpm + 0.001); + int bpmTenths = int((bpm - bpmUnits) * 10 + 0.001); + int bpmHundredths = int((bpm - bpmUnits - bpmTenths / 10.0) * 100 + 0.001); + + desc = i18n("%1.%2%3 qpm (%4.%5%6 bpm) "). + arg(qpmUnits).arg(qpmTenths).arg(qpmHundredths). + arg(bpmUnits).arg(bpmTenths).arg(bpmHundredths); + } + + QString timeString = makeTimeString(tempo.first, timeMode); + + new TempoListItem(comp, TempoListItem::Tempo, + tempo.first, i, m_list, timeString, + i18n("Tempo "), + desc); + } + } + + if (m_list->childCount() == 0) { + new QListViewItem(m_list, + i18n("<nothing at this filter level>")); + m_list->setSelectionMode(QListView::NoSelection); + stateChanged("have_selection", KXMLGUIClient::StateReverse); + } else { + m_list->setSelectionMode(QListView::Extended); + + // If no selection then select the first event + if (m_listSelection.size() == 0) + m_listSelection.push_back(0); + stateChanged("have_selection", KXMLGUIClient::StateNoReverse); + } + + // Set a selection from a range of indexes + // + std::vector<int>::iterator sIt = m_listSelection.begin(); + int index = 0; + + for (; sIt != m_listSelection.end(); ++sIt) { + index = *sIt; + + while (index > 0 && !m_list->itemAtIndex(index)) + index--; + + m_list->setSelected(m_list->itemAtIndex(index), true); + m_list->setCurrentItem(m_list->itemAtIndex(index)); + + // ensure visible + m_list->ensureItemVisible(m_list->itemAtIndex(index)); + } + + m_listSelection.clear(); + + return true; +} + +void +TempoView::makeInitialSelection(timeT time) +{ + m_listSelection.clear(); + + TempoListItem *goodItem = 0; + int goodItemNo = 0; + + for (int i = 0; m_list->itemAtIndex(i); ++i) { + + TempoListItem *item = dynamic_cast<TempoListItem *> + (m_list->itemAtIndex(i)); + + m_list->setSelected(item, false); + + if (item) { + if (item->getTime() > time) + break; + goodItem = item; + goodItemNo = i; + } + } + + if (goodItem) { + m_listSelection.push_back(goodItemNo); + m_list->setSelected(goodItem, true); + m_list->ensureItemVisible(goodItem); + } +} + +Segment * +TempoView::getCurrentSegment() +{ + if (m_segments.empty()) + return 0; + else + return *m_segments.begin(); +} + +QString +TempoView::makeTimeString(timeT time, int timeMode) +{ + switch (timeMode) { + + case 0: // musical time + { + int bar, beat, fraction, remainder; + getDocument()->getComposition().getMusicalTimeForAbsoluteTime + (time, bar, beat, fraction, remainder); + ++bar; + return QString("%1%2%3-%4%5-%6%7-%8%9 ") + .arg(bar / 100) + .arg((bar % 100) / 10) + .arg(bar % 10) + .arg(beat / 10) + .arg(beat % 10) + .arg(fraction / 10) + .arg(fraction % 10) + .arg(remainder / 10) + .arg(remainder % 10); + } + + case 1: // real time + { + RealTime rt = + getDocument()->getComposition().getElapsedRealTime(time); + // return QString("%1 ").arg(rt.toString().c_str()); + return QString("%1 ").arg(rt.toText().c_str()); + } + + default: + return QString("%1 ").arg(time); + } +} + +void +TempoView::refreshSegment(Segment * /*segment*/, + timeT /*startTime*/, + timeT /*endTime*/) +{ + RG_DEBUG << "TempoView::refreshSegment" << endl; + applyLayout(0); +} + +void +TempoView::updateView() +{ + m_list->update(); +} + +void +TempoView::slotEditCut() +{ + // not implemented yet -- can't use traditional clipboard (which + // only holds events from segments, or segments) +} + +void +TempoView::slotEditCopy() +{ + // likewise +} + +void +TempoView::slotEditPaste() +{ + // likewise +} + +void +TempoView::slotEditDelete() +{ + QPtrList<QListViewItem> selection = m_list->selectedItems(); + if (selection.count() == 0) + return ; + + RG_DEBUG << "TempoView::slotEditDelete - deleting " + << selection.count() << " items" << endl; + + QPtrListIterator<QListViewItem> it(selection); + QListViewItem *listItem; + TempoListItem *item; + int itemIndex = -1; + + m_ignoreUpdates = true; + bool haveSomething = false; + + // We want the Remove commands to be in reverse order, because + // removing one item by index will affect the indices of + // subsequent items. So we'll stack them onto here and then pull + // them off again. + std::vector<KCommand *> commands; + + while ((listItem = it.current()) != 0) { + item = dynamic_cast<TempoListItem*>((*it)); + + if (itemIndex == -1) + itemIndex = m_list->itemIndex(*it); + + if (item) { + if (item->getType() == TempoListItem::TimeSignature) { + commands.push_back(new RemoveTimeSignatureCommand + (item->getComposition(), + item->getIndex())); + haveSomething = true; + } else { + commands.push_back(new RemoveTempoChangeCommand + (item->getComposition(), + item->getIndex())); + haveSomething = true; + } + } + ++it; + } + + if (haveSomething) { + KMacroCommand *command = new KMacroCommand + (i18n("Delete Tempo or Time Signature")); + for (std::vector<KCommand *>::iterator i = commands.end(); + i != commands.begin();) { + command->addCommand(*--i); + } + addCommandToHistory(command); + } + + applyLayout(); + m_ignoreUpdates = false; +} + +void +TempoView::slotEditInsertTempo() +{ + timeT insertTime = 0; + QPtrList<QListViewItem> selection = m_list->selectedItems(); + + if (selection.count() > 0) { + TempoListItem *item = + dynamic_cast<TempoListItem*>(selection.getFirst()); + if (item) + insertTime = item->getTime(); + } + + TempoDialog dialog(this, getDocument(), true); + dialog.setTempoPosition(insertTime); + + connect(&dialog, + SIGNAL(changeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction)), + this, + SIGNAL(changeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction))); + + dialog.exec(); +} + +void +TempoView::slotEditInsertTimeSignature() +{ + timeT insertTime = 0; + QPtrList<QListViewItem> selection = m_list->selectedItems(); + + if (selection.count() > 0) { + TempoListItem *item = + dynamic_cast<TempoListItem*>(selection.getFirst()); + if (item) + insertTime = item->getTime(); + } + + Composition &composition(m_doc->getComposition()); + Rosegarden::TimeSignature sig = composition.getTimeSignatureAt(insertTime); + + TimeSignatureDialog dialog(this, &composition, insertTime, sig, true); + + if (dialog.exec() == QDialog::Accepted) { + + insertTime = dialog.getTime(); + + if (dialog.shouldNormalizeRests()) { + addCommandToHistory + (new AddTimeSignatureAndNormalizeCommand + (&composition, insertTime, dialog.getTimeSignature())); + } else { + addCommandToHistory + (new AddTimeSignatureCommand + (&composition, insertTime, dialog.getTimeSignature())); + } + } +} + +void +TempoView::slotEdit() +{ + RG_DEBUG << "TempoView::slotEdit" << endl; + + QPtrList<QListViewItem> selection = m_list->selectedItems(); + + if (selection.count() > 0) { + TempoListItem *item = + dynamic_cast<TempoListItem*>(selection.getFirst()); + if (item) + slotPopupEditor(item); + } +} + +void +TempoView::slotSelectAll() +{ + m_listSelection.clear(); + for (int i = 0; m_list->itemAtIndex(i); ++i) { + m_listSelection.push_back(i); + m_list->setSelected(m_list->itemAtIndex(i), true); + } +} + +void +TempoView::slotClearSelection() +{ + m_listSelection.clear(); + for (int i = 0; m_list->itemAtIndex(i); ++i) { + m_list->setSelected(m_list->itemAtIndex(i), false); + } +} + +void +TempoView::setupActions() +{ + EditViewBase::setupActions("tempoview.rc", false); + + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QIconSet icon(QPixmap(pixmapDir + "/toolbar/event-insert-tempo.png")); + + new KAction(AddTempoChangeCommand::getGlobalName(), icon, Key_I, this, + SLOT(slotEditInsertTempo()), actionCollection(), + "insert_tempo"); + + QCanvasPixmap pixmap(pixmapDir + "/toolbar/event-insert-timesig.png"); + icon = QIconSet(pixmap); + + new KAction(AddTimeSignatureCommand::getGlobalName(), icon, Key_G, this, + SLOT(slotEditInsertTimeSignature()), actionCollection(), + "insert_timesig"); + + pixmap.load(pixmapDir + "/toolbar/event-delete.png"); + icon = QIconSet(pixmap); + + new KAction(i18n("&Delete"), icon, Key_Delete, this, + SLOT(slotEditDelete()), actionCollection(), + "delete"); + + pixmap.load(pixmapDir + "/toolbar/event-edit.png"); + icon = QIconSet(pixmap); + + new KAction(i18n("&Edit Item"), icon, Key_E, this, + SLOT(slotEdit()), actionCollection(), + "edit"); + + new KAction(i18n("Select &All"), 0, this, + SLOT(slotSelectAll()), actionCollection(), + "select_all"); + + new KAction(i18n("Clear Selection"), Key_Escape, this, + SLOT(slotClearSelection()), actionCollection(), + "clear_selection"); + + m_config->setGroup(TempoViewConfigGroup); + int timeMode = m_config->readNumEntry("timemode", 0); + + KRadioAction *action; + + pixmap.load(pixmapDir + "/toolbar/time-musical.png"); + icon = QIconSet(pixmap); + + action = new KRadioAction(i18n("&Musical Times"), icon, 0, this, + SLOT(slotMusicalTime()), + actionCollection(), "time_musical"); + action->setExclusiveGroup("timeMode"); + if (timeMode == 0) + action->setChecked(true); + + pixmap.load(pixmapDir + "/toolbar/time-real.png"); + icon = QIconSet(pixmap); + + action = new KRadioAction(i18n("&Real Times"), icon, 0, this, + SLOT(slotRealTime()), + actionCollection(), "time_real"); + action->setExclusiveGroup("timeMode"); + if (timeMode == 1) + action->setChecked(true); + + pixmap.load(pixmapDir + "/toolbar/time-raw.png"); + icon = QIconSet(pixmap); + + action = new KRadioAction(i18n("Ra&w Times"), icon, 0, this, + SLOT(slotRawTime()), + actionCollection(), "time_raw"); + action->setExclusiveGroup("timeMode"); + if (timeMode == 2) + action->setChecked(true); + + createGUI(getRCFileName()); +} + +void +TempoView::initStatusBar() +{ + KStatusBar* sb = statusBar(); + + sb->insertItem(KTmpStatusMsg::getDefaultMsg(), + KTmpStatusMsg::getDefaultId(), 1); + sb->setItemAlignment(KTmpStatusMsg::getDefaultId(), + AlignLeft | AlignVCenter); +} + +QSize +TempoView::getViewSize() +{ + return m_list->size(); +} + +void +TempoView::setViewSize(QSize s) +{ + m_list->setFixedSize(s); +} + +void +TempoView::readOptions() +{ + m_config->setGroup(TempoViewConfigGroup); + EditViewBase::readOptions(); + m_filter = m_config->readNumEntry("filter", m_filter); + m_list->restoreLayout(m_config, TempoViewLayoutConfigGroupName); +} + +void +TempoView::slotSaveOptions() +{ + m_config->setGroup(TempoViewConfigGroup); + m_config->writeEntry("filter", m_filter); + m_list->saveLayout(m_config, TempoViewLayoutConfigGroupName); +} + +void +TempoView::slotModifyFilter(int button) +{ + QCheckBox *checkBox = dynamic_cast<QCheckBox*>(m_filterGroup->find(button)); + + if (checkBox == 0) + return ; + + if (checkBox->isChecked()) { + switch (button) { + case 0: + m_filter |= Tempo; + break; + + case 1: + m_filter |= TimeSignature; + break; + + default: + break; + } + + } else { + switch (button) { + case 0: + m_filter ^= Tempo; + break; + + case 1: + m_filter ^= TimeSignature; + break; + + default: + break; + } + } + + m_lastSetFilter = m_filter; + + applyLayout(0); +} + +void +TempoView::setButtonsToFilter() +{ + if (m_filter & Tempo) + m_tempoCheckBox->setChecked(true); + else + m_tempoCheckBox->setChecked(false); + + if (m_filter & TimeSignature) + m_timeSigCheckBox->setChecked(true); + else + m_timeSigCheckBox->setChecked(false); +} + +void +TempoView::slotMusicalTime() +{ + m_config->setGroup(TempoViewConfigGroup); + m_config->writeEntry("timemode", 0); + applyLayout(); +} + +void +TempoView::slotRealTime() +{ + m_config->setGroup(TempoViewConfigGroup); + m_config->writeEntry("timemode", 1); + applyLayout(); +} + +void +TempoView::slotRawTime() +{ + m_config->setGroup(TempoViewConfigGroup); + m_config->writeEntry("timemode", 2); + applyLayout(); +} + +void +TempoView::slotPopupEditor(QListViewItem *qitem) +{ + TempoListItem *item = dynamic_cast<TempoListItem *>(qitem); + if (!item) + return ; + + timeT time = item->getTime(); + + switch (item->getType()) { + + case TempoListItem::Tempo: + { + TempoDialog dialog(this, getDocument(), true); + dialog.setTempoPosition(time); + + connect(&dialog, + SIGNAL(changeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction)), + this, + SIGNAL(changeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction))); + + dialog.exec(); + break; + } + + case TempoListItem::TimeSignature: + { + Composition &composition(getDocument()->getComposition()); + Rosegarden::TimeSignature sig = composition.getTimeSignatureAt(time); + + TimeSignatureDialog dialog(this, &composition, time, sig, true); + + if (dialog.exec() == QDialog::Accepted) { + + time = dialog.getTime(); + + if (dialog.shouldNormalizeRests()) { + addCommandToHistory + (new AddTimeSignatureAndNormalizeCommand + (&composition, time, dialog.getTimeSignature())); + } else { + addCommandToHistory + (new AddTimeSignatureCommand + (&composition, time, dialog.getTimeSignature())); + } + } + } + + default: + break; + } +} + +void +TempoView::updateViewCaption() +{ + setCaption(i18n("%1 - Tempo and Time Signature Editor") + .arg(getDocument()->getTitle())); +} + +} +#include "TempoView.moc" diff --git a/src/gui/editors/tempo/TempoView.h b/src/gui/editors/tempo/TempoView.h new file mode 100644 index 0000000..9a78e1b --- /dev/null +++ b/src/gui/editors/tempo/TempoView.h @@ -0,0 +1,172 @@ + +/* -*- 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_TEMPOVIEW_H_ +#define _RG_TEMPOVIEW_H_ + +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "gui/dialogs/TempoDialog.h" +#include "gui/general/EditViewBase.h" +#include <qsize.h> +#include <qstring.h> +#include <vector> +#include "base/Event.h" + + +class QWidget; +class QListViewItem; +class QCloseEvent; +class QCheckBox; +class QButtonGroup; +class KListView; + + +namespace Rosegarden +{ + +class Segment; +class RosegardenGUIDoc; +class Composition; + + +/** + * Tempo and time signature list-style editor. This has some code + * in common with EventView, but not enough to make them any more + * shareable than simply through EditViewBase. Hopefully this one + * should prove considerably simpler, anyway. + */ + +class TempoView : public EditViewBase, public CompositionObserver +{ + Q_OBJECT + + enum Filter + { + None = 0x0000, + Tempo = 0x0001, + TimeSignature = 0x0002 + }; + +public: + TempoView(RosegardenGUIDoc *doc, QWidget *parent, timeT); + virtual ~TempoView(); + + virtual bool applyLayout(int staffNo = -1); + + virtual void refreshSegment(Segment *segment, + timeT startTime = 0, + timeT endTime = 0); + + virtual void updateView(); + + virtual void setupActions(); + virtual void initStatusBar(); + virtual QSize getViewSize(); + virtual void setViewSize(QSize); + + // Set the button states to the current filter positions + // + void setButtonsToFilter(); + + // Menu creation and show + // + void createMenu(); + + // Composition Observer callbacks + // + virtual void timeSignatureChanged(const Composition *); + virtual void tempoChanged(const Composition *); + +signals: + // forwarded from tempo dialog: + void changeTempo(timeT, // tempo change time + tempoT, // tempo value + tempoT, // tempo target + TempoDialog::TempoDialogAction); // tempo action + + void closing(); + +public slots: + // standard slots + virtual void slotEditCut(); + virtual void slotEditCopy(); + virtual void slotEditPaste(); + + // other edit slots + void slotEditDelete(); + void slotEditInsertTempo(); + void slotEditInsertTimeSignature(); + void slotEdit(); + + void slotSelectAll(); + void slotClearSelection(); + + void slotMusicalTime(); + void slotRealTime(); + void slotRawTime(); + + // on double click on the event list + // + void slotPopupEditor(QListViewItem*); + + // Change filter parameters + // + void slotModifyFilter(int); + +protected slots: + + virtual void slotSaveOptions(); + +protected: + + virtual void readOptions(); + void makeInitialSelection(timeT); + QString makeTimeString(timeT time, int timeMode); + virtual Segment *getCurrentSegment(); + virtual void updateViewCaption(); + + virtual void closeEvent(QCloseEvent *); + + //--------------- Data members --------------------------------- + KListView *m_list; + int m_filter; + + static int m_lastSetFilter; + + QButtonGroup *m_filterGroup; + QCheckBox *m_tempoCheckBox; + QCheckBox *m_timeSigCheckBox; + + std::vector<int> m_listSelection; + + bool m_ignoreUpdates; +}; + + + +} + +#endif |