diff options
Diffstat (limited to 'src/gui/editors/matrix/MatrixView.cpp')
-rw-r--r-- | src/gui/editors/matrix/MatrixView.cpp | 3076 |
1 files changed, 3076 insertions, 0 deletions
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 <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <richard.bown@ferventsoftware.com> + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "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" |