From 145364a8af6a1fec06556221e66d4b724a62fc9a Mon Sep 17 00:00:00 2001 From: tpearson Date: Mon, 1 Mar 2010 18:37:05 +0000 Subject: Added old abandoned KDE3 version of the RoseGarden MIDI tool git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/rosegarden@1097595 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- src/BaseFileList.txt | 102 + src/CMakeLists.txt | 208 + src/GUIFileList.txt | 975 +++ src/MiscFileList.txt | 6 + src/SequencerFileList.txt | 17 + src/SoundFileList.txt | 98 + src/TestFileList.txt | 6 + src/base/AnalysisTypes.cpp | 1118 +++ src/base/AnalysisTypes.h | 227 + src/base/AudioDevice.cpp | 107 + src/base/AudioDevice.h | 70 + src/base/AudioLevel.cpp | 272 + src/base/AudioLevel.h | 67 + src/base/AudioPluginInstance.cpp | 256 + src/base/AudioPluginInstance.h | 172 + src/base/BaseProperties.cpp | 133 + src/base/BaseProperties.h | 82 + src/base/BasicQuantizer.cpp | 253 + src/base/BasicQuantizer.h | 95 + src/base/Clipboard.cpp | 387 + src/base/Clipboard.h | 203 + src/base/Colour.cpp | 175 + src/base/Colour.h | 125 + src/base/ColourMap.cpp | 266 + src/base/ColourMap.h | 138 + src/base/Composition.cpp | 2225 ++++++ src/base/Composition.h | 1134 +++ src/base/CompositionTimeSliceAdapter.cpp | 283 + src/base/CompositionTimeSliceAdapter.h | 149 + src/base/Configuration.cpp | 232 + src/base/Configuration.h | 211 + src/base/ControlParameter.cpp | 144 + src/base/ControlParameter.h | 124 + src/base/Controllable.h | 48 + src/base/Device.cpp | 31 + src/base/Device.h | 102 + src/base/Equation.cpp | 69 + src/base/Equation.h | 51 + src/base/Event.cpp | 445 ++ src/base/Event.h | 584 ++ src/base/Exception.cpp | 46 + src/base/Exception.h | 47 + src/base/FastVector.h | 596 ++ src/base/Instrument.cpp | 645 ++ src/base/Instrument.h | 349 + src/base/LayoutEngine.cpp | 63 + src/base/LayoutEngine.h | 161 + src/base/LegatoQuantizer.cpp | 141 + src/base/LegatoQuantizer.h | 64 + src/base/Marker.cpp | 55 + src/base/Marker.h | 78 + src/base/MidiDevice.cpp | 839 ++ src/base/MidiDevice.h | 213 + src/base/MidiProgram.cpp | 224 + src/base/MidiProgram.h | 180 + src/base/MidiTypes.cpp | 320 + src/base/MidiTypes.h | 224 + src/base/NotationQuantizer.cpp | 1205 +++ src/base/NotationQuantizer.h | 93 + src/base/NotationRules.h | 133 + src/base/NotationTypes.cpp | 2436 ++++++ src/base/NotationTypes.h | 1342 ++++ src/base/Profiler.cpp | 187 + src/base/Profiler.h | 84 + src/base/Property.cpp | 169 + src/base/Property.h | 225 + src/base/PropertyMap.cpp | 101 + src/base/PropertyMap.h | 50 + src/base/PropertyName.cpp | 86 + src/base/PropertyName.h | 158 + src/base/Quantizer.cpp | 496 ++ src/base/Quantizer.h | 249 + src/base/RealTime.cpp | 236 + src/base/RealTime.h | 124 + src/base/RefreshStatus.h | 76 + src/base/RulerScale.cpp | 243 + src/base/RulerScale.h | 166 + src/base/ScriptAPI.cpp | 85 + src/base/ScriptAPI.h | 128 + src/base/Segment.cpp | 1294 ++++ src/base/Segment.h | 783 ++ src/base/SegmentMatrixHelper.cpp | 56 + src/base/SegmentMatrixHelper.h | 53 + src/base/SegmentNotationHelper.cpp | 2129 ++++++ src/base/SegmentNotationHelper.h | 591 ++ src/base/SegmentPerformanceHelper.cpp | 472 ++ src/base/SegmentPerformanceHelper.h | 126 + src/base/Selection.cpp | 318 + src/base/Selection.h | 263 + src/base/Sets.cpp | 108 + src/base/Sets.h | 698 ++ src/base/SnapGrid.cpp | 192 + src/base/SnapGrid.h | 183 + src/base/SoftSynthDevice.cpp | 174 + src/base/SoftSynthDevice.h | 70 + src/base/Staff.cpp | 213 + src/base/Staff.h | 149 + src/base/StaffExportTypes.h | 75 + src/base/Studio.cpp | 674 ++ src/base/Studio.h | 208 + src/base/Track.cpp | 201 + src/base/Track.h | 162 + src/base/TriggerSegment.cpp | 130 + src/base/TriggerSegment.h | 100 + src/base/ViewElement.cpp | 172 + src/base/ViewElement.h | 164 + src/base/XmlExportable.cpp | 197 + src/base/XmlExportable.h | 55 + src/base/test/Makefile | 57 + src/base/test/accidentals.cpp | 60 + src/base/test/colour.cpp | 222 + src/base/test/colour.output | 76 + src/base/test/pitch.cpp | 474 ++ src/base/test/seq/Makefile | 6 + src/base/test/seq/complainer.c | 74 + src/base/test/seq/generator.c | 96 + src/base/test/seq/queue-timer-jack.c | 166 + src/base/test/seq/queue-timer.c | 123 + src/base/test/test.cpp | 535 ++ src/base/test/thread.cpp | 126 + src/base/test/transpose.cpp | 83 + src/base/test/utf8.cpp | 96 + src/commands/edit/AddDotCommand.cpp | 98 + src/commands/edit/AddDotCommand.h | 68 + src/commands/edit/AddMarkerCommand.cpp | 67 + src/commands/edit/AddMarkerCommand.h | 71 + src/commands/edit/ChangeVelocityCommand.cpp | 68 + src/commands/edit/ChangeVelocityCommand.h | 68 + src/commands/edit/ClearTriggersCommand.cpp | 53 + src/commands/edit/ClearTriggersCommand.h | 66 + src/commands/edit/CollapseNotesCommand.cpp | 79 + src/commands/edit/CollapseNotesCommand.h | 65 + src/commands/edit/CopyCommand.cpp | 120 + src/commands/edit/CopyCommand.h | 82 + src/commands/edit/CutAndCloseCommand.cpp | 163 + src/commands/edit/CutAndCloseCommand.h | 82 + src/commands/edit/CutCommand.cpp | 59 + src/commands/edit/CutCommand.h | 62 + src/commands/edit/EraseCommand.cpp | 86 + src/commands/edit/EraseCommand.h | 66 + src/commands/edit/EventEditCommand.cpp | 64 + src/commands/edit/EventEditCommand.h | 69 + src/commands/edit/EventInsertionCommand.cpp | 58 + src/commands/edit/EventInsertionCommand.h | 62 + src/commands/edit/EventQuantizeCommand.cpp | 273 + src/commands/edit/EventQuantizeCommand.h | 98 + src/commands/edit/EventUnquantizeCommand.cpp | 106 + src/commands/edit/EventUnquantizeCommand.h | 73 + src/commands/edit/InsertTriggerNoteCommand.cpp | 132 + src/commands/edit/InsertTriggerNoteCommand.h | 78 + src/commands/edit/InvertCommand.cpp | 85 + src/commands/edit/InvertCommand.h | 67 + src/commands/edit/ModifyMarkerCommand.cpp | 95 + src/commands/edit/ModifyMarkerCommand.h | 78 + src/commands/edit/MoveAcrossSegmentsCommand.cpp | 76 + src/commands/edit/MoveAcrossSegmentsCommand.h | 63 + src/commands/edit/MoveCommand.cpp | 159 + src/commands/edit/MoveCommand.h | 69 + src/commands/edit/PasteEventsCommand.cpp | 321 + src/commands/edit/PasteEventsCommand.h | 112 + src/commands/edit/PasteSegmentsCommand.cpp | 153 + src/commands/edit/PasteSegmentsCommand.h | 79 + src/commands/edit/RemoveMarkerCommand.cpp | 83 + src/commands/edit/RemoveMarkerCommand.h | 75 + src/commands/edit/RescaleCommand.cpp | 138 + src/commands/edit/RescaleCommand.h | 71 + src/commands/edit/RetrogradeCommand.cpp | 121 + src/commands/edit/RetrogradeCommand.h | 67 + src/commands/edit/RetrogradeInvertCommand.cpp | 163 + src/commands/edit/RetrogradeInvertCommand.h | 67 + src/commands/edit/SelectionPropertyCommand.cpp | 128 + src/commands/edit/SelectionPropertyCommand.h | 82 + src/commands/edit/SetLyricsCommand.cpp | 192 + src/commands/edit/SetLyricsCommand.h | 66 + src/commands/edit/SetNoteTypeCommand.cpp | 87 + src/commands/edit/SetNoteTypeCommand.h | 72 + src/commands/edit/SetTriggerCommand.cpp | 74 + src/commands/edit/SetTriggerCommand.h | 83 + src/commands/edit/TransposeCommand.cpp | 83 + src/commands/edit/TransposeCommand.h | 83 + src/commands/matrix/MatrixEraseCommand.cpp | 70 + src/commands/matrix/MatrixEraseCommand.h | 62 + src/commands/matrix/MatrixInsertionCommand.cpp | 74 + src/commands/matrix/MatrixInsertionCommand.h | 64 + src/commands/matrix/MatrixModifyCommand.cpp | 81 + src/commands/matrix/MatrixModifyCommand.h | 63 + .../matrix/MatrixPercussionInsertionCommand.cpp | 192 + .../matrix/MatrixPercussionInsertionCommand.h | 73 + src/commands/notation/AddFingeringMarkCommand.cpp | 119 + src/commands/notation/AddFingeringMarkCommand.h | 64 + src/commands/notation/AddIndicationCommand.cpp | 171 + src/commands/notation/AddIndicationCommand.h | 76 + src/commands/notation/AddMarkCommand.cpp | 112 + src/commands/notation/AddMarkCommand.h | 63 + src/commands/notation/AddSlashesCommand.cpp | 53 + src/commands/notation/AddSlashesCommand.h | 60 + src/commands/notation/AddTextMarkCommand.cpp | 58 + src/commands/notation/AddTextMarkCommand.h | 65 + src/commands/notation/AutoBeamCommand.cpp | 48 + src/commands/notation/AutoBeamCommand.h | 62 + src/commands/notation/BeamCommand.cpp | 58 + src/commands/notation/BeamCommand.h | 60 + src/commands/notation/BreakCommand.cpp | 54 + src/commands/notation/BreakCommand.h | 60 + .../notation/ChangeSlurPositionCommand.cpp | 58 + src/commands/notation/ChangeSlurPositionCommand.h | 66 + src/commands/notation/ChangeStemsCommand.cpp | 53 + src/commands/notation/ChangeStemsCommand.h | 66 + src/commands/notation/ChangeStyleCommand.cpp | 66 + src/commands/notation/ChangeStyleCommand.h | 70 + src/commands/notation/ChangeTiePositionCommand.cpp | 54 + src/commands/notation/ChangeTiePositionCommand.h | 62 + src/commands/notation/ClefInsertionCommand.cpp | 137 + src/commands/notation/ClefInsertionCommand.h | 72 + src/commands/notation/CollapseRestsCommand.cpp | 54 + src/commands/notation/CollapseRestsCommand.h | 63 + src/commands/notation/DeCounterpointCommand.cpp | 57 + src/commands/notation/DeCounterpointCommand.h | 68 + src/commands/notation/EraseEventCommand.cpp | 105 + src/commands/notation/EraseEventCommand.h | 71 + .../notation/FixNotationQuantizeCommand.cpp | 87 + src/commands/notation/FixNotationQuantizeCommand.h | 61 + src/commands/notation/GraceCommand.cpp | 115 + src/commands/notation/GraceCommand.h | 60 + .../notation/GuitarChordInsertionCommand.cpp | 59 + .../notation/GuitarChordInsertionCommand.h | 61 + .../notation/IncrementDisplacementsCommand.cpp | 57 + .../notation/IncrementDisplacementsCommand.h | 66 + src/commands/notation/InterpretCommand.cpp | 602 ++ src/commands/notation/InterpretCommand.h | 100 + src/commands/notation/KeyInsertionCommand.cpp | 264 + src/commands/notation/KeyInsertionCommand.h | 91 + .../notation/MakeAccidentalsCautionaryCommand.cpp | 68 + .../notation/MakeAccidentalsCautionaryCommand.h | 63 + src/commands/notation/MakeChordCommand.cpp | 75 + src/commands/notation/MakeChordCommand.h | 66 + src/commands/notation/MakeNotesViableCommand.cpp | 57 + src/commands/notation/MakeNotesViableCommand.h | 67 + src/commands/notation/MakeRegionViableCommand.cpp | 48 + src/commands/notation/MakeRegionViableCommand.h | 62 + src/commands/notation/MultiKeyInsertionCommand.cpp | 80 + src/commands/notation/MultiKeyInsertionCommand.h | 73 + src/commands/notation/NormalizeRestsCommand.cpp | 52 + src/commands/notation/NormalizeRestsCommand.h | 64 + src/commands/notation/NoteInsertionCommand.cpp | 296 + src/commands/notation/NoteInsertionCommand.h | 98 + .../notation/RemoveFingeringMarksCommand.cpp | 54 + .../notation/RemoveFingeringMarksCommand.h | 61 + src/commands/notation/RemoveMarksCommand.cpp | 58 + src/commands/notation/RemoveMarksCommand.h | 61 + .../notation/RemoveNotationQuantizeCommand.cpp | 69 + .../notation/RemoveNotationQuantizeCommand.h | 61 + .../notation/ResetDisplacementsCommand.cpp | 52 + src/commands/notation/ResetDisplacementsCommand.h | 61 + src/commands/notation/RespellCommand.cpp | 141 + src/commands/notation/RespellCommand.h | 72 + src/commands/notation/RestInsertionCommand.cpp | 65 + src/commands/notation/RestInsertionCommand.h | 58 + src/commands/notation/RestoreSlursCommand.cpp | 58 + src/commands/notation/RestoreSlursCommand.h | 62 + src/commands/notation/RestoreStemsCommand.cpp | 52 + src/commands/notation/RestoreStemsCommand.h | 62 + src/commands/notation/RestoreTiesCommand.cpp | 51 + src/commands/notation/RestoreTiesCommand.h | 62 + src/commands/notation/SetVisibilityCommand.cpp | 57 + src/commands/notation/SetVisibilityCommand.h | 63 + src/commands/notation/SustainInsertionCommand.cpp | 66 + src/commands/notation/SustainInsertionCommand.h | 76 + src/commands/notation/TextChangeCommand.cpp | 62 + src/commands/notation/TextChangeCommand.h | 63 + src/commands/notation/TextInsertionCommand.cpp | 63 + src/commands/notation/TextInsertionCommand.h | 63 + src/commands/notation/TieNotesCommand.cpp | 72 + src/commands/notation/TieNotesCommand.h | 62 + src/commands/notation/TupletCommand.cpp | 91 + src/commands/notation/TupletCommand.h | 71 + src/commands/notation/UnGraceCommand.cpp | 42 + src/commands/notation/UnGraceCommand.h | 58 + src/commands/notation/UnTupletCommand.cpp | 54 + src/commands/notation/UnTupletCommand.h | 62 + src/commands/notation/UntieNotesCommand.cpp | 52 + src/commands/notation/UntieNotesCommand.h | 62 + src/commands/segment/AddTempoChangeCommand.cpp | 66 + src/commands/segment/AddTempoChangeCommand.h | 76 + .../AddTimeSignatureAndNormalizeCommand.cpp | 78 + .../segment/AddTimeSignatureAndNormalizeCommand.h | 53 + src/commands/segment/AddTimeSignatureCommand.cpp | 78 + src/commands/segment/AddTimeSignatureCommand.h | 71 + src/commands/segment/AddTracksCommand.cpp | 137 + src/commands/segment/AddTracksCommand.h | 77 + src/commands/segment/AddTriggerSegmentCommand.cpp | 90 + src/commands/segment/AddTriggerSegmentCommand.h | 72 + .../segment/AudioSegmentAutoSplitCommand.cpp | 191 + .../segment/AudioSegmentAutoSplitCommand.h | 71 + .../segment/AudioSegmentDistributeCommand.cpp | 156 + .../segment/AudioSegmentDistributeCommand.h | 86 + src/commands/segment/AudioSegmentInsertCommand.cpp | 136 + src/commands/segment/AudioSegmentInsertCommand.h | 77 + .../segment/AudioSegmentRescaleCommand.cpp | 210 + src/commands/segment/AudioSegmentRescaleCommand.h | 81 + .../segment/AudioSegmentResizeFromStartCommand.cpp | 87 + .../segment/AudioSegmentResizeFromStartCommand.h | 66 + src/commands/segment/AudioSegmentSplitCommand.cpp | 155 + src/commands/segment/AudioSegmentSplitCommand.h | 65 + .../segment/ChangeCompositionLengthCommand.cpp | 64 + .../segment/ChangeCompositionLengthCommand.h | 70 + .../segment/CreateTempoMapFromSegmentCommand.cpp | 166 + .../segment/CreateTempoMapFromSegmentCommand.h | 69 + src/commands/segment/CutRangeCommand.cpp | 47 + src/commands/segment/CutRangeCommand.h | 53 + src/commands/segment/DeleteRangeCommand.cpp | 127 + src/commands/segment/DeleteRangeCommand.h | 84 + src/commands/segment/DeleteTracksCommand.cpp | 161 + src/commands/segment/DeleteTracksCommand.h | 68 + .../segment/DeleteTriggerSegmentCommand.cpp | 78 + src/commands/segment/DeleteTriggerSegmentCommand.h | 66 + .../EraseSegmentsStartingInRangeCommand.cpp | 99 + .../segment/EraseSegmentsStartingInRangeCommand.h | 67 + src/commands/segment/InsertRangeCommand.cpp | 63 + src/commands/segment/InsertRangeCommand.h | 47 + src/commands/segment/ModifyDefaultTempoCommand.cpp | 48 + src/commands/segment/ModifyDefaultTempoCommand.h | 66 + src/commands/segment/MoveTracksCommand.cpp | 76 + src/commands/segment/MoveTracksCommand.h | 66 + src/commands/segment/OpenOrCloseRangeCommand.cpp | 181 + src/commands/segment/OpenOrCloseRangeCommand.h | 84 + src/commands/segment/PasteConductorDataCommand.cpp | 128 + src/commands/segment/PasteConductorDataCommand.h | 67 + src/commands/segment/PasteRangeCommand.cpp | 97 + src/commands/segment/PasteRangeCommand.h | 52 + .../segment/PasteToTriggerSegmentCommand.cpp | 129 + .../segment/PasteToTriggerSegmentCommand.h | 73 + src/commands/segment/RemoveTempoChangeCommand.cpp | 59 + src/commands/segment/RemoveTempoChangeCommand.h | 75 + .../segment/RemoveTimeSignatureCommand.cpp | 60 + src/commands/segment/RemoveTimeSignatureCommand.h | 74 + src/commands/segment/RenameTrackCommand.cpp | 75 + src/commands/segment/RenameTrackCommand.h | 67 + src/commands/segment/SegmentAutoSplitCommand.cpp | 205 + src/commands/segment/SegmentAutoSplitCommand.h | 66 + .../segment/SegmentChangePlayableRangeCommand.cpp | 77 + .../segment/SegmentChangePlayableRangeCommand.h | 67 + .../segment/SegmentChangeQuantizationCommand.cpp | 115 + .../segment/SegmentChangeQuantizationCommand.h | 73 + .../segment/SegmentChangeTransposeCommand.cpp | 72 + .../segment/SegmentChangeTransposeCommand.h | 65 + src/commands/segment/SegmentColourCommand.cpp | 65 + src/commands/segment/SegmentColourCommand.h | 66 + src/commands/segment/SegmentColourMapCommand.cpp | 64 + src/commands/segment/SegmentColourMapCommand.h | 71 + src/commands/segment/SegmentCommand.cpp | 42 + src/commands/segment/SegmentCommand.h | 59 + src/commands/segment/SegmentCommandRepeat.cpp | 59 + src/commands/segment/SegmentCommandRepeat.h | 81 + src/commands/segment/SegmentEraseCommand.cpp | 108 + src/commands/segment/SegmentEraseCommand.h | 70 + src/commands/segment/SegmentInsertCommand.cpp | 124 + src/commands/segment/SegmentInsertCommand.h | 76 + src/commands/segment/SegmentJoinCommand.cpp | 175 + src/commands/segment/SegmentJoinCommand.h | 65 + src/commands/segment/SegmentLabelCommand.cpp | 73 + src/commands/segment/SegmentLabelCommand.h | 67 + src/commands/segment/SegmentQuickCopyCommand.cpp | 71 + src/commands/segment/SegmentQuickCopyCommand.h | 68 + src/commands/segment/SegmentReconfigureCommand.cpp | 114 + src/commands/segment/SegmentReconfigureCommand.h | 81 + src/commands/segment/SegmentRecordCommand.cpp | 67 + src/commands/segment/SegmentRecordCommand.h | 67 + .../segment/SegmentRepeatToCopyCommand.cpp | 106 + src/commands/segment/SegmentRepeatToCopyCommand.h | 62 + src/commands/segment/SegmentRescaleCommand.cpp | 148 + src/commands/segment/SegmentRescaleCommand.h | 76 + .../segment/SegmentResizeFromStartCommand.cpp | 85 + .../segment/SegmentResizeFromStartCommand.h | 69 + .../segment/SegmentSingleRepeatToCopyCommand.cpp | 73 + .../segment/SegmentSingleRepeatToCopyCommand.h | 65 + .../segment/SegmentSplitByPitchCommand.cpp | 280 + src/commands/segment/SegmentSplitByPitchCommand.h | 83 + .../segment/SegmentSplitByRecordingSrcCommand.cpp | 153 + .../segment/SegmentSplitByRecordingSrcCommand.h | 70 + src/commands/segment/SegmentSplitCommand.cpp | 185 + src/commands/segment/SegmentSplitCommand.h | 65 + src/commands/segment/SegmentSyncClefCommand.cpp | 67 + src/commands/segment/SegmentSyncClefCommand.h | 55 + src/commands/segment/SegmentSyncCommand.cpp | 103 + src/commands/segment/SegmentSyncCommand.h | 66 + src/commands/segment/SegmentTransposeCommand.cpp | 123 + src/commands/segment/SegmentTransposeCommand.h | 64 + .../segment/SetTriggerSegmentBasePitchCommand.cpp | 74 + .../segment/SetTriggerSegmentBasePitchCommand.h | 63 + .../SetTriggerSegmentBaseVelocityCommand.cpp | 74 + .../segment/SetTriggerSegmentBaseVelocityCommand.h | 63 + .../SetTriggerSegmentDefaultRetuneCommand.cpp | 75 + .../SetTriggerSegmentDefaultRetuneCommand.h | 64 + .../SetTriggerSegmentDefaultTimeAdjustCommand.cpp | 74 + .../SetTriggerSegmentDefaultTimeAdjustCommand.h | 64 + src/commands/studio/AddControlParameterCommand.cpp | 75 + src/commands/studio/AddControlParameterCommand.h | 75 + .../studio/CreateOrDeleteDeviceCommand.cpp | 161 + src/commands/studio/CreateOrDeleteDeviceCommand.h | 88 + .../studio/ModifyControlParameterCommand.cpp | 75 + .../studio/ModifyControlParameterCommand.h | 74 + src/commands/studio/ModifyDeviceCommand.cpp | 198 + src/commands/studio/ModifyDeviceCommand.h | 109 + src/commands/studio/ModifyDeviceMappingCommand.cpp | 147 + src/commands/studio/ModifyDeviceMappingCommand.h | 71 + .../studio/ModifyInstrumentMappingCommand.cpp | 78 + .../studio/ModifyInstrumentMappingCommand.h | 76 + src/commands/studio/ReconnectDeviceCommand.cpp | 98 + src/commands/studio/ReconnectDeviceCommand.h | 70 + .../studio/RemoveControlParameterCommand.cpp | 75 + .../studio/RemoveControlParameterCommand.h | 73 + src/commands/studio/RenameDeviceCommand.cpp | 52 + src/commands/studio/RenameDeviceCommand.h | 71 + src/document/BasicCommand.cpp | 171 + src/document/BasicCommand.h | 112 + src/document/BasicSelectionCommand.cpp | 66 + src/document/BasicSelectionCommand.h | 67 + src/document/ConfigGroups.cpp | 53 + src/document/ConfigGroups.h | 56 + src/document/MultiViewCommandHistory.cpp | 386 + src/document/MultiViewCommandHistory.h | 152 + src/document/RoseXmlHandler.cpp | 2368 ++++++ src/document/RoseXmlHandler.h | 192 + src/document/RosegardenGUIDoc.cpp | 3117 ++++++++ src/document/RosegardenGUIDoc.h | 733 ++ src/document/XmlStorableEvent.cpp | 188 + src/document/XmlStorableEvent.h | 75 + src/document/XmlSubHandler.cpp | 37 + src/document/XmlSubHandler.h | 58 + src/document/io/CsoundExporter.cpp | 154 + src/document/io/CsoundExporter.h | 63 + src/document/io/HydrogenLoader.cpp | 74 + src/document/io/HydrogenLoader.h | 83 + src/document/io/HydrogenXMLHandler.cpp | 403 + src/document/io/HydrogenXMLHandler.h | 132 + src/document/io/LilyPondExporter.cpp | 2419 ++++++ src/document/io/LilyPondExporter.h | 262 + src/document/io/MupExporter.cpp | 453 ++ src/document/io/MupExporter.h | 89 + src/document/io/MusicXmlExporter.cpp | 555 ++ src/document/io/MusicXmlExporter.h | 87 + src/document/io/RG21Loader.cpp | 797 ++ src/document/io/RG21Loader.h | 162 + src/gui/application/LircClient.cpp | 100 + src/gui/application/LircClient.h | 71 + src/gui/application/LircCommander.cpp | 170 + src/gui/application/LircCommander.h | 112 + src/gui/application/RosegardenApplication.cpp | 145 + src/gui/application/RosegardenApplication.h | 97 + src/gui/application/RosegardenDCOP.h | 50 + src/gui/application/RosegardenGUIApp.cpp | 8073 ++++++++++++++++++++ src/gui/application/RosegardenGUIApp.cpp.orig | 8043 +++++++++++++++++++ src/gui/application/RosegardenGUIApp.h | 1691 ++++ src/gui/application/RosegardenGUIView.cpp | 2041 +++++ src/gui/application/RosegardenGUIView.h | 347 + src/gui/application/RosegardenIface.cpp | 82 + src/gui/application/RosegardenIface.h | 130 + src/gui/application/SetWaitCursor.cpp | 95 + src/gui/application/SetWaitCursor.h | 58 + src/gui/application/StartupTester.cpp | 248 + src/gui/application/StartupTester.h | 88 + src/gui/application/main.cpp | 741 ++ src/gui/configuration/AudioConfigurationPage.cpp | 323 + src/gui/configuration/AudioConfigurationPage.h | 107 + src/gui/configuration/AudioPropertiesPage.cpp | 184 + src/gui/configuration/AudioPropertiesPage.h | 89 + src/gui/configuration/ColourConfigurationPage.cpp | 165 + src/gui/configuration/ColourConfigurationPage.h | 87 + src/gui/configuration/ConfigurationPage.cpp | 37 + src/gui/configuration/ConfigurationPage.h | 104 + .../DocumentMetaConfigurationPage.cpp | 366 + .../configuration/DocumentMetaConfigurationPage.h | 76 + src/gui/configuration/GeneralConfigurationPage.cpp | 429 ++ src/gui/configuration/GeneralConfigurationPage.h | 116 + src/gui/configuration/HeadersConfigurationPage.cpp | 294 + src/gui/configuration/HeadersConfigurationPage.h | 80 + src/gui/configuration/LatencyConfigurationPage.cpp | 157 + src/gui/configuration/LatencyConfigurationPage.h | 87 + src/gui/configuration/MIDIConfigurationPage.cpp | 400 + src/gui/configuration/MIDIConfigurationPage.h | 104 + src/gui/configuration/MatrixConfigurationPage.cpp | 68 + src/gui/configuration/MatrixConfigurationPage.h | 69 + .../configuration/NotationConfigurationPage.cpp | 741 ++ src/gui/configuration/NotationConfigurationPage.h | 117 + src/gui/configuration/TabbedConfigurationPage.cpp | 79 + src/gui/configuration/TabbedConfigurationPage.h | 78 + src/gui/dialogs/AddTracksDialog.cpp | 110 + src/gui/dialogs/AddTracksDialog.h | 57 + src/gui/dialogs/AudioManagerDialog.cpp | 1257 +++ src/gui/dialogs/AudioManagerDialog.h | 206 + src/gui/dialogs/AudioPlayingDialog.cpp | 55 + src/gui/dialogs/AudioPlayingDialog.h | 56 + src/gui/dialogs/AudioPluginDialog.cpp | 916 +++ src/gui/dialogs/AudioPluginDialog.h | 167 + src/gui/dialogs/AudioSplitDialog.cpp | 339 + src/gui/dialogs/AudioSplitDialog.h | 88 + src/gui/dialogs/BeatsBarsDialog.cpp | 66 + src/gui/dialogs/BeatsBarsDialog.h | 63 + src/gui/dialogs/ClefDialog.cpp | 273 + src/gui/dialogs/ClefDialog.h | 93 + src/gui/dialogs/CompositionLengthDialog.cpp | 84 + src/gui/dialogs/CompositionLengthDialog.h | 64 + src/gui/dialogs/ConfigureDialog.cpp | 118 + src/gui/dialogs/ConfigureDialog.h | 58 + src/gui/dialogs/ConfigureDialogBase.cpp | 76 + src/gui/dialogs/ConfigureDialogBase.h | 69 + src/gui/dialogs/CountdownBar.cpp | 68 + src/gui/dialogs/CountdownBar.h | 59 + src/gui/dialogs/CountdownDialog.cpp | 159 + src/gui/dialogs/CountdownDialog.h | 87 + src/gui/dialogs/DocumentConfigureDialog.cpp | 151 + src/gui/dialogs/DocumentConfigureDialog.h | 60 + src/gui/dialogs/EventEditDialog.cpp | 528 ++ src/gui/dialogs/EventEditDialog.h | 113 + src/gui/dialogs/EventFilterDialog.cpp | 476 ++ src/gui/dialogs/EventFilterDialog.h | 170 + src/gui/dialogs/EventParameterDialog.cpp | 185 + src/gui/dialogs/EventParameterDialog.h | 80 + src/gui/dialogs/ExportDeviceDialog.cpp | 66 + src/gui/dialogs/ExportDeviceDialog.h | 60 + src/gui/dialogs/FileLocateDialog.cpp | 104 + src/gui/dialogs/FileLocateDialog.h | 66 + src/gui/dialogs/FileMergeDialog.cpp | 84 + src/gui/dialogs/FileMergeDialog.h | 63 + src/gui/dialogs/FloatEdit.cpp | 72 + src/gui/dialogs/FloatEdit.h | 68 + src/gui/dialogs/IdentifyTextCodecDialog.cpp | 173 + src/gui/dialogs/IdentifyTextCodecDialog.h | 71 + src/gui/dialogs/ImportDeviceDialog.cpp | 389 + src/gui/dialogs/ImportDeviceDialog.h | 110 + src/gui/dialogs/InterpretDialog.cpp | 123 + src/gui/dialogs/InterpretDialog.h | 65 + src/gui/dialogs/IntervalDialog.cpp | 367 + src/gui/dialogs/IntervalDialog.h | 94 + src/gui/dialogs/KeySignatureDialog.cpp | 402 + src/gui/dialogs/KeySignatureDialog.h | 118 + src/gui/dialogs/LilyPondOptionsDialog.cpp | 363 + src/gui/dialogs/LilyPondOptionsDialog.h | 86 + src/gui/dialogs/LyricEditDialog.cpp | 253 + src/gui/dialogs/LyricEditDialog.h | 78 + src/gui/dialogs/MakeOrnamentDialog.cpp | 73 + src/gui/dialogs/MakeOrnamentDialog.h | 62 + src/gui/dialogs/ManageMetronomeDialog.cpp | 508 ++ src/gui/dialogs/ManageMetronomeDialog.h | 94 + src/gui/dialogs/MarkerModifyDialog.cpp | 113 + src/gui/dialogs/MarkerModifyDialog.h | 84 + src/gui/dialogs/PasteNotationDialog.cpp | 101 + src/gui/dialogs/PasteNotationDialog.h | 72 + src/gui/dialogs/PitchDialog.cpp | 57 + src/gui/dialogs/PitchDialog.h | 58 + src/gui/dialogs/PitchPickerDialog.cpp | 58 + src/gui/dialogs/PitchPickerDialog.h | 57 + src/gui/dialogs/QuantizeDialog.cpp | 68 + src/gui/dialogs/QuantizeDialog.h | 60 + src/gui/dialogs/RescaleDialog.cpp | 131 + src/gui/dialogs/RescaleDialog.h | 68 + src/gui/dialogs/ShowSequencerStatusDialog.cpp | 79 + src/gui/dialogs/ShowSequencerStatusDialog.h | 54 + src/gui/dialogs/SimpleEventEditDialog.cpp | 1061 +++ src/gui/dialogs/SimpleEventEditDialog.h | 134 + src/gui/dialogs/SplitByPitchDialog.cpp | 111 + src/gui/dialogs/SplitByPitchDialog.h | 67 + src/gui/dialogs/SplitByRecordingSrcDialog.cpp | 114 + src/gui/dialogs/SplitByRecordingSrcDialog.h | 62 + src/gui/dialogs/TempoDialog.cpp | 475 ++ src/gui/dialogs/TempoDialog.h | 128 + src/gui/dialogs/TextEventDialog.cpp | 593 ++ src/gui/dialogs/TextEventDialog.h | 129 + src/gui/dialogs/TimeDialog.cpp | 80 + src/gui/dialogs/TimeDialog.h | 67 + src/gui/dialogs/TimeSignatureDialog.cpp | 316 + src/gui/dialogs/TimeSignatureDialog.h | 99 + src/gui/dialogs/TransportDialog.cpp | 1164 +++ src/gui/dialogs/TransportDialog.h | 231 + src/gui/dialogs/TriggerSegmentDialog.cpp | 181 + src/gui/dialogs/TriggerSegmentDialog.h | 71 + src/gui/dialogs/TupletDialog.cpp | 365 + src/gui/dialogs/TupletDialog.h | 99 + src/gui/dialogs/UnusedAudioSelectionDialog.cpp | 92 + src/gui/dialogs/UnusedAudioSelectionDialog.h | 62 + src/gui/dialogs/UseOrnamentDialog.cpp | 264 + src/gui/dialogs/UseOrnamentDialog.h | 82 + src/gui/editors/eventlist/EventView.cpp | 1606 ++++ src/gui/editors/eventlist/EventView.h | 205 + src/gui/editors/eventlist/EventViewItem.cpp | 68 + src/gui/editors/eventlist/EventViewItem.h | 101 + .../editors/eventlist/TrivialVelocityDialog.cpp | 48 + src/gui/editors/eventlist/TrivialVelocityDialog.h | 48 + src/gui/editors/guitar/Chord.cpp | 113 + src/gui/editors/guitar/Chord.h | 106 + src/gui/editors/guitar/ChordMap.cpp | 223 + src/gui/editors/guitar/ChordMap.h | 87 + src/gui/editors/guitar/ChordXmlHandler.cpp | 154 + src/gui/editors/guitar/ChordXmlHandler.h | 78 + src/gui/editors/guitar/Fingering.cpp | 152 + src/gui/editors/guitar/Fingering.h | 95 + src/gui/editors/guitar/FingeringBox.cpp | 293 + src/gui/editors/guitar/FingeringBox.h | 106 + src/gui/editors/guitar/FingeringListBoxItem.cpp | 36 + src/gui/editors/guitar/FingeringListBoxItem.h | 46 + src/gui/editors/guitar/GuitarChordEditorDialog.cpp | 109 + src/gui/editors/guitar/GuitarChordEditorDialog.h | 67 + .../editors/guitar/GuitarChordSelectorDialog.cpp | 475 ++ src/gui/editors/guitar/GuitarChordSelectorDialog.h | 120 + src/gui/editors/guitar/NoteSymbols.cpp | 486 ++ src/gui/editors/guitar/NoteSymbols.h | 192 + src/gui/editors/matrix/MatrixCanvasView.cpp | 302 + src/gui/editors/matrix/MatrixCanvasView.h | 162 + src/gui/editors/matrix/MatrixElement.cpp | 160 + src/gui/editors/matrix/MatrixElement.h | 138 + src/gui/editors/matrix/MatrixEraser.cpp | 110 + src/gui/editors/matrix/MatrixEraser.h | 69 + src/gui/editors/matrix/MatrixHLayout.cpp | 220 + src/gui/editors/matrix/MatrixHLayout.h | 150 + src/gui/editors/matrix/MatrixMover.cpp | 481 ++ src/gui/editors/matrix/MatrixMover.h | 112 + src/gui/editors/matrix/MatrixPainter.cpp | 370 + src/gui/editors/matrix/MatrixPainter.h | 105 + src/gui/editors/matrix/MatrixParameterBox.cpp | 99 + src/gui/editors/matrix/MatrixParameterBox.h | 76 + src/gui/editors/matrix/MatrixResizer.cpp | 333 + src/gui/editors/matrix/MatrixResizer.h | 102 + src/gui/editors/matrix/MatrixSelector.cpp | 629 ++ src/gui/editors/matrix/MatrixSelector.h | 177 + src/gui/editors/matrix/MatrixStaff.cpp | 232 + src/gui/editors/matrix/MatrixStaff.h | 111 + src/gui/editors/matrix/MatrixTool.cpp | 79 + src/gui/editors/matrix/MatrixTool.h | 74 + src/gui/editors/matrix/MatrixToolBox.cpp | 87 + src/gui/editors/matrix/MatrixToolBox.h | 60 + src/gui/editors/matrix/MatrixVLayout.cpp | 100 + src/gui/editors/matrix/MatrixVLayout.h | 91 + src/gui/editors/matrix/MatrixView.cpp | 3076 ++++++++ src/gui/editors/matrix/MatrixView.h | 692 ++ src/gui/editors/matrix/PianoKeyboard.cpp | 299 + src/gui/editors/matrix/PianoKeyboard.h | 133 + src/gui/editors/matrix/QCanvasMatrixDiamond.cpp | 82 + src/gui/editors/matrix/QCanvasMatrixDiamond.h | 61 + src/gui/editors/matrix/QCanvasMatrixRectangle.cpp | 44 + src/gui/editors/matrix/QCanvasMatrixRectangle.h | 60 + src/gui/editors/notation/ClefInserter.cpp | 132 + src/gui/editors/notation/ClefInserter.h | 83 + src/gui/editors/notation/FontViewFrame.cpp | 252 + src/gui/editors/notation/FontViewFrame.h | 77 + src/gui/editors/notation/GuitarChordInserter.cpp | 185 + src/gui/editors/notation/GuitarChordInserter.h | 96 + src/gui/editors/notation/HeadersGroup.cpp | 160 + src/gui/editors/notation/HeadersGroup.h | 144 + src/gui/editors/notation/NotationCanvasView.cpp | 485 ++ src/gui/editors/notation/NotationCanvasView.h | 218 + src/gui/editors/notation/NotationChord.cpp | 335 + src/gui/editors/notation/NotationChord.h | 90 + src/gui/editors/notation/NotationElement.cpp | 198 + src/gui/editors/notation/NotationElement.h | 176 + src/gui/editors/notation/NotationEraser.cpp | 115 + src/gui/editors/notation/NotationEraser.h | 81 + src/gui/editors/notation/NotationGroup.cpp | 979 +++ src/gui/editors/notation/NotationGroup.h | 133 + src/gui/editors/notation/NotationHLayout.cpp | 2110 +++++ src/gui/editors/notation/NotationHLayout.h | 446 ++ src/gui/editors/notation/NotationProperties.cpp | 85 + src/gui/editors/notation/NotationProperties.h | 108 + .../editors/notation/NotationSelectionPaster.cpp | 89 + src/gui/editors/notation/NotationSelectionPaster.h | 72 + src/gui/editors/notation/NotationSelector.cpp | 957 +++ src/gui/editors/notation/NotationSelector.h | 197 + src/gui/editors/notation/NotationStaff.cpp | 2300 ++++++ src/gui/editors/notation/NotationStaff.h | 488 ++ src/gui/editors/notation/NotationStrings.cpp | 301 + src/gui/editors/notation/NotationStrings.h | 121 + src/gui/editors/notation/NotationTool.cpp | 57 + src/gui/editors/notation/NotationTool.h | 93 + src/gui/editors/notation/NotationToolBox.cpp | 102 + src/gui/editors/notation/NotationToolBox.h | 65 + src/gui/editors/notation/NotationVLayout.cpp | 731 ++ src/gui/editors/notation/NotationVLayout.h | 122 + src/gui/editors/notation/NotationView.cpp | 7552 ++++++++++++++++++ src/gui/editors/notation/NotationView.h | 1131 +++ src/gui/editors/notation/NoteCharacter.cpp | 133 + src/gui/editors/notation/NoteCharacter.h | 93 + src/gui/editors/notation/NoteCharacterNames.cpp | 123 + src/gui/editors/notation/NoteCharacterNames.h | 120 + src/gui/editors/notation/NoteFont.cpp | 650 ++ src/gui/editors/notation/NoteFont.h | 184 + src/gui/editors/notation/NoteFontFactory.cpp | 236 + src/gui/editors/notation/NoteFontFactory.h | 71 + src/gui/editors/notation/NoteFontMap.cpp | 1088 +++ src/gui/editors/notation/NoteFontMap.h | 333 + src/gui/editors/notation/NoteFontViewer.cpp | 125 + src/gui/editors/notation/NoteFontViewer.h | 68 + src/gui/editors/notation/NoteInserter.cpp | 722 ++ src/gui/editors/notation/NoteInserter.h | 166 + src/gui/editors/notation/NotePixmapFactory.cpp | 3689 +++++++++ src/gui/editors/notation/NotePixmapFactory.h | 358 + src/gui/editors/notation/NotePixmapPainter.h | 148 + src/gui/editors/notation/NotePixmapParameters.cpp | 151 + src/gui/editors/notation/NotePixmapParameters.h | 161 + src/gui/editors/notation/NoteStyle.cpp | 485 ++ src/gui/editors/notation/NoteStyle.h | 142 + src/gui/editors/notation/NoteStyleFactory.cpp | 124 + src/gui/editors/notation/NoteStyleFactory.h | 61 + src/gui/editors/notation/NoteStyleFileReader.cpp | 193 + src/gui/editors/notation/NoteStyleFileReader.h | 59 + src/gui/editors/notation/RestInserter.cpp | 150 + src/gui/editors/notation/RestInserter.h | 76 + src/gui/editors/notation/SystemFont.cpp | 165 + src/gui/editors/notation/SystemFont.h | 63 + src/gui/editors/notation/SystemFontQt.cpp | 78 + src/gui/editors/notation/SystemFontQt.h | 49 + src/gui/editors/notation/SystemFontXft.cpp | 193 + src/gui/editors/notation/SystemFontXft.h | 58 + src/gui/editors/notation/TextInserter.cpp | 169 + src/gui/editors/notation/TextInserter.h | 78 + src/gui/editors/notation/TrackHeader.cpp | 450 ++ src/gui/editors/notation/TrackHeader.h | 219 + .../parameters/AudioInstrumentParameterPanel.cpp | 437 ++ .../parameters/AudioInstrumentParameterPanel.h | 107 + .../editors/parameters/InstrumentParameterBox.cpp | 265 + .../editors/parameters/InstrumentParameterBox.h | 126 + .../parameters/InstrumentParameterPanel.cpp | 61 + .../editors/parameters/InstrumentParameterPanel.h | 78 + .../parameters/MIDIInstrumentParameterPanel.cpp | 1175 +++ .../parameters/MIDIInstrumentParameterPanel.h | 137 + .../editors/parameters/RosegardenParameterArea.cpp | 227 + .../editors/parameters/RosegardenParameterArea.h | 108 + .../editors/parameters/RosegardenParameterBox.cpp | 89 + .../editors/parameters/RosegardenParameterBox.h | 92 + src/gui/editors/parameters/SegmentParameterBox.cpp | 1214 +++ src/gui/editors/parameters/SegmentParameterBox.h | 174 + src/gui/editors/parameters/TrackParameterBox.cpp | 1022 +++ src/gui/editors/parameters/TrackParameterBox.h | 161 + src/gui/editors/segment/ControlEditorDialog.cpp | 446 ++ src/gui/editors/segment/ControlEditorDialog.h | 122 + .../editors/segment/ControlParameterEditDialog.cpp | 325 + .../editors/segment/ControlParameterEditDialog.h | 92 + src/gui/editors/segment/ControlParameterItem.cpp | 34 + src/gui/editors/segment/ControlParameterItem.h | 65 + src/gui/editors/segment/MarkerEditor.cpp | 594 ++ src/gui/editors/segment/MarkerEditor.h | 124 + src/gui/editors/segment/MarkerEditorViewItem.cpp | 51 + src/gui/editors/segment/MarkerEditorViewItem.h | 70 + src/gui/editors/segment/PlayList.cpp | 254 + src/gui/editors/segment/PlayList.h | 93 + src/gui/editors/segment/PlayListDialog.cpp | 76 + src/gui/editors/segment/PlayListDialog.h | 71 + src/gui/editors/segment/PlayListView.cpp | 66 + src/gui/editors/segment/PlayListView.h | 52 + src/gui/editors/segment/PlayListViewItem.cpp | 42 + src/gui/editors/segment/PlayListViewItem.h | 47 + src/gui/editors/segment/TrackButtons.cpp | 1149 +++ src/gui/editors/segment/TrackButtons.h | 228 + src/gui/editors/segment/TrackEditor.cpp | 827 ++ src/gui/editors/segment/TrackEditor.h | 248 + src/gui/editors/segment/TrackEditorIface.cpp | 33 + src/gui/editors/segment/TrackEditorIface.h | 55 + src/gui/editors/segment/TrackHeader.cpp | 64 + src/gui/editors/segment/TrackHeader.h | 65 + src/gui/editors/segment/TrackLabel.cpp | 203 + src/gui/editors/segment/TrackLabel.h | 122 + src/gui/editors/segment/TrackVUMeter.cpp | 77 + src/gui/editors/segment/TrackVUMeter.h | 65 + src/gui/editors/segment/TriggerManagerItem.cpp | 60 + src/gui/editors/segment/TriggerManagerItem.h | 72 + src/gui/editors/segment/TriggerSegmentManager.cpp | 576 ++ src/gui/editors/segment/TriggerSegmentManager.h | 116 + .../segment/segmentcanvas/AudioPreviewPainter.cpp | 316 + .../segment/segmentcanvas/AudioPreviewPainter.h | 79 + .../segment/segmentcanvas/AudioPreviewThread.cpp | 267 + .../segment/segmentcanvas/AudioPreviewThread.h | 99 + .../segment/segmentcanvas/AudioPreviewUpdater.cpp | 149 + .../segment/segmentcanvas/AudioPreviewUpdater.h | 90 + .../segmentcanvas/CompositionColourCache.cpp | 62 + .../segment/segmentcanvas/CompositionColourCache.h | 69 + .../segment/segmentcanvas/CompositionItem.cpp | 34 + .../segment/segmentcanvas/CompositionItem.h | 67 + .../segmentcanvas/CompositionItemHelper.cpp | 150 + .../segment/segmentcanvas/CompositionItemHelper.h | 61 + .../segment/segmentcanvas/CompositionItemImpl.cpp | 67 + .../segment/segmentcanvas/CompositionItemImpl.h | 74 + .../segment/segmentcanvas/CompositionModel.cpp | 43 + .../segment/segmentcanvas/CompositionModel.h | 179 + .../segment/segmentcanvas/CompositionModelImpl.cpp | 1328 ++++ .../segment/segmentcanvas/CompositionModelImpl.h | 239 + .../segment/segmentcanvas/CompositionRect.cpp | 42 + .../segment/segmentcanvas/CompositionRect.h | 108 + .../segment/segmentcanvas/CompositionView.cpp | 1591 ++++ .../segment/segmentcanvas/CompositionView.h | 366 + .../editors/segment/segmentcanvas/PreviewRect.cpp | 34 + .../editors/segment/segmentcanvas/PreviewRect.h | 62 + .../segment/segmentcanvas/SegmentEraser.cpp | 88 + .../editors/segment/segmentcanvas/SegmentEraser.h | 67 + .../segment/segmentcanvas/SegmentItemPreview.cpp | 37 + .../segment/segmentcanvas/SegmentItemPreview.h | 91 + .../segment/segmentcanvas/SegmentJoiner.cpp | 73 + .../editors/segment/segmentcanvas/SegmentJoiner.h | 70 + .../editors/segment/segmentcanvas/SegmentMover.cpp | 348 + .../editors/segment/segmentcanvas/SegmentMover.h | 78 + .../segment/segmentcanvas/SegmentOrderer.cpp | 48 + .../editors/segment/segmentcanvas/SegmentOrderer.h | 59 + .../segment/segmentcanvas/SegmentPencil.cpp | 295 + .../editors/segment/segmentcanvas/SegmentPencil.h | 83 + .../segment/segmentcanvas/SegmentResizer.cpp | 393 + .../editors/segment/segmentcanvas/SegmentResizer.h | 87 + .../segment/segmentcanvas/SegmentSelector.cpp | 532 ++ .../segment/segmentcanvas/SegmentSelector.h | 109 + .../segment/segmentcanvas/SegmentSplitter.cpp | 175 + .../segment/segmentcanvas/SegmentSplitter.h | 83 + .../editors/segment/segmentcanvas/SegmentTool.cpp | 115 + .../editors/segment/segmentcanvas/SegmentTool.h | 105 + .../segment/segmentcanvas/SegmentToolBox.cpp | 102 + .../editors/segment/segmentcanvas/SegmentToolBox.h | 63 + src/gui/editors/tempo/TempoListItem.cpp | 52 + src/gui/editors/tempo/TempoListItem.h | 72 + src/gui/editors/tempo/TempoView.cpp | 839 ++ src/gui/editors/tempo/TempoView.h | 172 + src/gui/general/ActiveItem.cpp | 32 + src/gui/general/ActiveItem.h | 55 + src/gui/general/BarLine.cpp | 165 + src/gui/general/BarLine.h | 64 + src/gui/general/BaseTool.cpp | 89 + src/gui/general/BaseTool.h | 112 + src/gui/general/BaseToolBox.cpp | 58 + src/gui/general/BaseToolBox.h | 69 + src/gui/general/CanvasCursor.cpp | 52 + src/gui/general/CanvasCursor.h | 55 + src/gui/general/CanvasItemGC.cpp | 64 + src/gui/general/CanvasItemGC.h | 85 + src/gui/general/CategoryElement.cpp | 61 + src/gui/general/CategoryElement.h | 71 + src/gui/general/ClefIndex.cpp | 100 + src/gui/general/ClefIndex.h | 59 + src/gui/general/EditTool.cpp | 143 + src/gui/general/EditTool.h | 166 + src/gui/general/EditToolBox.cpp | 56 + src/gui/general/EditToolBox.h | 65 + src/gui/general/EditView.cpp | 1717 +++++ src/gui/general/EditView.h | 405 + src/gui/general/EditViewBase.cpp | 711 ++ src/gui/general/EditViewBase.h | 396 + src/gui/general/EditViewTimeSigNotifier.h | 56 + src/gui/general/GUIPalette.cpp | 311 + src/gui/general/GUIPalette.h | 185 + src/gui/general/HZoomable.cpp | 32 + src/gui/general/HZoomable.h | 53 + src/gui/general/LinedStaff.cpp | 1217 +++ src/gui/general/LinedStaff.h | 759 ++ src/gui/general/LinedStaffManager.cpp | 33 + src/gui/general/LinedStaffManager.h | 61 + src/gui/general/MidiPitchLabel.cpp | 74 + src/gui/general/MidiPitchLabel.h | 57 + src/gui/general/PixmapFunctions.cpp | 271 + src/gui/general/PixmapFunctions.h | 107 + src/gui/general/PresetElement.cpp | 68 + src/gui/general/PresetElement.h | 82 + src/gui/general/PresetGroup.cpp | 269 + src/gui/general/PresetGroup.h | 105 + src/gui/general/PresetHandlerDialog.cpp | 281 + src/gui/general/PresetHandlerDialog.h | 107 + src/gui/general/ProgressReporter.cpp | 53 + src/gui/general/ProgressReporter.h | 80 + src/gui/general/RosegardenCanvasView.cpp | 485 ++ src/gui/general/RosegardenCanvasView.h | 197 + src/gui/general/RosegardenScrollView.cpp | 416 + src/gui/general/RosegardenScrollView.h | 183 + src/gui/general/Spline.cpp | 130 + src/gui/general/Spline.h | 71 + src/gui/general/StaffLine.cpp | 64 + src/gui/general/StaffLine.h | 78 + src/gui/kdeext/KLedButton.cpp | 60 + src/gui/kdeext/KLedButton.h | 76 + src/gui/kdeext/KStartupLogo.cpp | 159 + src/gui/kdeext/KStartupLogo.h | 70 + src/gui/kdeext/KTmpStatusMsg.cpp | 70 + src/gui/kdeext/KTmpStatusMsg.h | 88 + src/gui/kdeext/QCanvasGroupableItem.cpp | 279 + src/gui/kdeext/QCanvasGroupableItem.h | 201 + src/gui/kdeext/QCanvasSimpleSprite.cpp | 217 + src/gui/kdeext/QCanvasSimpleSprite.h | 133 + src/gui/kdeext/RGLed.cpp | 729 ++ src/gui/kdeext/klearlook.cpp | 4095 ++++++++++ src/gui/kdeext/klearlook.h | 344 + src/gui/rulers/ChordNameRuler.cpp | 523 ++ src/gui/rulers/ChordNameRuler.h | 146 + src/gui/rulers/ControlChangeCommand.cpp | 50 + src/gui/rulers/ControlChangeCommand.h | 55 + src/gui/rulers/ControlItem.cpp | 195 + src/gui/rulers/ControlItem.h | 79 + src/gui/rulers/ControlRuler.cpp | 539 ++ src/gui/rulers/ControlRuler.h | 182 + src/gui/rulers/ControlRulerEventEraseCommand.cpp | 58 + src/gui/rulers/ControlRulerEventEraseCommand.h | 54 + src/gui/rulers/ControlRulerEventInsertCommand.cpp | 67 + src/gui/rulers/ControlRulerEventInsertCommand.h | 56 + src/gui/rulers/ControlSelector.cpp | 72 + src/gui/rulers/ControlSelector.h | 60 + src/gui/rulers/ControlTool.h | 39 + src/gui/rulers/ControllerEventAdapter.cpp | 83 + src/gui/rulers/ControllerEventAdapter.h | 53 + src/gui/rulers/ControllerEventsRuler.cpp | 499 ++ src/gui/rulers/ControllerEventsRuler.h | 118 + src/gui/rulers/DefaultVelocityColour.cpp | 55 + src/gui/rulers/DefaultVelocityColour.h | 54 + src/gui/rulers/ElementAdapter.h | 46 + src/gui/rulers/LoopRuler.cpp | 363 + src/gui/rulers/LoopRuler.h | 148 + src/gui/rulers/MarkerRuler.cpp | 490 ++ src/gui/rulers/MarkerRuler.h | 121 + src/gui/rulers/PercussionPitchRuler.cpp | 204 + src/gui/rulers/PercussionPitchRuler.h | 91 + src/gui/rulers/PitchRuler.cpp | 55 + src/gui/rulers/PitchRuler.h | 78 + src/gui/rulers/PropertyBox.cpp | 77 + src/gui/rulers/PropertyBox.h | 74 + src/gui/rulers/PropertyControlRuler.cpp | 441 ++ src/gui/rulers/PropertyControlRuler.h | 120 + src/gui/rulers/PropertyViewRuler.cpp | 175 + src/gui/rulers/PropertyViewRuler.h | 102 + src/gui/rulers/RawNoteRuler.cpp | 573 ++ src/gui/rulers/RawNoteRuler.h | 128 + src/gui/rulers/StandardRuler.cpp | 172 + src/gui/rulers/StandardRuler.h | 108 + src/gui/rulers/TempoColour.cpp | 55 + src/gui/rulers/TempoColour.h | 60 + src/gui/rulers/TempoRuler.cpp | 1091 +++ src/gui/rulers/TempoRuler.h | 180 + src/gui/rulers/TextRuler.cpp | 157 + src/gui/rulers/TextRuler.h | 112 + src/gui/rulers/VelocityColour.cpp | 120 + src/gui/rulers/VelocityColour.h | 106 + src/gui/rulers/ViewElementAdapter.cpp | 56 + src/gui/rulers/ViewElementAdapter.h | 59 + src/gui/seqmanager/AudioSegmentMmapper.cpp | 133 + src/gui/seqmanager/AudioSegmentMmapper.h | 61 + src/gui/seqmanager/CompositionMmapper.cpp | 174 + src/gui/seqmanager/CompositionMmapper.h | 75 + src/gui/seqmanager/ControlBlockMmapper.cpp | 226 + src/gui/seqmanager/ControlBlockMmapper.h | 83 + src/gui/seqmanager/MetronomeMmapper.cpp | 268 + src/gui/seqmanager/MetronomeMmapper.h | 87 + src/gui/seqmanager/MidiFilterDialog.cpp | 229 + src/gui/seqmanager/MidiFilterDialog.h | 71 + src/gui/seqmanager/SegmentMmapper.cpp | 562 ++ src/gui/seqmanager/SegmentMmapper.h | 112 + src/gui/seqmanager/SegmentMmapperFactory.cpp | 96 + src/gui/seqmanager/SegmentMmapperFactory.h | 63 + src/gui/seqmanager/SequenceManager.cpp | 2141 ++++++ src/gui/seqmanager/SequenceManager.h | 322 + src/gui/seqmanager/SequencerMapper.cpp | 105 + src/gui/seqmanager/SequencerMapper.h | 113 + src/gui/seqmanager/SpecialSegmentMmapper.cpp | 56 + src/gui/seqmanager/SpecialSegmentMmapper.h | 59 + src/gui/seqmanager/TempoSegmentMmapper.cpp | 77 + src/gui/seqmanager/TempoSegmentMmapper.h | 60 + src/gui/seqmanager/TimeSigSegmentMmapper.cpp | 72 + src/gui/seqmanager/TimeSigSegmentMmapper.h | 62 + src/gui/studio/AudioMixerWindow.cpp | 1734 +++++ src/gui/studio/AudioMixerWindow.h | 191 + src/gui/studio/AudioPlugin.cpp | 78 + src/gui/studio/AudioPlugin.h | 117 + src/gui/studio/AudioPluginClipboard.cpp | 32 + src/gui/studio/AudioPluginClipboard.h | 52 + src/gui/studio/AudioPluginManager.cpp | 307 + src/gui/studio/AudioPluginManager.h | 118 + src/gui/studio/AudioPluginOSCGUI.cpp | 234 + src/gui/studio/AudioPluginOSCGUI.h | 77 + src/gui/studio/AudioPluginOSCGUIManager.cpp | 711 ++ src/gui/studio/AudioPluginOSCGUIManager.h | 104 + src/gui/studio/BankEditorDialog.cpp | 1713 +++++ src/gui/studio/BankEditorDialog.h | 211 + src/gui/studio/ChangeRecordDeviceCommand.cpp | 66 + src/gui/studio/ChangeRecordDeviceCommand.h | 54 + src/gui/studio/DeviceEditorDialog.cpp | 406 + src/gui/studio/DeviceEditorDialog.h | 87 + src/gui/studio/DeviceManagerDialog.cpp | 833 ++ src/gui/studio/DeviceManagerDialog.h | 121 + src/gui/studio/MidiBankListViewItem.cpp | 98 + src/gui/studio/MidiBankListViewItem.h | 70 + src/gui/studio/MidiDeviceListViewItem.cpp | 88 + src/gui/studio/MidiDeviceListViewItem.h | 69 + src/gui/studio/MidiKeyMapListViewItem.cpp | 56 + src/gui/studio/MidiKeyMapListViewItem.h | 59 + src/gui/studio/MidiKeyMappingEditor.cpp | 197 + src/gui/studio/MidiKeyMappingEditor.h | 78 + src/gui/studio/MidiMixerVUMeter.cpp | 53 + src/gui/studio/MidiMixerVUMeter.h | 61 + src/gui/studio/MidiMixerWindow.cpp | 742 ++ src/gui/studio/MidiMixerWindow.h | 125 + src/gui/studio/MidiProgramsEditor.cpp | 631 ++ src/gui/studio/MidiProgramsEditor.h | 119 + src/gui/studio/MixerWindow.cpp | 75 + src/gui/studio/MixerWindow.h | 77 + src/gui/studio/NameSetEditor.cpp | 190 + src/gui/studio/NameSetEditor.h | 90 + src/gui/studio/OSCMessage.cpp | 87 + src/gui/studio/OSCMessage.h | 75 + src/gui/studio/RemapInstrumentDialog.cpp | 184 + src/gui/studio/RemapInstrumentDialog.h | 84 + src/gui/studio/StudioControl.cpp | 582 ++ src/gui/studio/StudioControl.h | 152 + src/gui/studio/SynthPluginManagerDialog.cpp | 360 + src/gui/studio/SynthPluginManagerDialog.h | 98 + src/gui/studio/TimerCallbackAssistant.cpp | 57 + src/gui/studio/TimerCallbackAssistant.h | 61 + src/gui/ui/RosegardenTransport.ui | 4361 +++++++++++ src/gui/ui/audiomanager.rc | 67 + src/gui/ui/bankeditor.rc | 22 + src/gui/ui/clefinserter.rc | 11 + src/gui/ui/controleditor.rc | 5 + src/gui/ui/devicemanager.rc | 5 + src/gui/ui/eventlist.rc | 105 + src/gui/ui/markereditor.rc | 37 + src/gui/ui/markerruler.rc | 14 + src/gui/ui/matrix.rc | 301 + src/gui/ui/matrixeraser.rc | 15 + src/gui/ui/matrixmover.rc | 15 + src/gui/ui/matrixpainter.rc | 22 + src/gui/ui/matrixresizer.rc | 15 + src/gui/ui/matrixselector.rc | 15 + src/gui/ui/midimixer.rc | 34 + src/gui/ui/mixer.rc | 65 + src/gui/ui/notation.rc | 853 +++ src/gui/ui/notationeraser.rc | 12 + src/gui/ui/notationselector.rc | 26 + src/gui/ui/noteinserter.rc | 23 + src/gui/ui/restinserter.rc | 13 + src/gui/ui/rosegardenui.rc | 440 ++ src/gui/ui/temporuler.rc | 19 + src/gui/ui/tempoview.rc | 96 + src/gui/ui/textinserter.rc | 11 + src/gui/ui/triggermanager.rc | 40 + src/gui/widgets/AudioFaderBox.cpp | 294 + src/gui/widgets/AudioFaderBox.h | 114 + src/gui/widgets/AudioListItem.h | 97 + src/gui/widgets/AudioListView.cpp | 67 + src/gui/widgets/AudioListView.h | 44 + src/gui/widgets/AudioRouteMenu.cpp | 381 + src/gui/widgets/AudioRouteMenu.h | 94 + src/gui/widgets/AudioVUMeter.cpp | 103 + src/gui/widgets/AudioVUMeter.h | 96 + src/gui/widgets/BigArrowButton.h | 47 + src/gui/widgets/CollapsingFrame.cpp | 148 + src/gui/widgets/CollapsingFrame.h | 75 + src/gui/widgets/ColourTable.cpp | 131 + src/gui/widgets/ColourTable.h | 72 + src/gui/widgets/ColourTableItem.cpp | 52 + src/gui/widgets/ColourTableItem.h | 60 + src/gui/widgets/CurrentProgressDialog.cpp | 84 + src/gui/widgets/CurrentProgressDialog.h | 81 + src/gui/widgets/DiatonicPitchChooser.cpp | 244 + src/gui/widgets/DiatonicPitchChooser.h | 103 + src/gui/widgets/Fader.cpp | 567 ++ src/gui/widgets/Fader.h | 137 + src/gui/widgets/HSpinBox.cpp | 81 + src/gui/widgets/HSpinBox.h | 67 + src/gui/widgets/Label.cpp | 2 + src/gui/widgets/Label.h | 63 + src/gui/widgets/MidiFaderWidget.cpp | 41 + src/gui/widgets/MidiFaderWidget.h | 72 + src/gui/widgets/PitchChooser.cpp | 113 + src/gui/widgets/PitchChooser.h | 73 + src/gui/widgets/PitchDragLabel.cpp | 269 + src/gui/widgets/PitchDragLabel.h | 99 + src/gui/widgets/PluginControl.cpp | 228 + src/gui/widgets/PluginControl.h | 104 + src/gui/widgets/ProgressBar.cpp | 44 + src/gui/widgets/ProgressBar.h | 56 + src/gui/widgets/ProgressDialog.cpp | 209 + src/gui/widgets/ProgressDialog.h | 98 + src/gui/widgets/QDeferScrollView.cpp | 52 + src/gui/widgets/QDeferScrollView.h | 75 + src/gui/widgets/QuantizeParameters.cpp | 497 ++ src/gui/widgets/QuantizeParameters.h | 117 + src/gui/widgets/RosegardenPopupMenu.h | 43 + src/gui/widgets/Rotary.cpp | 560 ++ src/gui/widgets/Rotary.h | 167 + src/gui/widgets/ScrollBox.cpp | 159 + src/gui/widgets/ScrollBox.h | 89 + src/gui/widgets/ScrollBoxDialog.cpp | 68 + src/gui/widgets/ScrollBoxDialog.h | 71 + src/gui/widgets/SpinBox.cpp | 73 + src/gui/widgets/SpinBox.h | 65 + src/gui/widgets/TextFloat.cpp | 112 + src/gui/widgets/TextFloat.h | 64 + src/gui/widgets/TimeWidget.cpp | 668 ++ src/gui/widgets/TimeWidget.h | 125 + src/gui/widgets/TristateCheckBox.cpp | 43 + src/gui/widgets/TristateCheckBox.h | 69 + src/gui/widgets/VUMeter.cpp | 694 ++ src/gui/widgets/VUMeter.h | 154 + src/gui/widgets/WheelyButton.cpp | 35 + src/gui/widgets/WheelyButton.h | 68 + src/gui/widgets/ZoomSlider.cpp | 34 + src/gui/widgets/ZoomSlider.h | 175 + src/helpers/rosegarden-audiofile-importer | 270 + src/helpers/rosegarden-lilypondview | 395 + src/helpers/rosegarden-project-package | 839 ++ src/misc/Debug.cpp | 396 + src/misc/Debug.h | 166 + src/misc/Strings.cpp | 110 + src/misc/Strings.h | 38 + src/misc/stableheaders.h | 208 + src/sequencer/ControlBlockMmapper.cpp | 81 + src/sequencer/ControlBlockMmapper.h | 94 + src/sequencer/MmappedSegment.cpp | 702 ++ src/sequencer/MmappedSegment.h | 185 + src/sequencer/RosegardenSequencerApp.cpp | 1850 +++++ src/sequencer/RosegardenSequencerApp.h | 531 ++ src/sequencer/RosegardenSequencerIface.h | 364 + src/sequencer/SequencerMmapper.cpp | 146 + src/sequencer/SequencerMmapper.h | 103 + src/sequencer/main.cpp | 246 + src/sound/AlsaDriver.cpp | 5476 +++++++++++++ src/sound/AlsaDriver.h | 561 ++ src/sound/AlsaPort.cpp | 192 + src/sound/AlsaPort.h | 86 + src/sound/AudioCache.cpp | 139 + src/sound/AudioCache.h | 98 + src/sound/AudioFile.cpp | 75 + src/sound/AudioFile.h | 216 + src/sound/AudioFileManager.cpp | 1257 +++ src/sound/AudioFileManager.h | 327 + src/sound/AudioFileTimeStretcher.cpp | 268 + src/sound/AudioFileTimeStretcher.h | 76 + src/sound/AudioPlayQueue.cpp | 501 ++ src/sound/AudioPlayQueue.h | 168 + src/sound/AudioProcess.cpp | 2463 ++++++ src/sound/AudioProcess.h | 390 + src/sound/AudioTimeStretcher.cpp | 667 ++ src/sound/AudioTimeStretcher.h | 221 + src/sound/Audit.cpp | 30 + src/sound/Audit.h | 60 + src/sound/BWFAudioFile.cpp | 171 + src/sound/BWFAudioFile.h | 94 + src/sound/ControlBlock.cpp | 181 + src/sound/ControlBlock.h | 128 + src/sound/DSSIPluginFactory.cpp | 396 + src/sound/DSSIPluginFactory.h | 72 + src/sound/DSSIPluginInstance.cpp | 1208 +++ src/sound/DSSIPluginInstance.h | 193 + src/sound/DummyDriver.h | 166 + src/sound/ExternalTransport.h | 67 + src/sound/JackDriver.cpp | 2480 ++++++ src/sound/JackDriver.h | 297 + src/sound/LADSPAPluginFactory.cpp | 841 ++ src/sound/LADSPAPluginFactory.h | 104 + src/sound/LADSPAPluginInstance.cpp | 435 ++ src/sound/LADSPAPluginInstance.h | 137 + src/sound/MP3AudioFile.cpp | 329 + src/sound/MP3AudioFile.h | 128 + src/sound/MappedCommon.h | 68 + src/sound/MappedComposition.cpp | 216 + src/sound/MappedComposition.h | 93 + src/sound/MappedDevice.cpp | 250 + src/sound/MappedDevice.h | 103 + src/sound/MappedEvent.cpp | 593 ++ src/sound/MappedEvent.h | 546 ++ src/sound/MappedInstrument.cpp | 153 + src/sound/MappedInstrument.h | 106 + src/sound/MappedRealTime.cpp | 62 + src/sound/MappedRealTime.h | 56 + src/sound/MappedStudio.cpp | 1719 +++++ src/sound/MappedStudio.h | 552 ++ src/sound/Midi.h | 184 + src/sound/MidiEvent.cpp | 289 + src/sound/MidiEvent.h | 141 + src/sound/MidiFile.cpp | 2261 ++++++ src/sound/MidiFile.h | 173 + src/sound/MidiMapping.xml | 133 + src/sound/PeakFile.cpp | 1033 +++ src/sound/PeakFile.h | 196 + src/sound/PeakFileManager.cpp | 327 + src/sound/PeakFileManager.h | 162 + src/sound/PlayableAudioFile.cpp | 1086 +++ src/sound/PlayableAudioFile.h | 219 + src/sound/PluginFactory.cpp | 120 + src/sound/PluginFactory.h | 97 + src/sound/PluginIdentifier.cpp | 72 + src/sound/PluginIdentifier.h | 50 + src/sound/RIFFAudioFile.cpp | 686 ++ src/sound/RIFFAudioFile.h | 168 + src/sound/RecordableAudioFile.cpp | 164 + src/sound/RecordableAudioFile.h | 68 + src/sound/RingBuffer.h | 572 ++ src/sound/RosegardenMidiRecord.mcopclass | 5 + src/sound/RunnablePluginInstance.cpp | 42 + src/sound/RunnablePluginInstance.h | 114 + src/sound/SF2PatchExtractor.cpp | 217 + src/sound/SF2PatchExtractor.h | 58 + src/sound/SampleWindow.h | 192 + src/sound/Scavenger.h | 211 + src/sound/SequencerDataBlock.cpp | 361 + src/sound/SequencerDataBlock.h | 140 + src/sound/SoundDriver.cpp | 391 + src/sound/SoundDriver.h | 529 ++ src/sound/SoundDriverFactory.cpp | 66 + src/sound/SoundDriverFactory.h | 37 + src/sound/SoundFile.cpp | 295 + src/sound/SoundFile.h | 155 + src/sound/WAVAudioFile.cpp | 255 + src/sound/WAVAudioFile.h | 93 + src/test/accidentals.cpp | 88 + src/test/dummy.cpp | 6 + src/test/segmenttransposecommand.cpp | 161 + src/test/transpose.cpp | 154 + 1205 files changed, 289781 insertions(+) create mode 100644 src/BaseFileList.txt create mode 100644 src/CMakeLists.txt create mode 100644 src/GUIFileList.txt create mode 100644 src/MiscFileList.txt create mode 100644 src/SequencerFileList.txt create mode 100644 src/SoundFileList.txt create mode 100644 src/TestFileList.txt create mode 100644 src/base/AnalysisTypes.cpp create mode 100644 src/base/AnalysisTypes.h create mode 100644 src/base/AudioDevice.cpp create mode 100644 src/base/AudioDevice.h create mode 100644 src/base/AudioLevel.cpp create mode 100644 src/base/AudioLevel.h create mode 100644 src/base/AudioPluginInstance.cpp create mode 100644 src/base/AudioPluginInstance.h create mode 100644 src/base/BaseProperties.cpp create mode 100644 src/base/BaseProperties.h create mode 100644 src/base/BasicQuantizer.cpp create mode 100644 src/base/BasicQuantizer.h create mode 100644 src/base/Clipboard.cpp create mode 100644 src/base/Clipboard.h create mode 100644 src/base/Colour.cpp create mode 100644 src/base/Colour.h create mode 100644 src/base/ColourMap.cpp create mode 100644 src/base/ColourMap.h create mode 100644 src/base/Composition.cpp create mode 100644 src/base/Composition.h create mode 100644 src/base/CompositionTimeSliceAdapter.cpp create mode 100644 src/base/CompositionTimeSliceAdapter.h create mode 100644 src/base/Configuration.cpp create mode 100644 src/base/Configuration.h create mode 100644 src/base/ControlParameter.cpp create mode 100644 src/base/ControlParameter.h create mode 100644 src/base/Controllable.h create mode 100644 src/base/Device.cpp create mode 100644 src/base/Device.h create mode 100644 src/base/Equation.cpp create mode 100644 src/base/Equation.h create mode 100644 src/base/Event.cpp create mode 100644 src/base/Event.h create mode 100644 src/base/Exception.cpp create mode 100644 src/base/Exception.h create mode 100644 src/base/FastVector.h create mode 100644 src/base/Instrument.cpp create mode 100644 src/base/Instrument.h create mode 100644 src/base/LayoutEngine.cpp create mode 100644 src/base/LayoutEngine.h create mode 100644 src/base/LegatoQuantizer.cpp create mode 100644 src/base/LegatoQuantizer.h create mode 100644 src/base/Marker.cpp create mode 100644 src/base/Marker.h create mode 100644 src/base/MidiDevice.cpp create mode 100644 src/base/MidiDevice.h create mode 100644 src/base/MidiProgram.cpp create mode 100644 src/base/MidiProgram.h create mode 100644 src/base/MidiTypes.cpp create mode 100644 src/base/MidiTypes.h create mode 100644 src/base/NotationQuantizer.cpp create mode 100644 src/base/NotationQuantizer.h create mode 100644 src/base/NotationRules.h create mode 100644 src/base/NotationTypes.cpp create mode 100644 src/base/NotationTypes.h create mode 100644 src/base/Profiler.cpp create mode 100644 src/base/Profiler.h create mode 100644 src/base/Property.cpp create mode 100644 src/base/Property.h create mode 100644 src/base/PropertyMap.cpp create mode 100644 src/base/PropertyMap.h create mode 100644 src/base/PropertyName.cpp create mode 100644 src/base/PropertyName.h create mode 100644 src/base/Quantizer.cpp create mode 100644 src/base/Quantizer.h create mode 100644 src/base/RealTime.cpp create mode 100644 src/base/RealTime.h create mode 100644 src/base/RefreshStatus.h create mode 100644 src/base/RulerScale.cpp create mode 100644 src/base/RulerScale.h create mode 100644 src/base/ScriptAPI.cpp create mode 100644 src/base/ScriptAPI.h create mode 100644 src/base/Segment.cpp create mode 100644 src/base/Segment.h create mode 100644 src/base/SegmentMatrixHelper.cpp create mode 100644 src/base/SegmentMatrixHelper.h create mode 100644 src/base/SegmentNotationHelper.cpp create mode 100644 src/base/SegmentNotationHelper.h create mode 100644 src/base/SegmentPerformanceHelper.cpp create mode 100644 src/base/SegmentPerformanceHelper.h create mode 100644 src/base/Selection.cpp create mode 100644 src/base/Selection.h create mode 100644 src/base/Sets.cpp create mode 100644 src/base/Sets.h create mode 100644 src/base/SnapGrid.cpp create mode 100644 src/base/SnapGrid.h create mode 100644 src/base/SoftSynthDevice.cpp create mode 100644 src/base/SoftSynthDevice.h create mode 100644 src/base/Staff.cpp create mode 100644 src/base/Staff.h create mode 100644 src/base/StaffExportTypes.h create mode 100644 src/base/Studio.cpp create mode 100644 src/base/Studio.h create mode 100644 src/base/Track.cpp create mode 100644 src/base/Track.h create mode 100644 src/base/TriggerSegment.cpp create mode 100644 src/base/TriggerSegment.h create mode 100644 src/base/ViewElement.cpp create mode 100644 src/base/ViewElement.h create mode 100644 src/base/XmlExportable.cpp create mode 100644 src/base/XmlExportable.h create mode 100644 src/base/test/Makefile create mode 100644 src/base/test/accidentals.cpp create mode 100644 src/base/test/colour.cpp create mode 100644 src/base/test/colour.output create mode 100644 src/base/test/pitch.cpp create mode 100644 src/base/test/seq/Makefile create mode 100644 src/base/test/seq/complainer.c create mode 100644 src/base/test/seq/generator.c create mode 100644 src/base/test/seq/queue-timer-jack.c create mode 100644 src/base/test/seq/queue-timer.c create mode 100644 src/base/test/test.cpp create mode 100644 src/base/test/thread.cpp create mode 100644 src/base/test/transpose.cpp create mode 100644 src/base/test/utf8.cpp create mode 100644 src/commands/edit/AddDotCommand.cpp create mode 100644 src/commands/edit/AddDotCommand.h create mode 100644 src/commands/edit/AddMarkerCommand.cpp create mode 100644 src/commands/edit/AddMarkerCommand.h create mode 100644 src/commands/edit/ChangeVelocityCommand.cpp create mode 100644 src/commands/edit/ChangeVelocityCommand.h create mode 100644 src/commands/edit/ClearTriggersCommand.cpp create mode 100644 src/commands/edit/ClearTriggersCommand.h create mode 100644 src/commands/edit/CollapseNotesCommand.cpp create mode 100644 src/commands/edit/CollapseNotesCommand.h create mode 100644 src/commands/edit/CopyCommand.cpp create mode 100644 src/commands/edit/CopyCommand.h create mode 100644 src/commands/edit/CutAndCloseCommand.cpp create mode 100644 src/commands/edit/CutAndCloseCommand.h create mode 100644 src/commands/edit/CutCommand.cpp create mode 100644 src/commands/edit/CutCommand.h create mode 100644 src/commands/edit/EraseCommand.cpp create mode 100644 src/commands/edit/EraseCommand.h create mode 100644 src/commands/edit/EventEditCommand.cpp create mode 100644 src/commands/edit/EventEditCommand.h create mode 100644 src/commands/edit/EventInsertionCommand.cpp create mode 100644 src/commands/edit/EventInsertionCommand.h create mode 100644 src/commands/edit/EventQuantizeCommand.cpp create mode 100644 src/commands/edit/EventQuantizeCommand.h create mode 100644 src/commands/edit/EventUnquantizeCommand.cpp create mode 100644 src/commands/edit/EventUnquantizeCommand.h create mode 100644 src/commands/edit/InsertTriggerNoteCommand.cpp create mode 100644 src/commands/edit/InsertTriggerNoteCommand.h create mode 100644 src/commands/edit/InvertCommand.cpp create mode 100644 src/commands/edit/InvertCommand.h create mode 100644 src/commands/edit/ModifyMarkerCommand.cpp create mode 100644 src/commands/edit/ModifyMarkerCommand.h create mode 100644 src/commands/edit/MoveAcrossSegmentsCommand.cpp create mode 100644 src/commands/edit/MoveAcrossSegmentsCommand.h create mode 100644 src/commands/edit/MoveCommand.cpp create mode 100644 src/commands/edit/MoveCommand.h create mode 100644 src/commands/edit/PasteEventsCommand.cpp create mode 100644 src/commands/edit/PasteEventsCommand.h create mode 100644 src/commands/edit/PasteSegmentsCommand.cpp create mode 100644 src/commands/edit/PasteSegmentsCommand.h create mode 100644 src/commands/edit/RemoveMarkerCommand.cpp create mode 100644 src/commands/edit/RemoveMarkerCommand.h create mode 100644 src/commands/edit/RescaleCommand.cpp create mode 100644 src/commands/edit/RescaleCommand.h create mode 100644 src/commands/edit/RetrogradeCommand.cpp create mode 100644 src/commands/edit/RetrogradeCommand.h create mode 100644 src/commands/edit/RetrogradeInvertCommand.cpp create mode 100644 src/commands/edit/RetrogradeInvertCommand.h create mode 100644 src/commands/edit/SelectionPropertyCommand.cpp create mode 100644 src/commands/edit/SelectionPropertyCommand.h create mode 100644 src/commands/edit/SetLyricsCommand.cpp create mode 100644 src/commands/edit/SetLyricsCommand.h create mode 100644 src/commands/edit/SetNoteTypeCommand.cpp create mode 100644 src/commands/edit/SetNoteTypeCommand.h create mode 100644 src/commands/edit/SetTriggerCommand.cpp create mode 100644 src/commands/edit/SetTriggerCommand.h create mode 100644 src/commands/edit/TransposeCommand.cpp create mode 100644 src/commands/edit/TransposeCommand.h create mode 100644 src/commands/matrix/MatrixEraseCommand.cpp create mode 100644 src/commands/matrix/MatrixEraseCommand.h create mode 100644 src/commands/matrix/MatrixInsertionCommand.cpp create mode 100644 src/commands/matrix/MatrixInsertionCommand.h create mode 100644 src/commands/matrix/MatrixModifyCommand.cpp create mode 100644 src/commands/matrix/MatrixModifyCommand.h create mode 100644 src/commands/matrix/MatrixPercussionInsertionCommand.cpp create mode 100644 src/commands/matrix/MatrixPercussionInsertionCommand.h create mode 100644 src/commands/notation/AddFingeringMarkCommand.cpp create mode 100644 src/commands/notation/AddFingeringMarkCommand.h create mode 100644 src/commands/notation/AddIndicationCommand.cpp create mode 100644 src/commands/notation/AddIndicationCommand.h create mode 100644 src/commands/notation/AddMarkCommand.cpp create mode 100644 src/commands/notation/AddMarkCommand.h create mode 100644 src/commands/notation/AddSlashesCommand.cpp create mode 100644 src/commands/notation/AddSlashesCommand.h create mode 100644 src/commands/notation/AddTextMarkCommand.cpp create mode 100644 src/commands/notation/AddTextMarkCommand.h create mode 100644 src/commands/notation/AutoBeamCommand.cpp create mode 100644 src/commands/notation/AutoBeamCommand.h create mode 100644 src/commands/notation/BeamCommand.cpp create mode 100644 src/commands/notation/BeamCommand.h create mode 100644 src/commands/notation/BreakCommand.cpp create mode 100644 src/commands/notation/BreakCommand.h create mode 100644 src/commands/notation/ChangeSlurPositionCommand.cpp create mode 100644 src/commands/notation/ChangeSlurPositionCommand.h create mode 100644 src/commands/notation/ChangeStemsCommand.cpp create mode 100644 src/commands/notation/ChangeStemsCommand.h create mode 100644 src/commands/notation/ChangeStyleCommand.cpp create mode 100644 src/commands/notation/ChangeStyleCommand.h create mode 100644 src/commands/notation/ChangeTiePositionCommand.cpp create mode 100644 src/commands/notation/ChangeTiePositionCommand.h create mode 100644 src/commands/notation/ClefInsertionCommand.cpp create mode 100644 src/commands/notation/ClefInsertionCommand.h create mode 100644 src/commands/notation/CollapseRestsCommand.cpp create mode 100644 src/commands/notation/CollapseRestsCommand.h create mode 100644 src/commands/notation/DeCounterpointCommand.cpp create mode 100644 src/commands/notation/DeCounterpointCommand.h create mode 100644 src/commands/notation/EraseEventCommand.cpp create mode 100644 src/commands/notation/EraseEventCommand.h create mode 100644 src/commands/notation/FixNotationQuantizeCommand.cpp create mode 100644 src/commands/notation/FixNotationQuantizeCommand.h create mode 100644 src/commands/notation/GraceCommand.cpp create mode 100644 src/commands/notation/GraceCommand.h create mode 100644 src/commands/notation/GuitarChordInsertionCommand.cpp create mode 100644 src/commands/notation/GuitarChordInsertionCommand.h create mode 100644 src/commands/notation/IncrementDisplacementsCommand.cpp create mode 100644 src/commands/notation/IncrementDisplacementsCommand.h create mode 100644 src/commands/notation/InterpretCommand.cpp create mode 100644 src/commands/notation/InterpretCommand.h create mode 100644 src/commands/notation/KeyInsertionCommand.cpp create mode 100644 src/commands/notation/KeyInsertionCommand.h create mode 100644 src/commands/notation/MakeAccidentalsCautionaryCommand.cpp create mode 100644 src/commands/notation/MakeAccidentalsCautionaryCommand.h create mode 100644 src/commands/notation/MakeChordCommand.cpp create mode 100644 src/commands/notation/MakeChordCommand.h create mode 100644 src/commands/notation/MakeNotesViableCommand.cpp create mode 100644 src/commands/notation/MakeNotesViableCommand.h create mode 100644 src/commands/notation/MakeRegionViableCommand.cpp create mode 100644 src/commands/notation/MakeRegionViableCommand.h create mode 100644 src/commands/notation/MultiKeyInsertionCommand.cpp create mode 100644 src/commands/notation/MultiKeyInsertionCommand.h create mode 100644 src/commands/notation/NormalizeRestsCommand.cpp create mode 100644 src/commands/notation/NormalizeRestsCommand.h create mode 100644 src/commands/notation/NoteInsertionCommand.cpp create mode 100644 src/commands/notation/NoteInsertionCommand.h create mode 100644 src/commands/notation/RemoveFingeringMarksCommand.cpp create mode 100644 src/commands/notation/RemoveFingeringMarksCommand.h create mode 100644 src/commands/notation/RemoveMarksCommand.cpp create mode 100644 src/commands/notation/RemoveMarksCommand.h create mode 100644 src/commands/notation/RemoveNotationQuantizeCommand.cpp create mode 100644 src/commands/notation/RemoveNotationQuantizeCommand.h create mode 100644 src/commands/notation/ResetDisplacementsCommand.cpp create mode 100644 src/commands/notation/ResetDisplacementsCommand.h create mode 100644 src/commands/notation/RespellCommand.cpp create mode 100644 src/commands/notation/RespellCommand.h create mode 100644 src/commands/notation/RestInsertionCommand.cpp create mode 100644 src/commands/notation/RestInsertionCommand.h create mode 100644 src/commands/notation/RestoreSlursCommand.cpp create mode 100644 src/commands/notation/RestoreSlursCommand.h create mode 100644 src/commands/notation/RestoreStemsCommand.cpp create mode 100644 src/commands/notation/RestoreStemsCommand.h create mode 100644 src/commands/notation/RestoreTiesCommand.cpp create mode 100644 src/commands/notation/RestoreTiesCommand.h create mode 100644 src/commands/notation/SetVisibilityCommand.cpp create mode 100644 src/commands/notation/SetVisibilityCommand.h create mode 100644 src/commands/notation/SustainInsertionCommand.cpp create mode 100644 src/commands/notation/SustainInsertionCommand.h create mode 100644 src/commands/notation/TextChangeCommand.cpp create mode 100644 src/commands/notation/TextChangeCommand.h create mode 100644 src/commands/notation/TextInsertionCommand.cpp create mode 100644 src/commands/notation/TextInsertionCommand.h create mode 100644 src/commands/notation/TieNotesCommand.cpp create mode 100644 src/commands/notation/TieNotesCommand.h create mode 100644 src/commands/notation/TupletCommand.cpp create mode 100644 src/commands/notation/TupletCommand.h create mode 100644 src/commands/notation/UnGraceCommand.cpp create mode 100644 src/commands/notation/UnGraceCommand.h create mode 100644 src/commands/notation/UnTupletCommand.cpp create mode 100644 src/commands/notation/UnTupletCommand.h create mode 100644 src/commands/notation/UntieNotesCommand.cpp create mode 100644 src/commands/notation/UntieNotesCommand.h create mode 100644 src/commands/segment/AddTempoChangeCommand.cpp create mode 100644 src/commands/segment/AddTempoChangeCommand.h create mode 100644 src/commands/segment/AddTimeSignatureAndNormalizeCommand.cpp create mode 100644 src/commands/segment/AddTimeSignatureAndNormalizeCommand.h create mode 100644 src/commands/segment/AddTimeSignatureCommand.cpp create mode 100644 src/commands/segment/AddTimeSignatureCommand.h create mode 100644 src/commands/segment/AddTracksCommand.cpp create mode 100644 src/commands/segment/AddTracksCommand.h create mode 100644 src/commands/segment/AddTriggerSegmentCommand.cpp create mode 100644 src/commands/segment/AddTriggerSegmentCommand.h create mode 100644 src/commands/segment/AudioSegmentAutoSplitCommand.cpp create mode 100644 src/commands/segment/AudioSegmentAutoSplitCommand.h create mode 100644 src/commands/segment/AudioSegmentDistributeCommand.cpp create mode 100644 src/commands/segment/AudioSegmentDistributeCommand.h create mode 100644 src/commands/segment/AudioSegmentInsertCommand.cpp create mode 100644 src/commands/segment/AudioSegmentInsertCommand.h create mode 100644 src/commands/segment/AudioSegmentRescaleCommand.cpp create mode 100644 src/commands/segment/AudioSegmentRescaleCommand.h create mode 100644 src/commands/segment/AudioSegmentResizeFromStartCommand.cpp create mode 100644 src/commands/segment/AudioSegmentResizeFromStartCommand.h create mode 100644 src/commands/segment/AudioSegmentSplitCommand.cpp create mode 100644 src/commands/segment/AudioSegmentSplitCommand.h create mode 100644 src/commands/segment/ChangeCompositionLengthCommand.cpp create mode 100644 src/commands/segment/ChangeCompositionLengthCommand.h create mode 100644 src/commands/segment/CreateTempoMapFromSegmentCommand.cpp create mode 100644 src/commands/segment/CreateTempoMapFromSegmentCommand.h create mode 100644 src/commands/segment/CutRangeCommand.cpp create mode 100644 src/commands/segment/CutRangeCommand.h create mode 100644 src/commands/segment/DeleteRangeCommand.cpp create mode 100644 src/commands/segment/DeleteRangeCommand.h create mode 100644 src/commands/segment/DeleteTracksCommand.cpp create mode 100644 src/commands/segment/DeleteTracksCommand.h create mode 100644 src/commands/segment/DeleteTriggerSegmentCommand.cpp create mode 100644 src/commands/segment/DeleteTriggerSegmentCommand.h create mode 100644 src/commands/segment/EraseSegmentsStartingInRangeCommand.cpp create mode 100644 src/commands/segment/EraseSegmentsStartingInRangeCommand.h create mode 100644 src/commands/segment/InsertRangeCommand.cpp create mode 100644 src/commands/segment/InsertRangeCommand.h create mode 100644 src/commands/segment/ModifyDefaultTempoCommand.cpp create mode 100644 src/commands/segment/ModifyDefaultTempoCommand.h create mode 100644 src/commands/segment/MoveTracksCommand.cpp create mode 100644 src/commands/segment/MoveTracksCommand.h create mode 100644 src/commands/segment/OpenOrCloseRangeCommand.cpp create mode 100644 src/commands/segment/OpenOrCloseRangeCommand.h create mode 100644 src/commands/segment/PasteConductorDataCommand.cpp create mode 100644 src/commands/segment/PasteConductorDataCommand.h create mode 100644 src/commands/segment/PasteRangeCommand.cpp create mode 100644 src/commands/segment/PasteRangeCommand.h create mode 100644 src/commands/segment/PasteToTriggerSegmentCommand.cpp create mode 100644 src/commands/segment/PasteToTriggerSegmentCommand.h create mode 100644 src/commands/segment/RemoveTempoChangeCommand.cpp create mode 100644 src/commands/segment/RemoveTempoChangeCommand.h create mode 100644 src/commands/segment/RemoveTimeSignatureCommand.cpp create mode 100644 src/commands/segment/RemoveTimeSignatureCommand.h create mode 100644 src/commands/segment/RenameTrackCommand.cpp create mode 100644 src/commands/segment/RenameTrackCommand.h create mode 100644 src/commands/segment/SegmentAutoSplitCommand.cpp create mode 100644 src/commands/segment/SegmentAutoSplitCommand.h create mode 100644 src/commands/segment/SegmentChangePlayableRangeCommand.cpp create mode 100644 src/commands/segment/SegmentChangePlayableRangeCommand.h create mode 100644 src/commands/segment/SegmentChangeQuantizationCommand.cpp create mode 100644 src/commands/segment/SegmentChangeQuantizationCommand.h create mode 100644 src/commands/segment/SegmentChangeTransposeCommand.cpp create mode 100644 src/commands/segment/SegmentChangeTransposeCommand.h create mode 100644 src/commands/segment/SegmentColourCommand.cpp create mode 100644 src/commands/segment/SegmentColourCommand.h create mode 100644 src/commands/segment/SegmentColourMapCommand.cpp create mode 100644 src/commands/segment/SegmentColourMapCommand.h create mode 100644 src/commands/segment/SegmentCommand.cpp create mode 100644 src/commands/segment/SegmentCommand.h create mode 100644 src/commands/segment/SegmentCommandRepeat.cpp create mode 100644 src/commands/segment/SegmentCommandRepeat.h create mode 100644 src/commands/segment/SegmentEraseCommand.cpp create mode 100644 src/commands/segment/SegmentEraseCommand.h create mode 100644 src/commands/segment/SegmentInsertCommand.cpp create mode 100644 src/commands/segment/SegmentInsertCommand.h create mode 100644 src/commands/segment/SegmentJoinCommand.cpp create mode 100644 src/commands/segment/SegmentJoinCommand.h create mode 100644 src/commands/segment/SegmentLabelCommand.cpp create mode 100644 src/commands/segment/SegmentLabelCommand.h create mode 100644 src/commands/segment/SegmentQuickCopyCommand.cpp create mode 100644 src/commands/segment/SegmentQuickCopyCommand.h create mode 100644 src/commands/segment/SegmentReconfigureCommand.cpp create mode 100644 src/commands/segment/SegmentReconfigureCommand.h create mode 100644 src/commands/segment/SegmentRecordCommand.cpp create mode 100644 src/commands/segment/SegmentRecordCommand.h create mode 100644 src/commands/segment/SegmentRepeatToCopyCommand.cpp create mode 100644 src/commands/segment/SegmentRepeatToCopyCommand.h create mode 100644 src/commands/segment/SegmentRescaleCommand.cpp create mode 100644 src/commands/segment/SegmentRescaleCommand.h create mode 100644 src/commands/segment/SegmentResizeFromStartCommand.cpp create mode 100644 src/commands/segment/SegmentResizeFromStartCommand.h create mode 100644 src/commands/segment/SegmentSingleRepeatToCopyCommand.cpp create mode 100644 src/commands/segment/SegmentSingleRepeatToCopyCommand.h create mode 100644 src/commands/segment/SegmentSplitByPitchCommand.cpp create mode 100644 src/commands/segment/SegmentSplitByPitchCommand.h create mode 100644 src/commands/segment/SegmentSplitByRecordingSrcCommand.cpp create mode 100644 src/commands/segment/SegmentSplitByRecordingSrcCommand.h create mode 100644 src/commands/segment/SegmentSplitCommand.cpp create mode 100644 src/commands/segment/SegmentSplitCommand.h create mode 100644 src/commands/segment/SegmentSyncClefCommand.cpp create mode 100644 src/commands/segment/SegmentSyncClefCommand.h create mode 100644 src/commands/segment/SegmentSyncCommand.cpp create mode 100644 src/commands/segment/SegmentSyncCommand.h create mode 100644 src/commands/segment/SegmentTransposeCommand.cpp create mode 100644 src/commands/segment/SegmentTransposeCommand.h create mode 100644 src/commands/segment/SetTriggerSegmentBasePitchCommand.cpp create mode 100644 src/commands/segment/SetTriggerSegmentBasePitchCommand.h create mode 100644 src/commands/segment/SetTriggerSegmentBaseVelocityCommand.cpp create mode 100644 src/commands/segment/SetTriggerSegmentBaseVelocityCommand.h create mode 100644 src/commands/segment/SetTriggerSegmentDefaultRetuneCommand.cpp create mode 100644 src/commands/segment/SetTriggerSegmentDefaultRetuneCommand.h create mode 100644 src/commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.cpp create mode 100644 src/commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.h create mode 100644 src/commands/studio/AddControlParameterCommand.cpp create mode 100644 src/commands/studio/AddControlParameterCommand.h create mode 100644 src/commands/studio/CreateOrDeleteDeviceCommand.cpp create mode 100644 src/commands/studio/CreateOrDeleteDeviceCommand.h create mode 100644 src/commands/studio/ModifyControlParameterCommand.cpp create mode 100644 src/commands/studio/ModifyControlParameterCommand.h create mode 100644 src/commands/studio/ModifyDeviceCommand.cpp create mode 100644 src/commands/studio/ModifyDeviceCommand.h create mode 100644 src/commands/studio/ModifyDeviceMappingCommand.cpp create mode 100644 src/commands/studio/ModifyDeviceMappingCommand.h create mode 100644 src/commands/studio/ModifyInstrumentMappingCommand.cpp create mode 100644 src/commands/studio/ModifyInstrumentMappingCommand.h create mode 100644 src/commands/studio/ReconnectDeviceCommand.cpp create mode 100644 src/commands/studio/ReconnectDeviceCommand.h create mode 100644 src/commands/studio/RemoveControlParameterCommand.cpp create mode 100644 src/commands/studio/RemoveControlParameterCommand.h create mode 100644 src/commands/studio/RenameDeviceCommand.cpp create mode 100644 src/commands/studio/RenameDeviceCommand.h create mode 100644 src/document/BasicCommand.cpp create mode 100644 src/document/BasicCommand.h create mode 100644 src/document/BasicSelectionCommand.cpp create mode 100644 src/document/BasicSelectionCommand.h create mode 100644 src/document/ConfigGroups.cpp create mode 100644 src/document/ConfigGroups.h create mode 100644 src/document/MultiViewCommandHistory.cpp create mode 100644 src/document/MultiViewCommandHistory.h create mode 100644 src/document/RoseXmlHandler.cpp create mode 100644 src/document/RoseXmlHandler.h create mode 100644 src/document/RosegardenGUIDoc.cpp create mode 100644 src/document/RosegardenGUIDoc.h create mode 100644 src/document/XmlStorableEvent.cpp create mode 100644 src/document/XmlStorableEvent.h create mode 100644 src/document/XmlSubHandler.cpp create mode 100644 src/document/XmlSubHandler.h create mode 100644 src/document/io/CsoundExporter.cpp create mode 100644 src/document/io/CsoundExporter.h create mode 100644 src/document/io/HydrogenLoader.cpp create mode 100644 src/document/io/HydrogenLoader.h create mode 100644 src/document/io/HydrogenXMLHandler.cpp create mode 100644 src/document/io/HydrogenXMLHandler.h create mode 100644 src/document/io/LilyPondExporter.cpp create mode 100644 src/document/io/LilyPondExporter.h create mode 100644 src/document/io/MupExporter.cpp create mode 100644 src/document/io/MupExporter.h create mode 100644 src/document/io/MusicXmlExporter.cpp create mode 100644 src/document/io/MusicXmlExporter.h create mode 100644 src/document/io/RG21Loader.cpp create mode 100644 src/document/io/RG21Loader.h create mode 100644 src/gui/application/LircClient.cpp create mode 100644 src/gui/application/LircClient.h create mode 100644 src/gui/application/LircCommander.cpp create mode 100644 src/gui/application/LircCommander.h create mode 100644 src/gui/application/RosegardenApplication.cpp create mode 100644 src/gui/application/RosegardenApplication.h create mode 100644 src/gui/application/RosegardenDCOP.h create mode 100644 src/gui/application/RosegardenGUIApp.cpp create mode 100644 src/gui/application/RosegardenGUIApp.cpp.orig create mode 100644 src/gui/application/RosegardenGUIApp.h create mode 100644 src/gui/application/RosegardenGUIView.cpp create mode 100644 src/gui/application/RosegardenGUIView.h create mode 100644 src/gui/application/RosegardenIface.cpp create mode 100644 src/gui/application/RosegardenIface.h create mode 100644 src/gui/application/SetWaitCursor.cpp create mode 100644 src/gui/application/SetWaitCursor.h create mode 100644 src/gui/application/StartupTester.cpp create mode 100644 src/gui/application/StartupTester.h create mode 100644 src/gui/application/main.cpp create mode 100644 src/gui/configuration/AudioConfigurationPage.cpp create mode 100644 src/gui/configuration/AudioConfigurationPage.h create mode 100644 src/gui/configuration/AudioPropertiesPage.cpp create mode 100644 src/gui/configuration/AudioPropertiesPage.h create mode 100644 src/gui/configuration/ColourConfigurationPage.cpp create mode 100644 src/gui/configuration/ColourConfigurationPage.h create mode 100644 src/gui/configuration/ConfigurationPage.cpp create mode 100644 src/gui/configuration/ConfigurationPage.h create mode 100644 src/gui/configuration/DocumentMetaConfigurationPage.cpp create mode 100644 src/gui/configuration/DocumentMetaConfigurationPage.h create mode 100644 src/gui/configuration/GeneralConfigurationPage.cpp create mode 100644 src/gui/configuration/GeneralConfigurationPage.h create mode 100644 src/gui/configuration/HeadersConfigurationPage.cpp create mode 100644 src/gui/configuration/HeadersConfigurationPage.h create mode 100644 src/gui/configuration/LatencyConfigurationPage.cpp create mode 100644 src/gui/configuration/LatencyConfigurationPage.h create mode 100644 src/gui/configuration/MIDIConfigurationPage.cpp create mode 100644 src/gui/configuration/MIDIConfigurationPage.h create mode 100644 src/gui/configuration/MatrixConfigurationPage.cpp create mode 100644 src/gui/configuration/MatrixConfigurationPage.h create mode 100644 src/gui/configuration/NotationConfigurationPage.cpp create mode 100644 src/gui/configuration/NotationConfigurationPage.h create mode 100644 src/gui/configuration/TabbedConfigurationPage.cpp create mode 100644 src/gui/configuration/TabbedConfigurationPage.h create mode 100644 src/gui/dialogs/AddTracksDialog.cpp create mode 100644 src/gui/dialogs/AddTracksDialog.h create mode 100644 src/gui/dialogs/AudioManagerDialog.cpp create mode 100644 src/gui/dialogs/AudioManagerDialog.h create mode 100644 src/gui/dialogs/AudioPlayingDialog.cpp create mode 100644 src/gui/dialogs/AudioPlayingDialog.h create mode 100644 src/gui/dialogs/AudioPluginDialog.cpp create mode 100644 src/gui/dialogs/AudioPluginDialog.h create mode 100644 src/gui/dialogs/AudioSplitDialog.cpp create mode 100644 src/gui/dialogs/AudioSplitDialog.h create mode 100644 src/gui/dialogs/BeatsBarsDialog.cpp create mode 100644 src/gui/dialogs/BeatsBarsDialog.h create mode 100644 src/gui/dialogs/ClefDialog.cpp create mode 100644 src/gui/dialogs/ClefDialog.h create mode 100644 src/gui/dialogs/CompositionLengthDialog.cpp create mode 100644 src/gui/dialogs/CompositionLengthDialog.h create mode 100644 src/gui/dialogs/ConfigureDialog.cpp create mode 100644 src/gui/dialogs/ConfigureDialog.h create mode 100644 src/gui/dialogs/ConfigureDialogBase.cpp create mode 100644 src/gui/dialogs/ConfigureDialogBase.h create mode 100644 src/gui/dialogs/CountdownBar.cpp create mode 100644 src/gui/dialogs/CountdownBar.h create mode 100644 src/gui/dialogs/CountdownDialog.cpp create mode 100644 src/gui/dialogs/CountdownDialog.h create mode 100644 src/gui/dialogs/DocumentConfigureDialog.cpp create mode 100644 src/gui/dialogs/DocumentConfigureDialog.h create mode 100644 src/gui/dialogs/EventEditDialog.cpp create mode 100644 src/gui/dialogs/EventEditDialog.h create mode 100644 src/gui/dialogs/EventFilterDialog.cpp create mode 100644 src/gui/dialogs/EventFilterDialog.h create mode 100644 src/gui/dialogs/EventParameterDialog.cpp create mode 100644 src/gui/dialogs/EventParameterDialog.h create mode 100644 src/gui/dialogs/ExportDeviceDialog.cpp create mode 100644 src/gui/dialogs/ExportDeviceDialog.h create mode 100644 src/gui/dialogs/FileLocateDialog.cpp create mode 100644 src/gui/dialogs/FileLocateDialog.h create mode 100644 src/gui/dialogs/FileMergeDialog.cpp create mode 100644 src/gui/dialogs/FileMergeDialog.h create mode 100644 src/gui/dialogs/FloatEdit.cpp create mode 100644 src/gui/dialogs/FloatEdit.h create mode 100644 src/gui/dialogs/IdentifyTextCodecDialog.cpp create mode 100644 src/gui/dialogs/IdentifyTextCodecDialog.h create mode 100644 src/gui/dialogs/ImportDeviceDialog.cpp create mode 100644 src/gui/dialogs/ImportDeviceDialog.h create mode 100644 src/gui/dialogs/InterpretDialog.cpp create mode 100644 src/gui/dialogs/InterpretDialog.h create mode 100644 src/gui/dialogs/IntervalDialog.cpp create mode 100644 src/gui/dialogs/IntervalDialog.h create mode 100644 src/gui/dialogs/KeySignatureDialog.cpp create mode 100644 src/gui/dialogs/KeySignatureDialog.h create mode 100644 src/gui/dialogs/LilyPondOptionsDialog.cpp create mode 100644 src/gui/dialogs/LilyPondOptionsDialog.h create mode 100644 src/gui/dialogs/LyricEditDialog.cpp create mode 100644 src/gui/dialogs/LyricEditDialog.h create mode 100644 src/gui/dialogs/MakeOrnamentDialog.cpp create mode 100644 src/gui/dialogs/MakeOrnamentDialog.h create mode 100644 src/gui/dialogs/ManageMetronomeDialog.cpp create mode 100644 src/gui/dialogs/ManageMetronomeDialog.h create mode 100644 src/gui/dialogs/MarkerModifyDialog.cpp create mode 100644 src/gui/dialogs/MarkerModifyDialog.h create mode 100644 src/gui/dialogs/PasteNotationDialog.cpp create mode 100644 src/gui/dialogs/PasteNotationDialog.h create mode 100644 src/gui/dialogs/PitchDialog.cpp create mode 100644 src/gui/dialogs/PitchDialog.h create mode 100644 src/gui/dialogs/PitchPickerDialog.cpp create mode 100644 src/gui/dialogs/PitchPickerDialog.h create mode 100644 src/gui/dialogs/QuantizeDialog.cpp create mode 100644 src/gui/dialogs/QuantizeDialog.h create mode 100644 src/gui/dialogs/RescaleDialog.cpp create mode 100644 src/gui/dialogs/RescaleDialog.h create mode 100644 src/gui/dialogs/ShowSequencerStatusDialog.cpp create mode 100644 src/gui/dialogs/ShowSequencerStatusDialog.h create mode 100644 src/gui/dialogs/SimpleEventEditDialog.cpp create mode 100644 src/gui/dialogs/SimpleEventEditDialog.h create mode 100644 src/gui/dialogs/SplitByPitchDialog.cpp create mode 100644 src/gui/dialogs/SplitByPitchDialog.h create mode 100644 src/gui/dialogs/SplitByRecordingSrcDialog.cpp create mode 100644 src/gui/dialogs/SplitByRecordingSrcDialog.h create mode 100644 src/gui/dialogs/TempoDialog.cpp create mode 100644 src/gui/dialogs/TempoDialog.h create mode 100644 src/gui/dialogs/TextEventDialog.cpp create mode 100644 src/gui/dialogs/TextEventDialog.h create mode 100644 src/gui/dialogs/TimeDialog.cpp create mode 100644 src/gui/dialogs/TimeDialog.h create mode 100644 src/gui/dialogs/TimeSignatureDialog.cpp create mode 100644 src/gui/dialogs/TimeSignatureDialog.h create mode 100644 src/gui/dialogs/TransportDialog.cpp create mode 100644 src/gui/dialogs/TransportDialog.h create mode 100644 src/gui/dialogs/TriggerSegmentDialog.cpp create mode 100644 src/gui/dialogs/TriggerSegmentDialog.h create mode 100644 src/gui/dialogs/TupletDialog.cpp create mode 100644 src/gui/dialogs/TupletDialog.h create mode 100644 src/gui/dialogs/UnusedAudioSelectionDialog.cpp create mode 100644 src/gui/dialogs/UnusedAudioSelectionDialog.h create mode 100644 src/gui/dialogs/UseOrnamentDialog.cpp create mode 100644 src/gui/dialogs/UseOrnamentDialog.h create mode 100644 src/gui/editors/eventlist/EventView.cpp create mode 100644 src/gui/editors/eventlist/EventView.h create mode 100644 src/gui/editors/eventlist/EventViewItem.cpp create mode 100644 src/gui/editors/eventlist/EventViewItem.h create mode 100644 src/gui/editors/eventlist/TrivialVelocityDialog.cpp create mode 100644 src/gui/editors/eventlist/TrivialVelocityDialog.h create mode 100644 src/gui/editors/guitar/Chord.cpp create mode 100644 src/gui/editors/guitar/Chord.h create mode 100644 src/gui/editors/guitar/ChordMap.cpp create mode 100644 src/gui/editors/guitar/ChordMap.h create mode 100644 src/gui/editors/guitar/ChordXmlHandler.cpp create mode 100644 src/gui/editors/guitar/ChordXmlHandler.h create mode 100644 src/gui/editors/guitar/Fingering.cpp create mode 100644 src/gui/editors/guitar/Fingering.h create mode 100644 src/gui/editors/guitar/FingeringBox.cpp create mode 100644 src/gui/editors/guitar/FingeringBox.h create mode 100644 src/gui/editors/guitar/FingeringListBoxItem.cpp create mode 100644 src/gui/editors/guitar/FingeringListBoxItem.h create mode 100644 src/gui/editors/guitar/GuitarChordEditorDialog.cpp create mode 100644 src/gui/editors/guitar/GuitarChordEditorDialog.h create mode 100644 src/gui/editors/guitar/GuitarChordSelectorDialog.cpp create mode 100644 src/gui/editors/guitar/GuitarChordSelectorDialog.h create mode 100644 src/gui/editors/guitar/NoteSymbols.cpp create mode 100644 src/gui/editors/guitar/NoteSymbols.h create mode 100644 src/gui/editors/matrix/MatrixCanvasView.cpp create mode 100644 src/gui/editors/matrix/MatrixCanvasView.h create mode 100644 src/gui/editors/matrix/MatrixElement.cpp create mode 100644 src/gui/editors/matrix/MatrixElement.h create mode 100644 src/gui/editors/matrix/MatrixEraser.cpp create mode 100644 src/gui/editors/matrix/MatrixEraser.h create mode 100644 src/gui/editors/matrix/MatrixHLayout.cpp create mode 100644 src/gui/editors/matrix/MatrixHLayout.h create mode 100644 src/gui/editors/matrix/MatrixMover.cpp create mode 100644 src/gui/editors/matrix/MatrixMover.h create mode 100644 src/gui/editors/matrix/MatrixPainter.cpp create mode 100644 src/gui/editors/matrix/MatrixPainter.h create mode 100644 src/gui/editors/matrix/MatrixParameterBox.cpp create mode 100644 src/gui/editors/matrix/MatrixParameterBox.h create mode 100644 src/gui/editors/matrix/MatrixResizer.cpp create mode 100644 src/gui/editors/matrix/MatrixResizer.h create mode 100644 src/gui/editors/matrix/MatrixSelector.cpp create mode 100644 src/gui/editors/matrix/MatrixSelector.h create mode 100644 src/gui/editors/matrix/MatrixStaff.cpp create mode 100644 src/gui/editors/matrix/MatrixStaff.h create mode 100644 src/gui/editors/matrix/MatrixTool.cpp create mode 100644 src/gui/editors/matrix/MatrixTool.h create mode 100644 src/gui/editors/matrix/MatrixToolBox.cpp create mode 100644 src/gui/editors/matrix/MatrixToolBox.h create mode 100644 src/gui/editors/matrix/MatrixVLayout.cpp create mode 100644 src/gui/editors/matrix/MatrixVLayout.h create mode 100644 src/gui/editors/matrix/MatrixView.cpp create mode 100644 src/gui/editors/matrix/MatrixView.h create mode 100644 src/gui/editors/matrix/PianoKeyboard.cpp create mode 100644 src/gui/editors/matrix/PianoKeyboard.h create mode 100644 src/gui/editors/matrix/QCanvasMatrixDiamond.cpp create mode 100644 src/gui/editors/matrix/QCanvasMatrixDiamond.h create mode 100644 src/gui/editors/matrix/QCanvasMatrixRectangle.cpp create mode 100644 src/gui/editors/matrix/QCanvasMatrixRectangle.h create mode 100644 src/gui/editors/notation/ClefInserter.cpp create mode 100644 src/gui/editors/notation/ClefInserter.h create mode 100644 src/gui/editors/notation/FontViewFrame.cpp create mode 100644 src/gui/editors/notation/FontViewFrame.h create mode 100644 src/gui/editors/notation/GuitarChordInserter.cpp create mode 100644 src/gui/editors/notation/GuitarChordInserter.h create mode 100644 src/gui/editors/notation/HeadersGroup.cpp create mode 100644 src/gui/editors/notation/HeadersGroup.h create mode 100644 src/gui/editors/notation/NotationCanvasView.cpp create mode 100644 src/gui/editors/notation/NotationCanvasView.h create mode 100644 src/gui/editors/notation/NotationChord.cpp create mode 100644 src/gui/editors/notation/NotationChord.h create mode 100644 src/gui/editors/notation/NotationElement.cpp create mode 100644 src/gui/editors/notation/NotationElement.h create mode 100644 src/gui/editors/notation/NotationEraser.cpp create mode 100644 src/gui/editors/notation/NotationEraser.h create mode 100644 src/gui/editors/notation/NotationGroup.cpp create mode 100644 src/gui/editors/notation/NotationGroup.h create mode 100644 src/gui/editors/notation/NotationHLayout.cpp create mode 100644 src/gui/editors/notation/NotationHLayout.h create mode 100644 src/gui/editors/notation/NotationProperties.cpp create mode 100644 src/gui/editors/notation/NotationProperties.h create mode 100644 src/gui/editors/notation/NotationSelectionPaster.cpp create mode 100644 src/gui/editors/notation/NotationSelectionPaster.h create mode 100644 src/gui/editors/notation/NotationSelector.cpp create mode 100644 src/gui/editors/notation/NotationSelector.h create mode 100644 src/gui/editors/notation/NotationStaff.cpp create mode 100644 src/gui/editors/notation/NotationStaff.h create mode 100644 src/gui/editors/notation/NotationStrings.cpp create mode 100644 src/gui/editors/notation/NotationStrings.h create mode 100644 src/gui/editors/notation/NotationTool.cpp create mode 100644 src/gui/editors/notation/NotationTool.h create mode 100644 src/gui/editors/notation/NotationToolBox.cpp create mode 100644 src/gui/editors/notation/NotationToolBox.h create mode 100644 src/gui/editors/notation/NotationVLayout.cpp create mode 100644 src/gui/editors/notation/NotationVLayout.h create mode 100644 src/gui/editors/notation/NotationView.cpp create mode 100644 src/gui/editors/notation/NotationView.h create mode 100644 src/gui/editors/notation/NoteCharacter.cpp create mode 100644 src/gui/editors/notation/NoteCharacter.h create mode 100644 src/gui/editors/notation/NoteCharacterNames.cpp create mode 100644 src/gui/editors/notation/NoteCharacterNames.h create mode 100644 src/gui/editors/notation/NoteFont.cpp create mode 100644 src/gui/editors/notation/NoteFont.h create mode 100644 src/gui/editors/notation/NoteFontFactory.cpp create mode 100644 src/gui/editors/notation/NoteFontFactory.h create mode 100644 src/gui/editors/notation/NoteFontMap.cpp create mode 100644 src/gui/editors/notation/NoteFontMap.h create mode 100644 src/gui/editors/notation/NoteFontViewer.cpp create mode 100644 src/gui/editors/notation/NoteFontViewer.h create mode 100644 src/gui/editors/notation/NoteInserter.cpp create mode 100644 src/gui/editors/notation/NoteInserter.h create mode 100644 src/gui/editors/notation/NotePixmapFactory.cpp create mode 100644 src/gui/editors/notation/NotePixmapFactory.h create mode 100644 src/gui/editors/notation/NotePixmapPainter.h create mode 100644 src/gui/editors/notation/NotePixmapParameters.cpp create mode 100644 src/gui/editors/notation/NotePixmapParameters.h create mode 100644 src/gui/editors/notation/NoteStyle.cpp create mode 100644 src/gui/editors/notation/NoteStyle.h create mode 100644 src/gui/editors/notation/NoteStyleFactory.cpp create mode 100644 src/gui/editors/notation/NoteStyleFactory.h create mode 100644 src/gui/editors/notation/NoteStyleFileReader.cpp create mode 100644 src/gui/editors/notation/NoteStyleFileReader.h create mode 100644 src/gui/editors/notation/RestInserter.cpp create mode 100644 src/gui/editors/notation/RestInserter.h create mode 100644 src/gui/editors/notation/SystemFont.cpp create mode 100644 src/gui/editors/notation/SystemFont.h create mode 100644 src/gui/editors/notation/SystemFontQt.cpp create mode 100644 src/gui/editors/notation/SystemFontQt.h create mode 100644 src/gui/editors/notation/SystemFontXft.cpp create mode 100644 src/gui/editors/notation/SystemFontXft.h create mode 100644 src/gui/editors/notation/TextInserter.cpp create mode 100644 src/gui/editors/notation/TextInserter.h create mode 100644 src/gui/editors/notation/TrackHeader.cpp create mode 100644 src/gui/editors/notation/TrackHeader.h create mode 100644 src/gui/editors/parameters/AudioInstrumentParameterPanel.cpp create mode 100644 src/gui/editors/parameters/AudioInstrumentParameterPanel.h create mode 100644 src/gui/editors/parameters/InstrumentParameterBox.cpp create mode 100644 src/gui/editors/parameters/InstrumentParameterBox.h create mode 100644 src/gui/editors/parameters/InstrumentParameterPanel.cpp create mode 100644 src/gui/editors/parameters/InstrumentParameterPanel.h create mode 100644 src/gui/editors/parameters/MIDIInstrumentParameterPanel.cpp create mode 100644 src/gui/editors/parameters/MIDIInstrumentParameterPanel.h create mode 100644 src/gui/editors/parameters/RosegardenParameterArea.cpp create mode 100644 src/gui/editors/parameters/RosegardenParameterArea.h create mode 100644 src/gui/editors/parameters/RosegardenParameterBox.cpp create mode 100644 src/gui/editors/parameters/RosegardenParameterBox.h create mode 100644 src/gui/editors/parameters/SegmentParameterBox.cpp create mode 100644 src/gui/editors/parameters/SegmentParameterBox.h create mode 100644 src/gui/editors/parameters/TrackParameterBox.cpp create mode 100644 src/gui/editors/parameters/TrackParameterBox.h create mode 100644 src/gui/editors/segment/ControlEditorDialog.cpp create mode 100644 src/gui/editors/segment/ControlEditorDialog.h create mode 100644 src/gui/editors/segment/ControlParameterEditDialog.cpp create mode 100644 src/gui/editors/segment/ControlParameterEditDialog.h create mode 100644 src/gui/editors/segment/ControlParameterItem.cpp create mode 100644 src/gui/editors/segment/ControlParameterItem.h create mode 100644 src/gui/editors/segment/MarkerEditor.cpp create mode 100644 src/gui/editors/segment/MarkerEditor.h create mode 100644 src/gui/editors/segment/MarkerEditorViewItem.cpp create mode 100644 src/gui/editors/segment/MarkerEditorViewItem.h create mode 100644 src/gui/editors/segment/PlayList.cpp create mode 100644 src/gui/editors/segment/PlayList.h create mode 100644 src/gui/editors/segment/PlayListDialog.cpp create mode 100644 src/gui/editors/segment/PlayListDialog.h create mode 100644 src/gui/editors/segment/PlayListView.cpp create mode 100644 src/gui/editors/segment/PlayListView.h create mode 100644 src/gui/editors/segment/PlayListViewItem.cpp create mode 100644 src/gui/editors/segment/PlayListViewItem.h create mode 100644 src/gui/editors/segment/TrackButtons.cpp create mode 100644 src/gui/editors/segment/TrackButtons.h create mode 100644 src/gui/editors/segment/TrackEditor.cpp create mode 100644 src/gui/editors/segment/TrackEditor.h create mode 100644 src/gui/editors/segment/TrackEditorIface.cpp create mode 100644 src/gui/editors/segment/TrackEditorIface.h create mode 100644 src/gui/editors/segment/TrackHeader.cpp create mode 100644 src/gui/editors/segment/TrackHeader.h create mode 100644 src/gui/editors/segment/TrackLabel.cpp create mode 100644 src/gui/editors/segment/TrackLabel.h create mode 100644 src/gui/editors/segment/TrackVUMeter.cpp create mode 100644 src/gui/editors/segment/TrackVUMeter.h create mode 100644 src/gui/editors/segment/TriggerManagerItem.cpp create mode 100644 src/gui/editors/segment/TriggerManagerItem.h create mode 100644 src/gui/editors/segment/TriggerSegmentManager.cpp create mode 100644 src/gui/editors/segment/TriggerSegmentManager.h create mode 100644 src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/AudioPreviewPainter.h create mode 100644 src/gui/editors/segment/segmentcanvas/AudioPreviewThread.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/AudioPreviewThread.h create mode 100644 src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/AudioPreviewUpdater.h create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionColourCache.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionColourCache.h create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionItem.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionItem.h create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionItemHelper.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionItemHelper.h create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionItemImpl.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionItemImpl.h create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionModel.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionModel.h create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionModelImpl.h create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionRect.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionRect.h create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionView.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/CompositionView.h create mode 100644 src/gui/editors/segment/segmentcanvas/PreviewRect.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/PreviewRect.h create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentEraser.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentEraser.h create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentItemPreview.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentItemPreview.h create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentJoiner.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentJoiner.h create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentMover.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentMover.h create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentOrderer.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentOrderer.h create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentPencil.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentPencil.h create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentResizer.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentResizer.h create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentSelector.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentSelector.h create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentSplitter.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentSplitter.h create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentTool.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentTool.h create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentToolBox.cpp create mode 100644 src/gui/editors/segment/segmentcanvas/SegmentToolBox.h create mode 100644 src/gui/editors/tempo/TempoListItem.cpp create mode 100644 src/gui/editors/tempo/TempoListItem.h create mode 100644 src/gui/editors/tempo/TempoView.cpp create mode 100644 src/gui/editors/tempo/TempoView.h create mode 100644 src/gui/general/ActiveItem.cpp create mode 100644 src/gui/general/ActiveItem.h create mode 100644 src/gui/general/BarLine.cpp create mode 100644 src/gui/general/BarLine.h create mode 100644 src/gui/general/BaseTool.cpp create mode 100644 src/gui/general/BaseTool.h create mode 100644 src/gui/general/BaseToolBox.cpp create mode 100644 src/gui/general/BaseToolBox.h create mode 100644 src/gui/general/CanvasCursor.cpp create mode 100644 src/gui/general/CanvasCursor.h create mode 100644 src/gui/general/CanvasItemGC.cpp create mode 100644 src/gui/general/CanvasItemGC.h create mode 100644 src/gui/general/CategoryElement.cpp create mode 100644 src/gui/general/CategoryElement.h create mode 100644 src/gui/general/ClefIndex.cpp create mode 100644 src/gui/general/ClefIndex.h create mode 100644 src/gui/general/EditTool.cpp create mode 100644 src/gui/general/EditTool.h create mode 100644 src/gui/general/EditToolBox.cpp create mode 100644 src/gui/general/EditToolBox.h create mode 100644 src/gui/general/EditView.cpp create mode 100644 src/gui/general/EditView.h create mode 100644 src/gui/general/EditViewBase.cpp create mode 100644 src/gui/general/EditViewBase.h create mode 100644 src/gui/general/EditViewTimeSigNotifier.h create mode 100644 src/gui/general/GUIPalette.cpp create mode 100644 src/gui/general/GUIPalette.h create mode 100644 src/gui/general/HZoomable.cpp create mode 100644 src/gui/general/HZoomable.h create mode 100644 src/gui/general/LinedStaff.cpp create mode 100644 src/gui/general/LinedStaff.h create mode 100644 src/gui/general/LinedStaffManager.cpp create mode 100644 src/gui/general/LinedStaffManager.h create mode 100644 src/gui/general/MidiPitchLabel.cpp create mode 100644 src/gui/general/MidiPitchLabel.h create mode 100644 src/gui/general/PixmapFunctions.cpp create mode 100644 src/gui/general/PixmapFunctions.h create mode 100644 src/gui/general/PresetElement.cpp create mode 100644 src/gui/general/PresetElement.h create mode 100644 src/gui/general/PresetGroup.cpp create mode 100644 src/gui/general/PresetGroup.h create mode 100644 src/gui/general/PresetHandlerDialog.cpp create mode 100644 src/gui/general/PresetHandlerDialog.h create mode 100644 src/gui/general/ProgressReporter.cpp create mode 100644 src/gui/general/ProgressReporter.h create mode 100644 src/gui/general/RosegardenCanvasView.cpp create mode 100644 src/gui/general/RosegardenCanvasView.h create mode 100644 src/gui/general/RosegardenScrollView.cpp create mode 100644 src/gui/general/RosegardenScrollView.h create mode 100644 src/gui/general/Spline.cpp create mode 100644 src/gui/general/Spline.h create mode 100644 src/gui/general/StaffLine.cpp create mode 100644 src/gui/general/StaffLine.h create mode 100644 src/gui/kdeext/KLedButton.cpp create mode 100644 src/gui/kdeext/KLedButton.h create mode 100644 src/gui/kdeext/KStartupLogo.cpp create mode 100644 src/gui/kdeext/KStartupLogo.h create mode 100644 src/gui/kdeext/KTmpStatusMsg.cpp create mode 100644 src/gui/kdeext/KTmpStatusMsg.h create mode 100644 src/gui/kdeext/QCanvasGroupableItem.cpp create mode 100644 src/gui/kdeext/QCanvasGroupableItem.h create mode 100644 src/gui/kdeext/QCanvasSimpleSprite.cpp create mode 100644 src/gui/kdeext/QCanvasSimpleSprite.h create mode 100644 src/gui/kdeext/RGLed.cpp create mode 100644 src/gui/kdeext/klearlook.cpp create mode 100644 src/gui/kdeext/klearlook.h create mode 100644 src/gui/rulers/ChordNameRuler.cpp create mode 100644 src/gui/rulers/ChordNameRuler.h create mode 100644 src/gui/rulers/ControlChangeCommand.cpp create mode 100644 src/gui/rulers/ControlChangeCommand.h create mode 100644 src/gui/rulers/ControlItem.cpp create mode 100644 src/gui/rulers/ControlItem.h create mode 100644 src/gui/rulers/ControlRuler.cpp create mode 100644 src/gui/rulers/ControlRuler.h create mode 100644 src/gui/rulers/ControlRulerEventEraseCommand.cpp create mode 100644 src/gui/rulers/ControlRulerEventEraseCommand.h create mode 100644 src/gui/rulers/ControlRulerEventInsertCommand.cpp create mode 100644 src/gui/rulers/ControlRulerEventInsertCommand.h create mode 100644 src/gui/rulers/ControlSelector.cpp create mode 100644 src/gui/rulers/ControlSelector.h create mode 100644 src/gui/rulers/ControlTool.h create mode 100644 src/gui/rulers/ControllerEventAdapter.cpp create mode 100644 src/gui/rulers/ControllerEventAdapter.h create mode 100644 src/gui/rulers/ControllerEventsRuler.cpp create mode 100644 src/gui/rulers/ControllerEventsRuler.h create mode 100644 src/gui/rulers/DefaultVelocityColour.cpp create mode 100644 src/gui/rulers/DefaultVelocityColour.h create mode 100644 src/gui/rulers/ElementAdapter.h create mode 100644 src/gui/rulers/LoopRuler.cpp create mode 100644 src/gui/rulers/LoopRuler.h create mode 100644 src/gui/rulers/MarkerRuler.cpp create mode 100644 src/gui/rulers/MarkerRuler.h create mode 100644 src/gui/rulers/PercussionPitchRuler.cpp create mode 100644 src/gui/rulers/PercussionPitchRuler.h create mode 100644 src/gui/rulers/PitchRuler.cpp create mode 100644 src/gui/rulers/PitchRuler.h create mode 100644 src/gui/rulers/PropertyBox.cpp create mode 100644 src/gui/rulers/PropertyBox.h create mode 100644 src/gui/rulers/PropertyControlRuler.cpp create mode 100644 src/gui/rulers/PropertyControlRuler.h create mode 100644 src/gui/rulers/PropertyViewRuler.cpp create mode 100644 src/gui/rulers/PropertyViewRuler.h create mode 100644 src/gui/rulers/RawNoteRuler.cpp create mode 100644 src/gui/rulers/RawNoteRuler.h create mode 100644 src/gui/rulers/StandardRuler.cpp create mode 100644 src/gui/rulers/StandardRuler.h create mode 100644 src/gui/rulers/TempoColour.cpp create mode 100644 src/gui/rulers/TempoColour.h create mode 100644 src/gui/rulers/TempoRuler.cpp create mode 100644 src/gui/rulers/TempoRuler.h create mode 100644 src/gui/rulers/TextRuler.cpp create mode 100644 src/gui/rulers/TextRuler.h create mode 100644 src/gui/rulers/VelocityColour.cpp create mode 100644 src/gui/rulers/VelocityColour.h create mode 100644 src/gui/rulers/ViewElementAdapter.cpp create mode 100644 src/gui/rulers/ViewElementAdapter.h create mode 100644 src/gui/seqmanager/AudioSegmentMmapper.cpp create mode 100644 src/gui/seqmanager/AudioSegmentMmapper.h create mode 100644 src/gui/seqmanager/CompositionMmapper.cpp create mode 100644 src/gui/seqmanager/CompositionMmapper.h create mode 100644 src/gui/seqmanager/ControlBlockMmapper.cpp create mode 100644 src/gui/seqmanager/ControlBlockMmapper.h create mode 100644 src/gui/seqmanager/MetronomeMmapper.cpp create mode 100644 src/gui/seqmanager/MetronomeMmapper.h create mode 100644 src/gui/seqmanager/MidiFilterDialog.cpp create mode 100644 src/gui/seqmanager/MidiFilterDialog.h create mode 100644 src/gui/seqmanager/SegmentMmapper.cpp create mode 100644 src/gui/seqmanager/SegmentMmapper.h create mode 100644 src/gui/seqmanager/SegmentMmapperFactory.cpp create mode 100644 src/gui/seqmanager/SegmentMmapperFactory.h create mode 100644 src/gui/seqmanager/SequenceManager.cpp create mode 100644 src/gui/seqmanager/SequenceManager.h create mode 100644 src/gui/seqmanager/SequencerMapper.cpp create mode 100644 src/gui/seqmanager/SequencerMapper.h create mode 100644 src/gui/seqmanager/SpecialSegmentMmapper.cpp create mode 100644 src/gui/seqmanager/SpecialSegmentMmapper.h create mode 100644 src/gui/seqmanager/TempoSegmentMmapper.cpp create mode 100644 src/gui/seqmanager/TempoSegmentMmapper.h create mode 100644 src/gui/seqmanager/TimeSigSegmentMmapper.cpp create mode 100644 src/gui/seqmanager/TimeSigSegmentMmapper.h create mode 100644 src/gui/studio/AudioMixerWindow.cpp create mode 100644 src/gui/studio/AudioMixerWindow.h create mode 100644 src/gui/studio/AudioPlugin.cpp create mode 100644 src/gui/studio/AudioPlugin.h create mode 100644 src/gui/studio/AudioPluginClipboard.cpp create mode 100644 src/gui/studio/AudioPluginClipboard.h create mode 100644 src/gui/studio/AudioPluginManager.cpp create mode 100644 src/gui/studio/AudioPluginManager.h create mode 100644 src/gui/studio/AudioPluginOSCGUI.cpp create mode 100644 src/gui/studio/AudioPluginOSCGUI.h create mode 100644 src/gui/studio/AudioPluginOSCGUIManager.cpp create mode 100644 src/gui/studio/AudioPluginOSCGUIManager.h create mode 100644 src/gui/studio/BankEditorDialog.cpp create mode 100644 src/gui/studio/BankEditorDialog.h create mode 100644 src/gui/studio/ChangeRecordDeviceCommand.cpp create mode 100644 src/gui/studio/ChangeRecordDeviceCommand.h create mode 100644 src/gui/studio/DeviceEditorDialog.cpp create mode 100644 src/gui/studio/DeviceEditorDialog.h create mode 100644 src/gui/studio/DeviceManagerDialog.cpp create mode 100644 src/gui/studio/DeviceManagerDialog.h create mode 100644 src/gui/studio/MidiBankListViewItem.cpp create mode 100644 src/gui/studio/MidiBankListViewItem.h create mode 100644 src/gui/studio/MidiDeviceListViewItem.cpp create mode 100644 src/gui/studio/MidiDeviceListViewItem.h create mode 100644 src/gui/studio/MidiKeyMapListViewItem.cpp create mode 100644 src/gui/studio/MidiKeyMapListViewItem.h create mode 100644 src/gui/studio/MidiKeyMappingEditor.cpp create mode 100644 src/gui/studio/MidiKeyMappingEditor.h create mode 100644 src/gui/studio/MidiMixerVUMeter.cpp create mode 100644 src/gui/studio/MidiMixerVUMeter.h create mode 100644 src/gui/studio/MidiMixerWindow.cpp create mode 100644 src/gui/studio/MidiMixerWindow.h create mode 100644 src/gui/studio/MidiProgramsEditor.cpp create mode 100644 src/gui/studio/MidiProgramsEditor.h create mode 100644 src/gui/studio/MixerWindow.cpp create mode 100644 src/gui/studio/MixerWindow.h create mode 100644 src/gui/studio/NameSetEditor.cpp create mode 100644 src/gui/studio/NameSetEditor.h create mode 100644 src/gui/studio/OSCMessage.cpp create mode 100644 src/gui/studio/OSCMessage.h create mode 100644 src/gui/studio/RemapInstrumentDialog.cpp create mode 100644 src/gui/studio/RemapInstrumentDialog.h create mode 100644 src/gui/studio/StudioControl.cpp create mode 100644 src/gui/studio/StudioControl.h create mode 100644 src/gui/studio/SynthPluginManagerDialog.cpp create mode 100644 src/gui/studio/SynthPluginManagerDialog.h create mode 100644 src/gui/studio/TimerCallbackAssistant.cpp create mode 100644 src/gui/studio/TimerCallbackAssistant.h create mode 100644 src/gui/ui/RosegardenTransport.ui create mode 100644 src/gui/ui/audiomanager.rc create mode 100644 src/gui/ui/bankeditor.rc create mode 100644 src/gui/ui/clefinserter.rc create mode 100644 src/gui/ui/controleditor.rc create mode 100644 src/gui/ui/devicemanager.rc create mode 100644 src/gui/ui/eventlist.rc create mode 100644 src/gui/ui/markereditor.rc create mode 100644 src/gui/ui/markerruler.rc create mode 100644 src/gui/ui/matrix.rc create mode 100644 src/gui/ui/matrixeraser.rc create mode 100644 src/gui/ui/matrixmover.rc create mode 100644 src/gui/ui/matrixpainter.rc create mode 100644 src/gui/ui/matrixresizer.rc create mode 100644 src/gui/ui/matrixselector.rc create mode 100644 src/gui/ui/midimixer.rc create mode 100644 src/gui/ui/mixer.rc create mode 100644 src/gui/ui/notation.rc create mode 100644 src/gui/ui/notationeraser.rc create mode 100644 src/gui/ui/notationselector.rc create mode 100644 src/gui/ui/noteinserter.rc create mode 100644 src/gui/ui/restinserter.rc create mode 100644 src/gui/ui/rosegardenui.rc create mode 100644 src/gui/ui/temporuler.rc create mode 100644 src/gui/ui/tempoview.rc create mode 100644 src/gui/ui/textinserter.rc create mode 100644 src/gui/ui/triggermanager.rc create mode 100644 src/gui/widgets/AudioFaderBox.cpp create mode 100644 src/gui/widgets/AudioFaderBox.h create mode 100644 src/gui/widgets/AudioListItem.h create mode 100644 src/gui/widgets/AudioListView.cpp create mode 100644 src/gui/widgets/AudioListView.h create mode 100644 src/gui/widgets/AudioRouteMenu.cpp create mode 100644 src/gui/widgets/AudioRouteMenu.h create mode 100644 src/gui/widgets/AudioVUMeter.cpp create mode 100644 src/gui/widgets/AudioVUMeter.h create mode 100644 src/gui/widgets/BigArrowButton.h create mode 100644 src/gui/widgets/CollapsingFrame.cpp create mode 100644 src/gui/widgets/CollapsingFrame.h create mode 100644 src/gui/widgets/ColourTable.cpp create mode 100644 src/gui/widgets/ColourTable.h create mode 100644 src/gui/widgets/ColourTableItem.cpp create mode 100644 src/gui/widgets/ColourTableItem.h create mode 100644 src/gui/widgets/CurrentProgressDialog.cpp create mode 100644 src/gui/widgets/CurrentProgressDialog.h create mode 100644 src/gui/widgets/DiatonicPitchChooser.cpp create mode 100644 src/gui/widgets/DiatonicPitchChooser.h create mode 100644 src/gui/widgets/Fader.cpp create mode 100644 src/gui/widgets/Fader.h create mode 100644 src/gui/widgets/HSpinBox.cpp create mode 100644 src/gui/widgets/HSpinBox.h create mode 100644 src/gui/widgets/Label.cpp create mode 100644 src/gui/widgets/Label.h create mode 100644 src/gui/widgets/MidiFaderWidget.cpp create mode 100644 src/gui/widgets/MidiFaderWidget.h create mode 100644 src/gui/widgets/PitchChooser.cpp create mode 100644 src/gui/widgets/PitchChooser.h create mode 100644 src/gui/widgets/PitchDragLabel.cpp create mode 100644 src/gui/widgets/PitchDragLabel.h create mode 100644 src/gui/widgets/PluginControl.cpp create mode 100644 src/gui/widgets/PluginControl.h create mode 100644 src/gui/widgets/ProgressBar.cpp create mode 100644 src/gui/widgets/ProgressBar.h create mode 100644 src/gui/widgets/ProgressDialog.cpp create mode 100644 src/gui/widgets/ProgressDialog.h create mode 100644 src/gui/widgets/QDeferScrollView.cpp create mode 100644 src/gui/widgets/QDeferScrollView.h create mode 100644 src/gui/widgets/QuantizeParameters.cpp create mode 100644 src/gui/widgets/QuantizeParameters.h create mode 100644 src/gui/widgets/RosegardenPopupMenu.h create mode 100644 src/gui/widgets/Rotary.cpp create mode 100644 src/gui/widgets/Rotary.h create mode 100644 src/gui/widgets/ScrollBox.cpp create mode 100644 src/gui/widgets/ScrollBox.h create mode 100644 src/gui/widgets/ScrollBoxDialog.cpp create mode 100644 src/gui/widgets/ScrollBoxDialog.h create mode 100644 src/gui/widgets/SpinBox.cpp create mode 100644 src/gui/widgets/SpinBox.h create mode 100644 src/gui/widgets/TextFloat.cpp create mode 100644 src/gui/widgets/TextFloat.h create mode 100644 src/gui/widgets/TimeWidget.cpp create mode 100644 src/gui/widgets/TimeWidget.h create mode 100644 src/gui/widgets/TristateCheckBox.cpp create mode 100644 src/gui/widgets/TristateCheckBox.h create mode 100644 src/gui/widgets/VUMeter.cpp create mode 100644 src/gui/widgets/VUMeter.h create mode 100644 src/gui/widgets/WheelyButton.cpp create mode 100644 src/gui/widgets/WheelyButton.h create mode 100644 src/gui/widgets/ZoomSlider.cpp create mode 100644 src/gui/widgets/ZoomSlider.h create mode 100755 src/helpers/rosegarden-audiofile-importer create mode 100755 src/helpers/rosegarden-lilypondview create mode 100755 src/helpers/rosegarden-project-package create mode 100644 src/misc/Debug.cpp create mode 100644 src/misc/Debug.h create mode 100644 src/misc/Strings.cpp create mode 100644 src/misc/Strings.h create mode 100644 src/misc/stableheaders.h create mode 100644 src/sequencer/ControlBlockMmapper.cpp create mode 100644 src/sequencer/ControlBlockMmapper.h create mode 100644 src/sequencer/MmappedSegment.cpp create mode 100644 src/sequencer/MmappedSegment.h create mode 100644 src/sequencer/RosegardenSequencerApp.cpp create mode 100644 src/sequencer/RosegardenSequencerApp.h create mode 100644 src/sequencer/RosegardenSequencerIface.h create mode 100644 src/sequencer/SequencerMmapper.cpp create mode 100644 src/sequencer/SequencerMmapper.h create mode 100644 src/sequencer/main.cpp create mode 100644 src/sound/AlsaDriver.cpp create mode 100644 src/sound/AlsaDriver.h create mode 100644 src/sound/AlsaPort.cpp create mode 100644 src/sound/AlsaPort.h create mode 100644 src/sound/AudioCache.cpp create mode 100644 src/sound/AudioCache.h create mode 100644 src/sound/AudioFile.cpp create mode 100644 src/sound/AudioFile.h create mode 100644 src/sound/AudioFileManager.cpp create mode 100644 src/sound/AudioFileManager.h create mode 100644 src/sound/AudioFileTimeStretcher.cpp create mode 100644 src/sound/AudioFileTimeStretcher.h create mode 100644 src/sound/AudioPlayQueue.cpp create mode 100644 src/sound/AudioPlayQueue.h create mode 100644 src/sound/AudioProcess.cpp create mode 100644 src/sound/AudioProcess.h create mode 100644 src/sound/AudioTimeStretcher.cpp create mode 100644 src/sound/AudioTimeStretcher.h create mode 100644 src/sound/Audit.cpp create mode 100644 src/sound/Audit.h create mode 100644 src/sound/BWFAudioFile.cpp create mode 100644 src/sound/BWFAudioFile.h create mode 100644 src/sound/ControlBlock.cpp create mode 100644 src/sound/ControlBlock.h create mode 100644 src/sound/DSSIPluginFactory.cpp create mode 100644 src/sound/DSSIPluginFactory.h create mode 100644 src/sound/DSSIPluginInstance.cpp create mode 100644 src/sound/DSSIPluginInstance.h create mode 100644 src/sound/DummyDriver.h create mode 100644 src/sound/ExternalTransport.h create mode 100644 src/sound/JackDriver.cpp create mode 100644 src/sound/JackDriver.h create mode 100644 src/sound/LADSPAPluginFactory.cpp create mode 100644 src/sound/LADSPAPluginFactory.h create mode 100644 src/sound/LADSPAPluginInstance.cpp create mode 100644 src/sound/LADSPAPluginInstance.h create mode 100644 src/sound/MP3AudioFile.cpp create mode 100644 src/sound/MP3AudioFile.h create mode 100644 src/sound/MappedCommon.h create mode 100644 src/sound/MappedComposition.cpp create mode 100644 src/sound/MappedComposition.h create mode 100644 src/sound/MappedDevice.cpp create mode 100644 src/sound/MappedDevice.h create mode 100644 src/sound/MappedEvent.cpp create mode 100644 src/sound/MappedEvent.h create mode 100644 src/sound/MappedInstrument.cpp create mode 100644 src/sound/MappedInstrument.h create mode 100644 src/sound/MappedRealTime.cpp create mode 100644 src/sound/MappedRealTime.h create mode 100644 src/sound/MappedStudio.cpp create mode 100644 src/sound/MappedStudio.h create mode 100644 src/sound/Midi.h create mode 100644 src/sound/MidiEvent.cpp create mode 100644 src/sound/MidiEvent.h create mode 100644 src/sound/MidiFile.cpp create mode 100644 src/sound/MidiFile.h create mode 100644 src/sound/MidiMapping.xml create mode 100644 src/sound/PeakFile.cpp create mode 100644 src/sound/PeakFile.h create mode 100644 src/sound/PeakFileManager.cpp create mode 100644 src/sound/PeakFileManager.h create mode 100644 src/sound/PlayableAudioFile.cpp create mode 100644 src/sound/PlayableAudioFile.h create mode 100644 src/sound/PluginFactory.cpp create mode 100644 src/sound/PluginFactory.h create mode 100644 src/sound/PluginIdentifier.cpp create mode 100644 src/sound/PluginIdentifier.h create mode 100644 src/sound/RIFFAudioFile.cpp create mode 100644 src/sound/RIFFAudioFile.h create mode 100644 src/sound/RecordableAudioFile.cpp create mode 100644 src/sound/RecordableAudioFile.h create mode 100644 src/sound/RingBuffer.h create mode 100644 src/sound/RosegardenMidiRecord.mcopclass create mode 100644 src/sound/RunnablePluginInstance.cpp create mode 100644 src/sound/RunnablePluginInstance.h create mode 100644 src/sound/SF2PatchExtractor.cpp create mode 100644 src/sound/SF2PatchExtractor.h create mode 100644 src/sound/SampleWindow.h create mode 100644 src/sound/Scavenger.h create mode 100644 src/sound/SequencerDataBlock.cpp create mode 100644 src/sound/SequencerDataBlock.h create mode 100644 src/sound/SoundDriver.cpp create mode 100644 src/sound/SoundDriver.h create mode 100644 src/sound/SoundDriverFactory.cpp create mode 100644 src/sound/SoundDriverFactory.h create mode 100644 src/sound/SoundFile.cpp create mode 100644 src/sound/SoundFile.h create mode 100644 src/sound/WAVAudioFile.cpp create mode 100644 src/sound/WAVAudioFile.h create mode 100644 src/test/accidentals.cpp create mode 100644 src/test/dummy.cpp create mode 100644 src/test/segmenttransposecommand.cpp create mode 100644 src/test/transpose.cpp (limited to 'src') diff --git a/src/BaseFileList.txt b/src/BaseFileList.txt new file mode 100644 index 0000000..f3f7b75 --- /dev/null +++ b/src/BaseFileList.txt @@ -0,0 +1,102 @@ +SET(base_SRCS + base/AnalysisTypes.cpp + base/AnalysisTypes.h + base/AudioDevice.cpp + base/AudioDevice.h + base/AudioLevel.cpp + base/AudioLevel.h + base/AudioPluginInstance.cpp + base/AudioPluginInstance.h + base/BaseProperties.cpp + base/BaseProperties.h + base/BasicQuantizer.cpp + base/BasicQuantizer.h + base/Clipboard.cpp + base/Clipboard.h + base/Colour.cpp + base/Colour.h + base/ColourMap.cpp + base/ColourMap.h + base/Composition.cpp + base/Composition.h + base/CompositionTimeSliceAdapter.cpp + base/CompositionTimeSliceAdapter.h + base/Configuration.cpp + base/Configuration.h + base/Controllable.h + base/ControlParameter.cpp + base/ControlParameter.h + base/Device.cpp + base/Device.h + base/Equation.cpp + base/Equation.h + base/Event.cpp + base/Event.h + base/Exception.cpp + base/Exception.h + base/FastVector.h + base/Instrument.cpp + base/Instrument.h + base/LayoutEngine.cpp + base/LayoutEngine.h + base/LegatoQuantizer.cpp + base/LegatoQuantizer.h + base/Marker.cpp + base/Marker.h + base/MidiDevice.cpp + base/MidiDevice.h + base/MidiProgram.cpp + base/MidiProgram.h + base/MidiTypes.cpp + base/MidiTypes.h + base/NotationQuantizer.cpp + base/NotationQuantizer.h + base/NotationRules.h + base/NotationTypes.cpp + base/NotationTypes.h + base/Profiler.cpp + base/Profiler.h + base/Property.cpp + base/Property.h + base/PropertyMap.cpp + base/PropertyMap.h + base/PropertyName.cpp + base/PropertyName.h + base/Quantizer.cpp + base/Quantizer.h + base/RealTime.cpp + base/RealTime.h + base/RefreshStatus.h + base/RulerScale.cpp + base/RulerScale.h + base/ScriptAPI.cpp + base/ScriptAPI.h + base/Segment.cpp + base/Segment.h + base/SegmentMatrixHelper.cpp + base/SegmentMatrixHelper.h + base/SegmentNotationHelper.cpp + base/SegmentNotationHelper.h + base/SegmentPerformanceHelper.cpp + base/SegmentPerformanceHelper.h + base/Selection.cpp + base/Selection.h + base/Sets.cpp + base/Sets.h + base/SnapGrid.cpp + base/SnapGrid.h + base/SoftSynthDevice.cpp + base/SoftSynthDevice.h + base/Staff.cpp + base/Staff.h + base/Studio.cpp + base/Studio.h + base/Track.cpp + base/Track.h + base/TriggerSegment.cpp + base/TriggerSegment.h + base/ViewElement.cpp + base/ViewElement.h + base/XmlExportable.cpp + base/XmlExportable.h +) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..d942e71 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,208 @@ +IF(NOT ROSEGARDEN_SOURCE_DIR) + MESSAGE(FATAL_ERROR "You need to run cmake or ccmake at the root directory") +ENDIF(NOT ROSEGARDEN_SOURCE_DIR) + +IF(USE_PCH) + ADD_PRECOMPILED_HEADER(precompiled_headers misc/stableheaders.h) +ENDIF(USE_PCH) + +LINK_DIRECTORIES( + ${KDE3_LIB_DIR} + ${ALSA_LIB_DIR} + ${JACK_LIB_DIR} + ${XFT_LIB_DIR} + ${LIBLO_LIB_DIR} + ${LRDF_LIB_DIR} + ${LIRC_LIB_DIR} + ${FFTW3F_LIB_DIR} +) + +INCLUDE_DIRECTORIES(AFTER + ${QT_INCLUDE_DIR} + ${KDE3_INCLUDE_DIR} + ${ALSA_INC_DIR} + ${JACK_INC_DIR} + ${XFT_INC_DIR} + ${DSSI_INC_DIR} + ${LIBLO_INC_DIR} + ${LADSPA_INC_DIR} + ${LRDF_INC_DIR} + ${LIRC_INC_DIR} + ${FFTW3F_INC_DIR} + base +) + +SET(CMAKE_INCLUDE_PATH ".") + +# Common sources +INCLUDE(BaseFileList.txt) +INCLUDE(MiscFileList.txt) + +# Sound sources +INCLUDE(SoundFileList.txt) + +KDE3_AUTOMOC(${common_sound_SRCS}) + +ADD_LIBRARY(RosegardenCommon STATIC + ${misc_SRCS} + ${base_SRCS} + ${common_sound_SRCS} +) + +IF(USE_PCH) + ADD_DEPENDENCIES(RosegardenCommon precompiled_headers) +ENDIF(USE_PCH) + +# sequencer executable +INCLUDE(SequencerFileList.txt) + +IF(WANT_SOUND) + SET(snd_SRCS ${sound_SRCS}) +ELSE(WANT_SOUND) + SET(snd_SRCS ${nosnd_SRCS}) +ENDIF(WANT_SOUND) + +KDE3_ADD_DCOP_SKELS(sequencer_SRCS ${seq_dcop_SRCS}) +KDE3_ADD_DCOP_STUBS(sequencer_SRCS ${seq_dcop_SRCS}) +KDE3_AUTOMOC(${sequencer_SRCS}) + +KDE3_ADD_EXECUTABLE(rosegardensequencer + ${snd_SRCS} + ${sequencer_SRCS} +) + +IF(USE_PCH) + ADD_DEPENDENCIES(rosegardensequencer precompiled_headers) +ENDIF(USE_PCH) + +TARGET_LINK_LIBRARIES(rosegardensequencer + ${QT_AND_KDECORE_LIBS} + ${ALSA_LIBS} + ${JACK_LIBS} + ${LIBLO_LIBS} + ${LRDF_LIBS} + ${FFTW3F_LIBS} + kdeui + RosegardenCommon +) + +# gui executable +INCLUDE(GUIFileList.txt) + +KDE3_ADD_DCOP_SKELS(gui_SRCS ${gui_dcop_SRCS}) +KDE3_ADD_DCOP_STUBS(gui_SRCS ${gui_dcop_SRCS}) +KDE3_ADD_UI_FILES(gui_SRCS ${ui_SRCS}) +KDE3_AUTOMOC(${gui_SRCS}) +KDE3_AUTOMOC(${segmentcanvas_SRCS}) +KDE3_AUTOMOC(${commands_SRCS}) +KDE3_AUTOMOC(${document_SRCS}) + +ADD_LIBRARY(RosegardenExtended STATIC + ${document_SRCS} + ${gui_SRCS} +) + +ADD_LIBRARY(RosegardenCommands STATIC + ${commands_SRCS} +) + +IF(USE_PCH) + ADD_DEPENDENCIES(RosegardenExtended precompiled_headers) +ENDIF(USE_PCH) + +IF(USE_PCH) + ADD_DEPENDENCIES(RosegardenCommands precompiled_headers) +ENDIF(USE_PCH) + +ADD_LIBRARY(RosegardenSegmentCanvas STATIC + ${segmentcanvas_SRCS} +) + +IF(USE_PCH) + ADD_DEPENDENCIES(RosegardenSegmentCanvas precompiled_headers) +ENDIF(USE_PCH) + + + +KDE3_ADD_EXECUTABLE(rosegarden + #${commands_SRCS} + #${document_SRCS} + #${gui_SRCS} + #${segmentcanvas_SRCS} + gui/application/main.cpp +) + +IF(USE_PCH) + ADD_DEPENDENCIES(rosegarden precompiled_headers) +ENDIF(USE_PCH) + +TARGET_LINK_LIBRARIES(rosegarden + ${QT_AND_KDECORE_LIBS} + ${LIBLO_LIBS} + ${LRDF_LIBS} + ${FFTW3F_LIBS} + ${XFT_LIBS} + ${LIRC_LIBS} + ${JACK_LIBS} + kio + kdeui + kdeprint + RosegardenExtended + RosegardenSegmentCanvas + RosegardenCommands + RosegardenCommon +) + +# Install targets +INSTALL(TARGETS rosegarden rosegardensequencer + RUNTIME DESTINATION ${KDE3EXECDIR} ) + +# Install helper scripts +INSTALL(PROGRAMS + helpers/rosegarden-lilypondview + helpers/rosegarden-project-package + helpers/rosegarden-audiofile-importer + DESTINATION ${KDE3EXECDIR} ) + +# Install GUI resource files +FILE(GLOB RC_FILES gui/ui/*.rc) +INSTALL(FILES ${RC_FILES} + DESTINATION ${KDE3DATADIR}/rosegarden ) + +IF(WANT_TEST) + INCLUDE(TestFileList.txt) + + CREATE_TEST_SOURCELIST(RosegardenTest + RosegardenTestDriver.cpp + ${tests_SRCS} +# base/test/transpose.cpp +# base/test/segmenttransposecommand.cpp + ) + + KDE3_ADD_EXECUTABLE(RosegardenTestDriver + RosegardenTestDriver.cpp + ${tests_SRCS} + ) + + TARGET_LINK_LIBRARIES(RosegardenTestDriver + ${QT_AND_KDECORE_LIBS} + ${LIBLO_LIBS} + ${LRDF_LIBS} + ${FFTW3F_LIBS} + ${XFT_LIBS} + ${LIRC_LIBS} + ${JACK_LIBS} + kio + kdeui + kdeprint + RosegardenExtended + RosegardenSegmentCanvas + RosegardenCommands + RosegardenCommon + ) + FOREACH(currenttest ${tests_SRCS}) + GET_FILENAME_COMPONENT(TestName ${currenttest} NAME_WE) + ADD_TEST(test/${TestName} RosegardenTestDriver test/${TestName}) + ENDFOREACH(currenttest ${tests_SRCS}) +ENDIF(WANT_TEST) + diff --git a/src/GUIFileList.txt b/src/GUIFileList.txt new file mode 100644 index 0000000..75403e0 --- /dev/null +++ b/src/GUIFileList.txt @@ -0,0 +1,975 @@ +# to rebuild the list: +# find commands/ document/ gui/ -iname '*.cpp' -or -iname '*.h' > list.txt + +SET(gui_dcop_SRCS + gui/application/RosegardenIface.h + gui/editors/segment/TrackEditorIface.h +) + +SET(ui_SRCS + gui/ui/RosegardenTransport.ui +) + +SET(commands_SRCS + commands/edit/AddDotCommand.cpp + commands/edit/AddDotCommand.h + commands/edit/AddMarkerCommand.cpp + commands/edit/AddMarkerCommand.h + commands/edit/ChangeVelocityCommand.cpp + commands/edit/ChangeVelocityCommand.h + commands/edit/ClearTriggersCommand.cpp + commands/edit/ClearTriggersCommand.h + commands/edit/CollapseNotesCommand.cpp + commands/edit/CollapseNotesCommand.h + commands/edit/CopyCommand.cpp + commands/edit/CopyCommand.h + commands/edit/CutAndCloseCommand.cpp + commands/edit/CutAndCloseCommand.h + commands/edit/CutCommand.cpp + commands/edit/CutCommand.h + commands/edit/EraseCommand.cpp + commands/edit/EraseCommand.h + commands/edit/EventEditCommand.cpp + commands/edit/EventEditCommand.h + commands/edit/EventInsertionCommand.cpp + commands/edit/EventInsertionCommand.h + commands/edit/EventQuantizeCommand.cpp + commands/edit/EventQuantizeCommand.h + commands/edit/EventUnquantizeCommand.cpp + commands/edit/EventUnquantizeCommand.h + commands/edit/InsertTriggerNoteCommand.cpp + commands/edit/InsertTriggerNoteCommand.h + commands/edit/InvertCommand.cpp + commands/edit/InvertCommand.h + commands/edit/ModifyMarkerCommand.cpp + commands/edit/ModifyMarkerCommand.h + commands/edit/MoveAcrossSegmentsCommand.cpp + commands/edit/MoveAcrossSegmentsCommand.h + commands/edit/MoveCommand.cpp + commands/edit/MoveCommand.h + commands/edit/PasteEventsCommand.cpp + commands/edit/PasteEventsCommand.h + commands/edit/PasteSegmentsCommand.cpp + commands/edit/PasteSegmentsCommand.h + commands/edit/RemoveMarkerCommand.cpp + commands/edit/RemoveMarkerCommand.h + commands/edit/RescaleCommand.cpp + commands/edit/RescaleCommand.h + commands/edit/RetrogradeCommand.cpp + commands/edit/RetrogradeCommand.h + commands/edit/RetrogradeInvertCommand.cpp + commands/edit/RetrogradeInvertCommand.h + commands/edit/SelectionPropertyCommand.cpp + commands/edit/SelectionPropertyCommand.h + commands/edit/SetLyricsCommand.cpp + commands/edit/SetLyricsCommand.h + commands/edit/SetNoteTypeCommand.cpp + commands/edit/SetNoteTypeCommand.h + commands/edit/SetTriggerCommand.cpp + commands/edit/SetTriggerCommand.h + commands/edit/TransposeCommand.cpp + commands/edit/TransposeCommand.h + commands/matrix/MatrixEraseCommand.cpp + commands/matrix/MatrixEraseCommand.h + commands/matrix/MatrixInsertionCommand.cpp + commands/matrix/MatrixInsertionCommand.h + commands/matrix/MatrixModifyCommand.cpp + commands/matrix/MatrixModifyCommand.h + commands/matrix/MatrixPercussionInsertionCommand.cpp + commands/matrix/MatrixPercussionInsertionCommand.h + commands/notation/AddFingeringMarkCommand.cpp + commands/notation/AddFingeringMarkCommand.h + commands/notation/AddIndicationCommand.cpp + commands/notation/AddIndicationCommand.h + commands/notation/AddMarkCommand.cpp + commands/notation/AddMarkCommand.h + commands/notation/AddSlashesCommand.cpp + commands/notation/AddSlashesCommand.h + commands/notation/AddTextMarkCommand.cpp + commands/notation/AddTextMarkCommand.h + commands/notation/AutoBeamCommand.cpp + commands/notation/AutoBeamCommand.h + commands/notation/BeamCommand.cpp + commands/notation/BeamCommand.h + commands/notation/BreakCommand.cpp + commands/notation/BreakCommand.h + commands/notation/ChangeSlurPositionCommand.cpp + commands/notation/ChangeSlurPositionCommand.h + commands/notation/ChangeStemsCommand.cpp + commands/notation/ChangeStemsCommand.h + commands/notation/ChangeStyleCommand.cpp + commands/notation/ChangeStyleCommand.h + commands/notation/ChangeTiePositionCommand.cpp + commands/notation/ChangeTiePositionCommand.h + commands/notation/ClefInsertionCommand.cpp + commands/notation/ClefInsertionCommand.h + commands/notation/CollapseRestsCommand.cpp + commands/notation/CollapseRestsCommand.h + commands/notation/DeCounterpointCommand.cpp + commands/notation/DeCounterpointCommand.h + commands/notation/EraseEventCommand.cpp + commands/notation/EraseEventCommand.h + commands/notation/FixNotationQuantizeCommand.cpp + commands/notation/FixNotationQuantizeCommand.h + commands/notation/GuitarChordInsertionCommand.cpp + commands/notation/GuitarChordInsertionCommand.h + commands/notation/GraceCommand.cpp + commands/notation/GraceCommand.h + commands/notation/IncrementDisplacementsCommand.cpp + commands/notation/IncrementDisplacementsCommand.h + commands/notation/InterpretCommand.cpp + commands/notation/InterpretCommand.h + commands/notation/KeyInsertionCommand.cpp + commands/notation/KeyInsertionCommand.h + commands/notation/MakeAccidentalsCautionaryCommand.cpp + commands/notation/MakeAccidentalsCautionaryCommand.h + commands/notation/MakeChordCommand.cpp + commands/notation/MakeChordCommand.h + commands/notation/MakeNotesViableCommand.cpp + commands/notation/MakeNotesViableCommand.h + commands/notation/MakeRegionViableCommand.cpp + commands/notation/MakeRegionViableCommand.h + commands/notation/MultiKeyInsertionCommand.cpp + commands/notation/MultiKeyInsertionCommand.h + commands/notation/NormalizeRestsCommand.cpp + commands/notation/NormalizeRestsCommand.h + commands/notation/NoteInsertionCommand.cpp + commands/notation/NoteInsertionCommand.h + commands/notation/RemoveFingeringMarksCommand.cpp + commands/notation/RemoveFingeringMarksCommand.h + commands/notation/RemoveMarksCommand.cpp + commands/notation/RemoveMarksCommand.h + commands/notation/RemoveNotationQuantizeCommand.cpp + commands/notation/RemoveNotationQuantizeCommand.h + commands/notation/ResetDisplacementsCommand.cpp + commands/notation/ResetDisplacementsCommand.h + commands/notation/RespellCommand.cpp + commands/notation/RespellCommand.h + commands/notation/RestInsertionCommand.cpp + commands/notation/RestInsertionCommand.h + commands/notation/RestoreSlursCommand.cpp + commands/notation/RestoreSlursCommand.h + commands/notation/RestoreStemsCommand.cpp + commands/notation/RestoreStemsCommand.h + commands/notation/RestoreTiesCommand.cpp + commands/notation/RestoreTiesCommand.h + commands/notation/SetVisibilityCommand.cpp + commands/notation/SetVisibilityCommand.h + commands/notation/SustainInsertionCommand.cpp + commands/notation/SustainInsertionCommand.h + commands/notation/TextChangeCommand.cpp + commands/notation/TextChangeCommand.h + commands/notation/TextInsertionCommand.cpp + commands/notation/TextInsertionCommand.h + commands/notation/TieNotesCommand.cpp + commands/notation/TieNotesCommand.h + commands/notation/TupletCommand.cpp + commands/notation/TupletCommand.h + commands/notation/UnGraceCommand.cpp + commands/notation/UnGraceCommand.h + commands/notation/UntieNotesCommand.cpp + commands/notation/UntieNotesCommand.h + commands/notation/UnTupletCommand.cpp + commands/notation/UnTupletCommand.h + commands/segment/AddTempoChangeCommand.cpp + commands/segment/AddTempoChangeCommand.h + commands/segment/AddTimeSignatureAndNormalizeCommand.cpp + commands/segment/AddTimeSignatureAndNormalizeCommand.h + commands/segment/AddTimeSignatureCommand.cpp + commands/segment/AddTimeSignatureCommand.h + commands/segment/AddTracksCommand.cpp + commands/segment/AddTracksCommand.h + commands/segment/AddTriggerSegmentCommand.cpp + commands/segment/AddTriggerSegmentCommand.h + commands/segment/AudioSegmentAutoSplitCommand.cpp + commands/segment/AudioSegmentAutoSplitCommand.h + commands/segment/AudioSegmentDistributeCommand.cpp + commands/segment/AudioSegmentDistributeCommand.h + commands/segment/AudioSegmentInsertCommand.cpp + commands/segment/AudioSegmentInsertCommand.h + commands/segment/AudioSegmentResizeFromStartCommand.cpp + commands/segment/AudioSegmentResizeFromStartCommand.h + commands/segment/AudioSegmentRescaleCommand.cpp + commands/segment/AudioSegmentRescaleCommand.h + commands/segment/AudioSegmentSplitCommand.cpp + commands/segment/AudioSegmentSplitCommand.h + commands/segment/ChangeCompositionLengthCommand.cpp + commands/segment/ChangeCompositionLengthCommand.h + commands/segment/CreateTempoMapFromSegmentCommand.cpp + commands/segment/CreateTempoMapFromSegmentCommand.h + commands/segment/CutRangeCommand.cpp + commands/segment/CutRangeCommand.h + commands/segment/DeleteRangeCommand.cpp + commands/segment/DeleteRangeCommand.h + commands/segment/DeleteTracksCommand.cpp + commands/segment/DeleteTracksCommand.h + commands/segment/DeleteTriggerSegmentCommand.cpp + commands/segment/DeleteTriggerSegmentCommand.h + commands/segment/EraseSegmentsStartingInRangeCommand.cpp + commands/segment/EraseSegmentsStartingInRangeCommand.h + commands/segment/InsertRangeCommand.cpp + commands/segment/InsertRangeCommand.h + commands/segment/ModifyDefaultTempoCommand.cpp + commands/segment/ModifyDefaultTempoCommand.h + commands/segment/MoveTracksCommand.cpp + commands/segment/MoveTracksCommand.h + commands/segment/OpenOrCloseRangeCommand.cpp + commands/segment/OpenOrCloseRangeCommand.h + commands/segment/PasteConductorDataCommand.cpp + commands/segment/PasteConductorDataCommand.h + commands/segment/PasteRangeCommand.cpp + commands/segment/PasteRangeCommand.h + commands/segment/PasteToTriggerSegmentCommand.cpp + commands/segment/PasteToTriggerSegmentCommand.h + commands/segment/RemoveTempoChangeCommand.cpp + commands/segment/RemoveTempoChangeCommand.h + commands/segment/RemoveTimeSignatureCommand.cpp + commands/segment/RemoveTimeSignatureCommand.h + commands/segment/RenameTrackCommand.cpp + commands/segment/RenameTrackCommand.h + commands/segment/SegmentAutoSplitCommand.cpp + commands/segment/SegmentAutoSplitCommand.h + commands/segment/SegmentChangePlayableRangeCommand.cpp + commands/segment/SegmentChangePlayableRangeCommand.h + commands/segment/SegmentChangeTransposeCommand.cpp + commands/segment/SegmentChangeTransposeCommand.h + commands/segment/SegmentChangeQuantizationCommand.cpp + commands/segment/SegmentChangeQuantizationCommand.h + commands/segment/SegmentColourCommand.cpp + commands/segment/SegmentColourCommand.h + commands/segment/SegmentColourMapCommand.cpp + commands/segment/SegmentColourMapCommand.h + commands/segment/SegmentCommand.cpp + commands/segment/SegmentCommand.h + commands/segment/SegmentCommandRepeat.cpp + commands/segment/SegmentCommandRepeat.h + commands/segment/SegmentEraseCommand.cpp + commands/segment/SegmentEraseCommand.h + commands/segment/SegmentInsertCommand.cpp + commands/segment/SegmentInsertCommand.h + commands/segment/SegmentJoinCommand.cpp + commands/segment/SegmentJoinCommand.h + commands/segment/SegmentLabelCommand.cpp + commands/segment/SegmentLabelCommand.h + commands/segment/SegmentQuickCopyCommand.cpp + commands/segment/SegmentQuickCopyCommand.h + commands/segment/SegmentReconfigureCommand.cpp + commands/segment/SegmentReconfigureCommand.h + commands/segment/SegmentRecordCommand.cpp + commands/segment/SegmentRecordCommand.h + commands/segment/SegmentRepeatToCopyCommand.cpp + commands/segment/SegmentRepeatToCopyCommand.h + commands/segment/SegmentRescaleCommand.cpp + commands/segment/SegmentRescaleCommand.h + commands/segment/SegmentResizeFromStartCommand.cpp + commands/segment/SegmentResizeFromStartCommand.h + commands/segment/SegmentSingleRepeatToCopyCommand.cpp + commands/segment/SegmentSingleRepeatToCopyCommand.h + commands/segment/SegmentSplitByPitchCommand.cpp + commands/segment/SegmentSplitByPitchCommand.h + commands/segment/SegmentSplitByRecordingSrcCommand.cpp + commands/segment/SegmentSplitByRecordingSrcCommand.h + commands/segment/SegmentSplitCommand.cpp + commands/segment/SegmentSplitCommand.h + commands/segment/SegmentSyncClefCommand.cpp + commands/segment/SegmentSyncClefCommand.h + commands/segment/SegmentSyncCommand.cpp + commands/segment/SegmentSyncCommand.h + commands/segment/SegmentTransposeCommand.cpp + commands/segment/SegmentTransposeCommand.h + commands/segment/SetTriggerSegmentBasePitchCommand.cpp + commands/segment/SetTriggerSegmentBasePitchCommand.h + commands/segment/SetTriggerSegmentBaseVelocityCommand.cpp + commands/segment/SetTriggerSegmentBaseVelocityCommand.h + commands/segment/SetTriggerSegmentDefaultRetuneCommand.cpp + commands/segment/SetTriggerSegmentDefaultRetuneCommand.h + commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.cpp + commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.h + commands/studio/AddControlParameterCommand.cpp + commands/studio/AddControlParameterCommand.h + commands/studio/CreateOrDeleteDeviceCommand.cpp + commands/studio/CreateOrDeleteDeviceCommand.h + commands/studio/ModifyControlParameterCommand.cpp + commands/studio/ModifyControlParameterCommand.h + commands/studio/ModifyDeviceCommand.cpp + commands/studio/ModifyDeviceCommand.h + commands/studio/ModifyDeviceMappingCommand.cpp + commands/studio/ModifyDeviceMappingCommand.h + commands/studio/ModifyInstrumentMappingCommand.cpp + commands/studio/ModifyInstrumentMappingCommand.h + commands/studio/ReconnectDeviceCommand.cpp + commands/studio/ReconnectDeviceCommand.h + commands/studio/RemoveControlParameterCommand.cpp + commands/studio/RemoveControlParameterCommand.h + commands/studio/RenameDeviceCommand.cpp + commands/studio/RenameDeviceCommand.h +) + +SET(document_SRCS + document/BasicCommand.cpp + document/BasicCommand.h + document/BasicSelectionCommand.cpp + document/BasicSelectionCommand.h + document/ConfigGroups.cpp + document/ConfigGroups.h + document/io/CsoundExporter.cpp + document/io/CsoundExporter.h + document/io/HydrogenLoader.cpp + document/io/HydrogenLoader.h + document/io/HydrogenXMLHandler.cpp + document/io/HydrogenXMLHandler.h + document/io/LilyPondExporter.cpp + document/io/LilyPondExporter.h + document/io/MupExporter.cpp + document/io/MupExporter.h + document/io/MusicXmlExporter.cpp + document/io/MusicXmlExporter.h + document/io/RG21Loader.cpp + document/io/RG21Loader.h + document/MultiViewCommandHistory.cpp + document/MultiViewCommandHistory.h + document/RosegardenGUIDoc.cpp + document/RosegardenGUIDoc.h + document/RoseXmlHandler.cpp + document/RoseXmlHandler.h + document/XmlStorableEvent.cpp + document/XmlStorableEvent.h + document/XmlSubHandler.cpp + document/XmlSubHandler.h +) + +SET(guitar_test_SRCS + gui/editors/guitar/test_barre.cpp + gui/editors/guitar/test_chordmap.cpp + gui/editors/guitar/test_chordname.cpp + gui/editors/guitar/test_fingeringconstruct.cpp + gui/editors/guitar/test_fingering.cpp + gui/editors/guitar/test_guitar.cpp + gui/editors/guitar/test_note.cpp +) + +SET(gui_SRCS + gui/application/LircClient.cpp + gui/application/LircClient.h + gui/application/LircCommander.cpp + gui/application/LircCommander.h + gui/application/RosegardenApplication.cpp + gui/application/RosegardenApplication.h + gui/application/RosegardenDCOP.h + gui/application/RosegardenGUIApp.cpp + gui/application/RosegardenGUIApp.h + gui/application/RosegardenGUIView.cpp + gui/application/RosegardenGUIView.h + gui/application/RosegardenIface.cpp + gui/application/RosegardenIface.h + gui/application/SetWaitCursor.cpp + gui/application/SetWaitCursor.h + gui/application/StartupTester.cpp + gui/application/StartupTester.h + gui/configuration/AudioConfigurationPage.cpp + gui/configuration/AudioConfigurationPage.h + gui/configuration/AudioPropertiesPage.cpp + gui/configuration/AudioPropertiesPage.h + gui/configuration/ColourConfigurationPage.cpp + gui/configuration/ColourConfigurationPage.h + gui/configuration/ConfigurationPage.cpp + gui/configuration/ConfigurationPage.h + gui/configuration/DocumentMetaConfigurationPage.cpp + gui/configuration/DocumentMetaConfigurationPage.h + gui/configuration/GeneralConfigurationPage.cpp + gui/configuration/GeneralConfigurationPage.h + gui/configuration/HeadersConfigurationPage.cpp + gui/configuration/HeadersConfigurationPage.h + gui/configuration/LatencyConfigurationPage.cpp + gui/configuration/LatencyConfigurationPage.h + gui/configuration/MatrixConfigurationPage.cpp + gui/configuration/MatrixConfigurationPage.h + gui/configuration/MIDIConfigurationPage.cpp + gui/configuration/MIDIConfigurationPage.h + gui/configuration/NotationConfigurationPage.cpp + gui/configuration/NotationConfigurationPage.h + gui/configuration/TabbedConfigurationPage.cpp + gui/configuration/TabbedConfigurationPage.h + gui/dialogs/AddTracksDialog.cpp + gui/dialogs/AddTracksDialog.h + gui/dialogs/AudioManagerDialog.cpp + gui/dialogs/AudioManagerDialog.h + gui/dialogs/AudioPlayingDialog.cpp + gui/dialogs/AudioPlayingDialog.h + gui/dialogs/AudioPluginDialog.cpp + gui/dialogs/AudioPluginDialog.h + gui/dialogs/AudioSplitDialog.cpp + gui/dialogs/AudioSplitDialog.h + gui/dialogs/BeatsBarsDialog.cpp + gui/dialogs/BeatsBarsDialog.h + gui/dialogs/ClefDialog.cpp + gui/dialogs/ClefDialog.h + gui/dialogs/CompositionLengthDialog.cpp + gui/dialogs/CompositionLengthDialog.h + gui/dialogs/ConfigureDialogBase.cpp + gui/dialogs/ConfigureDialogBase.h + gui/dialogs/ConfigureDialog.cpp + gui/dialogs/ConfigureDialog.h + gui/dialogs/CountdownBar.cpp + gui/dialogs/CountdownBar.h + gui/dialogs/CountdownDialog.cpp + gui/dialogs/CountdownDialog.h + gui/dialogs/DocumentConfigureDialog.cpp + gui/dialogs/DocumentConfigureDialog.h + gui/dialogs/EventEditDialog.cpp + gui/dialogs/EventEditDialog.h + gui/dialogs/EventFilterDialog.cpp + gui/dialogs/EventFilterDialog.h + gui/dialogs/EventParameterDialog.cpp + gui/dialogs/EventParameterDialog.h + gui/dialogs/ExportDeviceDialog.cpp + gui/dialogs/ExportDeviceDialog.h + gui/dialogs/FileLocateDialog.cpp + gui/dialogs/FileLocateDialog.h + gui/dialogs/FileMergeDialog.cpp + gui/dialogs/FileMergeDialog.h + gui/dialogs/FloatEdit.cpp + gui/dialogs/FloatEdit.h + gui/dialogs/IdentifyTextCodecDialog.cpp + gui/dialogs/IdentifyTextCodecDialog.h + gui/dialogs/ImportDeviceDialog.cpp + gui/dialogs/ImportDeviceDialog.h + gui/dialogs/InterpretDialog.cpp + gui/dialogs/InterpretDialog.h + gui/dialogs/IntervalDialog.cpp + gui/dialogs/IntervalDialog.h + gui/dialogs/KeySignatureDialog.cpp + gui/dialogs/KeySignatureDialog.h + gui/dialogs/LilyPondOptionsDialog.cpp + gui/dialogs/LilyPondOptionsDialog.h + gui/dialogs/LyricEditDialog.cpp + gui/dialogs/LyricEditDialog.h + gui/dialogs/MakeOrnamentDialog.cpp + gui/dialogs/MakeOrnamentDialog.h + gui/dialogs/ManageMetronomeDialog.cpp + gui/dialogs/ManageMetronomeDialog.h + gui/dialogs/MarkerModifyDialog.cpp + gui/dialogs/MarkerModifyDialog.h + gui/dialogs/PasteNotationDialog.cpp + gui/dialogs/PasteNotationDialog.h + gui/dialogs/PitchDialog.cpp + gui/dialogs/PitchDialog.h + gui/dialogs/PitchPickerDialog.cpp + gui/dialogs/PitchPickerDialog.h + gui/dialogs/QuantizeDialog.cpp + gui/dialogs/QuantizeDialog.h + gui/dialogs/RescaleDialog.cpp + gui/dialogs/RescaleDialog.h + gui/dialogs/ShowSequencerStatusDialog.cpp + gui/dialogs/ShowSequencerStatusDialog.h + gui/dialogs/SimpleEventEditDialog.cpp + gui/dialogs/SimpleEventEditDialog.h + gui/dialogs/SplitByPitchDialog.cpp + gui/dialogs/SplitByPitchDialog.h + gui/dialogs/SplitByRecordingSrcDialog.cpp + gui/dialogs/SplitByRecordingSrcDialog.h + gui/dialogs/TempoDialog.cpp + gui/dialogs/TempoDialog.h + gui/dialogs/TextEventDialog.cpp + gui/dialogs/TextEventDialog.h + gui/dialogs/TimeDialog.cpp + gui/dialogs/TimeDialog.h + gui/dialogs/TimeSignatureDialog.cpp + gui/dialogs/TimeSignatureDialog.h + gui/dialogs/TransportDialog.cpp + gui/dialogs/TransportDialog.h + gui/dialogs/TriggerSegmentDialog.cpp + gui/dialogs/TriggerSegmentDialog.h + gui/dialogs/TupletDialog.cpp + gui/dialogs/TupletDialog.h + gui/dialogs/UnusedAudioSelectionDialog.cpp + gui/dialogs/UnusedAudioSelectionDialog.h + gui/dialogs/UseOrnamentDialog.cpp + gui/dialogs/UseOrnamentDialog.h + gui/editors/eventlist/EventView.cpp + gui/editors/eventlist/EventView.h + gui/editors/eventlist/EventViewItem.cpp + gui/editors/eventlist/EventViewItem.h + gui/editors/eventlist/TrivialVelocityDialog.cpp + gui/editors/eventlist/TrivialVelocityDialog.h + gui/editors/guitar/NoteSymbols.cpp + gui/editors/guitar/NoteSymbols.h + gui/editors/guitar/Chord.h + gui/editors/guitar/Chord.cpp + gui/editors/guitar/Fingering.h + gui/editors/guitar/Fingering.cpp + gui/editors/guitar/FingeringBox.h + gui/editors/guitar/FingeringBox.cpp + gui/editors/guitar/FingeringListBoxItem.h + gui/editors/guitar/FingeringListBoxItem.cpp + gui/editors/guitar/ChordXmlHandler.h + gui/editors/guitar/ChordXmlHandler.cpp + gui/editors/guitar/ChordMap.h + gui/editors/guitar/ChordMap.cpp + gui/editors/guitar/GuitarChordSelectorDialog.h + gui/editors/guitar/GuitarChordSelectorDialog.cpp + gui/editors/guitar/GuitarChordEditorDialog.h + gui/editors/guitar/GuitarChordEditorDialog.cpp + gui/editors/matrix/MatrixCanvasView.cpp + gui/editors/matrix/MatrixCanvasView.h + gui/editors/matrix/MatrixElement.cpp + gui/editors/matrix/MatrixElement.h + gui/editors/matrix/MatrixEraser.cpp + gui/editors/matrix/MatrixEraser.h + gui/editors/matrix/MatrixHLayout.cpp + gui/editors/matrix/MatrixHLayout.h + gui/editors/matrix/MatrixMover.cpp + gui/editors/matrix/MatrixMover.h + gui/editors/matrix/MatrixPainter.cpp + gui/editors/matrix/MatrixPainter.h + gui/editors/matrix/MatrixParameterBox.cpp + gui/editors/matrix/MatrixParameterBox.h + gui/editors/matrix/MatrixResizer.cpp + gui/editors/matrix/MatrixResizer.h + gui/editors/matrix/MatrixSelector.cpp + gui/editors/matrix/MatrixSelector.h + gui/editors/matrix/MatrixStaff.cpp + gui/editors/matrix/MatrixStaff.h + gui/editors/matrix/MatrixToolBox.cpp + gui/editors/matrix/MatrixToolBox.h + gui/editors/matrix/MatrixTool.cpp + gui/editors/matrix/MatrixTool.h + gui/editors/matrix/MatrixView.cpp + gui/editors/matrix/MatrixView.h + gui/editors/matrix/MatrixVLayout.cpp + gui/editors/matrix/MatrixVLayout.h + gui/editors/matrix/PianoKeyboard.cpp + gui/editors/matrix/PianoKeyboard.h + gui/editors/matrix/QCanvasMatrixDiamond.cpp + gui/editors/matrix/QCanvasMatrixDiamond.h + gui/editors/matrix/QCanvasMatrixRectangle.cpp + gui/editors/matrix/QCanvasMatrixRectangle.h + gui/editors/notation/ClefInserter.cpp + gui/editors/notation/ClefInserter.h + gui/editors/notation/FontViewFrame.cpp + gui/editors/notation/FontViewFrame.h + gui/editors/notation/GuitarChordInserter.cpp + gui/editors/notation/GuitarChordInserter.h + gui/editors/notation/NotationCanvasView.cpp + gui/editors/notation/NotationCanvasView.h + gui/editors/notation/NotationChord.cpp + gui/editors/notation/NotationChord.h + gui/editors/notation/NotationElement.cpp + gui/editors/notation/NotationElement.h + gui/editors/notation/NotationEraser.cpp + gui/editors/notation/NotationEraser.h + gui/editors/notation/NotationGroup.cpp + gui/editors/notation/NotationGroup.h + gui/editors/notation/NotationHLayout.cpp + gui/editors/notation/NotationHLayout.h + gui/editors/notation/NotationProperties.cpp + gui/editors/notation/NotationProperties.h + gui/editors/notation/NotationSelectionPaster.cpp + gui/editors/notation/NotationSelectionPaster.h + gui/editors/notation/NotationSelector.cpp + gui/editors/notation/NotationSelector.h + gui/editors/notation/NotationStaff.cpp + gui/editors/notation/NotationStaff.h + gui/editors/notation/NotationStrings.cpp + gui/editors/notation/NotationStrings.h + gui/editors/notation/NotationToolBox.cpp + gui/editors/notation/NotationToolBox.h + gui/editors/notation/NotationTool.cpp + gui/editors/notation/NotationTool.h + gui/editors/notation/NotationView.cpp + gui/editors/notation/NotationView.h + gui/editors/notation/NotationVLayout.cpp + gui/editors/notation/NotationVLayout.h + gui/editors/notation/NoteCharacter.cpp + gui/editors/notation/NoteCharacter.h + gui/editors/notation/NoteCharacterNames.cpp + gui/editors/notation/NoteCharacterNames.h + gui/editors/notation/NoteFont.cpp + gui/editors/notation/NoteFontFactory.cpp + gui/editors/notation/NoteFontFactory.h + gui/editors/notation/NoteFont.h + gui/editors/notation/NoteFontMap.cpp + gui/editors/notation/NoteFontMap.h + gui/editors/notation/NoteFontViewer.cpp + gui/editors/notation/NoteFontViewer.h + gui/editors/notation/NoteInserter.cpp + gui/editors/notation/NoteInserter.h + gui/editors/notation/NotePixmapFactory.cpp + gui/editors/notation/NotePixmapFactory.h + gui/editors/notation/NotePixmapPainter.h + gui/editors/notation/NotePixmapParameters.cpp + gui/editors/notation/NotePixmapParameters.h + gui/editors/notation/NoteStyle.cpp + gui/editors/notation/NoteStyleFactory.cpp + gui/editors/notation/NoteStyleFactory.h + gui/editors/notation/NoteStyleFileReader.cpp + gui/editors/notation/NoteStyleFileReader.h + gui/editors/notation/NoteStyle.h + gui/editors/notation/RestInserter.cpp + gui/editors/notation/RestInserter.h + gui/editors/notation/SystemFont.cpp + gui/editors/notation/SystemFont.h + gui/editors/notation/SystemFontQt.cpp + gui/editors/notation/SystemFontQt.h + gui/editors/notation/SystemFontXft.cpp + gui/editors/notation/SystemFontXft.h + gui/editors/notation/TextInserter.cpp + gui/editors/notation/TextInserter.h + gui/editors/notation/TrackHeader.cpp + gui/editors/notation/TrackHeader.h + gui/editors/notation/HeadersGroup.cpp + gui/editors/notation/HeadersGroup.h + gui/editors/parameters/AudioInstrumentParameterPanel.cpp + gui/editors/parameters/AudioInstrumentParameterPanel.h + gui/editors/parameters/InstrumentParameterBox.cpp + gui/editors/parameters/InstrumentParameterBox.h + gui/editors/parameters/InstrumentParameterPanel.cpp + gui/editors/parameters/InstrumentParameterPanel.h + gui/editors/parameters/MIDIInstrumentParameterPanel.cpp + gui/editors/parameters/MIDIInstrumentParameterPanel.h + gui/editors/parameters/RosegardenParameterArea.cpp + gui/editors/parameters/RosegardenParameterArea.h + gui/editors/parameters/RosegardenParameterBox.cpp + gui/editors/parameters/RosegardenParameterBox.h + gui/editors/parameters/SegmentParameterBox.cpp + gui/editors/parameters/SegmentParameterBox.h + gui/editors/parameters/TrackParameterBox.cpp + gui/editors/parameters/TrackParameterBox.h + gui/editors/segment/ControlEditorDialog.cpp + gui/editors/segment/ControlEditorDialog.h + gui/editors/segment/ControlParameterEditDialog.cpp + gui/editors/segment/ControlParameterEditDialog.h + gui/editors/segment/ControlParameterItem.cpp + gui/editors/segment/ControlParameterItem.h + gui/editors/segment/MarkerEditor.cpp + gui/editors/segment/MarkerEditor.h + gui/editors/segment/MarkerEditorViewItem.cpp + gui/editors/segment/MarkerEditorViewItem.h + gui/editors/segment/PlayList.cpp + gui/editors/segment/PlayListDialog.cpp + gui/editors/segment/PlayListDialog.h + gui/editors/segment/PlayList.h + gui/editors/segment/PlayListView.cpp + gui/editors/segment/PlayListView.h + gui/editors/segment/PlayListViewItem.cpp + gui/editors/segment/PlayListViewItem.h + gui/editors/segment/TrackButtons.cpp + gui/editors/segment/TrackButtons.h + gui/editors/segment/TrackEditor.cpp + gui/editors/segment/TrackEditor.h + gui/editors/segment/TrackEditorIface.cpp + gui/editors/segment/TrackEditorIface.h + gui/editors/segment/TrackHeader.cpp + gui/editors/segment/TrackHeader.h + gui/editors/segment/TrackLabel.cpp + gui/editors/segment/TrackLabel.h + gui/editors/segment/TrackVUMeter.cpp + gui/editors/segment/TrackVUMeter.h + gui/editors/segment/TriggerManagerItem.cpp + gui/editors/segment/TriggerManagerItem.h + gui/editors/segment/TriggerSegmentManager.cpp + gui/editors/segment/TriggerSegmentManager.h + gui/editors/tempo/TempoListItem.cpp + gui/editors/tempo/TempoListItem.h + gui/editors/tempo/TempoView.cpp + gui/editors/tempo/TempoView.h + gui/general/ActiveItem.cpp + gui/general/ActiveItem.h + gui/general/BarLine.cpp + gui/general/BarLine.h + gui/general/BaseToolBox.cpp + gui/general/BaseToolBox.h + gui/general/BaseTool.cpp + gui/general/BaseTool.h + gui/general/CanvasCursor.cpp + gui/general/CanvasCursor.h + gui/general/CanvasItemGC.cpp + gui/general/CanvasItemGC.h + gui/general/CategoryElement.cpp + gui/general/CategoryElement.h + gui/general/ClefIndex.h + gui/general/ClefIndex.cpp + gui/general/EditToolBox.cpp + gui/general/EditToolBox.h + gui/general/EditTool.cpp + gui/general/EditTool.h + gui/general/EditViewBase.cpp + gui/general/EditViewBase.h + gui/general/EditView.cpp + gui/general/EditView.h + gui/general/EditViewTimeSigNotifier.h + gui/general/GUIPalette.cpp + gui/general/GUIPalette.h + gui/general/HZoomable.cpp + gui/general/HZoomable.h + gui/general/LinedStaff.cpp + gui/general/LinedStaff.h + gui/general/LinedStaffManager.cpp + gui/general/LinedStaffManager.h + gui/general/MidiPitchLabel.cpp + gui/general/MidiPitchLabel.h + gui/general/PixmapFunctions.cpp + gui/general/PixmapFunctions.h + gui/general/PresetElement.cpp + gui/general/PresetElement.h + gui/general/PresetGroup.cpp + gui/general/PresetGroup.h + gui/general/PresetHandlerDialog.cpp + gui/general/PresetHandlerDialog.h + gui/general/ProgressReporter.cpp + gui/general/ProgressReporter.h + gui/general/RosegardenCanvasView.cpp + gui/general/RosegardenCanvasView.h + gui/general/RosegardenScrollView.cpp + gui/general/RosegardenScrollView.h + gui/general/Spline.cpp + gui/general/Spline.h + gui/general/StaffLine.cpp + gui/general/StaffLine.h + gui/kdeext/KLedButton.cpp + gui/kdeext/KLedButton.h + gui/kdeext/KStartupLogo.cpp + gui/kdeext/KStartupLogo.h + gui/kdeext/KTmpStatusMsg.cpp + gui/kdeext/KTmpStatusMsg.h + gui/kdeext/QCanvasGroupableItem.cpp + gui/kdeext/QCanvasGroupableItem.h + gui/kdeext/QCanvasSimpleSprite.cpp + gui/kdeext/QCanvasSimpleSprite.h + gui/kdeext/RGLed.cpp + gui/kdeext/klearlook.cpp + gui/rulers/ChordNameRuler.cpp + gui/rulers/ChordNameRuler.h + gui/rulers/ControlChangeCommand.cpp + gui/rulers/ControlChangeCommand.h + gui/rulers/ControlItem.cpp + gui/rulers/ControlItem.h + gui/rulers/ControllerEventAdapter.cpp + gui/rulers/ControllerEventAdapter.h + gui/rulers/ControllerEventsRuler.cpp + gui/rulers/ControllerEventsRuler.h + gui/rulers/ControlRuler.cpp + gui/rulers/ControlRulerEventEraseCommand.cpp + gui/rulers/ControlRulerEventEraseCommand.h + gui/rulers/ControlRulerEventInsertCommand.cpp + gui/rulers/ControlRulerEventInsertCommand.h + gui/rulers/ControlRuler.h + gui/rulers/ControlSelector.cpp + gui/rulers/ControlSelector.h + gui/rulers/ControlTool.h + gui/rulers/DefaultVelocityColour.cpp + gui/rulers/DefaultVelocityColour.h + gui/rulers/ElementAdapter.h + gui/rulers/LoopRuler.cpp + gui/rulers/LoopRuler.h + gui/rulers/MarkerRuler.cpp + gui/rulers/MarkerRuler.h + gui/rulers/PercussionPitchRuler.cpp + gui/rulers/PercussionPitchRuler.h + gui/rulers/PitchRuler.cpp + gui/rulers/PitchRuler.h + gui/rulers/PropertyBox.cpp + gui/rulers/PropertyBox.h + gui/rulers/PropertyControlRuler.cpp + gui/rulers/PropertyControlRuler.h + gui/rulers/PropertyViewRuler.cpp + gui/rulers/PropertyViewRuler.h + gui/rulers/RawNoteRuler.cpp + gui/rulers/RawNoteRuler.h + gui/rulers/StandardRuler.cpp + gui/rulers/StandardRuler.h + gui/rulers/TempoColour.cpp + gui/rulers/TempoColour.h + gui/rulers/TempoRuler.cpp + gui/rulers/TempoRuler.h + gui/rulers/TextRuler.cpp + gui/rulers/TextRuler.h + gui/rulers/VelocityColour.cpp + gui/rulers/VelocityColour.h + gui/rulers/ViewElementAdapter.cpp + gui/rulers/ViewElementAdapter.h + gui/seqmanager/AudioSegmentMmapper.cpp + gui/seqmanager/AudioSegmentMmapper.h + gui/seqmanager/CompositionMmapper.cpp + gui/seqmanager/CompositionMmapper.h + gui/seqmanager/ControlBlockMmapper.cpp + gui/seqmanager/ControlBlockMmapper.h + gui/seqmanager/MetronomeMmapper.cpp + gui/seqmanager/MetronomeMmapper.h + gui/seqmanager/MidiFilterDialog.cpp + gui/seqmanager/MidiFilterDialog.h + gui/seqmanager/SegmentMmapper.cpp + gui/seqmanager/SegmentMmapperFactory.cpp + gui/seqmanager/SegmentMmapperFactory.h + gui/seqmanager/SegmentMmapper.h + gui/seqmanager/SequenceManager.cpp + gui/seqmanager/SequenceManager.h + gui/seqmanager/SequencerMapper.cpp + gui/seqmanager/SequencerMapper.h + gui/seqmanager/SpecialSegmentMmapper.cpp + gui/seqmanager/SpecialSegmentMmapper.h + gui/seqmanager/TempoSegmentMmapper.cpp + gui/seqmanager/TempoSegmentMmapper.h + gui/seqmanager/TimeSigSegmentMmapper.cpp + gui/seqmanager/TimeSigSegmentMmapper.h + gui/studio/AudioMixerWindow.cpp + gui/studio/AudioMixerWindow.h + gui/studio/AudioPluginClipboard.cpp + gui/studio/AudioPluginClipboard.h + gui/studio/AudioPlugin.cpp + gui/studio/AudioPlugin.h + gui/studio/AudioPluginManager.cpp + gui/studio/AudioPluginManager.h + gui/studio/AudioPluginOSCGUI.cpp + gui/studio/AudioPluginOSCGUI.h + gui/studio/AudioPluginOSCGUIManager.cpp + gui/studio/AudioPluginOSCGUIManager.h + gui/studio/BankEditorDialog.cpp + gui/studio/BankEditorDialog.h + gui/studio/ChangeRecordDeviceCommand.cpp + gui/studio/ChangeRecordDeviceCommand.h + gui/studio/DeviceEditorDialog.cpp + gui/studio/DeviceEditorDialog.h + gui/studio/DeviceManagerDialog.cpp + gui/studio/DeviceManagerDialog.h + gui/studio/MidiBankListViewItem.cpp + gui/studio/MidiBankListViewItem.h + gui/studio/MidiDeviceListViewItem.cpp + gui/studio/MidiDeviceListViewItem.h + gui/studio/MidiKeyMapListViewItem.cpp + gui/studio/MidiKeyMapListViewItem.h + gui/studio/MidiKeyMappingEditor.cpp + gui/studio/MidiKeyMappingEditor.h + gui/studio/MidiMixerVUMeter.cpp + gui/studio/MidiMixerVUMeter.h + gui/studio/MidiMixerWindow.cpp + gui/studio/MidiMixerWindow.h + gui/studio/MidiProgramsEditor.cpp + gui/studio/MidiProgramsEditor.h + gui/studio/MixerWindow.cpp + gui/studio/MixerWindow.h + gui/studio/NameSetEditor.cpp + gui/studio/NameSetEditor.h + gui/studio/OSCMessage.cpp + gui/studio/OSCMessage.h + gui/studio/RemapInstrumentDialog.cpp + gui/studio/RemapInstrumentDialog.h + gui/studio/StudioControl.cpp + gui/studio/StudioControl.h + gui/studio/SynthPluginManagerDialog.cpp + gui/studio/SynthPluginManagerDialog.h + gui/studio/TimerCallbackAssistant.cpp + gui/studio/TimerCallbackAssistant.h + gui/widgets/AudioFaderBox.cpp + gui/widgets/AudioFaderBox.h + gui/widgets/AudioListItem.h + gui/widgets/AudioListView.cpp + gui/widgets/AudioListView.h + gui/widgets/AudioRouteMenu.cpp + gui/widgets/AudioRouteMenu.h + gui/widgets/AudioVUMeter.cpp + gui/widgets/AudioVUMeter.h + gui/widgets/BigArrowButton.h + gui/widgets/CollapsingFrame.cpp + gui/widgets/CollapsingFrame.h + gui/widgets/ColourTable.cpp + gui/widgets/ColourTable.h + gui/widgets/ColourTableItem.cpp + gui/widgets/ColourTableItem.h + gui/widgets/CurrentProgressDialog.cpp + gui/widgets/CurrentProgressDialog.h + gui/widgets/DiatonicPitchChooser.cpp + gui/widgets/DiatonicPitchChooser.h + gui/widgets/Fader.cpp + gui/widgets/Fader.h + gui/widgets/HSpinBox.cpp + gui/widgets/HSpinBox.h + gui/widgets/Label.cpp + gui/widgets/Label.h + gui/widgets/MidiFaderWidget.cpp + gui/widgets/MidiFaderWidget.h + gui/widgets/PitchChooser.cpp + gui/widgets/PitchChooser.h + gui/widgets/PitchDragLabel.cpp + gui/widgets/PitchDragLabel.h + gui/widgets/PluginControl.cpp + gui/widgets/PluginControl.h + gui/widgets/ProgressBar.cpp + gui/widgets/ProgressBar.h + gui/widgets/ProgressDialog.cpp + gui/widgets/ProgressDialog.h + gui/widgets/QDeferScrollView.cpp + gui/widgets/QDeferScrollView.h + gui/widgets/QuantizeParameters.cpp + gui/widgets/QuantizeParameters.h + gui/widgets/RosegardenPopupMenu.h + gui/widgets/Rotary.cpp + gui/widgets/Rotary.h + gui/widgets/ScrollBox.cpp + gui/widgets/ScrollBoxDialog.cpp + gui/widgets/ScrollBoxDialog.h + gui/widgets/ScrollBox.h + gui/widgets/SpinBox.cpp + gui/widgets/SpinBox.h + gui/widgets/TextFloat.cpp + gui/widgets/TextFloat.h + gui/widgets/TimeWidget.cpp + gui/widgets/TimeWidget.h + gui/widgets/TristateCheckBox.cpp + gui/widgets/TristateCheckBox.h + gui/widgets/VUMeter.cpp + gui/widgets/VUMeter.h + gui/widgets/WheelyButton.cpp + gui/widgets/WheelyButton.h + gui/widgets/ZoomSlider.cpp + gui/widgets/ZoomSlider.h +) + +SET(segmentcanvas_SRCS + gui/editors/segment/segmentcanvas/AudioPreviewPainter.cpp + gui/editors/segment/segmentcanvas/AudioPreviewPainter.h + gui/editors/segment/segmentcanvas/AudioPreviewThread.cpp + gui/editors/segment/segmentcanvas/AudioPreviewThread.h + gui/editors/segment/segmentcanvas/AudioPreviewUpdater.cpp + gui/editors/segment/segmentcanvas/AudioPreviewUpdater.h + gui/editors/segment/segmentcanvas/CompositionColourCache.cpp + gui/editors/segment/segmentcanvas/CompositionColourCache.h + gui/editors/segment/segmentcanvas/CompositionItem.cpp + gui/editors/segment/segmentcanvas/CompositionItem.h + gui/editors/segment/segmentcanvas/CompositionItemHelper.cpp + gui/editors/segment/segmentcanvas/CompositionItemHelper.h + gui/editors/segment/segmentcanvas/CompositionItemImpl.cpp + gui/editors/segment/segmentcanvas/CompositionItemImpl.h + gui/editors/segment/segmentcanvas/CompositionModel.cpp + gui/editors/segment/segmentcanvas/CompositionModel.h + gui/editors/segment/segmentcanvas/CompositionModelImpl.cpp + gui/editors/segment/segmentcanvas/CompositionModelImpl.h + gui/editors/segment/segmentcanvas/CompositionRect.cpp + gui/editors/segment/segmentcanvas/CompositionRect.h + gui/editors/segment/segmentcanvas/CompositionView.cpp + gui/editors/segment/segmentcanvas/CompositionView.h + gui/editors/segment/segmentcanvas/PreviewRect.cpp + gui/editors/segment/segmentcanvas/PreviewRect.h + gui/editors/segment/segmentcanvas/SegmentEraser.cpp + gui/editors/segment/segmentcanvas/SegmentEraser.h + gui/editors/segment/segmentcanvas/SegmentItemPreview.cpp + gui/editors/segment/segmentcanvas/SegmentItemPreview.h + gui/editors/segment/segmentcanvas/SegmentJoiner.cpp + gui/editors/segment/segmentcanvas/SegmentJoiner.h + gui/editors/segment/segmentcanvas/SegmentMover.cpp + gui/editors/segment/segmentcanvas/SegmentMover.h + gui/editors/segment/segmentcanvas/SegmentOrderer.cpp + gui/editors/segment/segmentcanvas/SegmentOrderer.h + gui/editors/segment/segmentcanvas/SegmentPencil.cpp + gui/editors/segment/segmentcanvas/SegmentPencil.h + gui/editors/segment/segmentcanvas/SegmentResizer.cpp + gui/editors/segment/segmentcanvas/SegmentResizer.h + gui/editors/segment/segmentcanvas/SegmentSelector.cpp + gui/editors/segment/segmentcanvas/SegmentSelector.h + gui/editors/segment/segmentcanvas/SegmentSplitter.cpp + gui/editors/segment/segmentcanvas/SegmentSplitter.h + gui/editors/segment/segmentcanvas/SegmentToolBox.cpp + gui/editors/segment/segmentcanvas/SegmentToolBox.h + gui/editors/segment/segmentcanvas/SegmentTool.cpp + gui/editors/segment/segmentcanvas/SegmentTool.h +) diff --git a/src/MiscFileList.txt b/src/MiscFileList.txt new file mode 100644 index 0000000..66e3c82 --- /dev/null +++ b/src/MiscFileList.txt @@ -0,0 +1,6 @@ +SET(misc_SRCS + misc/Debug.cpp + misc/Debug.h + misc/Strings.cpp + misc/Strings.h +) diff --git a/src/SequencerFileList.txt b/src/SequencerFileList.txt new file mode 100644 index 0000000..bd85f75 --- /dev/null +++ b/src/SequencerFileList.txt @@ -0,0 +1,17 @@ + +SET(sequencer_SRCS + sequencer/ControlBlockMmapper.cpp + sequencer/ControlBlockMmapper.h + sequencer/main.cpp + sequencer/MmappedSegment.cpp + sequencer/MmappedSegment.h + sequencer/RosegardenSequencerApp.cpp + sequencer/RosegardenSequencerApp.h + sequencer/RosegardenSequencerIface.h + sequencer/SequencerMmapper.cpp + sequencer/SequencerMmapper.h +) + +SET(seq_dcop_SRCS + sequencer/RosegardenSequencerIface.h +) diff --git a/src/SoundFileList.txt b/src/SoundFileList.txt new file mode 100644 index 0000000..762f992 --- /dev/null +++ b/src/SoundFileList.txt @@ -0,0 +1,98 @@ +SET(common_sound_SRCS + sound/AudioCache.cpp + sound/AudioCache.h + sound/AudioFile.cpp + sound/AudioFile.h + sound/AudioFileManager.cpp + sound/AudioFileManager.h + sound/AudioPlayQueue.cpp + sound/AudioPlayQueue.h + sound/AudioProcess.cpp + sound/AudioProcess.h + sound/AudioFileTimeStretcher.cpp + sound/AudioFileTimeStretcher.h + sound/AudioTimeStretcher.cpp + sound/AudioTimeStretcher.h + sound/Audit.cpp + sound/Audit.h + sound/BWFAudioFile.cpp + sound/BWFAudioFile.h + sound/ControlBlock.cpp + sound/ControlBlock.h + sound/DSSIPluginFactory.cpp + sound/DSSIPluginFactory.h + sound/DSSIPluginInstance.cpp + sound/DSSIPluginInstance.h + sound/ExternalTransport.h + sound/LADSPAPluginFactory.cpp + sound/LADSPAPluginFactory.h + sound/LADSPAPluginInstance.cpp + sound/LADSPAPluginInstance.h + sound/MappedCommon.h + sound/MappedComposition.cpp + sound/MappedComposition.h + sound/MappedDevice.cpp + sound/MappedDevice.h + sound/MappedEvent.cpp + sound/MappedEvent.h + sound/MappedInstrument.cpp + sound/MappedInstrument.h + sound/MappedRealTime.cpp + sound/MappedRealTime.h + sound/MappedStudio.cpp + sound/MappedStudio.h + sound/MidiEvent.cpp + sound/MidiEvent.h + sound/MidiFile.cpp + sound/MidiFile.h + sound/Midi.h + sound/MP3AudioFile.cpp + sound/MP3AudioFile.h + sound/PeakFile.cpp + sound/PeakFile.h + sound/PeakFileManager.cpp + sound/PeakFileManager.h + sound/PlayableAudioFile.cpp + sound/PlayableAudioFile.h + sound/PluginFactory.cpp + sound/PluginFactory.h + sound/PluginIdentifier.cpp + sound/PluginIdentifier.h + sound/RecordableAudioFile.cpp + sound/RecordableAudioFile.h + sound/RIFFAudioFile.cpp + sound/RIFFAudioFile.h + sound/RingBuffer.h + sound/RunnablePluginInstance.cpp + sound/RunnablePluginInstance.h + sound/SampleWindow.h + sound/Scavenger.h + sound/SequencerDataBlock.cpp + sound/SequencerDataBlock.h + sound/SF2PatchExtractor.cpp + sound/SF2PatchExtractor.h + sound/SoundDriver.cpp + sound/SoundDriver.h + sound/SoundFile.cpp + sound/SoundFile.h + sound/WAVAudioFile.cpp + sound/WAVAudioFile.h +) + +SET(sound_SRCS + sound/AlsaDriver.cpp + sound/AlsaDriver.h + sound/AlsaPort.cpp + sound/AlsaPort.h + sound/DummyDriver.h + sound/JackDriver.cpp + sound/JackDriver.h + sound/SoundDriverFactory.cpp + sound/SoundDriverFactory.h +) + +SET(nosnd_SRCS + sound/DummyDriver.h + sound/SoundDriverFactory.cpp + sound/SoundDriverFactory.h +) diff --git a/src/TestFileList.txt b/src/TestFileList.txt new file mode 100644 index 0000000..470b4cb --- /dev/null +++ b/src/TestFileList.txt @@ -0,0 +1,6 @@ +SET(tests_SRCS + test/dummy.cpp + test/transpose.cpp + test/segmenttransposecommand.cpp + test/accidentals.cpp +) diff --git a/src/base/AnalysisTypes.cpp b/src/base/AnalysisTypes.cpp new file mode 100644 index 0000000..b2d8727 --- /dev/null +++ b/src/base/AnalysisTypes.cpp @@ -0,0 +1,1118 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2002 + Randall Farmer + + 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 +#include +#include +#include +#include // fabs, pow + +#include "NotationTypes.h" +#include "AnalysisTypes.h" +#include "Event.h" +#include "Segment.h" +#include "CompositionTimeSliceAdapter.h" +#include "BaseProperties.h" +#include "Composition.h" +#include "Profiler.h" + +#include "Sets.h" +#include "Quantizer.h" + + +namespace Rosegarden +{ + +using std::string; +using std::cerr; +using std::endl; +using std::multimap; +using std::vector; +using std::partial_sort_copy; + +/////////////////////////////////////////////////////////////////////////// +// Miscellany (doesn't analyze anything) +/////////////////////////////////////////////////////////////////////////// + +Key +AnalysisHelper::getKeyForEvent(Event *e, Segment &s) +{ + Segment::iterator i = + e ? s.findNearestTime(e->getAbsoluteTime()) //cc + : s.begin(); + + if (i==s.end()) return Key(); + + // This is an ugly loop. Is there a better way to iterate backwards + // through an STL container? + + while (true) { + if ((*i)->isa(Key::EventType)) { + return Key(**i); + } + if (i != s.begin()) --i; + else break; + } + + return Key(); +} + +/////////////////////////////////////////////////////////////////////////// +// Simple chord identification +/////////////////////////////////////////////////////////////////////////// + +void +AnalysisHelper::labelChords(CompositionTimeSliceAdapter &c, Segment &s, + const Rosegarden::Quantizer *quantizer) +{ + + Key key; + if (c.begin() != c.end()) key = getKeyForEvent(*c.begin(), s); + else key = getKeyForEvent(0, s); + + Profiler profiler("AnalysisHelper::labelChords", true); + + for (CompositionTimeSliceAdapter::iterator i = c.begin(); i != c.end(); ++i) { + + timeT time = (*i)->getAbsoluteTime(); + +// std::cerr << "AnalysisHelper::labelChords: time is " << time << ", type is " << (*i)->getType() << ", event is " << *i << " (itr is " << &i << ")" << std::endl; + + if ((*i)->isa(Key::EventType)) { + key = Key(**i); + Text text(key.getName(), Text::KeyName); + s.insert(text.getAsEvent(time)); + continue; + } + + if ((*i)->isa(Note::EventType)) { + + int bass = 999; + int mask = 0; + + GlobalChord chord(c, i, quantizer); + if (chord.size() == 0) continue; + + for (GlobalChord::iterator j = chord.begin(); j != chord.end(); ++j) { + long pitch = 999; + if ((**j)->get(BaseProperties::PITCH, pitch)) { + if (pitch < bass) { + assert(bass == 999); // should be in ascending order already + bass = pitch; + } + mask |= 1 << (pitch % 12); + } + } + + i = chord.getFinalElement(); + + if (mask == 0) continue; + + ChordLabel ch(key, mask, bass); + + if (ch.isValid()) + { + //std::cerr << ch.getName(key) << " at time " << time << std::endl; + + Text text(ch.getName(key), Text::ChordName); + s.insert(text.getAsEvent(time)); + } + } + + } +} + + +// ChordLabel +///////////////////////////////////////////////// + +ChordLabel::ChordMap ChordLabel::m_chordMap; + +ChordLabel::ChordLabel() +{ + checkMap(); +} + +ChordLabel::ChordLabel(Key key, int mask, int /* bass */) : + m_data() +{ + checkMap(); + + // Look for a chord built on an unaltered scale step of the current key. + + for (ChordMap::iterator i = m_chordMap.find(mask); + i != m_chordMap.end() && i->first==mask; ++i) + { + + if (Pitch(i->second.m_rootPitch).isDiatonicInKey(key)) + { + m_data = i->second; + } + + } + + /* + int rootBassInterval = ((bass - m_data.m_rootPitch + 12) % 12); + + // Pretend nobody cares about second and third inversions + // (i.e., bass must always be either root or third of chord) + if (rootBassInterval > 7) m_data.m_type=ChordTypes::NoChord; + else if (rootBassInterval > 4) m_data.m_type=ChordTypes::NoChord; + // Mark first-inversion and root-position chords as such + else if (rootBassInterval > 0) m_data.m_inversion=1; + else m_data.m_inversion=0; + */ + +} + +std::string +ChordLabel::getName(Key key) const +{ + return Pitch(m_data.m_rootPitch).getAsString(key.isSharp(), false) + + m_data.m_type; + // + (m_data.m_inversion>0 ? " in first inversion" : ""); +} + +int +ChordLabel::rootPitch() +{ + return m_data.m_rootPitch; +} + +bool +ChordLabel::isValid() const +{ + return m_data.m_type != ChordTypes::NoChord; +} + +bool +ChordLabel::operator<(const ChordLabel& other) const +{ + if (!isValid()) return true; + return getName(Key()) < other.getName(Key()); +} + +bool +ChordLabel::operator==(const ChordLabel& other) const +{ + return getName(Key()) == other.getName(Key()); +} + +void +ChordLabel::checkMap() +{ + if (!m_chordMap.empty()) return; + + const ChordType basicChordTypes[8] = + {ChordTypes::Major, ChordTypes::Minor, ChordTypes::Diminished, + ChordTypes::MajorSeventh, ChordTypes::DominantSeventh, + ChordTypes::MinorSeventh, ChordTypes::HalfDimSeventh, + ChordTypes::DimSeventh}; + + // What the basicChordMasks mean: each bit set in each one represents + // a pitch class (pitch%12) in a chord. C major has three pitch + // classes, C, E, and G natural; if you take the MIDI pitches + // of those notes modulo 12, you get 0, 4, and 7, so the mask for + // major triads is (1<<0)+(1<<4)+(1<<7). All the masks are for chords + // built on C. + + const int basicChordMasks[8] = + { + 1 + (1<<4) + (1<<7), // major + 1 + (1<<3) + (1<<7), // minor + 1 + (1<<3) + (1<<6), // diminished + 1 + (1<<4) + (1<<7) + (1<<11), // major 7th + 1 + (1<<4) + (1<<7) + (1<<10), // dominant 7th + 1 + (1<<3) + (1<<7) + (1<<10), // minor 7th + 1 + (1<<3) + (1<<6) + (1<<10), // half-diminished 7th + 1 + (1<<3) + (1<<6) + (1<<9) // diminished 7th + }; + + // Each mask is inserted into the map rotated twelve ways; each + // rotation is a mask you would get by transposing the chord + // to have a new root (i.e., C, C#, D, D#, E, F...) + + for (int i = 0; i < 8; ++i) + { + for (int j = 0; j < 12; ++j) + { + + m_chordMap.insert + ( + std::pair + ( + (basicChordMasks[i] << j | basicChordMasks[i] >> (12-j)) + & ((1<<12) - 1), + ChordData(basicChordTypes[i], j) + ) + ); + + } + } + +} + +/////////////////////////////////////////////////////////////////////////// +// Harmony guessing +/////////////////////////////////////////////////////////////////////////// + +void +AnalysisHelper::guessHarmonies(CompositionTimeSliceAdapter &c, Segment &s) +{ + HarmonyGuessList l; + + // 1. Get the list of possible harmonies + makeHarmonyGuessList(c, l); + + // 2. Refine the list of possible harmonies by preferring chords in the + // current key and looking for familiar progressions and + // tonicizations. + refineHarmonyGuessList(c, l, s); + + // 3. Put labels in the Segment. For the moment we just do the + // really naive thing with the segment arg to refineHarmonyGuessList: + // could do much better here +} + +// #### explain how this works: +// in terms of other functions (simple chord labelling, key guessing) +// in terms of basic concepts (pitch profile, harmony guess) +// in terms of flow + +void +AnalysisHelper::makeHarmonyGuessList(CompositionTimeSliceAdapter &c, + HarmonyGuessList &l) +{ + if (*c.begin() == *c.end()) return; + + checkHarmonyTable(); + + PitchProfile p; // defaults to all zeroes + TimeSignature timeSig; + timeT timeSigTime = 0; + timeT nextSigTime = (*c.begin())->getAbsoluteTime(); + + // Walk through the piece labelChords style + + // no increment (the first inner loop does the incrementing) + for (CompositionTimeSliceAdapter::iterator i = c.begin(); i != c.end(); ) + { + + // 2. Update the pitch profile + + timeT time = (*i)->getAbsoluteTime(); + + if (time >= nextSigTime) { + Composition *comp = c.getComposition(); + int sigNo = comp->getTimeSignatureNumberAt(time); + if (sigNo >= 0) { + std::pair sig = comp->getTimeSignatureChange(sigNo); + timeSigTime = sig.first; + timeSig = sig.second; + } + if (sigNo < comp->getTimeSignatureCount() - 1) { + nextSigTime = comp->getTimeSignatureChange(sigNo + 1).first; + } else { + nextSigTime = comp->getEndMarker(); + } + } + + double emphasis = + double(timeSig.getEmphasisForTime(time - timeSigTime)); + + // Scale all the components of the pitch profile down so that + // 1. notes that are a beat/bar away have less weight than notes + // from this beat/bar + // 2. the difference in weight depends on the metrical importance + // of the boundary between the notes: the previous beat should + // get less weight if this is the first beat of a new bar + + // ### possibly too much fade here + // also, fade should happen w/reference to how many notes played? + + PitchProfile delta; + int noteCount = 0; + + // no initialization + for ( ; i != c.end() && (*i)->getAbsoluteTime() == time; ++i) + { + if ((*i)->isa(Note::EventType)) + { + try { + int pitch = (*i)->get(BaseProperties::PITCH); + delta[pitch % 12] += 1 << int(emphasis); + ++noteCount; + } catch (...) { + std::cerr << "No pitch for note at " << time << "!" << std::endl; + } + } + } + + p *= 1. / (pow(2, emphasis) + 1 + noteCount); + p += delta; + + // 1. If there could have been a chord change, compare the current + // pitch profile with all of the profiles in the table to figure + // out which chords we are now nearest. + + // (If these events weren't on a beat boundary, assume there was no + // chord change and continue -- ### will need this back) +/* if ((!(i != c.end())) || + timeSig.getEmphasisForTime((*i)->getAbsoluteTime() - timeSigTime) < 3) + { + continue; + }*/ + + // (If the pitch profile hasn't changed much, continue) + + PitchProfile np = p.normalized(); + + HarmonyGuess possibleChords; + + possibleChords.reserve(m_harmonyTable.size()); + + for (HarmonyTable::iterator j = m_harmonyTable.begin(); + j != m_harmonyTable.end(); + ++j) + { + double score = np.productScorer(j->first); + possibleChords.push_back(ChordPossibility(score, j->second)); + } + + // 3. Save a short list of the nearest chords in the + // HarmonyGuessList passed in from guessHarmonies() + + l.push_back(std::pair(time, HarmonyGuess())); + + HarmonyGuess& smallerGuess = l.back().second; + + // Have to learn to love this: + + smallerGuess.resize(10); + + partial_sort_copy(possibleChords.begin(), + possibleChords.end(), + smallerGuess.begin(), + smallerGuess.end(), + cp_less()); + +#ifdef GIVE_HARMONYGUESS_DETAILS + std::cerr << "Time: " << time << std::endl; + + std::cerr << "Profile: "; + for (int k = 0; k < 12; ++k) + std::cerr << np[k] << " "; + std::cerr << std::endl; + + std::cerr << "Best guesses: " << std::endl; + for (HarmonyGuess::iterator debugi = smallerGuess.begin(); + debugi != smallerGuess.end(); + ++debugi) + { + std::cerr << debugi->first << ": " << debugi->second.getName(Key()) << std::endl; + } +#endif + + } + +} + +// Comparison function object -- can't declare this in the headers because +// this only works with pair instantiated, +// pair can't be instantiated while ChordLabel is an +// incomplete type, and ChordLabel is still an incomplete type at that +// point in the headers. + +bool +AnalysisHelper::cp_less::operator()(ChordPossibility l, ChordPossibility r) +{ + // Change name from "less?" + return l.first > r.first; +} + + +void +AnalysisHelper::refineHarmonyGuessList(CompositionTimeSliceAdapter &/* c */, + HarmonyGuessList &l, Segment &segment) +{ + // (Fetch the piece's starting key from the key guesser) + Key key; + + checkProgressionMap(); + + if (l.size() < 2) + { + l.clear(); + return; + } + + // Look at the list of harmony guesses two guesses at a time. + + HarmonyGuessList::iterator i = l.begin(); + // j stays ahead of i + HarmonyGuessList::iterator j = i + 1; + + ChordLabel bestGuessForFirstChord, bestGuessForSecondChord; + while (j != l.end()) + { + + double highestScore = 0; + + // For each possible pair of chords (i.e., two for loops here) + for (HarmonyGuess::iterator k = i->second.begin(); + k != i->second.end(); + ++k) + { + for (HarmonyGuess::iterator l = j->second.begin(); + l != j->second.end(); + ++l) + { + // Print the guess being processed: + + // std::cerr << k->second.getName(Key()) << "->" << l->second.getName(Key()) << std::endl; + + // For a first approximation, let's say the probability that + // a chord guess is correct is proportional to its score. Then + // the probability that a pair is correct is the product of + // its scores. + + double currentScore; + currentScore = k->first * l->first; + + // std::cerr << currentScore << std::endl; + + // Is this a familiar progression? Bonus if so. + + bool isFamiliar = false; + + // #### my ways of breaking up long function calls are haphazard + // also, does this code belong here? + + ProgressionMap::iterator pmi = + m_progressionMap.lower_bound( + ChordProgression(k->second, l->second) + ); + + // no initialization + for ( ; + pmi != m_progressionMap.end() + && pmi->first == k->second + && pmi->second == l->second; + ++pmi) + { + // key doesn't have operator== defined + if (key.getName() == pmi->homeKey.getName()) + { +// std::cerr << k->second.getName(Key()) << "->" << l->second.getName(Key()) << " is familiar" << std::endl; + isFamiliar = true; + break; + } + } + + if (isFamiliar) currentScore *= 5; // #### arbitrary + + // (Are voice-leading rules followed? Penalty if not) + + // Is this better than any pair examined so far? If so, set + // some variables that should end up holding the best chord + // progression + if (currentScore > highestScore) + { + bestGuessForFirstChord = k->second; + bestGuessForSecondChord = l->second; + highestScore = currentScore; + } + + } + } + + // Since we're not returning any results right now, print them + std::cerr << "Time: " << j->first << std::endl; + std::cerr << "Best chords: " + << bestGuessForFirstChord.getName(Key()) << ", " + << bestGuessForSecondChord.getName(Key()) << std::endl; + std::cerr << "Best score: " << highestScore << std::endl; + + // Using the best pair of chords: + + // Is the first chord diatonic? + + // If not, is it a secondary function? + // If so, change the current key + // If not, set an "implausible progression" flag + + // (Is the score of the best pair of chords reasonable? + // If not, set the flag.) + + // Was the progression plausible? + + // If so, replace the ten or so chords in the first guess with the + // first chord of the best pair, set + // first-iterator=second-iterator, and ++second-iterator + // (and possibly do the real key-setting) + + // If not, h.erase(second-iterator++) + + // Temporary hack to get _something_ interesting out: + Event *e; + e = Text(bestGuessForFirstChord.getName(Key()), Text::ChordName). + getAsEvent(j->first); + segment.insert(new Event(*e, e->getAbsoluteTime(), + e->getDuration(), e->getSubOrdering()-1)); + delete e; + + e = Text(bestGuessForSecondChord.getName(Key()), Text::ChordName). + getAsEvent(j->first); + segment.insert(e); + + // For now, just advance: + i = j; + ++j; + } +} + +AnalysisHelper::HarmonyTable AnalysisHelper::m_harmonyTable; + +void +AnalysisHelper::checkHarmonyTable() +{ + if (!m_harmonyTable.empty()) return; + + // Identical to basicChordTypes in ChordLabel::checkMap + const ChordType basicChordTypes[8] = + {ChordTypes::Major, ChordTypes::Minor, ChordTypes::Diminished, + ChordTypes::MajorSeventh, ChordTypes::DominantSeventh, + ChordTypes::MinorSeventh, ChordTypes::HalfDimSeventh, + ChordTypes::DimSeventh}; + + // Like basicChordMasks in ChordLabel::checkMap(), only with + // ints instead of bits + const int basicChordProfiles[8][12] = + { + // 0 1 2 3 4 5 6 7 8 9 10 11 + {1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0}, // major + {1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0}, // minor + {1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0}, // diminished + {1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1}, // major 7th + {1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0}, // dominant 7th + {1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0}, // minor 7th + {1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0}, // half-diminished 7th + {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0} // diminished 7th + }; + + for (int i = 0; i < 8; ++i) + { + for (int j = 0; j < 12; ++j) + { + PitchProfile p; + + for (int k = 0; k < 12; ++k) + p[(j + k) % 12] = (basicChordProfiles[i][k] == 1) + ? 1. + : -1.; + + PitchProfile np = p.normalized(); + + ChordLabel c(basicChordTypes[i], j); + + m_harmonyTable.push_back(std::pair(np, c)); + } + } + +} + +AnalysisHelper::ProgressionMap AnalysisHelper::m_progressionMap; + +void +AnalysisHelper::checkProgressionMap() +{ + if (!m_progressionMap.empty()) return; + // majorProgressionFirsts[0] = 5, majorProgressionSeconds[0]=1, so 5->1 is + // a valid progression. Note that the chord numbers are 1-based, like the + // Roman numeral symbols + const int majorProgressionFirsts[9] = + {5, 2, 6, 3, 7, 4, 4, 3, 5}; + const int majorProgressionSeconds[9] = + {1, 5, 2, 6, 1, 2, 5, 4, 6}; + + // For each major key + for (int i = 0; i < 12; ++i) + { + // Make the key + Key k(0, false); // tonicPitch = i, isMinor = false + // Add the common progressions + for (int j = 0; j < 9; ++j) + { + std::cerr << majorProgressionFirsts[j] << ", " << majorProgressionSeconds[j] << std::endl; + addProgressionToMap(k, + majorProgressionFirsts[j], + majorProgressionSeconds[j]); + } + // Add I->everything + for (int j = 1; j < 8; ++j) + { + addProgressionToMap(k, 1, j); + } + // (Add the progressions involving seventh chords) + // (Add I->seventh chords) + } + // (For each minor key) + // (Do what we just did for major keys) + +} + +void +AnalysisHelper::addProgressionToMap(Key k, + int firstChordNumber, + int secondChordNumber) +{ + // majorScalePitches[1] should be the pitch of the first step of + // the scale, so there's padding at the beginning of both these + // arrays. + const int majorScalePitches[] = {0, 0, 2, 4, 5, 7, 9, 11}; + const ChordType majorDiationicTriadTypes[] = + {ChordTypes::NoChord, ChordTypes::Major, ChordTypes::Minor, + ChordTypes::Minor, ChordTypes::Major, ChordTypes::Major, + ChordTypes::Minor, ChordTypes::Diminished}; + + int offset = k.getTonicPitch(); + + if (!k.isMinor()) + { + ChordLabel firstChord + ( + majorDiationicTriadTypes[firstChordNumber], + (majorScalePitches[firstChordNumber] + offset) % 12 + ); + ChordLabel secondChord + ( + majorDiationicTriadTypes[secondChordNumber], + (majorScalePitches[secondChordNumber] + offset) % 12 + ); + ChordProgression p(firstChord, secondChord, k); + m_progressionMap.insert(p); + } + // else handle minor-key chords + +} + +// AnalysisHelper::ChordProgression +///////////////////////////////////////////////// + +AnalysisHelper::ChordProgression::ChordProgression(ChordLabel first_, + ChordLabel second_, + Key key_) : + first(first_), + second(second_), + homeKey(key_) +{ + // nothing else +} + +bool +AnalysisHelper::ChordProgression::operator<(const AnalysisHelper::ChordProgression& other) const +{ + // no need for: + // if (first == other.first) return second < other.second; + return first < other.first; +} + +// AnalysisHelper::PitchProfile +///////////////////////////////////////////////// + +AnalysisHelper::PitchProfile::PitchProfile() +{ + for (int i = 0; i < 12; ++i) m_data[i] = 0; +} + +double& +AnalysisHelper::PitchProfile::operator[](int i) +{ + return m_data[i]; +} + +const double& +AnalysisHelper::PitchProfile::operator[](int i) const +{ + return m_data[i]; +} + +double +AnalysisHelper::PitchProfile::distance(const PitchProfile &other) +{ + double distance = 0; + + for (int i = 0; i < 12; ++i) + { + distance += fabs(other[i] - m_data[i]); + } + + return distance; +} + +double +AnalysisHelper::PitchProfile::dotProduct(const PitchProfile &other) +{ + double product = 0; + + for (int i = 0; i < 12; ++i) + { + product += other[i] * m_data[i]; + } + + return product; +} + +double +AnalysisHelper::PitchProfile::productScorer(const PitchProfile &other) +{ + double cumulativeProduct = 1; + double numbersInProduct = 0; + + for (int i = 0; i < 12; ++i) + { + if (other[i] > 0) + { + cumulativeProduct *= m_data[i]; + ++numbersInProduct; + } + } + + if (numbersInProduct > 0) + return pow(cumulativeProduct, 1/numbersInProduct); + + return 0; +} + +AnalysisHelper::PitchProfile +AnalysisHelper::PitchProfile::normalized() +{ + double size = 0; + PitchProfile normedProfile; + + for (int i = 0; i < 12; ++i) + { + size += fabs(m_data[i]); + } + + if (size == 0) size = 1; + + for (int i = 0; i < 12; ++i) + { + normedProfile[i] = m_data[i] / size; + } + + return normedProfile; +} + +AnalysisHelper::PitchProfile& +AnalysisHelper::PitchProfile::operator*=(double d) +{ + + for (int i = 0; i < 12; ++i) + { + m_data[i] *= d; + } + + return *this; +} + +AnalysisHelper::PitchProfile& +AnalysisHelper::PitchProfile::operator+=(const PitchProfile& d) +{ + + for (int i = 0; i < 12; ++i) + { + m_data[i] += d[i]; + } + + return *this; +} + +/////////////////////////////////////////////////////////////////////////// +// Time signature guessing +/////////////////////////////////////////////////////////////////////////// + +// #### this is too long +// should use constants for basic lengths, not numbers + +TimeSignature +AnalysisHelper::guessTimeSignature(CompositionTimeSliceAdapter &c) +{ + bool haveNotes = false; + + // 1. Guess the duration of the beat. The right beat length is going + // to be a common note length, and beat boundaries should be likely + // to have notes starting on them. + + vector beatScores(4, 0); + + // durations of quaver, dotted quaver, crotchet, dotted crotchet: + static const int commonBeatDurations[4] = {48, 72, 96, 144}; + + int j = 0; + for (CompositionTimeSliceAdapter::iterator i = c.begin(); + i != c.end() && j < 100; + ++i, ++j) + { + + // Skip non-notes + if (!(*i)->isa(Note::EventType)) continue; + haveNotes = true; + + for (int k = 0; k < 4; ++k) + { + + // Points for any note of the right length + if ((*i)->getDuration() == commonBeatDurations[k]) + beatScores[k]++; + + // Score for the probability that a note starts on a beat + // boundary. + + // Normally, to get the probability that a random beat boundary + // has a note on it, you'd add a constant for each note on a + // boundary and divide by the number of beat boundaries. + // Instead, this multiplies the constant (1/24) by + // commonBeatDurations[k], which is inversely proportional to + // the number of beat boundaries. + + if ((*i)->getAbsoluteTime() % commonBeatDurations[k] == 0) + beatScores[k] += commonBeatDurations[k] / 24; + + } + + } + + if (!haveNotes) return TimeSignature(); + + int beatDuration = 0, + bestScore = 0; + + for (int j = 0; j < 4; ++j) + { + if (beatScores[j] >= bestScore) + { + bestScore = beatScores[j]; + beatDuration = commonBeatDurations[j]; + } + } + + // 2. Guess whether the measure has two, three or four beats. The right + // measure length should make notes rarely cross barlines and have a + // high average length for notes at the start of bars. + + vector measureLengthScores(5, 0); + + for (CompositionTimeSliceAdapter::iterator i = c.begin(); + i != c.end() && j < 100; + ++i, ++j) + { + + if (!(*i)->isa(Note::EventType)) continue; + + // k is the guess at the number of beats in a measure + for (int k = 2; k < 5; ++k) + { + + // Determine whether this note crosses a barline; points for the + // measure length if it does NOT. + + int noteOffset = ((*i)->getAbsoluteTime() % (beatDuration * k)); + int noteEnd = noteOffset + (*i)->getDuration(); + if ( !(noteEnd > (beatDuration * k)) ) + measureLengthScores[k] += 10; + + + // Average length of notes at measure starts + + // Instead of dividing by the number of measure starts, this + // multiplies by the number of beats per measure, which is + // inversely proportional to the number of measure starts. + + if ((*i)->getAbsoluteTime() % (beatDuration * k) == 0) + measureLengthScores[k] += + (*i)->getDuration() * k / 24; + + } + + } + + int measureLength = 0; + + bestScore = 0; // reused from earlier + + for (int j = 2; j < 5; ++j) + { + if (measureLengthScores[j] >= bestScore) + { + bestScore = measureLengthScores[j]; + measureLength = j; + } + } + + // + // 3. Put the result in a TimeSignature object. + // + + int numerator = 0, denominator = 0; + + if (beatDuration % 72 == 0) + { + + numerator = 3 * measureLength; + + // 144 means the beat is a dotted crotchet, so the beat division + // is a quaver, so you want 8 on bottom + denominator = (144 * 8) / beatDuration; + + } + else + { + + numerator = measureLength; + + // 96 means that the beat is a crotchet, so you want 4 on bottom + denominator = (96 * 4) / beatDuration; + + } + + TimeSignature ts(numerator, denominator); + + return ts; + +} + +/////////////////////////////////////////////////////////////////////////// +// Key guessing +/////////////////////////////////////////////////////////////////////////// + +Key +AnalysisHelper::guessKey(CompositionTimeSliceAdapter &c) +{ + if (c.begin() == c.end()) return Key(); + + // 1. Figure out the distribution of emphasis over the twelve + // pitch clases in the first few bars. Pitches that occur + // more often have greater emphasis, and pitches that occur + // at stronger points in the bar have greater emphasis. + + vector weightedNoteCount(12, 0); + TimeSignature timeSig; + timeT timeSigTime = 0; + timeT nextSigTime = (*c.begin())->getAbsoluteTime(); + + int j = 0; + for (CompositionTimeSliceAdapter::iterator i = c.begin(); + i != c.end() && j < 100; ++i, ++j) + { + timeT time = (*i)->getAbsoluteTime(); + + if (time >= nextSigTime) { + Composition *comp = c.getComposition(); + int sigNo = comp->getTimeSignatureNumberAt(time); + if (sigNo >= 0) { + std::pair sig = comp->getTimeSignatureChange(sigNo); + timeSigTime = sig.first; + timeSig = sig.second; + } + if (sigNo < comp->getTimeSignatureCount() - 1) { + nextSigTime = comp->getTimeSignatureChange(sigNo + 1).first; + } else { + nextSigTime = comp->getEndMarker(); + } + } + + // Skip any other non-notes + if (!(*i)->isa(Note::EventType)) continue; + + try { + // Get pitch, metric strength of this event + int pitch = (*i)->get(BaseProperties::PITCH)%12; + int emphasis = + 1 << timeSig.getEmphasisForTime((*i)->getAbsoluteTime() - timeSigTime); + + // Count notes + weightedNoteCount[pitch] += emphasis; + + } catch (...) { + std::cerr << "No pitch for note at " << time << "!" << std::endl; + } + } + + // 2. Figure out what key best fits the distribution of emphasis. + // Notes outside a piece's key are rarely heavily emphasized, + // and the tonic and dominant of the key are likely to appear. + + // This part is longer than it should be. + + int bestTonic = -1; + bool bestKeyIsMinor = false; + int lowestCost = 999999999; + + for (int k = 0; k < 12; ++k) + { + int cost = + // accidentals are costly + weightedNoteCount[(k + 1 ) % 12] + + weightedNoteCount[(k + 3 ) % 12] + + weightedNoteCount[(k + 6 ) % 12] + + weightedNoteCount[(k + 8 ) % 12] + + weightedNoteCount[(k + 10) % 12] + // tonic is very good + - weightedNoteCount[ k ] * 5 + // dominant is good + - weightedNoteCount[(k + 7 ) % 12]; + if (cost < lowestCost) + { + bestTonic = k; + lowestCost = cost; + } + } + + for (int k = 0; k < 12; ++k) + { + int cost = + // accidentals are costly + weightedNoteCount[(k + 1 ) % 12] + + weightedNoteCount[(k + 4 ) % 12] + + weightedNoteCount[(k + 6 ) % 12] + // no cost for raised steps 6/7 (k+9, k+11) + // tonic is very good + - weightedNoteCount[ k ] * 5 + // dominant is good + - weightedNoteCount[(k + 7 ) % 12]; + if (cost < lowestCost) + { + bestTonic = k; + bestKeyIsMinor = true; + lowestCost = cost; + } + } + + return Key(bestTonic, bestKeyIsMinor); + +} + +} diff --git a/src/base/AnalysisTypes.h b/src/base/AnalysisTypes.h new file mode 100644 index 0000000..d7eabad --- /dev/null +++ b/src/base/AnalysisTypes.h @@ -0,0 +1,227 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2002 + Randall Farmer + + 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 _ANALYSISTYPES_H_ +#define _ANALYSISTYPES_H_ + +#include +#include +#include +#include + +#include "NotationTypes.h" + +namespace Rosegarden +{ + +class Segment; +class Event; +class CompositionTimeSliceAdapter; +class Quantizer; + +/////////////////////////////////////////////////////////////////////////// + +typedef std::string ChordType; +class ChordLabel; + +namespace ChordTypes +{ +const ChordType +NoChord = "no-chord", + Major = "", + Minor = "m", + Diminished = "dim", + MajorSeventh = "M7", + DominantSeventh = "7", + MinorSeventh = "m7", + HalfDimSeventh = "7b5", + DimSeventh = "dim7"; +} + +/////////////////////////////////////////////////////////////////////////// + +/** + * ChordLabel names chords and identifies them from their masks. See + * ChordLabel::checkMap() for details on what the masks are and + * AnalysisHelper::labelChords() for an example. + */ + +class ChordLabel +{ +public: + ChordLabel(); + ChordLabel(Key key, int mask, int bass); + ChordLabel(ChordType type, int rootPitch, int inversion = 0) : + m_data(type, rootPitch, inversion) { }; + int rootPitch(); + /** + * Gives the name of the chord in lead-sheet notation: C, Dm, + * G#7b5... + */ + std::string getName(Key key) const; + /** + * Gives the name of the chord in roman-numeral notation: I, ii, + * VMm7... + */ +// std::string getRomanNumeral(Key key); + bool isValid() const; + bool operator<(const ChordLabel& other) const; + // ### I can't believe this is necessary, but the compiler + // is asking for it + bool operator==(const ChordLabel& other) const; + +private: + // #### are m_* names appropriate for a struct? + // shouldn't I find a neater way to keep a ChordMap? + struct ChordData + { + ChordData(ChordType type, int rootPitch, int inversion = 0) : + m_type(type), + m_rootPitch(rootPitch), + m_inversion(inversion) { }; + + ChordData() : + m_type(ChordTypes::NoChord), + m_rootPitch(0), + m_inversion(0) { }; + + ChordType m_type; + int m_rootPitch; + int m_inversion; + }; + ChordData m_data; + void checkMap(); + + typedef std::multimap ChordMap; + static ChordMap m_chordMap; +}; + +/////////////////////////////////////////////////////////////////////////// + +class AnalysisHelper +{ +public: + AnalysisHelper() {}; + + /** + * Returns the key in force during a given event. + */ + Key getKeyForEvent(Event *e, Segment &s); + + /** + * Inserts in the given Segment labels for all of the chords found in + * the timeslice in the given CompositionTimeSliceAdapter. + */ + void labelChords(CompositionTimeSliceAdapter &c, Segment &s, + const Quantizer *quantizer); + + /** + * Returns a time signature that is probably reasonable for the + * given timeslice. + */ + TimeSignature guessTimeSignature(CompositionTimeSliceAdapter &c); + + /** + * Returns a guess at the starting key of the given timeslice. + */ + Key guessKey(CompositionTimeSliceAdapter &c); + + /** + * Like labelChords, but the algorithm is more complicated. This tries + * to guess the chords that should go under a beat even when all of the + * chord members aren't played at once. + */ + void guessHarmonies(CompositionTimeSliceAdapter &c, Segment &s); + +protected: + // ### THESE NAMES ARE AWFUL. MUST GREP THEM OUT OF EXISTENCE. + typedef std::pair ChordPossibility; + typedef std::vector HarmonyGuess; + typedef std::vector > HarmonyGuessList; + struct cp_less : public std::binary_function + { + bool operator()(ChordPossibility l, ChordPossibility r); + }; + + /// For use by guessHarmonies + void makeHarmonyGuessList(CompositionTimeSliceAdapter &c, + HarmonyGuessList &l); + + /// For use by guessHarmonies + void refineHarmonyGuessList(CompositionTimeSliceAdapter &c, + HarmonyGuessList& l, + Segment &); + + /// For use by guessHarmonies (makeHarmonyGuessList) + class PitchProfile + { + public: + PitchProfile(); + double& operator[](int i); + const double& operator[](int i) const; + double distance(const PitchProfile &other); + double dotProduct(const PitchProfile &other); + double productScorer(const PitchProfile &other); + PitchProfile normalized(); + PitchProfile& operator*=(double d); + PitchProfile& operator+=(const PitchProfile &d); + private: + double m_data[12]; + }; + + /// For use by guessHarmonies (makeHarmonyGuessList) + typedef std::vector > HarmonyTable; + static HarmonyTable m_harmonyTable; + + /// For use by guessHarmonies (makeHarmonyGuessList) + void checkHarmonyTable(); + + /// For use by guessHarmonies (refineHarmonyGuessList) + // #### grep ProgressionMap to something else + struct ChordProgression { + ChordProgression(ChordLabel first_, + ChordLabel second_ = ChordLabel(), + Key key_ = Key()); + ChordLabel first; + ChordLabel second; + Key homeKey; + // double commonness... + bool operator<(const ChordProgression& other) const; + }; + typedef std::set ProgressionMap; + static ProgressionMap m_progressionMap; + + /// For use by guessHarmonies (refineHarmonyGuessList) + void checkProgressionMap(); + + /// For use by checkProgressionMap + void addProgressionToMap(Key k, + int firstChordNumber, + int secondChordNumber); + +}; + +} + +#endif diff --git a/src/base/AudioDevice.cpp b/src/base/AudioDevice.cpp new file mode 100644 index 0000000..d9ff1f2 --- /dev/null +++ b/src/base/AudioDevice.cpp @@ -0,0 +1,107 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "AudioDevice.h" +#include "Instrument.h" + +#include + + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + + +namespace Rosegarden +{ + +AudioDevice::AudioDevice():Device(0, "Default Audio Device", Device::Audio) +{ +} + +AudioDevice::AudioDevice(DeviceId id, const std::string &name): + Device(id, name, Device::Audio) +{ +} + + +AudioDevice::AudioDevice(const AudioDevice &dev): + Device(dev.getId(), dev.getName(), dev.getType()) +{ + // Copy the instruments + // + InstrumentList insList = dev.getAllInstruments(); + InstrumentList::iterator iIt = insList.begin(); + for (; iIt != insList.end(); iIt++) + m_instruments.push_back(new Instrument(**iIt)); +} + +AudioDevice::~AudioDevice() +{ +} + + +std::string +AudioDevice::toXmlString() +{ + std::stringstream audioDevice; + InstrumentList::iterator iit; + + audioDevice << " " << std::endl; + + for (iit = m_instruments.begin(); iit != m_instruments.end(); ++iit) + audioDevice << (*iit)->toXmlString(); + + audioDevice << " " +#if (__GNUC__ < 3) + << std::endl << std::ends; +#else + << std::endl; +#endif + + return audioDevice.str(); +} + + +// Add to instrument list +// +void +AudioDevice::addInstrument(Instrument *instrument) +{ + m_instruments.push_back(instrument); +} + +// For the moment just use the first audio Instrument +// +InstrumentId +AudioDevice::getPreviewInstrument() +{ + return AudioInstrumentBase; +} + +} + + diff --git a/src/base/AudioDevice.h b/src/base/AudioDevice.h new file mode 100644 index 0000000..671c781 --- /dev/null +++ b/src/base/AudioDevice.h @@ -0,0 +1,70 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 + +#include "Device.h" +#include "Instrument.h" + +// An AudioDevice defines Instruments where we can play our +// audio Segments. +// +// +// +#ifndef _AUDIODEVICE_H_ +#define _AUDIODEVICE_H_ + +namespace Rosegarden +{ + +class AudioDevice : public Device +{ + +public: + AudioDevice(); + AudioDevice(DeviceId id, const std::string &name); + virtual ~AudioDevice(); + + // Copy constructor + // + AudioDevice(const AudioDevice &); + + virtual void addInstrument(Instrument*); + + // An untainted Instrument we can use for playing previews + // + InstrumentId getPreviewInstrument(); + + // Turn into XML string + // + virtual std::string toXmlString(); + + virtual InstrumentList getAllInstruments() const { return m_instruments; } + virtual InstrumentList getPresentationInstruments() const + { return m_instruments; } + +private: + +}; + +} + +#endif // _AUDIODEVICE_H_ diff --git a/src/base/AudioLevel.cpp b/src/base/AudioLevel.cpp new file mode 100644 index 0000000..6772c97 --- /dev/null +++ b/src/base/AudioLevel.cpp @@ -0,0 +1,272 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "AudioLevel.h" +#include +#include +#include +#include + +namespace Rosegarden { + +const float AudioLevel::DB_FLOOR = -1000.0; + +struct FaderDescription +{ + FaderDescription(float _minDb, float _maxDb, float _zeroPoint) : + minDb(_minDb), maxDb(_maxDb), zeroPoint(_zeroPoint) { } + + float minDb; + float maxDb; + float zeroPoint; // as fraction of total throw +}; + +static const FaderDescription faderTypes[] = { + FaderDescription(-40.0, +6.0, 0.75), // short + FaderDescription(-70.0, +10.0, 0.80), // long + FaderDescription(-70.0, 0.0, 1.00), // IEC268 + FaderDescription(-70.0, +10.0, 0.80), // IEC268 long + FaderDescription(-40.0, 0.0, 1.00), // preview +}; + +typedef std::vector LevelList; +static std::map previewLevelCache; +static const LevelList &getPreviewLevelCache(int levels); + +float +AudioLevel::multiplier_to_dB(float multiplier) +{ + if (multiplier == 0.0) return DB_FLOOR; + float dB = 10 * log10f(multiplier); + return dB; +} + +float +AudioLevel::dB_to_multiplier(float dB) +{ + if (dB == DB_FLOOR) return 0.0; + float m = powf(10.0, dB / 10.0); + return m; +} + +/* IEC 60-268-18 fader levels. Thanks to Steve Harris. */ + +static float iec_dB_to_fader(float db) +{ + float def = 0.0f; // Meter deflection %age + + if (db < -70.0f) { + def = 0.0f; + } else if (db < -60.0f) { + def = (db + 70.0f) * 0.25f; + } else if (db < -50.0f) { + def = (db + 60.0f) * 0.5f + 5.0f; + } else if (db < -40.0f) { + def = (db + 50.0f) * 0.75f + 7.5f; + } else if (db < -30.0f) { + def = (db + 40.0f) * 1.5f + 15.0f; + } else if (db < -20.0f) { + def = (db + 30.0f) * 2.0f + 30.0f; + } else { + def = (db + 20.0f) * 2.5f + 50.0f; + } + + return def; +} + +static float iec_fader_to_dB(float def) // Meter deflection %age +{ + float db = 0.0f; + + if (def >= 50.0f) { + db = (def - 50.0f) / 2.5f - 20.0f; + } else if (def >= 30.0f) { + db = (def - 30.0f) / 2.0f - 30.0f; + } else if (def >= 15.0f) { + db = (def - 15.0f) / 1.5f - 40.0f; + } else if (def >= 7.5f) { + db = (def - 7.5f) / 0.75f - 50.0f; + } else if (def >= 5.0f) { + db = (def - 5.0f) / 0.5f - 60.0f; + } else { + db = (def / 0.25f) - 70.0f; + } + + return db; +} + +float +AudioLevel::fader_to_dB(int level, int maxLevel, FaderType type) +{ + if (level == 0) return DB_FLOOR; + + if (type == IEC268Meter || type == IEC268LongMeter) { + + float maxPercent = iec_dB_to_fader(faderTypes[type].maxDb); + float percent = float(level) * maxPercent / float(maxLevel); + float dB = iec_fader_to_dB(percent); + return dB; + + } else { // scale proportional to sqrt(fabs(dB)) + + int zeroLevel = int(maxLevel * faderTypes[type].zeroPoint); + + if (level >= zeroLevel) { + + float value = level - zeroLevel; + float scale = float(maxLevel - zeroLevel) / + sqrtf(faderTypes[type].maxDb); + value /= scale; + float dB = powf(value, 2.0); + return dB; + + } else { + + float value = zeroLevel - level; + float scale = zeroLevel / sqrtf(0.0 - faderTypes[type].minDb); + value /= scale; + float dB = powf(value, 2.0); + return 0.0 - dB; + } + } +} + + +int +AudioLevel::dB_to_fader(float dB, int maxLevel, FaderType type) +{ + if (dB == DB_FLOOR) return 0; + + if (type == IEC268Meter || type == IEC268LongMeter) { + + // The IEC scale gives a "percentage travel" for a given dB + // level, but it reaches 100% at 0dB. So we want to treat the + // result not as a percentage, but as a scale between 0 and + // whatever the "percentage" for our (possibly >0dB) max dB is. + + float maxPercent = iec_dB_to_fader(faderTypes[type].maxDb); + float percent = iec_dB_to_fader(dB); + int faderLevel = int((maxLevel * percent) / maxPercent + 0.01); + + if (faderLevel < 0) faderLevel = 0; + if (faderLevel > maxLevel) faderLevel = maxLevel; + return faderLevel; + + } else { + + int zeroLevel = int(maxLevel * faderTypes[type].zeroPoint); + + if (dB >= 0.0) { + + float value = sqrtf(dB); + float scale = (maxLevel - zeroLevel) / sqrtf(faderTypes[type].maxDb); + value *= scale; + int level = int(value + 0.01) + zeroLevel; + if (level > maxLevel) level = maxLevel; + return level; + + } else { + + dB = 0.0 - dB; + float value = sqrtf(dB); + float scale = zeroLevel / sqrtf(0.0 - faderTypes[type].minDb); + value *= scale; + int level = zeroLevel - int(value + 0.01); + if (level < 0) level = 0; + return level; + } + } +} + + +float +AudioLevel::fader_to_multiplier(int level, int maxLevel, FaderType type) +{ + if (level == 0) return 0.0; + return dB_to_multiplier(fader_to_dB(level, maxLevel, type)); +} + +int +AudioLevel::multiplier_to_fader(float multiplier, int maxLevel, FaderType type) +{ + if (multiplier == 0.0) return 0; + float dB = multiplier_to_dB(multiplier); + int fader = dB_to_fader(dB, maxLevel, type); + return fader; +} + + +const LevelList & +getPreviewLevelCache(int levels) +{ + LevelList &ll = previewLevelCache[levels]; + if (ll.empty()) { + for (int i = 0; i <= levels; ++i) { + float m = AudioLevel::fader_to_multiplier + (i, levels, AudioLevel::PreviewLevel); + if (levels == 1) m /= 100; // noise + ll.push_back(m); + } + } + return ll; +} + +int +AudioLevel::multiplier_to_preview(float m, int levels) +{ + const LevelList &ll = getPreviewLevelCache(levels); + int result = -1; + + int lo = 0, hi = levels; + + // binary search + int level = -1; + while (result < 0) { + int newlevel = (lo + hi) / 2; + if (newlevel == level || + newlevel == 0 || + newlevel == levels) { + result = newlevel; + break; + } + level = newlevel; + if (ll[level] >= m) { + hi = level; + } else if (ll[level+1] >= m) { + result = level; + } else { + lo = level; + } + } + + return result; +} + +float +AudioLevel::preview_to_multiplier(int level, int levels) +{ + const LevelList &ll = getPreviewLevelCache(levels); + return ll[level]; +} + + +} + diff --git a/src/base/AudioLevel.h b/src/base/AudioLevel.h new file mode 100644 index 0000000..2dc742d --- /dev/null +++ b/src/base/AudioLevel.h @@ -0,0 +1,67 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _AUDIO_LEVEL_H_ +#define _AUDIO_LEVEL_H_ + +namespace Rosegarden { + +/** + * We need to represent audio levels in three different ways: as dB + * values; as a floating-point multiplier for gain; and as an integer + * on a scale for fader position and vu level. This class does the + * necessary conversions. + */ + +class AudioLevel +{ +public: + + static const float DB_FLOOR; + + enum FaderType { + ShortFader = 0, // -40 -> +6 dB + LongFader = 1, // -70 -> +10 dB + IEC268Meter = 2, // -70 -> 0 dB + IEC268LongMeter = 3, // -70 -> +10 dB (0dB aligns with LongFader) + PreviewLevel = 4 + }; + + static float multiplier_to_dB(float multiplier); + static float dB_to_multiplier(float dB); + + static float fader_to_dB(int level, int maxLevel, FaderType type); + static int dB_to_fader(float dB, int maxFaderLevel, FaderType type); + + static float fader_to_multiplier(int level, int maxLevel, FaderType type); + static int multiplier_to_fader(float multiplier, int maxFaderLevel, + FaderType type); + + // fast if "levels" doesn't change often -- for audio segment previews + static int multiplier_to_preview(float multiplier, int levels); + static float preview_to_multiplier(int level, int levels); +}; + +} + +#endif + + diff --git a/src/base/AudioPluginInstance.cpp b/src/base/AudioPluginInstance.cpp new file mode 100644 index 0000000..112687b --- /dev/null +++ b/src/base/AudioPluginInstance.cpp @@ -0,0 +1,256 @@ +// -*- c-basic-offset: 4 -*- +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "AudioPluginInstance.h" +#include "Instrument.h" + +#include +#include + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +namespace Rosegarden +{ + +// ------------------ PluginPort --------------------- +// + +PluginPort::PluginPort(int number, + std::string name, + PluginPort::PortType type, + PluginPort::PortDisplayHint hint, + PortData lowerBound, + PortData upperBound, + PortData defaultValue): + m_number(number), + m_name(name), + m_type(type), + m_displayHint(hint), + m_lowerBound(lowerBound), + m_upperBound(upperBound), + m_default(defaultValue) +{ +} + +AudioPluginInstance::AudioPluginInstance(unsigned int position): + m_mappedId(-1), + m_identifier(""), + m_position(position), + m_assigned(false), + m_bypass(false), + m_program("") +{ +} + +AudioPluginInstance::AudioPluginInstance(std::string identifier, + unsigned int position): + m_mappedId(-1), + m_identifier(identifier), + m_position(position), + m_assigned(true) +{ +} + +std::string +AudioPluginInstance::toXmlString() +{ + + std::stringstream plugin; + + if (m_assigned == false) + { +#if (__GNUC__ < 3) + plugin << std::ends; +#endif + return plugin.str(); + } + + if (m_position == Instrument::SYNTH_PLUGIN_POSITION) { + plugin << " " << std::endl; + + for (unsigned int i = 0; i < m_ports.size(); i++) + { + plugin << " number + << "\" value=\"" + << m_ports[i]->value + << "\" changed=\"" + << (m_ports[i]->changedSinceProgramChange ? "true" : "false") + << "\"/>" << std::endl; + } + + for (ConfigMap::iterator i = m_config.begin(); i != m_config.end(); ++i) { + plugin << " first) << "\" value=\"" + << encode(i->second) << "\"/>" << std::endl; + } + + if (m_position == Instrument::SYNTH_PLUGIN_POSITION) { + plugin << " "; + } else { + plugin << " "; + } + +#if (__GNUC__ < 3) + plugin << std::endl << std::ends; +#else + plugin << std::endl; +#endif + + return plugin.str(); +} + + +void +AudioPluginInstance::addPort(int number, PortData value) +{ + m_ports.push_back(new PluginPortInstance(number, value)); +} + + +bool +AudioPluginInstance::removePort(int number) +{ + PortInstanceIterator it = m_ports.begin(); + + for (; it != m_ports.end(); ++it) + { + if ((*it)->number == number) + { + delete (*it); + m_ports.erase(it); + return true; + } + } + + return false; +} + + +PluginPortInstance* +AudioPluginInstance::getPort(int number) +{ + PortInstanceIterator it = m_ports.begin(); + + for (; it != m_ports.end(); ++it) + { + if ((*it)->number == number) + return *it; + } + + return 0; +} + +void +AudioPluginInstance::clearPorts() +{ + PortInstanceIterator it = m_ports.begin(); + for (; it != m_ports.end(); ++it) + delete (*it); + m_ports.erase(m_ports.begin(), m_ports.end()); +} + +std::string +AudioPluginInstance::getConfigurationValue(std::string k) const +{ + ConfigMap::const_iterator i = m_config.find(k); + if (i != m_config.end()) return i->second; + return ""; +} + +void +AudioPluginInstance::setProgram(std::string program) +{ + m_program = program; + + PortInstanceIterator it = m_ports.begin(); + for (; it != m_ports.end(); ++it) { + (*it)->changedSinceProgramChange = false; + } +} + +void +AudioPluginInstance::setConfigurationValue(std::string k, std::string v) +{ + m_config[k] = v; +} + +std::string +AudioPluginInstance::getDistinctiveConfigurationText() const +{ + std::string base = getConfigurationValue("load"); + + if (base == "") { + for (ConfigMap::const_iterator i = m_config.begin(); + i != m_config.end(); ++i) { + + if (!strncmp(i->first.c_str(), + "__ROSEGARDEN__", + strlen("__ROSEGARDEN__"))) continue; + + if (i->second != "" && i->second[0] == '/') { + base = i->second; + break; + } else if (base != "") { + base = i->second; + } + } + } + + if (base == "") return ""; + + std::string::size_type s = base.rfind('/'); + if (s < base.length() - 1) base = base.substr(s + 1); + + std::string::size_type d = base.rfind('.'); + if (d < base.length() - 1 && d > 0) base = base.substr(0, d); + + return base; +} + + +} + diff --git a/src/base/AudioPluginInstance.h b/src/base/AudioPluginInstance.h new file mode 100644 index 0000000..7641228 --- /dev/null +++ b/src/base/AudioPluginInstance.h @@ -0,0 +1,172 @@ +// -*- c-indentation-style:"stroustrup" c-basic-offset: 4 -*- +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 +#include +#include + +#include "XmlExportable.h" + +// An Instrument on needs to implement these to render an instance +// of the plugin at the sequencer. +// + +#ifndef _AUDIOPLUGININSTANCE_H_ +#define _AUDIOPLUGININSTANCE_H_ + +namespace Rosegarden +{ + +typedef float PortData; + +class PluginPort +{ +public: + typedef enum + { + Input = 0x01, + Output = 0x02, + Control = 0x04, + Audio = 0x08 + } PortType; + + typedef enum + { + NoHint = 0x00, + Toggled = 0x01, + Integer = 0x02, + Logarithmic = 0x04 + } PortDisplayHint; + + PluginPort(int number, + std::string m_name, + PortType type, + PortDisplayHint displayHint, + PortData lowerBound, + PortData upperBound, + PortData defaultValue); + + int getNumber() const { return m_number; } + std::string getName() const { return m_name; } + PortType getType() const { return m_type; } + PortDisplayHint getDisplayHint() const { return m_displayHint; } + PortData getLowerBound() const { return m_lowerBound; } + PortData getUpperBound() const { return m_upperBound; } + PortData getDefaultValue() const { return m_default; } + +protected: + + int m_number; + std::string m_name; + PortType m_type; + PortDisplayHint m_displayHint; + PortData m_lowerBound; + PortData m_upperBound; + PortData m_default; +}; + +class PluginPortInstance +{ +public: + PluginPortInstance(unsigned int n, + float v) + : number(n), value(v), changedSinceProgramChange(false) {;} + + int number; + PortData value; + bool changedSinceProgramChange; + + void setValue(PortData v) { value = v; changedSinceProgramChange = true; } +}; + +typedef std::vector::iterator PortInstanceIterator; + +class AudioPluginInstance : public XmlExportable +{ +public: + AudioPluginInstance(unsigned int position); + + AudioPluginInstance(std::string identifier, + unsigned int position); + + void setIdentifier(std::string identifier) { m_identifier = identifier; } + std::string getIdentifier() const { return m_identifier; } + + void setPosition(unsigned int position) { m_position = position; } + unsigned int getPosition() const { return m_position; } + + PortInstanceIterator begin() { return m_ports.begin(); } + PortInstanceIterator end() { return m_ports.end(); } + + // Port management + // + void addPort(int number, PortData value); + bool removePort(int number); + PluginPortInstance* getPort(int number); + void clearPorts(); + + unsigned int getPortCount() const { return m_ports.size(); } + + // export + std::string toXmlString(); + + // Is the instance assigned to a plugin? + // + void setAssigned(bool ass) { m_assigned = ass; } + bool isAssigned() const { return m_assigned; } + + void setBypass(bool bypass) { m_bypass = bypass; } + bool isBypassed() const { return m_bypass; } + + void setProgram(std::string program); + std::string getProgram() const { return m_program; } + + int getMappedId() const { return m_mappedId; } + void setMappedId(int value) { m_mappedId = value; } + + typedef std::map ConfigMap; + void clearConfiguration() { m_config.clear(); } + const ConfigMap &getConfiguration() { return m_config; } + std::string getConfigurationValue(std::string k) const; + void setConfigurationValue(std::string k, std::string v); + + std::string getDistinctiveConfigurationText() const; + +protected: + + int m_mappedId; + std::string m_identifier; + std::vector m_ports; + unsigned int m_position; + + // Is the plugin actually assigned i.e. should we create + // a matching instance at the sequencer? + // + bool m_assigned; + bool m_bypass; + + std::string m_program; + + ConfigMap m_config; +}; + +} + +#endif // _AUDIOPLUGININSTANCE_H_ diff --git a/src/base/BaseProperties.cpp b/src/base/BaseProperties.cpp new file mode 100644 index 0000000..adff519 --- /dev/null +++ b/src/base/BaseProperties.cpp @@ -0,0 +1,133 @@ +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "BaseProperties.h" +#include + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +namespace Rosegarden +{ + +namespace BaseProperties +{ + +// Some of the most basic property names are defined in individual +// classes in NotationTypes.h -- those are the ones that are used to +// store the value of a clef/key/timesig event, whereas things like +// notes have their values calculated from the duration property. + +// We mostly define persistent properties with lower-case names and +// non-persistent ones with mixed-case. That's just because lower- +// case looks nicer in XML, whereas mixed-case is friendlier for the +// sorts of long names sometimes found in cached calculations. + +const PropertyName PITCH = "pitch"; +const PropertyName VELOCITY = "velocity"; +const PropertyName ACCIDENTAL = "accidental"; + +const PropertyName NOTE_TYPE = "notetype"; +const PropertyName NOTE_DOTS = "notedots"; + +const PropertyName MARK_COUNT = "marks"; + +PropertyName getMarkPropertyName(int markNo) +{ + static std::vector firstFive; + + if (firstFive.size() == 0) { + firstFive.push_back(PropertyName("mark1")); + firstFive.push_back(PropertyName("mark2")); + firstFive.push_back(PropertyName("mark3")); + firstFive.push_back(PropertyName("mark4")); + firstFive.push_back(PropertyName("mark5")); + } + + if (markNo < 5) return firstFive[markNo]; + + // This is slower than it looks, because it means we need to do + // the PropertyName interning process for each string -- hence the + // firstFive cache + + std::stringstream markPropertyName; + +#if (__GNUC__ < 3) + markPropertyName << "mark" << (markNo + 1) << std::ends; +#else + markPropertyName << "mark" << (markNo + 1); +#endif + + return markPropertyName.str(); +} + +const PropertyName TIED_BACKWARD = "tiedback"; +const PropertyName TIED_FORWARD = "tiedforward"; +const PropertyName TIE_IS_ABOVE = "tieabove"; + +// capitalised for back-compatibility (used to be in NotationProperties) +const PropertyName HEIGHT_ON_STAFF = "HeightOnStaff"; +const PropertyName NOTE_STYLE = "NoteStyle"; +const PropertyName BEAMED = "Beamed"; + +const PropertyName BEAMED_GROUP_ID = "groupid"; +const PropertyName BEAMED_GROUP_TYPE = "grouptype"; + +const PropertyName BEAMED_GROUP_TUPLET_BASE = "tupletbase"; +const PropertyName BEAMED_GROUP_TUPLED_COUNT = "tupledcount"; +const PropertyName BEAMED_GROUP_UNTUPLED_COUNT = "untupledcount"; + +// persistent, but mixed-case anyway +const PropertyName IS_GRACE_NOTE = "IsGraceNote"; + +// obsolete +const PropertyName HAS_GRACE_NOTES = "HasGraceNotes"; + +// non-persistent +const PropertyName MAY_HAVE_GRACE_NOTES = "MayHaveGraceNotes"; + +const std::string GROUP_TYPE_BEAMED = "beamed"; +const std::string GROUP_TYPE_TUPLED = "tupled"; +const std::string GROUP_TYPE_GRACE = "grace"; + +const PropertyName TRIGGER_SEGMENT_ID = "triggersegmentid"; +const PropertyName TRIGGER_SEGMENT_RETUNE = "triggersegmentretune"; +const PropertyName TRIGGER_SEGMENT_ADJUST_TIMES = "triggersegmentadjusttimes"; + +const std::string TRIGGER_SEGMENT_ADJUST_NONE = "none"; +const std::string TRIGGER_SEGMENT_ADJUST_SQUISH = "squish"; +const std::string TRIGGER_SEGMENT_ADJUST_SYNC_START = "syncstart"; +const std::string TRIGGER_SEGMENT_ADJUST_SYNC_END = "syncend"; + +const PropertyName RECORDED_CHANNEL = "recordedchannel"; +const PropertyName RECORDED_PORT = "recordedport"; + +const PropertyName DISPLACED_X = "displacedx"; +const PropertyName DISPLACED_Y = "displacedy"; + +const PropertyName INVISIBLE = "invisible"; + +} + +} + diff --git a/src/base/BaseProperties.h b/src/base/BaseProperties.h new file mode 100644 index 0000000..f83b2f7 --- /dev/null +++ b/src/base/BaseProperties.h @@ -0,0 +1,82 @@ +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _BASE_PROPERTIES_H_ +#define _BASE_PROPERTIES_H_ + +#include "PropertyName.h" + +namespace Rosegarden +{ + +namespace BaseProperties +{ + +extern const PropertyName PITCH; +extern const PropertyName VELOCITY; +extern const PropertyName ACCIDENTAL; + +extern const PropertyName NOTE_TYPE; +extern const PropertyName NOTE_DOTS; + +extern const PropertyName MARK_COUNT; +extern PropertyName getMarkPropertyName(int markNo); + +extern const PropertyName TIED_BACKWARD; +extern const PropertyName TIED_FORWARD; +extern const PropertyName TIE_IS_ABOVE; // optional; default position if absent + +extern const PropertyName BEAMED_GROUP_ID; +extern const PropertyName BEAMED_GROUP_TYPE; + +extern const PropertyName BEAMED_GROUP_TUPLET_BASE; +extern const PropertyName BEAMED_GROUP_TUPLED_COUNT; +extern const PropertyName BEAMED_GROUP_UNTUPLED_COUNT; + +extern const PropertyName IS_GRACE_NOTE; +extern const PropertyName HAS_GRACE_NOTES; // obsolete +extern const PropertyName MAY_HAVE_GRACE_NOTES; // hint for use by performance helper + +extern const std::string GROUP_TYPE_BEAMED; +extern const std::string GROUP_TYPE_TUPLED; +extern const std::string GROUP_TYPE_GRACE; // obsolete + +extern const PropertyName TRIGGER_SEGMENT_ID; +extern const PropertyName TRIGGER_SEGMENT_RETUNE; +extern const PropertyName TRIGGER_SEGMENT_ADJUST_TIMES; + +extern const std::string TRIGGER_SEGMENT_ADJUST_NONE; +extern const std::string TRIGGER_SEGMENT_ADJUST_SQUISH; +extern const std::string TRIGGER_SEGMENT_ADJUST_SYNC_START; +extern const std::string TRIGGER_SEGMENT_ADJUST_SYNC_END; + +extern const PropertyName RECORDED_CHANNEL; +extern const PropertyName RECORDED_PORT; + +extern const PropertyName DISPLACED_X; +extern const PropertyName DISPLACED_Y; + +extern const PropertyName INVISIBLE; + +} + +} + +#endif + diff --git a/src/base/BasicQuantizer.cpp b/src/base/BasicQuantizer.cpp new file mode 100644 index 0000000..7cfc0db --- /dev/null +++ b/src/base/BasicQuantizer.cpp @@ -0,0 +1,253 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "BasicQuantizer.h" +#include "BaseProperties.h" +#include "NotationTypes.h" +#include "Selection.h" +#include "Composition.h" +#include "Profiler.h" + +#include +#include +#include // for sprintf +#include + +using std::cout; +using std::cerr; +using std::endl; + +namespace Rosegarden +{ + +using namespace BaseProperties; + +const std::string Quantizer::RawEventData = ""; +const std::string Quantizer::DefaultTarget = "DefaultQ"; +const std::string Quantizer::GlobalSource = "GlobalQ"; +const std::string Quantizer::NotationPrefix = "Notation"; + +BasicQuantizer::BasicQuantizer(timeT unit, bool doDurations, + int swing, int iterate) : + Quantizer(RawEventData), + m_unit(unit), + m_durations(doDurations), + m_swing(swing), + m_iterate(iterate) +{ + if (m_unit < 0) m_unit = Note(Note::Shortest).getDuration(); +} + +BasicQuantizer::BasicQuantizer(std::string source, std::string target, + timeT unit, bool doDurations, + int swing, int iterate) : + Quantizer(source, target), + m_unit(unit), + m_durations(doDurations), + m_swing(swing), + m_iterate(iterate) +{ + if (m_unit < 0) m_unit = Note(Note::Shortest).getDuration(); +} + +BasicQuantizer::BasicQuantizer(const BasicQuantizer &q) : + Quantizer(q.m_target), + m_unit(q.m_unit), + m_durations(q.m_durations), + m_swing(q.m_swing), + m_iterate(q.m_iterate) +{ + // nothing else +} + +BasicQuantizer::~BasicQuantizer() +{ + // nothing +} + +void +BasicQuantizer::quantizeSingle(Segment *s, Segment::iterator i) const +{ + timeT d = getFromSource(*i, DurationValue); + + if (d == 0 && (*i)->isa(Note::EventType)) { + s->erase(i); + return; + } + + if (m_unit == 0) return; + + timeT t = getFromSource(*i, AbsoluteTimeValue); + timeT d0(d), t0(t); + + timeT barStart = s->getBarStartForTime(t); + + t -= barStart; + + int n = t / m_unit; + timeT low = n * m_unit; + timeT high = low + m_unit; + timeT swingOffset = (m_unit * m_swing) / 300; + + if (high - t > t - low) { + t = low; + } else { + t = high; + ++n; + } + + if (n % 2 == 1) { + t += swingOffset; + } + + if (m_durations && d != 0) { + + low = (d / m_unit) * m_unit; + high = low + m_unit; + + if (low > 0 && (high - d > d - low)) { + d = low; + } else { + d = high; + } + + int n1 = n + d / m_unit; + + if (n % 2 == 0) { // start not swung + if (n1 % 2 == 0) { // end not swung + // do nothing + } else { // end swung + d += swingOffset; + } + } else { // start swung + if (n1 % 2 == 0) { // end not swung + d -= swingOffset; + } else { + // do nothing + } + } + } + + t += barStart; + + timeT t1(t), d1(d); + t = (t - t0) * m_iterate / 100 + t0; + d = (d - d0) * m_iterate / 100 + d0; + + // if an iterative quantize results in something much closer than + // the shortest actual note resolution we have, just snap it + if (m_iterate != 100) { + timeT close = Note(Note::Shortest).getDuration()/2; + if (t >= t1 - close && t <= t1 + close) t = t1; + if (d >= d1 - close && d <= d1 + close) d = d1; + } + + if (t0 != t || d0 != d) setToTarget(s, i, t, d); +} + + +std::vector +BasicQuantizer::getStandardQuantizations() +{ + checkStandardQuantizations(); + return m_standardQuantizations; +} + +void +BasicQuantizer::checkStandardQuantizations() +{ + if (m_standardQuantizations.size() > 0) return; + + for (Note::Type nt = Note::Semibreve; nt >= Note::Shortest; --nt) { + + int i1 = (nt < Note::Quaver ? 1 : 0); + for (int i = 0; i <= i1; ++i) { + + int divisor = (1 << (Note::Semibreve - nt)); + if (i) divisor = divisor * 3 / 2; + + timeT unit = Note(Note::Semibreve).getDuration() / divisor; + m_standardQuantizations.push_back(unit); + } + } +} + +timeT +BasicQuantizer::getStandardQuantization(Segment *s) +{ + checkStandardQuantizations(); + timeT unit = -1; + + for (Segment::iterator i = s->begin(); s->isBeforeEndMarker(i); ++i) { + + if (!(*i)->isa(Rosegarden::Note::EventType)) continue; + timeT myUnit = getUnitFor(*i); + if (unit < 0 || myUnit < unit) unit = myUnit; + } + + return unit; +} + +timeT +BasicQuantizer::getStandardQuantization(EventSelection *s) +{ + checkStandardQuantizations(); + timeT unit = -1; + + if (!s) return 0; + + for (EventSelection::eventcontainer::iterator i = + s->getSegmentEvents().begin(); + i != s->getSegmentEvents().end(); ++i) { + + if (!(*i)->isa(Rosegarden::Note::EventType)) continue; + timeT myUnit = getUnitFor(*i); + if (unit < 0 || myUnit < unit) unit = myUnit; + } + + return unit; +} + +timeT +BasicQuantizer::getUnitFor(Event *e) +{ + timeT absTime = e->getAbsoluteTime(); + timeT myQuantizeUnit = 0; + + // m_quantizations is in descending order of duration; + // stop when we reach one that divides into the note's time + + for (unsigned int i = 0; i < m_standardQuantizations.size(); ++i) { + if (absTime % m_standardQuantizations[i] == 0) { + myQuantizeUnit = m_standardQuantizations[i]; + break; + } + } + + return myQuantizeUnit; +} + +std::vector +BasicQuantizer::m_standardQuantizations; + + +} diff --git a/src/base/BasicQuantizer.h b/src/base/BasicQuantizer.h new file mode 100644 index 0000000..1a9a7b7 --- /dev/null +++ b/src/base/BasicQuantizer.h @@ -0,0 +1,95 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 BASIC_QUANTIZER_H +#define BASIC_QUANTIZER_H + +#include "Quantizer.h" + +namespace Rosegarden { + +class BasicQuantizer : public Quantizer +{ +public: + // The default unit is the shortest note type. A unit of + // zero means do no quantization (rather pointlessly). + BasicQuantizer(timeT unit = -1, bool doDurations = false, + int swingPercent = 0, int iteratePercent = 100); + BasicQuantizer(std::string source, std::string target, + timeT unit = -1, bool doDurations = false, + int swingPercent = 0, int iteratePercent = 100); + BasicQuantizer(const BasicQuantizer &); + virtual ~BasicQuantizer(); + + void setUnit(timeT unit) { m_unit = unit; } + timeT getUnit() const { return m_unit; } + + void setDoDurations(bool doDurations) { m_durations = doDurations; } + bool getDoDurations() const { return m_durations; } + + void setSwing(int percent) { m_swing = percent; } + int getSwing() const { return m_swing; } + + void setIterative(int percent) { m_iterate = percent; } + int getIterative() const { return m_iterate; } + + /** + * Return the standard quantization units in descending order of + * unit duration + */ + static std::vector getStandardQuantizations(); + + /** + * Study the given segment; if all the events in it have times + * that match one or more of the standard quantizations, return + * the longest standard quantization unit to match. Otherwise + * return 0. + */ + static timeT getStandardQuantization(Segment *); + + /** + * Study the given selection; if all the events in it have times + * that match one or more of the standard quantizations, return + * the longest standard quantization unit to match. Otherwise + * return 0. + */ + static timeT getStandardQuantization(EventSelection *); + +protected: + virtual void quantizeSingle(Segment *, + Segment::iterator) const; + +private: + BasicQuantizer &operator=(const BasicQuantizer &); // not provided + + timeT m_unit; + bool m_durations; + int m_swing; + int m_iterate; + + static std::vector m_standardQuantizations; + static void checkStandardQuantizations(); + static timeT getUnitFor(Event *); +}; + +} + +#endif diff --git a/src/base/Clipboard.cpp b/src/base/Clipboard.cpp new file mode 100644 index 0000000..71ff03f --- /dev/null +++ b/src/base/Clipboard.cpp @@ -0,0 +1,387 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Clipboard.h" +#include "Selection.h" + +namespace Rosegarden +{ + +Clipboard::Clipboard() : + m_partial(false), + m_haveTimeSigSelection(false), + m_haveTempoSelection(false), + m_nominalStart(0), + m_nominalEnd(0) +{ + // nothing +} + +Clipboard::Clipboard(const Clipboard &c) : + m_partial(false) +{ + copyFrom(&c); +} + +Clipboard & +Clipboard::operator=(const Clipboard &c) +{ + copyFrom(&c); + return *this; +} + +Clipboard::~Clipboard() +{ + clear(); +} + +void +Clipboard::clear() +{ + for (iterator i = begin(); i != end(); ++i) { + delete *i; + } + m_segments.clear(); + clearTimeSignatureSelection(); + clearTempoSelection(); + clearNominalRange(); + m_partial = false; +} + +bool +Clipboard::isEmpty() const +{ + return (m_segments.size() == 0 && + !m_haveTimeSigSelection && + !m_haveTempoSelection && + m_nominalStart == m_nominalEnd); +} + +bool +Clipboard::isSingleSegment() const +{ + return (m_segments.size() == 1 && + !m_haveTimeSigSelection && + !m_haveTempoSelection); +} + +Segment * +Clipboard::getSingleSegment() const +{ + if (isSingleSegment()) return *begin(); + else return 0; +} + +bool +Clipboard::isPartial() const +{ + return m_partial; +} + +Segment * +Clipboard::newSegment() +{ + Segment *s = new Segment(); + m_segments.insert(s); + // don't change m_partial + return s; +} + +Segment * +Clipboard::newSegment(const Segment *copyFrom) +{ + Segment *s = new Segment(*copyFrom); + m_segments.insert(s); + // don't change m_partial + return s; +} + +void +Clipboard::newSegment(const Segment *copyFrom, timeT from, timeT to, + bool expandRepeats) +{ + // create with copy ctor so as to inherit track, instrument etc + Segment *s = new Segment(*copyFrom); + + if (from <= s->getStartTime() && to >= s->getEndMarkerTime()) { + m_segments.insert(s); + s->setEndTime(s->getEndMarkerTime()); + // don't change m_partial + return; + } + + timeT segStart = copyFrom->getStartTime(); + timeT segEnd = copyFrom->getEndMarkerTime(); + timeT segDuration = segEnd - segStart; + + int firstRepeat = 0; + int lastRepeat = 0; + + if (!copyFrom->isRepeating() || segDuration <= 0) { + expandRepeats = false; + } + + if (expandRepeats) { + firstRepeat = (from - segStart) / segDuration; + lastRepeat = (to - segStart) / segDuration; + to = std::min(to, copyFrom->getRepeatEndTime()); + } + + s->setRepeating(false); + + if (s->getType() == Segment::Audio) { + + Composition *c = copyFrom->getComposition(); + + for (int repeat = firstRepeat; repeat <= lastRepeat; ++repeat) { + + timeT wrappedFrom = segStart; + timeT wrappedTo = segEnd; + + if (!expandRepeats) { + wrappedFrom = from; + wrappedTo = to; + } else { + if (repeat == firstRepeat) { + wrappedFrom = segStart + (from - segStart) % segDuration; + } + if (repeat == lastRepeat) { + wrappedTo = segStart + (to - segStart) % segDuration; + } + } + + if (wrappedFrom > segStart) { + if (c) { + s->setAudioStartTime + (s->getAudioStartTime() + + c->getRealTimeDifference(segStart + repeat * segDuration, + from)); + } + s->setStartTime(from); + } else { + s->setStartTime(segStart + repeat * segDuration); + } + + if (wrappedTo < segEnd) { + s->setEndMarkerTime(to); + if (c) { + s->setAudioEndTime + (s->getAudioStartTime() + + c->getRealTimeDifference(segStart + repeat * segDuration, + to)); + } + } else { + s->setEndMarkerTime(segStart + (repeat + 1) * segDuration); + } + + m_segments.insert(s); + if (repeat < lastRepeat) { + s = new Segment(*copyFrom); + s->setRepeating(false); + } + } + + m_partial = true; + return; + } + + s->erase(s->begin(), s->end()); + + for (int repeat = firstRepeat; repeat <= lastRepeat; ++repeat) { + + Segment::const_iterator ifrom = copyFrom->begin(); + Segment::const_iterator ito = copyFrom->end(); + + if (!expandRepeats) { + ifrom = copyFrom->findTime(from); + ito = copyFrom->findTime(to); + } else { + if (repeat == firstRepeat) { + ifrom = copyFrom->findTime + (segStart + (from - segStart) % segDuration); + } + if (repeat == lastRepeat) { + ito = copyFrom->findTime + (segStart + (to - segStart) % segDuration); + } + } + + for (Segment::const_iterator i = ifrom; + i != ito && copyFrom->isBeforeEndMarker(i); ++i) { + + timeT absTime = (*i)->getAbsoluteTime() + repeat * segDuration; + timeT duration = (*i)->getDuration(); + + Event *e = (*i)->copyMoving(repeat * segDuration); + + if (absTime + duration <= to) { + + s->insert(e); + + } else { + + s->insert(new Event(*e, + e->getAbsoluteTime(), + duration, + e->getSubOrdering(), + e->getNotationAbsoluteTime(), + e->getNotationDuration())); + delete e; + } + } + } + + // need to call getEndMarkerTime() on copyFrom, not on s, because + // its return value may depend on the composition it's in + if (copyFrom->getEndMarkerTime() > to) { + s->setEndMarkerTime(to); + } + + m_segments.insert(s); + m_partial = true; + return; +} + +Segment * +Clipboard::newSegment(const EventSelection *copyFrom) +{ + // create with copy ctor so as to inherit track, instrument etc + Segment *s = new Segment(copyFrom->getSegment()); + s->erase(s->begin(), s->end()); + + const EventSelection::eventcontainer &events(copyFrom->getSegmentEvents()); + for (EventSelection::eventcontainer::const_iterator i = events.begin(); + i != events.end(); ++i) { + s->insert(new Event(**i)); + } + + m_segments.insert(s); + m_partial = true; + return s; +} + +void +Clipboard::setTimeSignatureSelection(const TimeSignatureSelection &ts) +{ + m_timeSigSelection = ts; + m_haveTimeSigSelection = true; +} + +void +Clipboard::clearTimeSignatureSelection() +{ + m_timeSigSelection = TimeSignatureSelection(); + m_haveTimeSigSelection = false; +} + +const TimeSignatureSelection & +Clipboard::getTimeSignatureSelection() const +{ + return m_timeSigSelection; +} + +void +Clipboard::setTempoSelection(const TempoSelection &ts) +{ + m_tempoSelection = ts; + m_haveTempoSelection = true; +} + +void +Clipboard::clearTempoSelection() +{ + m_tempoSelection = TempoSelection(); + m_haveTempoSelection = false; +} + +const TempoSelection & +Clipboard::getTempoSelection() const +{ + return m_tempoSelection; +} + +void +Clipboard::copyFrom(const Clipboard *c) +{ + if (c == this) return; + clear(); + + for (Clipboard::const_iterator i = c->begin(); i != c->end(); ++i) { + newSegment(*i); + } + + m_partial = c->m_partial; + + m_timeSigSelection = c->m_timeSigSelection; + m_haveTimeSigSelection = c->m_haveTimeSigSelection; + + m_tempoSelection = c->m_tempoSelection; + m_haveTempoSelection = c->m_haveTempoSelection; + + m_nominalStart = c->m_nominalStart; + m_nominalEnd = c->m_nominalEnd; +} + +timeT +Clipboard::getBaseTime() const +{ + if (hasNominalRange()) { + return m_nominalStart; + } + + timeT t = 0; + + for (iterator i = begin(); i != end(); ++i) { + if (i == begin() || (*i)->getStartTime() < t) { + t = (*i)->getStartTime(); + } + } + + if (m_haveTimeSigSelection && !m_timeSigSelection.empty()) { + if (m_timeSigSelection.begin()->first < t) { + t = m_timeSigSelection.begin()->first; + } + } + + if (m_haveTempoSelection && !m_tempoSelection.empty()) { + if (m_tempoSelection.begin()->first < t) { + t = m_tempoSelection.begin()->first; + } + } + + return t; +} + +void +Clipboard::setNominalRange(timeT start, timeT end) +{ + m_nominalStart = start; + m_nominalEnd = end; +} + +void +Clipboard::getNominalRange(timeT &start, timeT &end) +{ + start = m_nominalStart; + end = m_nominalEnd; +} + +} diff --git a/src/base/Clipboard.h b/src/base/Clipboard.h new file mode 100644 index 0000000..e205e33 --- /dev/null +++ b/src/base/Clipboard.h @@ -0,0 +1,203 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _CLIPBOARD_H_ +#define _CLIPBOARD_H_ + +#include +#include "Segment.h" +#include "Selection.h" + +namespace Rosegarden +{ +class EventSelection; + +/** + * Simple container for segments, that can serve as a clipboard for + * editing operations. Conceptually it has two "modes", + * single-segment and multiple-segment, although there's no particular + * distinction behind the scenes. The Clipboard owns all the segments + * it contains -- they should always be deep copies, not aliases. + */ + +class Clipboard +{ +public: + typedef std::multiset segmentcontainer; + typedef segmentcontainer::iterator iterator; + typedef segmentcontainer::const_iterator const_iterator; + + Clipboard(); + Clipboard(const Clipboard &); + Clipboard &operator=(const Clipboard &); + virtual ~Clipboard(); + + /** + * Empty the clipboard. + */ + void clear(); + + /** + * Return true if the clipboard is empty. + */ + bool isEmpty() const; + + iterator begin() { return m_segments.begin(); } + const_iterator begin() const { return m_segments.begin(); } + iterator end() { return m_segments.end(); } + const_iterator end() const { return m_segments.end(); } + + /** + * Return true if the clipboard only contains a single segment. + * Single-segment and multi-segment are conceptually rather + * separate -- for example, you can only paste into a segment + * from a single-segment clipboard. + */ + bool isSingleSegment() const; + + /** + * Return true if the clipboard contains at least one segment + * that originated as only part of another segment. If a + * paste is made from a clipboard with isPartial true, the + * paste command will generally want to be sure to normalize + * rests etc on the pasted region afterwards. + */ + bool isPartial() const; + + /** + * Return the single segment contained by the clipboard. + * If the clipboard is empty or contains more than one segment, + * returns null. (Use the iterator accessors begin()/end() to + * read from a clipboard for which isSingleSegment is false.) + */ + Segment *getSingleSegment() const; + + /** + * Add a new empty segment to the clipboard, and return a + * pointer to it. (The clipboard retains ownership.) + */ + Segment *newSegment(); + + /** + * Add a new segment to the clipboard, containing copies of + * the events in copyFrom. (The clipboard retains ownership + * of the new segment.) + */ + Segment *newSegment(const Segment *copyFrom); + + /** + * Add one or more new segments to the clipboard, containing + * copies of the events in copyFrom found between from and to. If + * expandRepeats is true, include any events found in the + * repeating trail of the segment within this time. (The + * clipboard retains ownership of the new segment(s).) + * + * This may insert more than one new segment, if it is required to + * insert a repeating section of an audio segment. For this + * reason it does not return the inserted segment (even though in + * most situations it will only insert one). + */ + void newSegment(const Segment *copyFrom, timeT from, timeT to, + bool expandRepeats); + + /** + * Add a new segment to the clipboard, containing copied of + * the events in the given selection. + */ + Segment *newSegment(const EventSelection *copyFrom); + + /** + * Add a time signature selection to this clipboard, replacing any + * that already exists. + */ + void setTimeSignatureSelection(const TimeSignatureSelection &); + + bool hasTimeSignatureSelection() const { return m_haveTimeSigSelection; } + + /** + * Remove any time signature selection from the clipboard. + */ + void clearTimeSignatureSelection(); + + /** + * Retrieve any time signature selection found in the clipboard. + */ + const TimeSignatureSelection &getTimeSignatureSelection() const; + + /** + * Add a tempo selection to this clipboard, replacing any + * that already exists. + */ + void setTempoSelection(const TempoSelection &); + + bool hasTempoSelection() const { return m_haveTempoSelection; } + + /** + * Remove any tempo selection from the clipboard. + */ + void clearTempoSelection(); + + /** + * Retrieve any tempo selection found in the clipboard. + */ + const TempoSelection &getTempoSelection() const; + + /** + * Clear the current clipboard and re-fill it by copying from c. + */ + void copyFrom(const Clipboard *c); + + /** + * Get the earliest start time for anything in this clipboard, + * or the start of the nominal range if there is one. + */ + timeT getBaseTime() const; + + /** + * Set nominal start and end times for the range in the clipboard, + * if it is intended to cover a particular time range regardless + * of whether the data in it covers the full range or not. + */ + void setNominalRange(timeT start, timeT end); + + void clearNominalRange() { setNominalRange(0, 0); } + + bool hasNominalRange() const { return m_nominalStart != m_nominalEnd; } + + void getNominalRange(timeT &start, timeT &end); + +private: + segmentcontainer m_segments; + bool m_partial; + + TimeSignatureSelection m_timeSigSelection; + bool m_haveTimeSigSelection; + + TempoSelection m_tempoSelection; + bool m_haveTempoSelection; + + timeT m_nominalStart; + timeT m_nominalEnd; +}; + +} + +#endif diff --git a/src/base/Colour.cpp b/src/base/Colour.cpp new file mode 100644 index 0000000..ea1f5a2 --- /dev/null +++ b/src/base/Colour.cpp @@ -0,0 +1,175 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2003 + Mark Hymers + + 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 "Colour.h" + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +namespace Rosegarden +{ + +// The Colour Class + +Colour::Colour() +{ + m_r = 0; + m_g = 0; + m_b = 0; +} + +Colour::Colour(const unsigned int red, const unsigned int green, const unsigned int blue) +{ + this->setColour(red, green, blue); +} + +Colour::Colour(const Colour& input) +{ + this->setColour(input.getRed(), input.getGreen(), input.getBlue()); +} + +Colour::~Colour() +{ + +} + +Colour& +Colour::operator= (const Colour& input) +{ + // We don't have to check for assignment to self because it'll have + // no nasty effects (in fact, it'll do what it should - nothing) + this->setColour(input.getRed(), input.getGreen(), input.getBlue()); + return *this; +} + +void +Colour::setColour(const unsigned int red, const unsigned int green, const unsigned int blue) +{ + (red<=255) ? m_r=red : m_r=0; + (green<=255) ? m_g=green : m_g=0; + (blue<=255) ? m_b=blue : m_b=0; +} + +void +Colour::getColour(unsigned int &red, unsigned int &green, unsigned int &blue) const +{ + red = m_r; + green = m_g; + blue = m_b; +} + +unsigned int +Colour::getRed() const +{ + return m_r; +} + +unsigned int +Colour::getBlue() const +{ + return m_b; +} + +unsigned int +Colour::getGreen() const +{ + return m_g; +} + +void +Colour::setRed(const unsigned int red) +{ + (red<=255) ? m_r=red : m_r=0; +} + +void +Colour::setBlue(const unsigned int blue) +{ + (blue<=255) ? m_b=blue: m_b=0; +} + +void +Colour::setGreen(const unsigned int green) +{ + (green<=255) ? m_g=green : m_g=0; +} + +Colour +Colour::getContrastingColour() const +{ + Colour ret(255-m_r, 255-m_g, 255-m_b); + return ret; +} + +std::string +Colour::toXmlString() const +{ + std::stringstream output; + + output << "" << std::endl << std::ends; +#else + << "\"/>" << std::endl; +#endif + + return output.str(); +} + +std::string +Colour::dataToXmlString() const +{ + std::stringstream output; + output << "red=\"" << m_r + << "\" green=\"" << m_g + << "\" blue=\"" << m_b +#if (__GNUC__ < 3) + << "\"" << std::ends; +#else + << "\""; +#endif + + return output.str(); +} + +// Generic Colour routines: + +Colour +getCombinationColour(const Colour &input1, const Colour &input2) +{ + Colour ret((input1.getRed()+input2.getRed())/2, + (input1.getGreen()+input2.getGreen())/2, + (input1.getBlue()+input2.getBlue())/2); + return ret; + +} + +} diff --git a/src/base/Colour.h b/src/base/Colour.h new file mode 100644 index 0000000..ba8cd6f --- /dev/null +++ b/src/base/Colour.h @@ -0,0 +1,125 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2003 + Mark Hymers + + 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 _BASE_COLOUR_H_ +#define _BASE_COLOUR_H_ + +#include + +namespace Rosegarden +{ + +/** + * Colour is our internal colour storage mechanism; it's extremely basic + * but does what it needs to + */ + +class Colour +{ +public: + /** + * Create a Colour with values initialised to r=0, g=0, b=0 + * i.e. Black. + */ + Colour(); + + /** + * Create a Colour with the specified red, green, blue values. + * If out of specification (i.e. < 0 || > 255 the value will be set to 0. + */ + Colour(unsigned int red, unsigned int green, unsigned int blue); + Colour(const Colour& input); + + ~Colour(); + Colour& operator= (const Colour& input); + + /** + * Set the colour as appropriate; as with the constructor invalid values + * will be set to 0. + */ + void setColour(unsigned int red, unsigned int green, unsigned int blue); + + /** + * Sets the three pointers to the values stored in the colour. + */ + void getColour(unsigned int &red, unsigned int &green, unsigned int &blue) const; + + /** + * Returns the current Red value of the colour as an integer. + */ + unsigned int getRed() const; + + /** + * Returns the current Blue value of the colour as an integer. + */ + unsigned int getBlue() const; + + /** + * Returns the current Green value of the colour as an integer. + */ + unsigned int getGreen() const; + + /** + * Sets the Red value of the current colour. If the value isn't + * between 0 and 255 inclusive, it will set to 0 + */ + void setRed(unsigned int input); + + /** + * Sets the Blue value of the current colour. If the value isn't + * between 0 and 255 inclusive, it will set to 0 + */ + void setBlue(unsigned int input); + + /** + * Sets the Green value of the current colour. If the value isn't + * between 0 and 255 inclusive, it will set to 0 + */ + void setGreen(unsigned int input); + + /** + * This uses a simple calculation to give us a contrasting colour. + * Useful for working out a visible text colour given + * any background colour + */ + Colour getContrastingColour() const; + + std::string toXmlString() const; + + std::string dataToXmlString() const; + +private: + unsigned int m_r, m_g, m_b; +}; + + /** + * This works out a colour which is directly in between the two inputs. + * Useful for working out what overlapping Segments should be coloured as + */ + Colour getCombinationColour(const Colour &input1, const Colour &input2); + +} + +#endif diff --git a/src/base/ColourMap.cpp b/src/base/ColourMap.cpp new file mode 100644 index 0000000..322a4a7 --- /dev/null +++ b/src/base/ColourMap.cpp @@ -0,0 +1,266 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2003 + Mark Hymers + + 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 + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +#include "ColourMap.h" +#include "XmlExportable.h" + +namespace Rosegarden +{ + +ColourMap::ColourMap() +{ + // Set up the default colour. The #defines can be found in ColourMap.h + Colour tempcolour(COLOUR_DEF_R, COLOUR_DEF_G, COLOUR_DEF_B); + m_map[0] = make_pair(tempcolour, std::string("")); +} + +ColourMap::ColourMap(const Colour& input) +{ + // Set up the default colour based on the input + m_map[0] = make_pair(input, std::string("")); +} + +ColourMap::~ColourMap() +{ + // Everything should destroy itself automatically +} + +bool +ColourMap::deleteItemByIndex(const unsigned int item_num) +{ + // We explicitly refuse to delete the default colour + if (item_num == 0) + return false; + + unsigned int n_e = m_map.erase(item_num); + if (n_e != 0) + { + return true; + } + + // Otherwise we didn't find the right item + return false; +} + +Colour +ColourMap::getColourByIndex(const unsigned int item_num) const +{ + // Iterate over the m_map and if we find a match, return the + // Colour. If we don't match, return the default colour. m_map + // was initialised with at least one item in the ctor, so this is + // safe. + Colour ret = (*m_map.begin()).second.first; + + for (RCMap::const_iterator position = m_map.begin(); + position != m_map.end(); ++position) + if (position->first == item_num) + ret = position->second.first; + + return ret; +} + +std::string +ColourMap::getNameByIndex(const unsigned int item_num) const +{ + // Iterate over the m_map and if we find a match, return the name. + // If we don't match, return the default colour's name. m_map was + // initialised with at least one item in the ctor, so this is + // safe. + std::string ret = (*m_map.begin()).second.second; + + for (RCMap::const_iterator position = m_map.begin(); + position != m_map.end(); ++position) + if (position->first == item_num) + ret = position->second.second; + + return ret; +} + +bool +ColourMap::addItem(const Colour colour, const std::string name) +{ + // If we want to limit the number of colours, here's the place to do it + unsigned int highest=0; + + for (RCMap::iterator position = m_map.begin(); position != m_map.end(); ++position) + { + if (position->first != highest) + break; + + ++highest; + } + + m_map[highest] = make_pair(colour, name); + + return true; +} + +// WARNING: This version of addItem is only for use by rosexmlhandler.cpp +bool +ColourMap::addItem(const Colour colour, const std::string name, const unsigned int id) +{ + m_map[id] = make_pair(colour, name); + + return true; +} + +bool +ColourMap::modifyNameByIndex(const unsigned int item_num, const std::string name) +{ + // We don't allow a name to be given to the default colour + if (item_num == 0) + return false; + + for (RCMap::iterator position = m_map.begin(); position != m_map.end(); ++position) + if (position->first == item_num) + { + position->second.second = name; + return true; + } + + // We didn't find the element + return false; +} + +bool +ColourMap::modifyColourByIndex(const unsigned int item_num, const Colour colour) +{ + for (RCMap::iterator position = m_map.begin(); position != m_map.end(); ++position) + if (position->first == item_num) + { + position->second.first = colour; + return true; + } + + // We didn't find the element + return false; +} + +bool +ColourMap::swapItems(const unsigned int item_1, const unsigned int item_2) +{ + // It would make no difference but we return false because + // we haven't altered the iterator (see docs in ColourMap.h) + if (item_1 == item_2) + return false; + + // We refuse to swap the default colour for something else + // Basically because what would we do with the strings? + if ((item_1 == 0) || (item_2 == 0)) + return false; + + unsigned int one = 0, two = 0; + + // Check that both elements exist + // It's not worth bothering about optimising this + for (RCMap::iterator position = m_map.begin(); position != m_map.end(); ++position) + { + if (position->first == item_1) one = position->first; + if (position->first == item_2) two = position->first; + } + + // If they both exist, do it + // There's probably a nicer way to do this + if ((one != 0) && (two != 0)) + { + Colour tempC = m_map[one].first; + std::string tempS = m_map[one].second; + m_map[one].first = m_map[two].first; + m_map[one].second = m_map[two].second; + m_map[two].first = tempC; + m_map[two].second = tempS; + + return true; + } + + // Else they didn't + return false; +} + +RCMap::const_iterator +ColourMap::begin() +{ + RCMap::const_iterator ret = m_map.begin(); + return ret; +} + +RCMap::const_iterator +ColourMap::end() +{ + RCMap::const_iterator ret = m_map.end(); + return ret; +} + +ColourMap& +ColourMap::operator=(const ColourMap& input) +{ + if (this != &input) + m_map = input.m_map; + + return *this; +} + +int +ColourMap::size() const +{ + return m_map.size(); +} + +std::string +ColourMap::toXmlString(std::string name) const +{ + std::stringstream output; + + output << " " << std::endl; + + for (RCMap::const_iterator pos = m_map.begin(); pos != m_map.end(); ++pos) + { + output << " " << " first + << "\" name=\"" << XmlExportable::encode(pos->second.second) + << "\" " << pos->second.first.dataToXmlString() << "/>" << std::endl; + } + +#if (__GNUC__ < 3) + output << " " << std::endl << std::ends; +#else + output << " " << std::endl; +#endif + + + return output.str(); + +} + +} diff --git a/src/base/ColourMap.h b/src/base/ColourMap.h new file mode 100644 index 0000000..973c1e0 --- /dev/null +++ b/src/base/ColourMap.h @@ -0,0 +1,138 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2003 + Mark Hymers + + 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 +#include +#include +#include "Colour.h" + +#ifndef _BASE_COLOURMAP_H_ +#define _BASE_COLOURMAP_H_ + +// These are the default, default colour +#define COLOUR_DEF_R 255 +#define COLOUR_DEF_G 234 +#define COLOUR_DEF_B 182 + +namespace Rosegarden +{ + typedef std::map, std::less > RCMap; + +/** + * ColourMap is our table which maps the unsigned integer keys stored in + * segments to both a Colour and a String containing the 'name' + */ + +class ColourMap +{ +public: + // Functions: + + /** + * Initialises an ColourMap with a default element set to + * whatever COLOUR_DEF_X defines the colour to be (see the source file) + */ + ColourMap(); + /** + * Initialises an ColourMap with a default element set to + * the value of the Colour passed in. + */ + ColourMap(const Colour& input); + ~ColourMap(); + + /** + * Returns the Colour associated with the item_num passed in. Note that + * if the item_num doesn't represent a valid item, the routine returns + * the value of the Default colour. This means that if somehow some of + * the Segments get out of sync with the ColourMap and have invalid + * colour values, they'll be set to the Composition default colour. + */ + Colour getColourByIndex(unsigned int item_num) const; + + /** + * Returns the string associated with the item_num passed in. If the + * item_num doesn't exist, it'll return "" (the same name as the default + * colour has - for internationalization reasons). + */ + std::string getNameByIndex(unsigned int item_num) const; + + /** + * If item_num exists, this routine deletes it from the map. + */ + bool deleteItemByIndex(unsigned int item_num); + + /** + * This routine adds a Colour using the lowest possible index. + */ + bool addItem(Colour colour, std::string name); + + /** + * This routine adds a Colour using the given id. ONLY FOR USE IN + * rosexmlhandler.cpp + */ + bool addItem(Colour colour, std::string name, unsigned int id); + + /** + * If the item with item_num exists and isn't the default, this + * routine modifies the string associated with it + */ + bool modifyNameByIndex(unsigned int item_num, std::string name); + + /** + * If the item with item_num exists, this routine modifies the + * Colour associated with it + */ + bool modifyColourByIndex(unsigned int item_num, Colour colour); + + /** + * If both items exist, swap them. + */ + bool swapItems(unsigned int item_1, unsigned int item_2); + +// void replace(ColourMap &input); + + /** + * This returns a const iterator pointing to m_map.begin() + */ + RCMap::const_iterator begin(); + + /** + * This returns a const iterator pointing to m_map.end() + */ + RCMap::const_iterator end(); + + std::string toXmlString(std::string name) const; + + ColourMap& operator=(const ColourMap& input); + + int size() const; + +private: + RCMap m_map; +}; + +} + +#endif diff --git a/src/base/Composition.cpp b/src/base/Composition.cpp new file mode 100644 index 0000000..cde3a8b --- /dev/null +++ b/src/base/Composition.cpp @@ -0,0 +1,2225 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Composition.h" +#include "misc/Debug.h" +#include "Segment.h" +#include "FastVector.h" +#include "BaseProperties.h" +#include "BasicQuantizer.h" +#include "NotationQuantizer.h" + +#include +#include +#include +#include +#include + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +using std::cerr; +using std::endl; + +//#define DEBUG_BAR_STUFF 1 +//#define DEBUG_TEMPO_STUFF 1 + + +namespace Rosegarden +{ + +const PropertyName Composition::NoAbsoluteTimeProperty = "NoAbsoluteTime"; +const PropertyName Composition::BarNumberProperty = "BarNumber"; + +const std::string Composition::TempoEventType = "tempo"; +const PropertyName Composition::TempoProperty = "Tempo"; +const PropertyName Composition::TargetTempoProperty = "TargetTempo"; +const PropertyName Composition::TempoTimestampProperty = "TimestampSec"; + + +bool +Composition::ReferenceSegmentEventCmp::operator()(const Event &e1, + const Event &e2) const +{ + if (e1.has(NoAbsoluteTimeProperty) || + e2.has(NoAbsoluteTimeProperty)) { + RealTime r1 = getTempoTimestamp(&e1); + RealTime r2 = getTempoTimestamp(&e2); + return r1 < r2; + } else { + return e1 < e2; + } +} + +Composition::ReferenceSegment::ReferenceSegment(std::string eventType) : + m_eventType(eventType) +{ + // nothing +} + +Composition::ReferenceSegment::~ReferenceSegment() +{ + clear(); +} + +void +Composition::ReferenceSegment::clear() +{ + for (iterator it = begin(); it != end(); ++it) delete (*it); + Impl::erase(begin(), end()); +} + +timeT +Composition::ReferenceSegment::getDuration() const +{ + const_iterator i = end(); + if (i == begin()) return 0; + --i; + + return (*i)->getAbsoluteTime() + (*i)->getDuration(); +} + +Composition::ReferenceSegment::iterator +Composition::ReferenceSegment::find(Event *e) +{ + return std::lower_bound + (begin(), end(), e, ReferenceSegmentEventCmp()); +} + +Composition::ReferenceSegment::iterator +Composition::ReferenceSegment::insert(Event *e) +{ + if (!e->isa(m_eventType)) { + throw Event::BadType(std::string("event in ReferenceSegment"), + m_eventType, e->getType(), __FILE__, __LINE__); + } + + iterator i = find(e); + + if (i != end() && (*i)->getAbsoluteTime() == e->getAbsoluteTime()) { + + Event *old = (*i); + (*i) = e; + delete old; + return i; + + } else { + return Impl::insert(i, e); + } +} + +void +Composition::ReferenceSegment::erase(Event *e) +{ + iterator i = find(e); + if (i != end()) Impl::erase(i); +} + +Composition::ReferenceSegment::iterator +Composition::ReferenceSegment::findTime(timeT t) +{ + Event dummy("dummy", t, 0, MIN_SUBORDERING); + return find(&dummy); +} + +Composition::ReferenceSegment::iterator +Composition::ReferenceSegment::findRealTime(RealTime t) +{ + Event dummy("dummy", 0, 0, MIN_SUBORDERING); + dummy.set(NoAbsoluteTimeProperty, true); + setTempoTimestamp(&dummy, t); + return find(&dummy); +} + +Composition::ReferenceSegment::iterator +Composition::ReferenceSegment::findNearestTime(timeT t) +{ + iterator i = findTime(t); + if (i == end() || (*i)->getAbsoluteTime() > t) { + if (i == begin()) return end(); + else --i; + } + return i; +} + +Composition::ReferenceSegment::iterator +Composition::ReferenceSegment::findNearestRealTime(RealTime t) +{ + iterator i = findRealTime(t); + if (i == end() || (getTempoTimestamp(*i) > t)) { + if (i == begin()) return end(); + else --i; + } + return i; +} + + + +int Composition::m_defaultNbBars = 100; + +Composition::Composition() : + m_solo(false), // default is not soloing + m_selectedTrack(0), + m_timeSigSegment(TimeSignature::EventType), + m_tempoSegment(TempoEventType), + m_barPositionsNeedCalculating(true), + m_tempoTimestampsNeedCalculating(true), + m_basicQuantizer(new BasicQuantizer()), + m_notationQuantizer(new NotationQuantizer()), + m_position(0), + m_defaultTempo(getTempoForQpm(120.0)), + m_minTempo(0), + m_maxTempo(0), + m_startMarker(0), + m_endMarker(getBarRange(m_defaultNbBars).first), + m_loopStart(0), + m_loopEnd(0), + m_playMetronome(false), + m_recordMetronome(true), + m_nextTriggerSegmentId(0) +{ + // nothing else +} + +Composition::~Composition() +{ + if (!m_observers.empty()) { + cerr << "Warning: Composition::~Composition() with " << m_observers.size() + << " observers still extant" << endl; + cerr << "Observers are:"; + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + cerr << " " << (void *)(*i); + cerr << " [" << typeid(**i).name() << "]"; + } + cerr << endl; + } + + notifySourceDeletion(); + clear(); + delete m_basicQuantizer; + delete m_notationQuantizer; +} + +Composition::iterator +Composition::addSegment(Segment *segment) +{ + iterator res = weakAddSegment(segment); + + if (res != end()) { + updateRefreshStatuses(); + notifySegmentAdded(segment); + } + + return res; +} + +Composition::iterator +Composition::weakAddSegment(Segment *segment) +{ + if (!segment) return end(); + + iterator res = m_segments.insert(segment); + segment->setComposition(this); + + return res; +} + +void +Composition::deleteSegment(Composition::iterator i) +{ + if (i == end()) return; + + Segment *p = (*i); + p->setComposition(0); + + m_segments.erase(i); + notifySegmentRemoved(p); + delete p; + + updateRefreshStatuses(); +} + +bool +Composition::deleteSegment(Segment *segment) +{ + iterator i = findSegment(segment); + if (i == end()) return false; + + deleteSegment(i); + return true; +} + +bool +Composition::detachSegment(Segment *segment) +{ + bool res = weakDetachSegment(segment); + + if (res) { + notifySegmentRemoved(segment); + updateRefreshStatuses(); + } + + return res; +} + +bool +Composition::weakDetachSegment(Segment *segment) +{ + iterator i = findSegment(segment); + if (i == end()) return false; + + segment->setComposition(0); + m_segments.erase(i); + + return true; +} + +bool +Composition::contains(const Segment *s) +{ + iterator i = findSegment(s); + return (i != end()); +} + +Composition::iterator +Composition::findSegment(const Segment *s) +{ + iterator i = m_segments.lower_bound(const_cast(s)); + + while (i != end()) { + if (*i == s) break; + if ((*i)->getStartTime() > s->getStartTime()) return end(); + ++i; + } + + return i; +} + +void Composition::setSegmentStartTime(Segment *segment, timeT startTime) +{ + // remove the segment from the multiset + iterator i = findSegment(segment); + if (i == end()) return; + + m_segments.erase(i); + + segment->setStartTimeDataMember(startTime); + + // re-add it + m_segments.insert(segment); +} + +int +Composition::getMaxContemporaneousSegmentsOnTrack(TrackId track) const +{ + // Could be made faster, but only if it needs to be. + + // This is similar to the polyphony calculation in + // DocumentMetaConfigurationPage ctor. + + std::set simultaneous; + std::multimap ends; + + int maximum = 0; + + for (const_iterator i = begin(); i != end(); ++i) { + if ((*i)->getTrack() != track) continue; + timeT t0 = (*i)->getStartTime(); + timeT t1 = (*i)->getRepeatEndTime(); +// std::cerr << "getMaxContemporaneousSegmentsOnTrack(" << track << "): segment " << *i << " from " << t0 << " to " << t1 << std::endl; + while (!ends.empty() && t0 >= ends.begin()->first) { + simultaneous.erase(ends.begin()->second); + ends.erase(ends.begin()); + } + simultaneous.insert(*i); + ends.insert(std::multimap::value_type(t1, *i)); + int current = simultaneous.size(); + if (current > maximum) maximum = current; + } + + return maximum; +} + +int +Composition::getSegmentVoiceIndex(const Segment *segment) const +{ + TrackId track = segment->getTrack(); + + // See function above + + std::map indices; + std::set used; + std::multimap ends; + + int maximum = 0; + + for (const_iterator i = begin(); i != end(); ++i) { + if ((*i)->getTrack() != track) continue; + timeT t0 = (*i)->getStartTime(); + timeT t1 = (*i)->getRepeatEndTime(); + int index; + while (!ends.empty() && t0 >= ends.begin()->first) { + index = indices[ends.begin()->second]; + used.erase(index); + indices.erase(ends.begin()->second); + ends.erase(ends.begin()); + } + for (index = 0; ; ++index) { + if (used.find(index) == used.end()) break; + } + if (*i == segment) return index; + indices[*i] = index; + used.insert(index); + ends.insert(std::multimap::value_type(t1, *i)); + } + + std::cerr << "WARNING: Composition::getSegmentVoiceIndex: segment " + << segment << " not found in composition" << std::endl; + return 0; +} + +TriggerSegmentRec * +Composition::addTriggerSegment(Segment *s, int pitch, int velocity) +{ + TriggerSegmentId id = m_nextTriggerSegmentId; + return addTriggerSegment(s, id, pitch, velocity); +} + +TriggerSegmentRec * +Composition::addTriggerSegment(Segment *s, TriggerSegmentId id, int pitch, int velocity) +{ + TriggerSegmentRec *rec = getTriggerSegmentRec(id); + if (rec) return 0; + rec = new TriggerSegmentRec(id, s, pitch, velocity); + m_triggerSegments.insert(rec); + s->setComposition(this); + if (m_nextTriggerSegmentId <= id) m_nextTriggerSegmentId = id + 1; + return rec; +} + +void +Composition::deleteTriggerSegment(TriggerSegmentId id) +{ + TriggerSegmentRec dummyRec(id, 0); + triggersegmentcontaineriterator i = m_triggerSegments.find(&dummyRec); + if (i == m_triggerSegments.end()) return; + (*i)->getSegment()->setComposition(0); + delete (*i)->getSegment(); + delete *i; + m_triggerSegments.erase(i); +} + +void +Composition::detachTriggerSegment(TriggerSegmentId id) +{ + TriggerSegmentRec dummyRec(id, 0); + triggersegmentcontaineriterator i = m_triggerSegments.find(&dummyRec); + if (i == m_triggerSegments.end()) return; + (*i)->getSegment()->setComposition(0); + delete *i; + m_triggerSegments.erase(i); +} + +void +Composition::clearTriggerSegments() +{ + for (triggersegmentcontaineriterator i = m_triggerSegments.begin(); + i != m_triggerSegments.end(); ++i) { + delete (*i)->getSegment(); + delete *i; + } + m_triggerSegments.clear(); +} + +int +Composition::getTriggerSegmentId(Segment *s) +{ + for (triggersegmentcontaineriterator i = m_triggerSegments.begin(); + i != m_triggerSegments.end(); ++i) { + if ((*i)->getSegment() == s) return (*i)->getId(); + } + return -1; +} + +Segment * +Composition::getTriggerSegment(TriggerSegmentId id) +{ + TriggerSegmentRec *rec = getTriggerSegmentRec(id); + if (!rec) return 0; + return rec->getSegment(); +} + +TriggerSegmentRec * +Composition::getTriggerSegmentRec(TriggerSegmentId id) +{ + TriggerSegmentRec dummyRec(id, 0); + triggersegmentcontaineriterator i = m_triggerSegments.find(&dummyRec); + if (i == m_triggerSegments.end()) return 0; + return *i; +} + +TriggerSegmentId +Composition::getNextTriggerSegmentId() const +{ + return m_nextTriggerSegmentId; +} + +void +Composition::setNextTriggerSegmentId(TriggerSegmentId id) +{ + m_nextTriggerSegmentId = id; +} + +void +Composition::updateTriggerSegmentReferences() +{ + std::map refs; + + for (iterator i = begin(); i != end(); ++i) { + for (Segment::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { + if ((*j)->has(BaseProperties::TRIGGER_SEGMENT_ID)) { + TriggerSegmentId id = + (*j)->get(BaseProperties::TRIGGER_SEGMENT_ID); + refs[id].insert((*i)->getRuntimeId()); + } + } + } + + for (std::map::iterator i = refs.begin(); + i != refs.end(); ++i) { + TriggerSegmentRec *rec = getTriggerSegmentRec(i->first); + if (rec) rec->setReferences(i->second); + } +} + + +timeT +Composition::getDuration() const +{ + timeT maxDuration = 0; + + for (segmentcontainer::const_iterator i = m_segments.begin(); + i != m_segments.end(); ++i) { + + timeT segmentTotal = (*i)->getEndTime(); + + if (segmentTotal > maxDuration) { + maxDuration = segmentTotal; + } + } + + return maxDuration; +} + +void +Composition::setStartMarker(const timeT &sM) +{ + m_startMarker = sM; + updateRefreshStatuses(); +} + +void +Composition::setEndMarker(const timeT &eM) +{ + bool shorten = (eM < m_endMarker); + m_endMarker = eM; + updateRefreshStatuses(); + notifyEndMarkerChange(shorten); +} + +void +Composition::clear() +{ + while (m_segments.size() > 0) { + deleteSegment(begin()); + } + + clearTracks(); + clearMarkers(); + clearTriggerSegments(); + + m_timeSigSegment.clear(); + m_tempoSegment.clear(); + m_defaultTempo = getTempoForQpm(120.0); + m_minTempo = 0; + m_maxTempo = 0; + m_loopStart = 0; + m_loopEnd = 0; + m_position = 0; + m_startMarker = 0; + m_endMarker = getBarRange(m_defaultNbBars).first; + m_solo = false; + m_selectedTrack = 0; + updateRefreshStatuses(); +} + +void +Composition::calculateBarPositions() const +{ + if (!m_barPositionsNeedCalculating) return; + +#ifdef DEBUG_BAR_STUFF + cerr << "Composition::calculateBarPositions" << endl; +#endif + + ReferenceSegment &t = m_timeSigSegment; + ReferenceSegment::iterator i; + + timeT lastBarNo = 0; + timeT lastSigTime = 0; + timeT barDuration = TimeSignature().getBarDuration(); + + if (getStartMarker() < 0) { + if (!t.empty() && (*t.begin())->getAbsoluteTime() <= 0) { + barDuration = TimeSignature(**t.begin()).getBarDuration(); + } + lastBarNo = getStartMarker() / barDuration; + lastSigTime = getStartMarker(); +#ifdef DEBUG_BAR_STUFF + cerr << "Composition::calculateBarPositions: start marker = " << getStartMarker() << ", so initial bar number = " << lastBarNo << endl; +#endif + } + + for (i = t.begin(); i != t.end(); ++i) { + + timeT myTime = (*i)->getAbsoluteTime(); + int n = (myTime - lastSigTime) / barDuration; + + // should only happen for first time sig, when it's at time < 0: + if (myTime < lastSigTime) --n; + + // would there be a new bar here anyway? + if (barDuration * n + lastSigTime == myTime) { // yes + n += lastBarNo; + } else { // no + n += lastBarNo + 1; + } + +#ifdef DEBUG_BAR_STUFF + cerr << "Composition::calculateBarPositions: bar " << n + << " at " << myTime << endl; +#endif + + (*i)->set(BarNumberProperty, n); + + lastBarNo = n; + lastSigTime = myTime; + barDuration = TimeSignature(**i).getBarDuration(); + } + + m_barPositionsNeedCalculating = false; +} + +int +Composition::getNbBars() const +{ + calculateBarPositions(); + + // the "-1" is a small kludge to deal with the case where the + // composition has a duration that's an exact number of bars + int bars = getBarNumber(getDuration() - 1) + 1; + +#ifdef DEBUG_BAR_STUFF + cerr << "Composition::getNbBars: returning " << bars << endl; +#endif + return bars; +} + +int +Composition::getBarNumber(timeT t) const +{ + calculateBarPositions(); + ReferenceSegment::iterator i = m_timeSigSegment.findNearestTime(t); + int n; + + if (i == m_timeSigSegment.end()) { // precedes any time signatures + + timeT bd = TimeSignature().getBarDuration(); + if (t < 0) { // see comment in getTimeSignatureAtAux + i = m_timeSigSegment.begin(); + if (i != m_timeSigSegment.end() && (*i)->getAbsoluteTime() <= 0) { + bd = TimeSignature(**i).getBarDuration(); + } + } + + n = t / bd; + if (t < 0) { + // negative bars should be rounded down, except where + // the time is on a barline in which case we already + // have the right value (i.e. time -1920 is bar -1, + // but time -3840 is also bar -1, in 4/4) + if (n * bd != t) --n; + } + + } else { + + n = (*i)->get(BarNumberProperty); + timeT offset = t - (*i)->getAbsoluteTime(); + n += offset / TimeSignature(**i).getBarDuration(); + } + +#ifdef DEBUG_BAR_STUFF + cerr << "Composition::getBarNumber(" << t << "): returning " << n << endl; +#endif + return n; +} + + +std::pair +Composition::getBarRangeForTime(timeT t) const +{ + return getBarRange(getBarNumber(t)); +} + + +std::pair +Composition::getBarRange(int n) const +{ + calculateBarPositions(); + + Event dummy("dummy", 0); + dummy.set(BarNumberProperty, n); + + ReferenceSegment::iterator j = std::lower_bound + (m_timeSigSegment.begin(), m_timeSigSegment.end(), + &dummy, BarNumberComparator()); + ReferenceSegment::iterator i = j; + + if (i == m_timeSigSegment.end() || (*i)->get(BarNumberProperty) > n) { + if (i == m_timeSigSegment.begin()) i = m_timeSigSegment.end(); + else --i; + } else ++j; // j needs to point to following barline + + timeT start, finish; + + if (i == m_timeSigSegment.end()) { // precedes any time sig changes + + timeT barDuration = TimeSignature().getBarDuration(); + if (n < 0) { // see comment in getTimeSignatureAtAux + i = m_timeSigSegment.begin(); + if (i != m_timeSigSegment.end() && (*i)->getAbsoluteTime() <= 0) { + barDuration = TimeSignature(**i).getBarDuration(); + } + } + + start = n * barDuration; + finish = start + barDuration; + +#ifdef DEBUG_BAR_STUFF + cerr << "Composition::getBarRange[1]: bar " << n << ": (" << start + << " -> " << finish << ")" << endl; +#endif + + } else { + + timeT barDuration = TimeSignature(**i).getBarDuration(); + start = (*i)->getAbsoluteTime() + + (n - (*i)->get(BarNumberProperty)) * barDuration; + finish = start + barDuration; + +#ifdef DEBUG_BAR_STUFF + cerr << "Composition::getBarRange[2]: bar " << n << ": (" << start + << " -> " << finish << ")" << endl; +#endif + } + + // partial bar + if (j != m_timeSigSegment.end() && finish > (*j)->getAbsoluteTime()) { + finish = (*j)->getAbsoluteTime(); +#ifdef DEBUG_BAR_STUFF + cerr << "Composition::getBarRange[3]: bar " << n << ": (" << start + << " -> " << finish << ")" << endl; +#endif + } + + return std::pair(start, finish); +} + + +int +Composition::addTimeSignature(timeT t, TimeSignature timeSig) +{ +#ifdef DEBUG_BAR_STUFF + cerr << "Composition::addTimeSignature(" << t << ", " << timeSig.getNumerator() << "/" << timeSig.getDenominator() << ")" << endl; +#endif + + ReferenceSegment::iterator i = + m_timeSigSegment.insert(timeSig.getAsEvent(t)); + m_barPositionsNeedCalculating = true; + + updateRefreshStatuses(); + notifyTimeSignatureChanged(); + + return std::distance(m_timeSigSegment.begin(), i); +} + +TimeSignature +Composition::getTimeSignatureAt(timeT t) const +{ + TimeSignature timeSig; + (void)getTimeSignatureAt(t, timeSig); + return timeSig; +} + +timeT +Composition::getTimeSignatureAt(timeT t, TimeSignature &timeSig) const +{ + ReferenceSegment::iterator i = getTimeSignatureAtAux(t); + + if (i == m_timeSigSegment.end()) { + timeSig = TimeSignature(); + return 0; + } else { + timeSig = TimeSignature(**i); + return (*i)->getAbsoluteTime(); + } +} + +TimeSignature +Composition::getTimeSignatureInBar(int barNo, bool &isNew) const +{ + isNew = false; + timeT t = getBarRange(barNo).first; + + ReferenceSegment::iterator i = getTimeSignatureAtAux(t); + + if (i == m_timeSigSegment.end()) return TimeSignature(); + if (t == (*i)->getAbsoluteTime()) isNew = true; + + return TimeSignature(**i); +} + +Composition::ReferenceSegment::iterator +Composition::getTimeSignatureAtAux(timeT t) const +{ + ReferenceSegment::iterator i = m_timeSigSegment.findNearestTime(t); + + // In negative time, if there's no time signature actually defined + // prior to the point of interest then we use the next time + // signature after it, so long as it's no later than time zero. + // This is the only rational way to deal with count-in bars where + // the correct time signature otherwise won't appear until we hit + // bar zero. + + if (t < 0 && i == m_timeSigSegment.end()) { + i = m_timeSigSegment.begin(); + if (i != m_timeSigSegment.end() && (*i)->getAbsoluteTime() > 0) { + i = m_timeSigSegment.end(); + } + } + + return i; +} + +int +Composition::getTimeSignatureCount() const +{ + return m_timeSigSegment.size(); +} + +int +Composition::getTimeSignatureNumberAt(timeT t) const +{ + ReferenceSegment::iterator i = getTimeSignatureAtAux(t); + if (i == m_timeSigSegment.end()) return -1; + else return std::distance(m_timeSigSegment.begin(), i); +} + +std::pair +Composition::getTimeSignatureChange(int n) const +{ + return std::pair + (m_timeSigSegment[n]->getAbsoluteTime(), + TimeSignature(*m_timeSigSegment[n])); +} + +void +Composition::removeTimeSignature(int n) +{ + m_timeSigSegment.erase(m_timeSigSegment[n]); + m_barPositionsNeedCalculating = true; + updateRefreshStatuses(); + notifyTimeSignatureChanged(); +} + + +tempoT +Composition::getTempoAtTime(timeT t) const +{ + ReferenceSegment::iterator i = m_tempoSegment.findNearestTime(t); + + // In negative time, if there's no tempo event actually defined + // prior to the point of interest then we use the next one after + // it, so long as it's no later than time zero. This is the only + // rational way to deal with count-in bars where the correct + // tempo otherwise won't appear until we hit bar zero. See also + // getTimeSignatureAt + + if (i == m_tempoSegment.end()) { + if (t < 0) { +#ifdef DEBUG_TEMPO_STUFF + cerr << "Composition: Negative time " << t << " for tempo, using 0" << endl; +#endif + return getTempoAtTime(0); + } + else return m_defaultTempo; + } + + tempoT tempo = (tempoT)((*i)->get(TempoProperty)); + + if ((*i)->has(TargetTempoProperty)) { + + tempoT target = (tempoT)((*i)->get(TargetTempoProperty)); + ReferenceSegment::iterator j = i; + ++j; + + if (target > 0 || (target == 0 && j != m_tempoSegment.end())) { + + timeT t0 = (*i)->getAbsoluteTime(); + timeT t1 = (j != m_tempoSegment.end() ? + (*j)->getAbsoluteTime() : getEndMarker()); + + if (t1 < t0) return tempo; + + if (target == 0) { + target = (tempoT)((*j)->get(TempoProperty)); + } + + // tempo ramps are linear in 1/tempo + double s0 = 1.0 / double(tempo); + double s1 = 1.0 / double(target); + double s = s0 + (t - t0) * ((s1 - s0) / (t1 - t0)); + + tempoT result = tempoT((1.0 / s) + 0.01); + +#ifdef DEBUG_TEMPO_STUFF + cerr << "Composition: Calculated tempo " << result << " at " << t << endl; +#endif + + return result; + } + } + +#ifdef DEBUG_TEMPO_STUFF + cerr << "Composition: Found tempo " << tempo << " at " << t << endl; +#endif + return tempo; +} + +int +Composition::addTempoAtTime(timeT time, tempoT tempo, tempoT targetTempo) +{ + // If there's an existing tempo at this time, the ReferenceSegment + // object will remove the duplicate, but we have to ensure that + // the minimum and maximum tempos are updated if necessary. + + bool fullTempoUpdate = false; + + int n = getTempoChangeNumberAt(time); + if (n >= 0) { + std::pair tc = getTempoChange(n); + if (tc.first == time) { + if (tc.second == m_minTempo || tc.second == m_maxTempo) { + fullTempoUpdate = true; + } else { + std::pair tr = getTempoRamping(n); + if (tr.first && + (tr.second == m_minTempo || tr.second == m_maxTempo)) { + fullTempoUpdate = true; + } + } + } + } + + Event *tempoEvent = new Event(TempoEventType, time); + tempoEvent->set(TempoProperty, tempo); + + if (targetTempo >= 0) { + tempoEvent->set(TargetTempoProperty, targetTempo); + } + + ReferenceSegment::iterator i = m_tempoSegment.insert(tempoEvent); + + if (fullTempoUpdate) { + + updateExtremeTempos(); + + } else { + + if (tempo < m_minTempo || m_minTempo == 0) m_minTempo = tempo; + if (targetTempo > 0 && targetTempo < m_minTempo) m_minTempo = targetTempo; + + if (tempo > m_maxTempo || m_maxTempo == 0) m_maxTempo = tempo; + if (targetTempo > 0 && targetTempo > m_maxTempo) m_maxTempo = targetTempo; + } + + m_tempoTimestampsNeedCalculating = true; + updateRefreshStatuses(); + +#ifdef DEBUG_TEMPO_STUFF + cerr << "Composition: Added tempo " << tempo << " at " << time << endl; +#endif + notifyTempoChanged(); + + return std::distance(m_tempoSegment.begin(), i); +} + +int +Composition::getTempoChangeCount() const +{ + return m_tempoSegment.size(); +} + +int +Composition::getTempoChangeNumberAt(timeT t) const +{ + ReferenceSegment::iterator i = m_tempoSegment.findNearestTime(t); + if (i == m_tempoSegment.end()) return -1; + else return std::distance(m_tempoSegment.begin(), i); +} + +std::pair +Composition::getTempoChange(int n) const +{ + return std::pair + (m_tempoSegment[n]->getAbsoluteTime(), + tempoT(m_tempoSegment[n]->get(TempoProperty))); +} + +std::pair +Composition::getTempoRamping(int n, bool calculate) const +{ + tempoT target = -1; + if (m_tempoSegment[n]->has(TargetTempoProperty)) { + target = m_tempoSegment[n]->get(TargetTempoProperty); + } + bool ramped = (target >= 0); + if (target == 0) { + if (calculate) { + if (m_tempoSegment.size() > n+1) { + target = m_tempoSegment[n+1]->get(TempoProperty); + } + } + } + if (target < 0 || (calculate && (target == 0))) { + target = m_tempoSegment[n]->get(TempoProperty); + } + return std::pair(ramped, target); +} + +void +Composition::removeTempoChange(int n) +{ + tempoT oldTempo = m_tempoSegment[n]->get(TempoProperty); + tempoT oldTarget = -1; + + if (m_tempoSegment[n]->has(TargetTempoProperty)) { + oldTarget = m_tempoSegment[n]->get(TargetTempoProperty); + } + + m_tempoSegment.erase(m_tempoSegment[n]); + m_tempoTimestampsNeedCalculating = true; + + if (oldTempo == m_minTempo || + oldTempo == m_maxTempo || + (oldTarget > 0 && oldTarget == m_minTempo) || + (oldTarget > 0 && oldTarget == m_maxTempo)) { + updateExtremeTempos(); + } + + updateRefreshStatuses(); + notifyTempoChanged(); +} + +void +Composition::updateExtremeTempos() +{ + m_minTempo = 0; + m_maxTempo = 0; + for (ReferenceSegment::iterator i = m_tempoSegment.begin(); + i != m_tempoSegment.end(); ++i) { + tempoT tempo = (*i)->get(TempoProperty); + tempoT target = -1; + if ((*i)->has(TargetTempoProperty)) { + target = (*i)->get(TargetTempoProperty); + } + if (tempo < m_minTempo || m_minTempo == 0) m_minTempo = tempo; + if (target > 0 && target < m_minTempo) m_minTempo = target; + if (tempo > m_maxTempo || m_maxTempo == 0) m_maxTempo = tempo; + if (target > 0 && target > m_maxTempo) m_maxTempo = target; + } + if (m_minTempo == 0) { + m_minTempo = m_defaultTempo; + m_maxTempo = m_defaultTempo; + } +} + +RealTime +Composition::getElapsedRealTime(timeT t) const +{ + calculateTempoTimestamps(); + + ReferenceSegment::iterator i = m_tempoSegment.findNearestTime(t); + if (i == m_tempoSegment.end()) { + i = m_tempoSegment.begin(); + if (t >= 0 || + (i == m_tempoSegment.end() || (*i)->getAbsoluteTime() > 0)) { + return time2RealTime(t, m_defaultTempo); + } + } + + RealTime elapsed; + + tempoT target = -1; + timeT nextTempoTime = t; + + if (!getTempoTarget(i, target, nextTempoTime)) target = -1; + + if (target > 0) { + elapsed = getTempoTimestamp(*i) + + time2RealTime(t - (*i)->getAbsoluteTime(), + tempoT((*i)->get(TempoProperty)), + nextTempoTime - (*i)->getAbsoluteTime(), + target); + } else { + elapsed = getTempoTimestamp(*i) + + time2RealTime(t - (*i)->getAbsoluteTime(), + tempoT((*i)->get(TempoProperty))); + } + +#ifdef DEBUG_TEMPO_STUFF + cerr << "Composition::getElapsedRealTime: " << t << " -> " + << elapsed << " (last tempo change at " << (*i)->getAbsoluteTime() << ")" << endl; +#endif + + return elapsed; +} + +timeT +Composition::getElapsedTimeForRealTime(RealTime t) const +{ + calculateTempoTimestamps(); + + ReferenceSegment::iterator i = m_tempoSegment.findNearestRealTime(t); + if (i == m_tempoSegment.end()) { + i = m_tempoSegment.begin(); + if (t >= RealTime::zeroTime || + (i == m_tempoSegment.end() || (*i)->getAbsoluteTime() > 0)) { + return realTime2Time(t, m_defaultTempo); + } + } + + timeT elapsed; + + tempoT target = -1; + timeT nextTempoTime = 0; + if (!getTempoTarget(i, target, nextTempoTime)) target = -1; + + if (target > 0) { + elapsed = (*i)->getAbsoluteTime() + + realTime2Time(t - getTempoTimestamp(*i), + (tempoT)((*i)->get(TempoProperty)), + nextTempoTime - (*i)->getAbsoluteTime(), + target); + } else { + elapsed = (*i)->getAbsoluteTime() + + realTime2Time(t - getTempoTimestamp(*i), + (tempoT)((*i)->get(TempoProperty))); + } + +#ifdef DEBUG_TEMPO_STUFF + static int doError = true; + if (doError) { + doError = false; + RealTime cfReal = getElapsedRealTime(elapsed); + timeT cfTimeT = getElapsedTimeForRealTime(cfReal); + doError = true; + cerr << "getElapsedTimeForRealTime: " << t << " -> " + << elapsed << " (error " << (cfReal - t) + << " or " << (cfTimeT - elapsed) << ", tempo " + << (*i)->getAbsoluteTime() << ":" + << (tempoT)((*i)->get(TempoProperty)) << ")" << endl; + } +#endif + return elapsed; +} + +void +Composition::calculateTempoTimestamps() const +{ + if (!m_tempoTimestampsNeedCalculating) return; + + timeT lastTimeT = 0; + RealTime lastRealTime; + + tempoT tempo = m_defaultTempo; + tempoT target = -1; + +#ifdef DEBUG_TEMPO_STUFF + cerr << "Composition::calculateTempoTimestamps: Tempo events are:" << endl; +#endif + + for (ReferenceSegment::iterator i = m_tempoSegment.begin(); + i != m_tempoSegment.end(); ++i) { + + RealTime myTime; + + if (target > 0) { + myTime = lastRealTime + + time2RealTime((*i)->getAbsoluteTime() - lastTimeT, tempo, + (*i)->getAbsoluteTime() - lastTimeT, target); + } else { + myTime = lastRealTime + + time2RealTime((*i)->getAbsoluteTime() - lastTimeT, tempo); + } + + setTempoTimestamp(*i, myTime); + +#ifdef DEBUG_TEMPO_STUFF + (*i)->dump(cerr); +#endif + + lastRealTime = myTime; + lastTimeT = (*i)->getAbsoluteTime(); + tempo = tempoT((*i)->get(TempoProperty)); + + target = -1; + timeT nextTempoTime = 0; + if (!getTempoTarget(i, target, nextTempoTime)) target = -1; + } + + m_tempoTimestampsNeedCalculating = false; +} + +#ifdef DEBUG_TEMPO_STUFF +static int DEBUG_silence_recursive_tempo_printout = 0; +#endif + +RealTime +Composition::time2RealTime(timeT t, tempoT tempo) const +{ + static timeT cdur = Note(Note::Crotchet).getDuration(); + + double dt = (double(t) * 100000 * 60) / (double(tempo) * cdur); + + int sec = int(dt); + int nsec = int((dt - sec) * 1000000000); + + RealTime rt(sec, nsec); + +#ifdef DEBUG_TEMPO_STUFF + if (!DEBUG_silence_recursive_tempo_printout) { + cerr << "Composition::time2RealTime: t " << t << ", sec " << sec << ", nsec " + << nsec << ", tempo " << tempo + << ", cdur " << cdur << ", dt " << dt << ", rt " << rt << endl; + DEBUG_silence_recursive_tempo_printout = 1; + timeT ct = realTime2Time(rt, tempo); + timeT et = t - ct; + RealTime ert = time2RealTime(et, tempo); + cerr << "cf. realTime2Time(" << rt << ") -> " << ct << " [err " << et << " (" << ert << "?)]" << endl; + DEBUG_silence_recursive_tempo_printout=0; + } +#endif + + return rt; +} + +RealTime +Composition::time2RealTime(timeT time, tempoT tempo, + timeT targetTime, tempoT targetTempo) const +{ + static timeT cdur = Note(Note::Crotchet).getDuration(); + + // The real time elapsed at musical time t, in seconds, during a + // smooth tempo change from "tempo" at musical time zero to + // "targetTempo" at musical time "targetTime", is + // + // 2 + // at + t (b - a) + // --------- + // 2n + // where + // + // a is the initial tempo in seconds per tick + // b is the target tempo in seconds per tick + // n is targetTime in ticks + + if (targetTime == 0 || targetTempo == tempo) { + return time2RealTime(time, targetTempo); + } + + double a = (100000 * 60) / (double(tempo) * cdur); + double b = (100000 * 60) / (double(targetTempo) * cdur); + double t = time; + double n = targetTime; + double result = (a * t) + (t * t * (b - a)) / (2 * n); + + int sec = int(result); + int nsec = int((result - sec) * 1000000000); + + RealTime rt(sec, nsec); + +#ifdef DEBUG_TEMPO_STUFF + if (!DEBUG_silence_recursive_tempo_printout) { + cerr << "Composition::time2RealTime[2]: time " << time << ", tempo " + << tempo << ", targetTime " << targetTime << ", targetTempo " + << targetTempo << ": rt " << rt << endl; + DEBUG_silence_recursive_tempo_printout = 1; +// RealTime nextRt = time2RealTime(targetTime, tempo, targetTime, targetTempo); + timeT ct = realTime2Time(rt, tempo, targetTime, targetTempo); + cerr << "cf. realTime2Time: rt " << rt << " -> " << ct << endl; + DEBUG_silence_recursive_tempo_printout=0; + } +#endif + + return rt; +} + +timeT +Composition::realTime2Time(RealTime rt, tempoT tempo) const +{ + static timeT cdur = Note(Note::Crotchet).getDuration(); + + double tsec = (double(rt.sec) * cdur) * (tempo / (60.0 * 100000.0)); + double tnsec = (double(rt.nsec) * cdur) * (tempo / 100000.0); + + double dt = tsec + (tnsec / 60000000000.0); + timeT t = (timeT)(dt + (dt < 0 ? -1e-6 : 1e-6)); + +#ifdef DEBUG_TEMPO_STUFF + if (!DEBUG_silence_recursive_tempo_printout) { + cerr << "Composition::realTime2Time: rt.sec " << rt.sec << ", rt.nsec " + << rt.nsec << ", tempo " << tempo + << ", cdur " << cdur << ", tsec " << tsec << ", tnsec " << tnsec << ", dt " << dt << ", t " << t << endl; + DEBUG_silence_recursive_tempo_printout = 1; + RealTime crt = time2RealTime(t, tempo); + RealTime ert = rt - crt; + timeT et = realTime2Time(ert, tempo); + cerr << "cf. time2RealTime(" << t << ") -> " << crt << " [err " << ert << " (" << et << "?)]" << endl; + DEBUG_silence_recursive_tempo_printout = 0; + } +#endif + + return t; +} + +timeT +Composition::realTime2Time(RealTime rt, tempoT tempo, + timeT targetTime, tempoT targetTempo) const +{ + static timeT cdur = Note(Note::Crotchet).getDuration(); + + // Inverse of the expression in time2RealTime above. + // + // The musical time elapsed at real time t, in ticks, during a + // smooth tempo change from "tempo" at real time zero to + // "targetTempo" at real time "targetTime", is + // + // 2na (+/-) sqrt((2nb)^2 + 8(b-a)tn) + // - ---------------------------------- + // 2(b-a) + // where + // + // a is the initial tempo in seconds per tick + // b is the target tempo in seconds per tick + // n is target real time in ticks + + if (targetTempo == tempo) return realTime2Time(rt, tempo); + + double a = (100000 * 60) / (double(tempo) * cdur); + double b = (100000 * 60) / (double(targetTempo) * cdur); + double t = double(rt.sec) + double(rt.nsec) / 1e9; + double n = targetTime; + + double term1 = 2.0 * n * a; + double term2 = (2.0 * n * a) * (2.0 * n * a) + 8 * (b - a) * t * n; + + if (term2 < 0) { + // We're screwed, but at least let's not crash + std::cerr << "ERROR: Composition::realTime2Time: term2 < 0 (it's " << term2 << ")" << std::endl; +#ifdef DEBUG_TEMPO_STUFF + std::cerr << "rt = " << rt << ", tempo = " << tempo << ", targetTime = " << targetTime << ", targetTempo = " << targetTempo << std::endl; + std::cerr << "n = " << n << ", b = " << b << ", a = " << a << ", t = " << t < 0) term3 = -term3; + + double result = - (term1 + term3) / (2 * (b - a)); + +#ifdef DEBUG_TEMPO_STUFF + std::cerr << "Composition::realTime2Time:" <getId() << std::endl; + } + + return 0; +} + +// Move a track object to a new id and position in the container - +// used when deleting and undoing deletion of tracks. +// +// +void Composition::resetTrackIdAndPosition(TrackId oldId, TrackId newId, + int position) +{ + trackiterator titerator = m_tracks.find(oldId); + + if (titerator != m_tracks.end()) + { + // detach old track + Track *track = (*titerator).second; + m_tracks.erase(titerator); + + // set new position and + track->setId(newId); + track->setPosition(position); + m_tracks[newId] = track; + + // modify segment mappings + // + for (segmentcontainer::const_iterator i = m_segments.begin(); + i != m_segments.end(); ++i) + { + if ((*i)->getTrack() == oldId) (*i)->setTrack(newId); + } + + checkSelectedAndRecordTracks(); + updateRefreshStatuses(); + notifyTrackChanged(getTrackById(newId)); + } + else + std::cerr << "Composition::resetTrackIdAndPosition - " + << "can't move track " << oldId << " to " << newId + << std::endl; +} + +void Composition::setSelectedTrack(TrackId track) +{ + m_selectedTrack = track; + notifySoloChanged(); +} + +void Composition::setSolo(bool value) +{ + m_solo = value; + notifySoloChanged(); +} + +// Insert a Track representation into the Composition +// +void Composition::addTrack(Track *track) +{ + // make sure a track with the same id isn't already there + // + if (m_tracks.find(track->getId()) == m_tracks.end()) { + + m_tracks[track->getId()] = track; + track->setOwningComposition(this); + updateRefreshStatuses(); + notifyTrackChanged(track); + + } else { + std::cerr << "Composition::addTrack(" + << track << "), id = " << track->getId() + << " - WARNING - track id already present " + << __FILE__ << ":" << __LINE__ << std::endl; + // throw Exception("track id already present"); + } +} + + +void Composition::deleteTrack(Rosegarden::TrackId track) +{ + trackiterator titerator = m_tracks.find(track); + + if (titerator == m_tracks.end()) { + + std::cerr << "Composition::deleteTrack : no track of id " << track << std::endl; + throw Exception("track id not found"); + + } else { + + delete ((*titerator).second); + m_tracks.erase(titerator); + checkSelectedAndRecordTracks(); + updateRefreshStatuses(); + notifyTrackDeleted(track); + } + +} + +bool Composition::detachTrack(Rosegarden::Track *track) +{ + trackiterator it = m_tracks.begin(); + + for (; it != m_tracks.end(); ++it) + { + if ((*it).second == track) + break; + } + + if (it == m_tracks.end()) { + std::cerr << "Composition::detachTrack() : no such track " << track << std::endl; + throw Exception("track id not found"); + return false; + } + + ((*it).second)->setOwningComposition(0); + + m_tracks.erase(it); + updateRefreshStatuses(); + checkSelectedAndRecordTracks(); + + return true; +} + +void Composition::checkSelectedAndRecordTracks() +{ + // reset m_selectedTrack and m_recordTrack to the next valid track id + // if the track they point to has been deleted + + if (m_tracks.find(m_selectedTrack) == m_tracks.end()) { + + m_selectedTrack = getClosestValidTrackId(m_selectedTrack); + notifySoloChanged(); + + } + + for (recordtrackcontainer::iterator i = m_recordTracks.begin(); + i != m_recordTracks.end(); ++i) { + if (m_tracks.find(*i) == m_tracks.end()) { + m_recordTracks.erase(i); + } + } + +} + +TrackId +Composition::getClosestValidTrackId(TrackId id) const +{ + long diff = LONG_MAX; + TrackId closestValidTrackId = 0; + + for (trackcontainer::const_iterator i = getTracks().begin(); + i != getTracks().end(); ++i) { + + long cdiff = labs(i->second->getId() - id); + + if (cdiff < diff) { + diff = cdiff; + closestValidTrackId = i->second->getId(); + + } else break; // std::map is sorted, so if the diff increases, we're passed closest valid id + + } + + return closestValidTrackId; +} + +TrackId +Composition::getMinTrackId() const +{ + if (getTracks().size() == 0) return 0; + + trackcontainer::const_iterator i = getTracks().begin(); + return i->first; +} + +TrackId +Composition::getMaxTrackId() const +{ + if (getTracks().size() == 0) return 0; + + trackcontainer::const_iterator i = getTracks().end(); + --i; + + return i->first; +} + +void +Composition::setTrackRecording(TrackId track, bool recording) +{ + if (recording) { + m_recordTracks.insert(track); + } else { + m_recordTracks.erase(track); + } + Track *t = getTrackById(track); + if (t) { + t->setArmed(recording); + } +} + +bool +Composition::isTrackRecording(TrackId track) const +{ + return m_recordTracks.find(track) != m_recordTracks.end(); +} + + +// Export the Composition as XML, also iterates through +// Tracks and any further sub-objects +// +// +std::string Composition::toXmlString() +{ + std::stringstream composition; + + composition << "" << endl << endl; + + composition << endl; + + for (trackiterator tit = getTracks().begin(); + tit != getTracks().end(); + ++tit) + { + if ((*tit).second) + composition << " " << (*tit).second->toXmlString() << endl; + } + + composition << endl; + + for (ReferenceSegment::iterator i = m_timeSigSegment.begin(); + i != m_timeSigSegment.end(); ++i) { + + // Might be nice just to stream the events, but that's + // normally done by XmlStorableEvent in gui/ at the + // moment. Still, this isn't too much of a hardship + + composition << " getAbsoluteTime() + << "\" numerator=\"" + << (*i)->get(TimeSignature::NumeratorPropertyName) + << "\" denominator=\"" + << (*i)->get(TimeSignature::DenominatorPropertyName) + << "\""; + + bool common = false; + (*i)->get(TimeSignature::ShowAsCommonTimePropertyName, common); + if (common) composition << " common=\"true\""; + + bool hidden = false; + (*i)->get(TimeSignature::IsHiddenPropertyName, hidden); + if (hidden) composition << " hidden=\"true\""; + + bool hiddenBars = false; + (*i)->get(TimeSignature::HasHiddenBarsPropertyName, hiddenBars); + if (hiddenBars) composition << " hiddenbars=\"true\""; + + composition << "/>" << endl; + } + + composition << endl; + + for (ReferenceSegment::iterator i = m_tempoSegment.begin(); + i != m_tempoSegment.end(); ++i) { + + tempoT tempo = tempoT((*i)->get(TempoProperty)); + tempoT target = -1; + if ((*i)->has(TargetTempoProperty)) { + target = tempoT((*i)->get(TargetTempoProperty)); + } + composition << " getAbsoluteTime() + << "\" bph=\"" << ((tempo * 6) / 10000) + << "\" tempo=\"" << tempo; + if (target >= 0) { + composition << "\" target=\"" << target; + } + composition << "\"/>" << endl; + } + + composition << endl; + + composition << "" << endl + << m_metadata.toXmlString() << endl + << "" << endl << endl; + + composition << "" << endl; + for (markerconstiterator mIt = m_markers.begin(); + mIt != m_markers.end(); ++mIt) + { + composition << (*mIt)->toXmlString(); + } + composition << "" << endl; + + +#if (__GNUC__ < 3) + composition << "" << std::ends; +#else + composition << ""; +#endif + + return composition.str(); +} + +void +Composition::clearTracks() +{ + trackiterator it = m_tracks.begin(); + + for (; it != m_tracks.end(); it++) + delete ((*it).second); + + m_tracks.erase(m_tracks.begin(), m_tracks.end()); +} + +Track* +Composition::getTrackByPosition(int position) const +{ + trackconstiterator it = m_tracks.begin(); + + for (; it != m_tracks.end(); it++) + { + if ((*it).second->getPosition() == position) + return (*it).second; + } + + return 0; + +} + +int +Composition::getTrackPositionById(TrackId id) const +{ + Track *track = getTrackById(id); + if (!track) return -1; + return track->getPosition(); +} + + +Rosegarden::TrackId +Composition::getNewTrackId() const +{ + // Re BR #1070325: another track deletion problem + // Formerly this was returning the count of tracks currently in + // existence -- returning a duplicate ID if some had been deleted + // from the middle. Let's find one that's really available instead. + + TrackId highWater = 0; + + trackconstiterator it = m_tracks.begin(); + + for (; it != m_tracks.end(); it++) + { + if ((*it).second->getId() >= highWater) + highWater = (*it).second->getId() + 1; + } + + return highWater; +} + + +void +Composition::notifySegmentAdded(Segment *s) const +{ + // If there is an earlier repeating segment on the same track, we + // need to notify the change of its repeat end time + + for (const_iterator i = begin(); i != end(); ++i) { + + if (((*i)->getTrack() == s->getTrack()) + && ((*i)->isRepeating()) + && ((*i)->getStartTime() < s->getStartTime())) { + + notifySegmentRepeatEndChanged(*i, (*i)->getRepeatEndTime()); + } + } + + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->segmentAdded(this, s); + } +} + + +void +Composition::notifySegmentRemoved(Segment *s) const +{ + // If there is an earlier repeating segment on the same track, we + // need to notify the change of its repeat end time + + for (const_iterator i = begin(); i != end(); ++i) { + + if (((*i)->getTrack() == s->getTrack()) + && ((*i)->isRepeating()) + && ((*i)->getStartTime() < s->getStartTime())) { + + notifySegmentRepeatEndChanged(*i, (*i)->getRepeatEndTime()); + } + } + + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->segmentRemoved(this, s); + } +} + +void +Composition::notifySegmentRepeatChanged(Segment *s, bool repeat) const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->segmentRepeatChanged(this, s, repeat); + } +} + +void +Composition::notifySegmentRepeatEndChanged(Segment *s, timeT t) const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->segmentRepeatEndChanged(this, s, t); + } +} + +void +Composition::notifySegmentEventsTimingChanged(Segment *s, timeT delay, RealTime rtDelay) const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->segmentEventsTimingChanged(this, s, delay, rtDelay); + } +} + +void +Composition::notifySegmentTransposeChanged(Segment *s, int transpose) const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->segmentTransposeChanged(this, s, transpose); + } +} + +void +Composition::notifySegmentTrackChanged(Segment *s, TrackId oldId, TrackId newId) const +{ + // If there is an earlier repeating segment on either the + // origin or destination track, we need to notify the change + // of its repeat end time + + for (const_iterator i = begin(); i != end(); ++i) { + + if (((*i)->getTrack() == oldId || (*i)->getTrack() == newId) + && ((*i)->isRepeating()) + && ((*i)->getStartTime() < s->getStartTime())) { + + notifySegmentRepeatEndChanged(*i, (*i)->getRepeatEndTime()); + } + } + + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->segmentTrackChanged(this, s, newId); + } +} + +void +Composition::notifySegmentStartChanged(Segment *s, timeT t) +{ + updateRefreshStatuses(); // not ideal, but best way to ensure track heights are recomputed + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->segmentStartChanged(this, s, t); + } +} + +void +Composition::notifySegmentEndMarkerChange(Segment *s, bool shorten) +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->segmentEndMarkerChanged(this, s, shorten); + } +} + +void +Composition::notifyEndMarkerChange(bool shorten) const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->endMarkerTimeChanged(this, shorten); + } +} + +void +Composition::notifyTrackChanged(Track *t) const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->trackChanged(this, t); + } +} + +void +Composition::notifyTrackDeleted(TrackId t) const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->trackDeleted(this, t); + } +} + +void +Composition::notifyMetronomeChanged() const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->metronomeChanged(this); + } +} + +void +Composition::notifyTimeSignatureChanged() const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->timeSignatureChanged(this); + } +} + +void +Composition::notifySoloChanged() const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->soloChanged(this, isSolo(), getSelectedTrack()); + } +} + +void +Composition::notifyTempoChanged() const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->tempoChanged(this); + } +} + + +void +Composition::notifySourceDeletion() const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->compositionDeleted(this); + } +} + + +void breakpoint() +{ + //std::cerr << "breakpoint()\n"; +} + +// Just empty out the markers +void +Composition::clearMarkers() +{ + markerconstiterator it = m_markers.begin(); + + for (; it != m_markers.end(); ++it) + { + delete *it; + } + + m_markers.clear(); +} + +void +Composition::addMarker(Rosegarden::Marker *marker) +{ + m_markers.push_back(marker); + updateRefreshStatuses(); +} + +bool +Composition::detachMarker(Rosegarden::Marker *marker) +{ + markeriterator it = m_markers.begin(); + + for (; it != m_markers.end(); ++it) + { + if (*it == marker) + { + m_markers.erase(it); + updateRefreshStatuses(); + return true; + } + } + + return false; +} + +bool +Composition::isMarkerAtPosition(Rosegarden::timeT time) const +{ + markerconstiterator it = m_markers.begin(); + + for (; it != m_markers.end(); ++it) + if ((*it)->getTime() == time) return true; + + return false; +} + +void +Composition::setSegmentColourMap(Rosegarden::ColourMap &newmap) +{ + m_segmentColourMap = newmap; + + updateRefreshStatuses(); +} + +void +Composition::setGeneralColourMap(Rosegarden::ColourMap &newmap) +{ + m_generalColourMap = newmap; + + updateRefreshStatuses(); +} + +void +Composition::dump(std::ostream& out, bool) const +{ + out << "Composition segments : " << endl; + + for(iterator i = begin(); i != end(); ++i) { + Segment* s = *i; + + out << "Segment start : " << s->getStartTime() << " - end : " << s->getEndMarkerTime() + << " - repeating : " << s->isRepeating() + << " - track id : " << s->getTrack() + << " - label : " << s->getLabel() + << endl; + + } + +} + + + +} + + diff --git a/src/base/Composition.h b/src/base/Composition.h new file mode 100644 index 0000000..24865dd --- /dev/null +++ b/src/base/Composition.h @@ -0,0 +1,1134 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _COMPOSITION_H_ +#define _COMPOSITION_H_ + +#include +#include + +#include "FastVector.h" + +#include "RealTime.h" +#include "Segment.h" +#include "Track.h" +#include "Configuration.h" +#include "XmlExportable.h" +#include "ColourMap.h" +#include "TriggerSegment.h" + +#include "Marker.h" + +namespace Rosegarden +{ +// We store tempo in quarter-notes per minute * 10^5 (hundred +// thousandths of a quarter-note per minute). This means the maximum +// tempo in a 32-bit integer is about 21400 qpm. We use a signed int +// for compatibility with the Event integer type -- but note that we +// use 0 (rather than -1) to indicate "tempo not set", by convention +// (though see usage of target tempo in e.g. addTempoAtTime). +typedef int tempoT; + +class Quantizer; +class BasicQuantizer; +class NotationQuantizer; + +/** + * Composition contains a complete representation of a piece of music. + * It is a container for multiple Segments, as well as any associated + * non-Event data. + * + * The Composition owns the Segments it holds, and deletes them on + * destruction. When Segments are removed, it will also delete them. + */ + +class CompositionObserver; + +class Composition : public XmlExportable +{ + friend class Track; // to call notifyTrackChanged() + friend class Segment; // to call notifySegmentRepeatChanged() + +public: + typedef std::multiset segmentcontainer; + typedef segmentcontainer::iterator iterator; + typedef segmentcontainer::const_iterator const_iterator; + + typedef std::map trackcontainer; + typedef trackcontainer::iterator trackiterator; + typedef trackcontainer::const_iterator trackconstiterator; + + typedef std::vector markercontainer; + typedef markercontainer::iterator markeriterator; + typedef markercontainer::const_iterator markerconstiterator; + + typedef std::set triggersegmentcontainer; + typedef triggersegmentcontainer::iterator triggersegmentcontaineriterator; + typedef triggersegmentcontainer::const_iterator triggersegmentcontainerconstiterator; + + typedef std::set recordtrackcontainer; + typedef recordtrackcontainer::iterator recordtrackiterator; + typedef recordtrackcontainer::const_iterator recordtrackconstiterator; + + Composition(); + virtual ~Composition(); + +private: + Composition(const Composition &); + Composition &operator=(const Composition &); +public: + + /** + * Remove all Segments from the Composition and destroy them + */ + void clear(); + + /** + * Return the absolute end time of the segment that ends last + */ + timeT getDuration() const; + + + ////// + // + // START AND END MARKERS + + timeT getStartMarker() const { return m_startMarker; } + timeT getEndMarker() const { return m_endMarker; } + + void setStartMarker(const timeT &sM); + void setEndMarker(const timeT &eM); + + + ////// + // + // INSTRUMENT & TRACK + + Track* getTrackById(TrackId track) const; + + Track* getTrackByPosition(int position) const; + + int getTrackPositionById(TrackId track) const; // -1 if not found + + trackcontainer& getTracks() { return m_tracks; } + + const trackcontainer& getTracks() const { return m_tracks; } + + // Reset id and position + void resetTrackIdAndPosition(TrackId oldId, TrackId newId, int position); + + TrackId getMinTrackId() const; + TrackId getMaxTrackId() const; + + const recordtrackcontainer &getRecordTracks() const { return m_recordTracks; } + void setTrackRecording(TrackId track, bool recording); + bool isTrackRecording(TrackId track) const; + + // Get and set Solo Track + // + TrackId getSelectedTrack() const { return m_selectedTrack; } + + void setSelectedTrack(TrackId track); + + // Are we soloing a Track? + // + bool isSolo() const { return m_solo; } + void setSolo(bool value); + + unsigned int getNbTracks() const { return m_tracks.size(); } + + /** + * Clear out the Track container + */ + void clearTracks(); + + /** + * Insert a new Track. The Composition takes over ownership of + * the track object. + */ + void addTrack(Track *track); + + /** + * Delete a Track by index + */ + void deleteTrack(TrackId track); + + /** + * Detach a Track (revert ownership of the Track object to the + * caller). + */ + bool detachTrack(Track *track); + + /** + * Get the highest running track id (generated and kept + * through addTrack) + */ + TrackId getNewTrackId() const; + + + ////// + // + // MARKERS + + markercontainer& getMarkers() { return m_markers; } + const markercontainer& getMarkers() const { return m_markers; } + + /** + * Add a new Marker. The Composition takes ownership of the + * marker object. + */ + void addMarker(Marker *marker); + + /** + * Detach a Marker (revert ownership of the Marker object to the + * caller). + */ + bool detachMarker(Marker *marker); + + bool isMarkerAtPosition(timeT time) const; + + void clearMarkers(); + + + ////// + // + // SEGMENT + + segmentcontainer& getSegments() { return m_segments; } + const segmentcontainer& getSegments() const { return m_segments; } + + unsigned int getNbSegments() const { return m_segments.size(); } + + /** + * Add a new Segment and return an iterator pointing to it + * The inserted Segment is owned by the Composition object + */ + iterator addSegment(Segment*); + + /** + * Delete the Segment pointed to by the specified iterator + * + * NOTE: The Segment is deleted from the Composition and + * destroyed + */ + void deleteSegment(iterator); + + /** + * Delete the Segment if it is part of the Composition + * \return true if the Segment was found and deleted + * + * NOTE: The Segment is deleted from the composition and + * destroyed + */ + bool deleteSegment(Segment*); + + /** + * DO NOT USE THIS METHOD + * + * Set a Segment's start time while keeping the integrity of the + * Composition multiset. + * + * The segment is removed and re-inserted from the composition + * so the ordering is preserved. + */ + void setSegmentStartTime(Segment*, timeT); + + /** + * Test whether a Segment exists in this Composition. + */ + bool contains(const Segment *); + + /** + * Return an iterator pointing at the given Segment, or end() + * if it does not exist in this Composition. + */ + iterator findSegment(const Segment *); + + /** + * Remove the Segment if it is part of the Composition, + * but do not destroy it (passing it to addSegment again + * would restore it correctly). + * \return true if the Segment was found and removed + * + * NOTE: Many of the Segment methods will fail if the + * Segment is not in a Composition. You should not + * expect to do anything meaningful with a Segment that + * has been detached from the Composition in this way. + */ + bool detachSegment(Segment*); + + /** + * Add a new Segment which has been "weakly detached" + * + * Like addSegment(), but doesn't send the segmentAdded signal + * nor updating refresh statuses + */ + iterator weakAddSegment(Segment*); + + /** + * Detach a segment which you're going to re-add (with weakAddSegment) + * later. + * Like detachSegment(), but without sending the segmentDeleted signal + * nor updating refresh statuses. + */ + bool weakDetachSegment(Segment*); + + /** + * Get the largest number of segments that "overlap" at any one + * time on the given track. I have given this function a nice + * long name to make it feel important. + */ + int getMaxContemporaneousSegmentsOnTrack(TrackId track) const; + + /** + * Retrieve a "vertical" index for this segment within its track. + * Currently this is based on studying the way that segments on + * the track overlap and returning the lowest integer such that no + * prior starting segment that overlaps with this one would use + * the same integer. In future this could use proper voice + * ordering. + */ + int getSegmentVoiceIndex(const Segment *) const; + + + ////// + // + // TRIGGER SEGMENTS + + triggersegmentcontainer &getTriggerSegments() { return m_triggerSegments; } + const triggersegmentcontainer &getTriggerSegments() const { return m_triggerSegments; } + + /** + * Add a new trigger Segment with a given base pitch and base + * velocity, and return its record. If pitch or velocity is -1, + * it will be taken from the first note event in the segment + */ + TriggerSegmentRec *addTriggerSegment(Segment *, int pitch = -1, int velocity = -1); + + /** + * Delete a trigger Segment. + */ + void deleteTriggerSegment(TriggerSegmentId); + + /** + * Detach a trigger Segment from the Composition. + */ + void detachTriggerSegment(TriggerSegmentId); + + /** + * Delete all trigger Segments. + */ + void clearTriggerSegments(); + + /** + * Return the TriggerSegmentId for the given Segment, or -1 if it is + * not a trigger Segment. + */ + int getTriggerSegmentId(Segment *); + + /** + * Return the Segment for a given TriggerSegmentId + */ + Segment *getTriggerSegment(TriggerSegmentId); + + /** + * Return the TriggerSegmentRec (with Segment, base pitch, base velocity, + * references etc) for a given TriggerSegmentId + */ + TriggerSegmentRec *getTriggerSegmentRec(TriggerSegmentId); + + /** + * Add a new trigger Segment with a given ID and base pitch and + * velocity. Fails and returns 0 if the ID is already in use. + * This is intended for use from file load or from undo/redo. + */ + TriggerSegmentRec *addTriggerSegment(Segment *, TriggerSegmentId, + int basePitch = -1, int baseVelocity = -1); + + /** + * Get the ID of the next trigger segment that will be inserted. + */ + TriggerSegmentId getNextTriggerSegmentId() const; + + /** + * Specify the next trigger ID. This is intended for use from file + * load only. Do not use this function unless you know what you're + * doing. + */ + void setNextTriggerSegmentId(TriggerSegmentId); + + /** + * Update the trigger segment references for all trigger segments. + * To be called after file load. + */ + void updateTriggerSegmentReferences(); + + + ////// + // + // BAR + + /** + * Return the total number of bars in the composition + */ + int getNbBars() const; + + /** + * Return the number of the bar that starts at or contains time t. + * + * Will happily return computed bar numbers for times before + * the start or beyond the real end of the composition. + */ + int getBarNumber(timeT t) const; + + /** + * Return the starting time of bar n + */ + timeT getBarStart(int n) const { + return getBarRange(n).first; + } + + /** + * Return the ending time of bar n + */ + timeT getBarEnd(int n) const { + return getBarRange(n).second; + } + + /** + * Return the time range of bar n. + * + * Will happily return theoretical timings for bars before the + * start or beyond the end of composition (i.e. there is no + * requirement that 0 <= n < getNbBars()). + */ + std::pair getBarRange(int n) const; + + /** + * Return the starting time of the bar that contains time t + */ + timeT getBarStartForTime(timeT t) const { + return getBarRangeForTime(t).first; + } + + /** + * Return the ending time of the bar that contains time t + */ + timeT getBarEndForTime(timeT t) const { + return getBarRangeForTime(t).second; + } + + /** + * Return the starting and ending times of the bar that contains + * time t. + * + * Will happily return theoretical timings for bars before the + * start or beyond the end of composition. + */ + std::pair getBarRangeForTime(timeT t) const; + + /** + * Get the default number of bars in a new empty composition + */ + static int getDefaultNbBars() { return m_defaultNbBars; } + + /** + * Set the default number of bars in a new empty composition + */ + static void setDefaultNbBars(int b) { m_defaultNbBars = b; } + + + ////// + // + // TIME SIGNATURE + + /** + * Add the given time signature at the given time. Returns the + * resulting index of the time signature (suitable for passing + * to removeTimeSignature, for example) + */ + int addTimeSignature(timeT t, TimeSignature timeSig); + + /** + * Return the time signature in effect at time t + */ + TimeSignature getTimeSignatureAt(timeT t) const; + + /** + * Return the time signature in effect at time t, and the time at + * which it came into effect + */ + timeT getTimeSignatureAt(timeT, TimeSignature &) const; + + /** + * Return the time signature in effect in bar n. Also sets + * isNew to true if the time signature is a new one that did + * not appear in the previous bar. + */ + TimeSignature getTimeSignatureInBar(int n, bool &isNew) const; + + /** + * Return the total number of time signature changes in the + * composition. + */ + int getTimeSignatureCount() const; + + /** + * Return the index of the last time signature change before + * or at the given time, in a range suitable for passing to + * getTimeSignatureChange. Return -1 if there has been no + * time signature by this time. + */ + int getTimeSignatureNumberAt(timeT time) const; + + /** + * Return the absolute time of and time signature introduced + * by time-signature change n. + */ + std::pair getTimeSignatureChange(int n) const; + + /** + * Remove time signature change event n from the composition. + */ + void removeTimeSignature(int n); + + + + ////// + // + // TEMPO + + /** + * Return the (approximate) number of quarters per minute for a + * given tempo. + */ + static double getTempoQpm(tempoT tempo) { return double(tempo) / 100000.0; } + static tempoT getTempoForQpm(double qpm) { return tempoT(qpm * 100000 + 0.01); } + + /** + * Return the tempo in effect at time t. If a ramped tempo change + * is in effect at the time, it will be properly interpolated and + * a computed value returned. + */ + tempoT getTempoAtTime(timeT t) const; + + /** + * Return the tempo in effect at the current playback position. + */ + tempoT getCurrentTempo() const { return getTempoAtTime(getPosition()); } + + /** + * Set a default tempo for the composition. This will be + * overridden by any tempo events encountered during playback. + */ + void setCompositionDefaultTempo(tempoT tempo) { m_defaultTempo = tempo; } + tempoT getCompositionDefaultTempo() const { return m_defaultTempo; } + + /** + * Add a tempo-change event at the given time, to the given tempo. + * Removes any existing tempo event at that time. Returns the + * index of the new tempo event in a form suitable for passing to + * removeTempoChange. + * + * If targetTempo == -1, adds a single constant tempo change. + * If targetTempo == 0, adds a smooth tempo ramp from this tempo + * change to the next. + * If targetTempo > 0, adds a smooth tempo ramp from this tempo + * ending at targetTempo at the time of the next tempo change. + */ + int addTempoAtTime(timeT time, tempoT tempo, tempoT targetTempo = -1); + + /** + * Return the number of tempo changes in the composition. + */ + int getTempoChangeCount() const; + + /** + * Return the index of the last tempo change before the given + * time, in a range suitable for passing to getTempoChange. + * Return -1 if the default tempo is in effect at this time. + */ + int getTempoChangeNumberAt(timeT time) const; + + /** + * Return the absolute time of and tempo introduced by tempo + * change number n. If the tempo is ramped, this returns only + * the starting tempo. + */ + std::pair getTempoChange(int n) const; + + /** + * Return whether the tempo change number n is a ramped tempo or + * not, and if it is, return the target tempo for the ramp. + * + * If calculate is false, return a target tempo of 0 if the tempo + * change is defined to ramp to the following tempo. If calculate + * is true, return a target tempo equal to the following tempo in + * this case. + */ + std::pair getTempoRamping(int n, bool calculate = true) const; + + /** + * Remove tempo change event n from the composition. + */ + void removeTempoChange(int n); + + /** + * Get the slowest assigned tempo in the composition. + */ + tempoT getMinTempo() const { + return ((m_minTempo != 0) ? m_minTempo : m_defaultTempo); + } + + /** + * Get the fastest assigned tempo in the composition. + */ + tempoT getMaxTempo() const { + return ((m_maxTempo != 0) ? m_maxTempo : m_defaultTempo); + } + + + ////// + // + // REAL TIME + + /** + * Return the number of microseconds elapsed between + * the beginning of the composition and the given timeT time. + * (timeT units are independent of tempo; this takes into + * account any tempo changes in the first t units of time.) + * + * This is a fairly efficient operation, not dependent on the + * magnitude of t or the number of tempo changes in the piece. + */ + RealTime getElapsedRealTime(timeT t) const; + + /** + * Return the nearest time in timeT units to the point at the + * given number of microseconds after the beginning of the + * composition. (timeT units are independent of tempo; this takes + * into account any tempo changes in the first t microseconds.) + * The result will be approximate, as timeT units are obviously + * less precise than microseconds. + * + * This is a fairly efficient operation, not dependent on the + * magnitude of t or the number of tempo changes in the piece. + */ + timeT getElapsedTimeForRealTime(RealTime t) const; + + /** + * Return the number of microseconds elapsed between + * the two given timeT indices into the composition, taking + * into account any tempo changes between the two times. + */ + RealTime getRealTimeDifference(timeT t0, timeT t1) const { + if (t1 > t0) return getElapsedRealTime(t1) - getElapsedRealTime(t0); + else return getElapsedRealTime(t0) - getElapsedRealTime(t1); + } + + + ////// + // + // OTHER TIME CONVERSIONS + + /** + * Return (by reference) the bar number and beat/division values + * corresponding to a given absolute time. + */ + void getMusicalTimeForAbsoluteTime(timeT absoluteTime, + int &bar, int &beat, + int &fraction, int &remainder); + + /** + * Return (by reference) the number of bars and beats/divisions + * corresponding to a given duration. The absolute time at which + * the duration starts is also required, so as to know the correct + * time signature. + */ + void getMusicalTimeForDuration(timeT absoluteTime, timeT duration, + int &bars, int &beats, + int &fractions, int &remainder); + + /** + * Return the absolute time corresponding to a given bar number + * and beat/division values. + */ + timeT getAbsoluteTimeForMusicalTime(int bar, int beat, + int fraction, int remainder); + + /** + * Return the duration corresponding to a given number of bars and + * beats/divisions. The absolute time at which the duration + * starts is also required, so as to know the correct time + * signature. + */ + timeT getDurationForMusicalTime(timeT absoluteTime, + int bars, int beats, + int fractions, int remainder); + + + /** + * Get the current playback position. + */ + timeT getPosition() const { return m_position; } + + /** + * Set the current playback position. + */ + void setPosition(timeT position); + + + + ////// + // + // LOOP + + timeT getLoopStart() const { return m_loopStart; } + timeT getLoopEnd() const { return m_loopEnd;} + + void setLoopStart(const timeT &lS) { m_loopStart = lS; } + void setLoopEnd(const timeT &lE) { m_loopEnd = lE; } + + // Determine if we're currently looping + // + bool isLooping() const { return (m_loopStart != m_loopEnd); } + + + + ////// + // + // OTHER STUFF + + + // Some set<> API delegation + iterator begin() { return m_segments.begin(); } + const_iterator begin() const { return m_segments.begin(); } + iterator end() { return m_segments.end(); } + const_iterator end() const { return m_segments.end(); } + + + // XML exportable method + // + virtual std::string toXmlString(); + + // Who's making this racket? + // + Configuration &getMetadata() { + return m_metadata; + } + const Configuration &getMetadata() const { + return m_metadata; + } + + std::string getCopyrightNote() const { + return m_metadata.get(CompositionMetadataKeys::Copyright, + ""); + } + void setCopyrightNote(const std::string &cr) { + m_metadata.set(CompositionMetadataKeys::Copyright, cr); + } + + + // We can have the metronome on or off while playing or + // recording - get and set values from here + // + bool usePlayMetronome() const { return m_playMetronome; } + bool useRecordMetronome() const { return m_recordMetronome; } + + void setPlayMetronome(bool value); + void setRecordMetronome(bool value); + + + // Colour stuff + ColourMap& getSegmentColourMap() { return m_segmentColourMap; } + const ColourMap& getSegmentColourMap() const { return m_segmentColourMap; } + void setSegmentColourMap(ColourMap &newmap); + + // General colourmap for non-segments + // + ColourMap& getGeneralColourMap() { return m_generalColourMap; } + void setGeneralColourMap(ColourMap &newmap); + + + ////// + // + // QUANTIZERS + + /** + * Return a quantizer that quantizes to the our most basic + * units (i.e. a unit quantizer whose unit is our shortest + * note duration). + */ + const BasicQuantizer *getBasicQuantizer() const { + return m_basicQuantizer; + } + + /** + * Return a quantizer that does quantization for notation + * only. + */ + const NotationQuantizer *getNotationQuantizer() const { + return m_notationQuantizer; + } + + + ////// + // + // REFRESH STATUS + + // delegate RefreshStatusArray API + unsigned int getNewRefreshStatusId() { + return m_refreshStatusArray.getNewRefreshStatusId(); + } + + RefreshStatus& getRefreshStatus(unsigned int id) { + return m_refreshStatusArray.getRefreshStatus(id); + } + + /// Set all refresh statuses to true + void updateRefreshStatuses() { + m_refreshStatusArray.updateRefreshStatuses(); + } + + + void addObserver(CompositionObserver *obs) { m_observers.push_back(obs); } + void removeObserver(CompositionObserver *obs) { m_observers.remove(obs); } + + ////// + // DEBUG FACILITIES + void dump(std::ostream&, bool full=false) const; + +protected: + + static const std::string TempoEventType; + static const PropertyName TempoProperty; + static const PropertyName TargetTempoProperty; + + static const PropertyName NoAbsoluteTimeProperty; + static const PropertyName BarNumberProperty; + static const PropertyName TempoTimestampProperty; + + + struct ReferenceSegmentEventCmp + { + bool operator()(const Event &e1, const Event &e2) const; + bool operator()(const Event *e1, const Event *e2) const { + return operator()(*e1, *e2); + } + }; + + struct BarNumberComparator + { + bool operator()(const Event &e1, const Event &e2) const { + return (e1.get(BarNumberProperty) < + e2.get(BarNumberProperty)); + } + bool operator()(const Event *e1, const Event *e2) const { + return operator()(*e1, *e2); + } + }; + + /** + * Ensure the selected and record trackids still point to something valid + * Must be called after deletion of detach of a track + */ + void checkSelectedAndRecordTracks(); + TrackId getClosestValidTrackId(TrackId id) const; + + + //--------------- Data members --------------------------------- + // + trackcontainer m_tracks; + segmentcontainer m_segments; + + // The tracks we are armed for record on + // + recordtrackcontainer m_recordTracks; + + // Are we soloing and if so which Track? + // + bool m_solo; + TrackId m_selectedTrack; + + /** + * This is a bit like a segment, but can only contain one sort of + * event, and can only have one event at each absolute time + */ + class ReferenceSegment : + public FastVector // not a set: want random access for bars + { + typedef FastVector Impl; + + public: + ReferenceSegment(std::string eventType); + virtual ~ReferenceSegment(); + private: + ReferenceSegment(const ReferenceSegment &); + ReferenceSegment& operator=(const ReferenceSegment &); + public: + typedef Impl::iterator iterator; + typedef Impl::size_type size_type; + typedef Impl::difference_type difference_type; + + void clear(); + + timeT getDuration() const; + + /// Inserts a single event, removing any existing one at that time + iterator insert(Event *e); // may throw Event::BadType + + void erase(Event *e); + + iterator findTime(timeT time); + iterator findNearestTime(timeT time); + + iterator findRealTime(RealTime time); + iterator findNearestRealTime(RealTime time); + + std::string getEventType() const { return m_eventType; } + + private: + iterator find(Event *e); + std::string m_eventType; + }; + + /// Contains time signature events + mutable ReferenceSegment m_timeSigSegment; + + /// Contains tempo events + mutable ReferenceSegment m_tempoSegment; + + /// affects m_timeSigSegment + void calculateBarPositions() const; + mutable bool m_barPositionsNeedCalculating; + ReferenceSegment::iterator getTimeSignatureAtAux(timeT t) const; + + /// affects m_tempoSegment + void calculateTempoTimestamps() const; + mutable bool m_tempoTimestampsNeedCalculating; + RealTime time2RealTime(timeT time, tempoT tempo) const; + RealTime time2RealTime(timeT time, tempoT tempo, + timeT targetTempoTime, tempoT targetTempo) const; + timeT realTime2Time(RealTime rtime, tempoT tempo) const; + timeT realTime2Time(RealTime rtime, tempoT tempo, + timeT targetTempoTime, tempoT targetTempo) const; + + bool getTempoTarget(ReferenceSegment::const_iterator i, + tempoT &target, + timeT &targetTime) const; + + static RealTime getTempoTimestamp(const Event *e); + static void setTempoTimestamp(Event *e, RealTime r); + + typedef std::list ObserverSet; + ObserverSet m_observers; + + void notifySegmentAdded(Segment *) const; + void notifySegmentRemoved(Segment *) const; + void notifySegmentRepeatChanged(Segment *, bool) const; + void notifySegmentRepeatEndChanged(Segment *, timeT) const; + void notifySegmentEventsTimingChanged(Segment *s, timeT delay, RealTime rtDelay) const; + void notifySegmentTransposeChanged(Segment *s, int transpose) const; + void notifySegmentTrackChanged(Segment *s, TrackId oldId, TrackId newId) const; + void notifySegmentStartChanged(Segment *, timeT); + void notifySegmentEndMarkerChange(Segment *s, bool shorten); + void notifyEndMarkerChange(bool shorten) const; + void notifyTrackChanged(Track*) const; + void notifyTrackDeleted(TrackId) const; + void notifyMetronomeChanged() const; + void notifyTimeSignatureChanged() const; + void notifySoloChanged() const; + void notifyTempoChanged() const; + void notifySourceDeletion() const; + + void updateExtremeTempos(); + + BasicQuantizer *m_basicQuantizer; + NotationQuantizer *m_notationQuantizer; + + timeT m_position; + tempoT m_defaultTempo; + tempoT m_minTempo; // cached from tempo segment + tempoT m_maxTempo; // cached from tempo segment + + // Notional Composition markers - these define buffers for the + // start and end of the piece, Segments can still exist outside + // of these markers - these are for visual and playback cueing. + // + timeT m_startMarker; + timeT m_endMarker; + + static int m_defaultNbBars; + + // Loop start and end positions. If they're both the same + // value (usually 0) then there's no loop set. + // + timeT m_loopStart; + timeT m_loopEnd; + + Configuration m_metadata; + + bool m_playMetronome; + bool m_recordMetronome; + + RefreshStatusArray m_refreshStatusArray; + + // User defined markers in the composition + // + markercontainer m_markers; + + // Trigger segments (unsorted segments fired by events elsewhere) + // + triggersegmentcontainer m_triggerSegments; + TriggerSegmentId m_nextTriggerSegmentId; + + ColourMap m_segmentColourMap; + ColourMap m_generalColourMap; +}; + + +/** + * If you subclass from CompositionObserver, you can then attach to a + * Composition to receive notification when something changes. + * + * Normally all the methods in this class would be pure virtual. But + * because there are so many, that imposes far too much work on the + * subclass implementation in a case where it only really wants to + * know about one thing, such as segments being deleted. So we have + * empty default implementations, and you'll just have to take a bit + * more care to make sure you really are making the correct + * declarations in the subclass. + */ + +class CompositionObserver +{ +public: + CompositionObserver() : m_compositionDeleted(false) {} + + virtual ~CompositionObserver() {} + + /** + * Called after the segment has been added to the composition + */ + virtual void segmentAdded(const Composition *, Segment *) { } + + /** + * Called after the segment has been removed from the segment, + * and just before it is deleted + */ + virtual void segmentRemoved(const Composition *, Segment *) { } + + /** + * Called when the segment's repeat status has changed + */ + virtual void segmentRepeatChanged(const Composition *, Segment *, bool) { } + + /** + * Called when the segment's repeat end time has changed + */ + virtual void segmentRepeatEndChanged(const Composition *, Segment *, timeT) { } + + /** + * Called when the segment's delay timing has changed + */ + virtual void segmentEventsTimingChanged(const Composition *, Segment *, + timeT /* delay */, + RealTime /* rtDelay */) { } + + /** + * Called when the segment's transpose value has changed + */ + virtual void segmentTransposeChanged(const Composition *, Segment *, + int /* transpose */) { } + + /** + * Called when the segment's start time has changed + */ + virtual void segmentStartChanged(const Composition *, Segment *, + timeT /* newStartTime */) { } + + /** + * Called when the segment's end marker time has changed + */ + virtual void segmentEndMarkerChanged(const Composition *, Segment *, + bool /* shorten */) { } + + /** + * Called when the segment's track has changed + */ + virtual void segmentTrackChanged(const Composition *, Segment *, + TrackId /* id */) { } + + /** + * Called after the composition's end marker time has been + * changed + */ + virtual void endMarkerTimeChanged(const Composition *, bool /* shorten */) { } + + /** + * Called when a track is changed (instrument id, muted status...) + */ + virtual void trackChanged(const Composition *, Track*) { } + + /** + * Called when a track has been deleted + */ + virtual void trackDeleted(const Composition *, TrackId) { } + + /** + * Called when some time signature has changed + */ + virtual void timeSignatureChanged(const Composition *) { } + + /** + * Called when metronome status has changed (on/off) + */ + virtual void metronomeChanged(const Composition *) { } + + /** + * Called when solo status changes (solo on/off, and selected track) + */ + virtual void soloChanged(const Composition *, bool /* solo */, + TrackId /* selectedTrack */) { } + + /** + * Called when solo status changes (solo on/off, and selected track) + */ + virtual void tempoChanged(const Composition *) { } + + /** + * Called from the composition dtor + */ + virtual void compositionDeleted(const Composition *) { + m_compositionDeleted = true; + } + + bool isCompositionDeleted() { return m_compositionDeleted; } + +protected: + bool m_compositionDeleted; +}; + +} + + +#endif + diff --git a/src/base/CompositionTimeSliceAdapter.cpp b/src/base/CompositionTimeSliceAdapter.cpp new file mode 100644 index 0000000..b91b804 --- /dev/null +++ b/src/base/CompositionTimeSliceAdapter.cpp @@ -0,0 +1,283 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2002 + Randall Farmer + with additional work by Chris Cannam. + + 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. +*/ + +// !!!TODO: handle timeslices + +#include +#include + +#include "CompositionTimeSliceAdapter.h" +#include "Segment.h" +#include "Composition.h" +#include "Selection.h" + +namespace Rosegarden { + +using std::list; +using std::pair; + +CompositionTimeSliceAdapter::CompositionTimeSliceAdapter(Composition *c, + timeT begin, + timeT end) : + m_composition(c), + m_begin(begin), + m_end(end) +{ + if (begin == end) { + m_begin = 0; + m_end = c->getDuration(); + } + + for (Composition::iterator ci = m_composition->begin(); + ci != m_composition->end(); ++ci) { + m_segmentList.push_back(*ci); + } +} + +CompositionTimeSliceAdapter::CompositionTimeSliceAdapter(Composition *c, + SegmentSelection* s, + timeT begin, + timeT end) : + m_composition(c), + m_begin(begin), + m_end(end) +{ + if (begin == end) { + m_begin = 0; + m_end = c->getDuration(); + } + + for (Composition::iterator ci = m_composition->begin(); + ci != m_composition->end(); ++ci) { + if (!s || s->find(*ci) != s->end()) { + m_segmentList.push_back(*ci); + } + } +} + +CompositionTimeSliceAdapter::CompositionTimeSliceAdapter(Composition *c, + const TrackSet &trackIDs, + timeT begin, + timeT end) : + m_composition(c), + m_begin(begin), + m_end(end) +{ + if (begin == end) { + m_begin = 0; + m_end = c->getDuration(); + } + + for (Composition::iterator ci = m_composition->begin(); + ci != m_composition->end(); ++ci) { + if (trackIDs.find((*ci)->getTrack()) != trackIDs.end()) { + m_segmentList.push_back(*ci); + } + } +} + +CompositionTimeSliceAdapter::iterator +CompositionTimeSliceAdapter::begin() const +{ + if (m_beginItr.m_a == 0) { + m_beginItr = iterator(this); + fill(m_beginItr, false); + } + return m_beginItr; +} + +CompositionTimeSliceAdapter::iterator +CompositionTimeSliceAdapter::end() const +{ + return iterator(this); +} + +void +CompositionTimeSliceAdapter::fill(iterator &i, bool atEnd) const +{ + // The segment iterators should all point to events starting at or + // after m_begin (if atEnd false) or at or before m_end (if atEnd true). + + for (unsigned int k = 0; k < m_segmentList.size(); ++k) { + Segment::iterator j = m_segmentList[k]->findTime(atEnd ? m_end : m_begin); + i.m_segmentItrList.push_back(j); + } + + // fill m_curEvent & m_curTrack + if (!atEnd) ++i; +} + +CompositionTimeSliceAdapter::iterator& +CompositionTimeSliceAdapter::iterator::operator=(const iterator &i) +{ + if (&i == this) return *this; + m_segmentItrList.clear(); + + for (segmentitrlist::const_iterator j = i.m_segmentItrList.begin(); + j != i.m_segmentItrList.end(); ++j) { + m_segmentItrList.push_back(Segment::iterator(*j)); + } + + m_a = i.m_a; + m_curTrack = i.m_curTrack; + m_curEvent = i.m_curEvent; + m_needFill = i.m_needFill; + return *this; +} + +CompositionTimeSliceAdapter::iterator::iterator(const iterator &i) : + m_a(i.m_a), + m_curEvent(i.m_curEvent), + m_curTrack(i.m_curTrack), + m_needFill(i.m_needFill) +{ + for (segmentitrlist::const_iterator j = i.m_segmentItrList.begin(); + j != i.m_segmentItrList.end(); ++j) { + m_segmentItrList.push_back(Segment::iterator(*j)); + } +} + +CompositionTimeSliceAdapter::iterator& +CompositionTimeSliceAdapter::iterator::operator++() +{ + assert(m_a != 0); + + // needFill is only set true for iterators created at end() + if (m_needFill) { + m_a->fill(*this, true); + m_needFill = false; + } + + Event *e = 0; + unsigned int pos = 0; + + for (unsigned int i = 0; i < m_a->m_segmentList.size(); ++i) { + + if (!m_a->m_segmentList[i]->isBeforeEndMarker(m_segmentItrList[i])) continue; + + if (!e || strictLessThan(*m_segmentItrList[i], e)) { + e = *m_segmentItrList[i]; + m_curTrack = m_a->m_segmentList[i]->getTrack(); + pos = i; + } + } + + // Check whether we're past the end time, if there is one + if (!e || e->getAbsoluteTime() >= m_a->m_end) { + m_curEvent = 0; + m_curTrack = -1; + return *this; + } + + // e is now an Event* less than or equal to any that the iterator + // hasn't already passed over + m_curEvent = e; + + // m_segmentItrList[pos] is a segment::iterator that points to e + ++m_segmentItrList[pos]; + + return *this; +} + +CompositionTimeSliceAdapter::iterator& +CompositionTimeSliceAdapter::iterator::operator--() +{ + assert(m_a != 0); + + // needFill is only set true for iterators created at end() + if (m_needFill) { + m_a->fill(*this, true); + m_needFill = false; + } + + Event *e = 0; + int pos = -1; + + // Decrement is more subtle than increment. We have to scan the + // iterators available, and decrement the one that points to + // m_curEvent. Then to fill m_curEvent we need to find the next + // greatest event back that is not itself m_curEvent. + + for (unsigned int i = 0; i < m_a->m_segmentList.size(); ++i) { + + if (m_segmentItrList[i] == m_a->m_segmentList[i]->begin()) continue; + + Segment::iterator si(m_segmentItrList[i]); + --si; + + if (*si == m_curEvent) { + pos = i; + } else if (!e || !strictLessThan(*si, e)) { + e = *si; + m_curTrack = m_a->m_segmentList[i]->getTrack(); + } + } + + if (e) m_curEvent = e; + if (pos >= 0) { + --m_segmentItrList[pos]; + } + + return *this; +} + +bool +CompositionTimeSliceAdapter::iterator::operator==(const iterator& other) const { + return m_a == other.m_a && m_curEvent == other.m_curEvent; +} + +bool +CompositionTimeSliceAdapter::iterator::operator!=(const iterator& other) const { + return !operator==(other); +} + +Event * +CompositionTimeSliceAdapter::iterator::operator*() const { + return m_curEvent; +} + +Event & +CompositionTimeSliceAdapter::iterator::operator->() const { + return *m_curEvent; +} + +int +CompositionTimeSliceAdapter::iterator::getTrack() const { + return m_curTrack; +} + +bool +CompositionTimeSliceAdapter::iterator::strictLessThan(Event *e1, Event *e2) { + // We need a complete ordering of events -- we can't cope with two events + // comparing equal. i.e. one of e1 < e2 and e2 < e1 must be true. The + // ordering can be arbitrary -- we just compare addresses for events the + // event comparator doesn't distinguish between. We know we're always + // dealing with event pointers, not copies of events. + if (*e1 < *e2) return true; + else if (*e2 < *e1) return false; + else return e1 < e2; +} + +} diff --git a/src/base/CompositionTimeSliceAdapter.h b/src/base/CompositionTimeSliceAdapter.h new file mode 100644 index 0000000..01307e3 --- /dev/null +++ b/src/base/CompositionTimeSliceAdapter.h @@ -0,0 +1,149 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2002 + Randall Farmer + with additional work by Chris Cannam. + + 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 _COMPOSITION_TIMESLICE_ADAPTER_H_ +#define _COMPOSITION_TIMESLICE_ADAPTER_H_ + +#include +#include + +#include "Segment.h" + +namespace Rosegarden { + +class Event; +class Composition; +class SegmentSelection; + +/** + * CompositionTimeSliceAdapter provides the ability to iterate through + * all the events in a Composition in time order, across many segments + * at once. + * + * The CompositionTimeSliceAdapter is suitable for use as the backing + * container for the Set classes, notably GenericChord (see Sets.h). + * This combination enables you to iterate through a Composition as a + * sequence of chords composed of all Events on a set of Segments that + * lie within a particular quantize range of one another. + */ + +class CompositionTimeSliceAdapter +{ +public: + class iterator; + typedef std::set TrackSet; + + /** + * Construct a CompositionTimeSliceAdapter that operates on the + * given section in time of the given composition. If begin and + * end are equal, the whole composition will be used. + */ + CompositionTimeSliceAdapter(Composition* c, + timeT begin = 0, + timeT end = 0); + + /** + * Construct a CompositionTimeSliceAdapter that operates on the + * given section in time of the given set of segments within the + * given composition. If begin and end are equal, the whole + * duration of the composition will be used. + */ + CompositionTimeSliceAdapter(Composition* c, + SegmentSelection* s, + timeT begin = 0, + timeT end = 0); + + /** + * Construct a CompositionTimeSliceAdapter that operates on the + * given section in time of all the segments in the given set of + * tracks within the given composition. If begin and end are + * equal, the whole duration of the composition will be used. + */ + CompositionTimeSliceAdapter(Composition *c, + const TrackSet &trackIDs, + timeT begin = 0, + timeT end = 0); + + ~CompositionTimeSliceAdapter() { }; + + // bit sloppy -- we don't have a const_iterator + iterator begin() const; + iterator end() const; + + typedef std::vector segmentlist; + typedef std::vector segmentitrlist; + + Composition *getComposition() { return m_composition; } + + class iterator { + friend class CompositionTimeSliceAdapter; + + public: + iterator() : + m_a(0), m_curEvent(0), m_curTrack(-1), m_needFill(true) { } + iterator(const CompositionTimeSliceAdapter *a) : + m_a(a), m_curEvent(0), m_curTrack(-1), m_needFill(true) { } + iterator(const iterator &); + iterator &operator=(const iterator &); + ~iterator() { }; + + iterator &operator++(); + iterator &operator--(); + + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const; + + Event *operator*() const; + Event &operator->() const; + + int getTrack() const; + + private: + segmentitrlist m_segmentItrList; + const CompositionTimeSliceAdapter *m_a; + Event* m_curEvent; + int m_curTrack; + bool m_needFill; + + static bool strictLessThan(Event *, Event *); + }; + + +private: + friend class iterator; + + Composition* m_composition; + mutable iterator m_beginItr; + timeT m_begin; + timeT m_end; + + segmentlist m_segmentList; + + void fill(iterator &, bool atEnd) const; +}; + +} + +#endif diff --git a/src/base/Configuration.cpp b/src/base/Configuration.cpp new file mode 100644 index 0000000..a3d836f --- /dev/null +++ b/src/base/Configuration.cpp @@ -0,0 +1,232 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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. +*/ + + +// Class to hold extraneous bits of configuration which +// don't sit inside the Composition itself - sequencer +// and other general stuff that we want to keep separate. +// +// + +#include +#include + +#include "Configuration.h" + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + + +namespace Rosegarden +{ + +Configuration::Configuration(const Configuration &conf) : + PropertyMap(), + XmlExportable() +{ + clear(); + + // Copy everything + // + for (const_iterator i = conf.begin(); i != conf.end(); ++i) + insert(PropertyPair(i->first, i->second->clone())); +} + +Configuration::~Configuration() +{ + clear(); +} + + +std::vector +Configuration::getPropertyNames() +{ + std::vector v; + for (const_iterator i = begin(); i != end(); ++i) { + v.push_back(i->first.getName()); + } + std::sort(v.begin(), v.end()); + return v; +} + + +bool +Configuration::has(const PropertyName &name) const +{ + const_iterator i = find(name); + return (i != end()); +} + + +std::string +Configuration::toXmlString() +{ + using std::endl; + std::stringstream config; + + // This simple implementation just assumes everything's a string. + // Override it if you want something fancier (or reimplement it to + // support the whole gamut -- the reader in rosexmlhandler.cpp + // already can) + + for (const_iterator i = begin(); i != end(); ++i) { + config << "first.getName()) << "\" value=\"" + << encode(get(i->first)) << "\"/>" << endl; + } + +#if (__GNUC__ < 3) + config << endl << std::ends; +#else + config << endl; +#endif + + return config.str(); +} + +Configuration& +Configuration::operator=(const Configuration &conf) +{ + clear(); + + // Copy everything + // + for (const_iterator i = conf.begin(); i != conf.end(); ++i) + insert(PropertyPair(i->first, i->second->clone())); + + return (*this); +} + + + +namespace CompositionMetadataKeys +{ + const PropertyName Copyright = "copyright"; + const PropertyName Composer = "composer"; + const PropertyName Title = "title"; + const PropertyName Subtitle = "subtitle"; + const PropertyName Arranger = "arranger"; + // The following are recognized only by LilyPond output + const PropertyName Dedication = "dedication"; + const PropertyName Subsubtitle = "subsubtitle"; + const PropertyName Poet = "poet"; + const PropertyName Meter = "meter"; + const PropertyName Opus = "opus"; + const PropertyName Instrument = "instrument"; + const PropertyName Piece = "piece"; + const PropertyName Tagline = "tagline"; + + // The tab order of the edit fields in HeadersConfigurationPage + // is defined by the creation order of the edit fields. + // The edit fields are created in the order of the keys in getFixedKeys(). + std::vector getFixedKeys() { + std::vector keys; + keys.push_back(Dedication); + keys.push_back(Title); + keys.push_back(Subtitle); + keys.push_back(Subsubtitle); + keys.push_back(Poet); + keys.push_back(Instrument); + keys.push_back(Composer); + keys.push_back(Meter); + keys.push_back(Arranger); + keys.push_back(Piece); + keys.push_back(Opus); + keys.push_back(Copyright); + keys.push_back(Tagline); + + return keys; + } +} + + +// Keep these in lower case +const PropertyName DocumentConfiguration::SequencerOptions = "sequenceroptions"; +const PropertyName DocumentConfiguration::ZoomLevel = "zoomlevel"; +const PropertyName DocumentConfiguration::TransportMode = "transportmode"; + + +DocumentConfiguration::DocumentConfiguration() +{ + set(ZoomLevel, 0); + set(TransportMode, ""); // apparently generates an exception if not initialized +} + +DocumentConfiguration::DocumentConfiguration(const DocumentConfiguration &conf): + Configuration() +{ + for (const_iterator i = conf.begin(); i != conf.end(); ++i) + insert(PropertyPair(i->first, i->second->clone())); +} + +DocumentConfiguration::~DocumentConfiguration() +{ + clear(); +} + + +DocumentConfiguration& +DocumentConfiguration::operator=(const DocumentConfiguration &conf) +{ + clear(); + + for (const_iterator i = conf.begin(); i != conf.end(); ++i) + insert(PropertyPair(i->first, i->second->clone())); + + return *this; +} + + +// Convert to XML string for export +// +std::string +DocumentConfiguration::toXmlString() +{ + using std::endl; + + std::stringstream config; + + config << endl << "" << endl; + + config << " <" << ZoomLevel << " type=\"Int\">" << get(ZoomLevel) + << "\n"; + + config << " <" << TransportMode << " type=\"String\">" << get(TransportMode) + << "\n"; + + config << "" << endl; + +#if (__GNUC__ < 3) + config << endl << std::ends; +#else + config << endl; +#endif + + return config.str(); +} + +} + + diff --git a/src/base/Configuration.h b/src/base/Configuration.h new file mode 100644 index 0000000..23b3776 --- /dev/null +++ b/src/base/Configuration.h @@ -0,0 +1,211 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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. +*/ + + +// Class to hold extraenous bits of configuration which +// don't sit inside the Composition itself - sequencer +// and other general stuff that we want to keep separate. +// +// + +#include +#include + +#include "Instrument.h" +#include "RealTime.h" +#include "PropertyMap.h" +#include "Exception.h" +#include "XmlExportable.h" + +#ifndef _CONFIGURATION_H_ +#define _CONFIGURATION_H_ + +namespace Rosegarden +{ + +class Configuration : public PropertyMap, public XmlExportable +{ +public: + class NoData : public Exception { + public: + NoData(std::string property, std::string file, int line) : + Exception("No data found for property " + property, file, line) { } + }; + + class BadType : public Exception { + public: + BadType(std::string property, std::string expected, std::string actual, + std::string file, int line) : + Exception("Bad type for " + property + " (expected " + + expected + ", found " + actual + ")", file, line) { } + }; + + Configuration() {;} + Configuration(const Configuration &); + ~Configuration(); + + bool has(const PropertyName &name) const; + + template + void + set(const PropertyName &name, + typename PropertyDefn

::basic_type value); + + /** + * get() with a default value + */ + template + typename PropertyDefn

::basic_type + get(const PropertyName &name, + typename PropertyDefn

::basic_type defaultVal) const; + + /** + * regular get() + */ + template + typename PropertyDefn

::basic_type get(const PropertyName &name) const; + + // For exporting -- doesn't write the part of + // the element in case you want to write it into another element + // + virtual std::string toXmlString(); + + /// Return all the contained property names in alphabetical order + std::vector getPropertyNames(); + + // Assignment + // + Configuration& operator=(const Configuration &); + +private: + +}; + +namespace CompositionMetadataKeys +{ + extern const PropertyName Composer; + extern const PropertyName Arranger; + extern const PropertyName Copyright; + extern const PropertyName Title; + extern const PropertyName Subtitle; + // The following are recognized only by LilyPond output + extern const PropertyName Subsubtitle; + extern const PropertyName Dedication; + extern const PropertyName Poet; + extern const PropertyName Meter; + extern const PropertyName Opus; + extern const PropertyName Instrument; + extern const PropertyName Piece; + extern const PropertyName Tagline; + + + std::vector getFixedKeys(); +} + +class DocumentConfiguration : public Configuration +{ +public: + DocumentConfiguration(); + DocumentConfiguration(const DocumentConfiguration &); + ~DocumentConfiguration(); + + DocumentConfiguration& operator=(const DocumentConfiguration &); + + // for exporting -- doesn't write the part of + // the element in case you want to write it into another element + // + virtual std::string toXmlString(); + + // Property names + static const PropertyName SequencerOptions; + + static const PropertyName ZoomLevel; + + static const PropertyName TransportMode; +}; + + +template +void +Configuration::set(const PropertyName &name, + typename PropertyDefn

::basic_type value) +{ + iterator i = find(name); + + if (i != end()) { + + // A property with the same name has + // already been set - recycle it, just change the data + PropertyStoreBase *sb = i->second; + (static_cast *>(sb))->setData(value); + + } else { + + PropertyStoreBase *p = new PropertyStore

(value); + insert(PropertyPair(name, p)); + + } + +} + +template +typename PropertyDefn

::basic_type +Configuration::get(const PropertyName &name, + typename PropertyDefn

::basic_type defaultVal) const + +{ + const_iterator i = find(name); + + if (i == end()) return defaultVal; + + PropertyStoreBase *sb = i->second; + if (sb->getType() == P) { + return (static_cast *>(sb))->getData(); + } else { + throw BadType(name.getName(), + PropertyDefn

::typeName(), sb->getTypeName(), + __FILE__, __LINE__); + } +} + +template +typename PropertyDefn

::basic_type +Configuration::get(const PropertyName &name) const + +{ + const_iterator i = find(name); + + if (i == end()) throw NoData(name.getName(), __FILE__, __LINE__); + + PropertyStoreBase *sb = i->second; + if (sb->getType() == P) { + return (static_cast *>(sb))->getData(); + } else { + throw BadType(name.getName(), + PropertyDefn

::typeName(), sb->getTypeName(), + __FILE__, __LINE__); + } +} + + +} + +#endif // _AUDIODEVICE_H_ diff --git a/src/base/ControlParameter.cpp b/src/base/ControlParameter.cpp new file mode 100644 index 0000000..8606cf3 --- /dev/null +++ b/src/base/ControlParameter.cpp @@ -0,0 +1,144 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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. +*/ + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +#include "ControlParameter.h" +#include "MidiTypes.h" + +namespace Rosegarden +{ + +ControlParameter::ControlParameter(): + m_name(""), + m_type(Rosegarden::Controller::EventType), + m_description(""), + m_min(0), + m_max(127), + m_default(0), + m_controllerValue(0), + m_colourIndex(0), + m_ipbPosition(-1) // doesn't appear on IPB by default +{ +} + + +ControlParameter::ControlParameter(const std::string &name, + const std::string &type, + const std::string &description, + int min, + int max, + int def, + MidiByte controllerValue, + unsigned int colour, + int ipbPosition): + m_name(name), + m_type(type), + m_description(description), + m_min(min), + m_max(max), + m_default(def), + m_controllerValue(controllerValue), + m_colourIndex(colour), + m_ipbPosition(ipbPosition) +{ +} + + +ControlParameter::ControlParameter(const ControlParameter &control): + XmlExportable(), + m_name(control.getName()), + m_type(control.getType()), + m_description(control.getDescription()), + m_min(control.getMin()), + m_max(control.getMax()), + m_default(control.getDefault()), + m_controllerValue(control.getControllerValue()), + m_colourIndex(control.getColourIndex()), + m_ipbPosition(control.getIPBPosition()) +{ +} + +ControlParameter& +ControlParameter::operator=(const ControlParameter &control) +{ + m_name = control.getName(); + m_type = control.getType(); + m_description = control.getDescription(); + m_min = control.getMin(); + m_max = control.getMax(); + m_default = control.getDefault(); + m_controllerValue = control.getControllerValue(); + m_colourIndex = control.getColourIndex(); + m_ipbPosition = control.getIPBPosition(); + + return *this; +} + +bool ControlParameter::operator==(const ControlParameter &control) +{ + return m_type == control.getType() && + m_controllerValue == control.getControllerValue() && + m_min == control.getMin() && + m_max == control.getMax(); +} + +bool operator<(const ControlParameter &a, const ControlParameter &b) +{ + if (a.m_type != b.m_type) + return a.m_type < b.m_type; + else if (a.m_controllerValue != b.m_controllerValue) + return a.m_controllerValue < b.m_controllerValue; + else + return false; +} + + +std::string +ControlParameter::toXmlString() +{ + std::stringstream control; + + control << " " << endl << std::ends; +#else + control << "\"/>" << std::endl; +#endif + + return control.str(); +} + +} diff --git a/src/base/ControlParameter.h b/src/base/ControlParameter.h new file mode 100644 index 0000000..d9e487f --- /dev/null +++ b/src/base/ControlParameter.h @@ -0,0 +1,124 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _CONTROLPARAMETER_H_ +#define _CONTROLPARAMETER_H_ + +#include + +#include "XmlExportable.h" +#include "MidiProgram.h" + +namespace Rosegarden +{ + +class ControlParameter : public XmlExportable +{ +public: + ControlParameter(); + ControlParameter(const std::string &name, + const std::string &type, + const std::string &description, + int min = 0, + int max = 127, + int def = 0, + MidiByte controllerValue = 0, + unsigned int colour = 0, + int ipbPositon = -1); + ControlParameter(const ControlParameter &control); + ControlParameter& operator=(const ControlParameter &control); + bool operator==(const ControlParameter &control); + + friend bool operator<(const ControlParameter &a, const ControlParameter &b); + + // ControlParameter comparison on IPB position + // + struct ControlPositionCmp + { + bool operator()(ControlParameter *c1, + ControlParameter *c2) + { + return (c1->getIPBPosition() < c2->getIPBPosition()); + } + + bool operator()(const ControlParameter &c1, + const ControlParameter &c2) + { + return (c1.getIPBPosition() < c2.getIPBPosition()); + } + }; + + std::string getName() const { return m_name; } + std::string getType() const { return m_type; } + std::string getDescription() const { return m_description; } + + int getMin() const { return m_min; } + int getMax() const { return m_max; } + int getDefault() const { return m_default; } + + MidiByte getControllerValue() const { return m_controllerValue; } + + unsigned int getColourIndex() const { return m_colourIndex; } + + int getIPBPosition() const { return m_ipbPosition; } + + void setName(const std::string &name) { m_name = name; } + void setType(const std::string &type) { m_type = type; } + void setDescription(const std::string &des) { m_description = des; } + + void setMin(int min) { m_min = min; } + void setMax(int max) { m_max = max; } + void setDefault(int def) { m_default = def; } + + void setControllerValue(MidiByte con) { m_controllerValue = con; } + + void setColourIndex(unsigned int colour) { m_colourIndex = colour; } + + void setIPBPosition(int position) { m_ipbPosition = position; } + + virtual std::string toXmlString(); + +protected: + + // ControlParameter name as it's displayed ("Velocity", "Controller") + std::string m_name; + + // use event types in here ("controller", "pitchbend"); + std::string m_type; + + std::string m_description; + + int m_min; + int m_max; + int m_default; + + MidiByte m_controllerValue; + + unsigned int m_colourIndex; + + int m_ipbPosition; // position on Instrument Parameter Box + + +}; + +} + +#endif // _CONTROLPARAMETER_H_ diff --git a/src/base/Controllable.h b/src/base/Controllable.h new file mode 100644 index 0000000..9062d13 --- /dev/null +++ b/src/base/Controllable.h @@ -0,0 +1,48 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _CONTROLLABLE_DEVICE_H_ +#define _CONTROLLABLE_DEVICE_H_ + +#include "ControlParameter.h" + +namespace Rosegarden +{ + +typedef std::vector ControlList; + +class Controllable +{ +public: + virtual ~Controllable() {} + + virtual const ControlList &getControlParameters() const = 0; + virtual const ControlParameter *getControlParameter(int index) const = 0; + virtual const ControlParameter *getControlParameter(const std::string &type, + MidiByte controllerNumber) const = 0; + +protected: + Controllable() { } +}; + +} + +#endif diff --git a/src/base/Device.cpp b/src/base/Device.cpp new file mode 100644 index 0000000..796846a --- /dev/null +++ b/src/base/Device.cpp @@ -0,0 +1,31 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Device.h" + +namespace Rosegarden +{ + +const DeviceId Device::NO_DEVICE = 10000; +const DeviceId Device::ALL_DEVICES = 10001; +const DeviceId Device::CONTROL_DEVICE = 10002; + +} diff --git a/src/base/Device.h b/src/base/Device.h new file mode 100644 index 0000000..47a8ec0 --- /dev/null +++ b/src/base/Device.h @@ -0,0 +1,102 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _DEVICE_H_ +#define _DEVICE_H_ + +#include "XmlExportable.h" +#include "Instrument.h" +#include +#include + +// A Device can query underlying hardware/sound APIs to +// generate a list of Instruments. +// + +namespace Rosegarden +{ + +typedef unsigned int DeviceId; + +class Instrument; +typedef std::vector InstrumentList; + +class Device : public XmlExportable +{ +public: + typedef enum + { + Midi, + Audio, + SoftSynth + } DeviceType; + + // special device ids + static const DeviceId NO_DEVICE; + static const DeviceId ALL_DEVICES; + static const DeviceId CONTROL_DEVICE; + + Device(DeviceId id, const std::string &name, DeviceType type): + m_name(name), m_type(type), m_id(id) { } + + virtual ~Device() + { + InstrumentList::iterator it = m_instruments.begin(); + for (; it != m_instruments.end(); it++) + delete (*it); + m_instruments.erase(m_instruments.begin(), m_instruments.end()); + } + + void setType(DeviceType type) { m_type = type; } + DeviceType getType() const { return m_type; } + + void setName(const std::string &name) { m_name = name; } + std::string getName() const { return m_name; } + + void setId(DeviceId id) { m_id = id; } + DeviceId getId() const { return m_id; } + + // Accessing instrument lists - Devices should only + // show the world what they want it to see + // + virtual void addInstrument(Instrument*) = 0; + + // Two functions - one to return all Instruments on a + // Device - one to return all Instruments that a user + // is allowed to select (Presentation Instruments). + // + virtual InstrumentList getAllInstruments() const = 0; + virtual InstrumentList getPresentationInstruments() const = 0; + + std::string getConnection() const { return m_connection; } + void setConnection(std::string connection) { m_connection = connection; } + +protected: + InstrumentList m_instruments; + std::string m_name; + DeviceType m_type; + DeviceId m_id; + std::string m_connection; +}; + +} + +#endif // _DEVICE_H_ diff --git a/src/base/Equation.cpp b/src/base/Equation.cpp new file mode 100644 index 0000000..a97fca4 --- /dev/null +++ b/src/base/Equation.cpp @@ -0,0 +1,69 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Equation.h" + +namespace Rosegarden { + +void Equation::solve(Unknown u, double &y, double &m, double &x, double &c) +{ + switch(u) { + case Y: y = m*x + c; break; + case M: m = (y - c) / x; break; + case X: x = (y - c) / m; break; + case C: c = y - m*x; break; + } +} + +void Equation::solve(Unknown u, int &y, double &m, int &x, int &c) +{ + switch(u) { + case Y: y = static_cast(m*x) + c; break; + case M: m = static_cast(y - c) / static_cast(x); break; + case X: x = static_cast(static_cast(y - c) / m); break; + case C: c = y - static_cast(m*x); break; + } +} + +void Equation::solveForYByEndPoints(Point a, Point b, double x, double &y) +{ + double m, c, y1, x1; + + m = static_cast(b.y - a.y) / static_cast(b.x - a.x); + + x1 = a.x; y1 = a.y; + solve(C, y1, m, x1, c); + solve(Y, y, m, x, c); +} + +void Equation::solveForYByEndPoints(Point a, Point b, int x, int &y) +{ + double m; + int c; + + m = static_cast(b.y - a.y) / static_cast(b.x - a.x); + + solve(C, a.y, m, a.x, c); + solve(Y, y, m, x, c); +} + +} diff --git a/src/base/Equation.h b/src/base/Equation.h new file mode 100644 index 0000000..61377a5 --- /dev/null +++ b/src/base/Equation.h @@ -0,0 +1,51 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _EQUATION_H_ +#define _EQUATION_H_ + +namespace Rosegarden { + +/** + * Equation solving helper class + */ +class Equation +{ +public: + enum Unknown { Y, M, X, C }; + + struct Point { + Point(int xx, int yy) : x(xx), y(yy) { } + int x; + int y; + }; + + static void solve(Unknown u, double &y, double &m, double &x, double &c); + static void solve(Unknown u, int &y, double &m, int &x, int &c); + + static void solveForYByEndPoints(Point a, Point b, double x, double &y); + static void solveForYByEndPoints(Point a, Point b, int x, int &y); +}; + +} + +#endif diff --git a/src/base/Event.cpp b/src/base/Event.cpp new file mode 100644 index 0000000..e63e51b --- /dev/null +++ b/src/base/Event.cpp @@ -0,0 +1,445 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 +#include +#include +#include "Event.h" +#include "XmlExportable.h" + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +namespace Rosegarden +{ +using std::string; +using std::ostream; + +PropertyName Event::EventData::NotationTime = "!notationtime"; +PropertyName Event::EventData::NotationDuration = "!notationduration"; + + +Event::EventData::EventData(const std::string &type, timeT absoluteTime, + timeT duration, short subOrdering) : + m_refCount(1), + m_type(type), + m_absoluteTime(absoluteTime), + m_duration(duration), + m_subOrdering(subOrdering), + m_properties(0) +{ + // empty +} + +Event::EventData::EventData(const std::string &type, timeT absoluteTime, + timeT duration, short subOrdering, + const PropertyMap *properties) : + m_refCount(1), + m_type(type), + m_absoluteTime(absoluteTime), + m_duration(duration), + m_subOrdering(subOrdering), + m_properties(properties ? new PropertyMap(*properties) : 0) +{ + // empty +} + +Event::EventData *Event::EventData::unshare() +{ + --m_refCount; + + EventData *newData = new EventData + (m_type, m_absoluteTime, m_duration, m_subOrdering, m_properties); + + return newData; +} + +Event::EventData::~EventData() +{ + if (m_properties) delete m_properties; +} + +timeT +Event::EventData::getNotationTime() const +{ + if (!m_properties) return m_absoluteTime; + PropertyMap::const_iterator i = m_properties->find(NotationTime); + if (i == m_properties->end()) return m_absoluteTime; + else return static_cast *>(i->second)->getData(); +} + +timeT +Event::EventData::getNotationDuration() const +{ + if (!m_properties) return m_duration; + PropertyMap::const_iterator i = m_properties->find(NotationDuration); + if (i == m_properties->end()) return m_duration; + else return static_cast *>(i->second)->getData(); +} + +void +Event::EventData::setTime(const PropertyName &name, timeT t, timeT deft) +{ + if (!m_properties) m_properties = new PropertyMap(); + PropertyMap::iterator i = m_properties->find(name); + + if (t != deft) { + if (i == m_properties->end()) { + m_properties->insert(PropertyPair(name, new PropertyStore(t))); + } else { + static_cast *>(i->second)->setData(t); + } + } else if (i != m_properties->end()) { + delete i->second; + m_properties->erase(i); + } +} + +PropertyMap * +Event::find(const PropertyName &name, PropertyMap::iterator &i) +{ + PropertyMap *map = m_data->m_properties; + + if (!map || ((i = map->find(name)) == map->end())) { + + map = m_nonPersistentProperties; + if (!map) return 0; + + i = map->find(name); + if (i == map->end()) return 0; + } + + return map; +} + +bool +Event::has(const PropertyName &name) const +{ +#ifndef NDEBUG + ++m_hasCount; +#endif + + PropertyMap::const_iterator i; + const PropertyMap *map = find(name, i); + if (map) return true; + else return false; +} + +void +Event::unset(const PropertyName &name) +{ +#ifndef NDEBUG + ++m_unsetCount; +#endif + + unshare(); + PropertyMap::iterator i; + PropertyMap *map = find(name, i); + if (map) { + delete i->second; + map->erase(i); + } +} + + +PropertyType +Event::getPropertyType(const PropertyName &name) const + // throw (NoData) +{ + PropertyMap::const_iterator i; + const PropertyMap *map = find(name, i); + if (map) { + return i->second->getType(); + } else { + throw NoData(name.getName(), __FILE__, __LINE__); + } +} + + +string +Event::getPropertyTypeAsString(const PropertyName &name) const + // throw (NoData) +{ + PropertyMap::const_iterator i; + const PropertyMap *map = find(name, i); + if (map) { + return i->second->getTypeName(); + } else { + throw NoData(name.getName(), __FILE__, __LINE__); + } +} + + +string +Event::getAsString(const PropertyName &name) const + // throw (NoData) +{ + PropertyMap::const_iterator i; + const PropertyMap *map = find(name, i); + if (map) { + return i->second->unparse(); + } else { + throw NoData(name.getName(), __FILE__, __LINE__); + } +} + +// We could derive from XmlExportable and make this a virtual method +// overriding XmlExportable's pure virtual. We don't, because this +// class has no other virtual methods and for such a core class we +// could do without the overhead (given that it wouldn't really gain +// us anything anyway). + +string +Event::toXmlString() +{ + return toXmlString(0); +} + +string +Event::toXmlString(timeT expectedTime) +{ + std::stringstream out; + + out << ""; + + // Save all persistent properties as elements + + PropertyNames propertyNames(getPersistentPropertyNames()); + for (PropertyNames::const_iterator i = propertyNames.begin(); + i != propertyNames.end(); ++i) { + + out << "getName()) << "\" "; + string type = getPropertyTypeAsString(*i); + for (unsigned int j = 0; j < type.size(); ++j) { + type[j] = (isupper(type[j]) ? tolower(type[j]) : type[j]); + } + + out << type << "=\"" + << XmlExportable::encode(getAsString(*i)) + << "\"/>"; + } + + // Save non-persistent properties (the persistence applies to + // copying events, not load/save) as elements + // unless they're view-local. View-local properties are + // assumed to have "::" in their name somewhere. + + propertyNames = getNonPersistentPropertyNames(); + for (PropertyNames::const_iterator i = propertyNames.begin(); + i != propertyNames.end(); ++i) { + + std::string s(i->getName()); + if (s.find("::") != std::string::npos) continue; + + out << ""; + } + + out << ""; + +#if (__GNUC__ < 3) + out << std::ends; +#endif + + return out.str(); +} + + +#ifndef NDEBUG +void +Event::dump(ostream& out) const +{ + out << "Event type : " << m_data->m_type.c_str() << '\n'; + + out << "\tAbsolute Time : " << m_data->m_absoluteTime + << "\n\tDuration : " << m_data->m_duration + << "\n\tSub-ordering : " << m_data->m_subOrdering + << "\n\tPersistent properties : \n"; + + if (m_data->m_properties) { + for (PropertyMap::const_iterator i = m_data->m_properties->begin(); + i != m_data->m_properties->end(); ++i) { + out << "\t\t" << i->first.getName() << " [" << i->first.getValue() << "] \t" << *(i->second) << "\n"; + } + } + + if (m_nonPersistentProperties) { + out << "\n\tNon-persistent properties : \n"; + + for (PropertyMap::const_iterator i = m_nonPersistentProperties->begin(); + i != m_nonPersistentProperties->end(); ++i) { + out << "\t\t" << i->first.getName() << " [" << i->first.getValue() << "] \t" << *(i->second) << '\n'; + } + } + + out << "Event storage size : " << getStorageSize() << '\n'; +} + + +int Event::m_getCount = 0; +int Event::m_setCount = 0; +int Event::m_setMaybeCount = 0; +int Event::m_hasCount = 0; +int Event::m_unsetCount = 0; +clock_t Event::m_lastStats = clock(); + +void +Event::dumpStats(ostream& out) +{ + clock_t now = clock(); + int ms = (now - m_lastStats) * 1000 / CLOCKS_PER_SEC; + out << "\nEvent stats, since start of run or last report (" + << ms << "ms ago):" << std::endl; + + out << "Calls to get<>: " << m_getCount << std::endl; + out << "Calls to set<>: " << m_setCount << std::endl; + out << "Calls to setMaybe<>: " << m_setMaybeCount << std::endl; + out << "Calls to has: " << m_hasCount << std::endl; + out << "Calls to unset: " << m_unsetCount << std::endl; + + m_getCount = m_setCount = m_setMaybeCount = m_hasCount = m_unsetCount = 0; + m_lastStats = clock(); +} + +#else + +void +Event::dumpStats(ostream&) +{ + // nothing +} + +#endif + +Event::PropertyNames +Event::getPropertyNames() const +{ + PropertyNames v; + if (m_data->m_properties) { + for (PropertyMap::const_iterator i = m_data->m_properties->begin(); + i != m_data->m_properties->end(); ++i) { + v.push_back(i->first); + } + } + if (m_nonPersistentProperties) { + for (PropertyMap::const_iterator i = m_nonPersistentProperties->begin(); + i != m_nonPersistentProperties->end(); ++i) { + v.push_back(i->first); + } + } + return v; +} + +Event::PropertyNames +Event::getPersistentPropertyNames() const +{ + PropertyNames v; + if (m_data->m_properties) { + for (PropertyMap::const_iterator i = m_data->m_properties->begin(); + i != m_data->m_properties->end(); ++i) { + v.push_back(i->first); + } + } + return v; +} + +Event::PropertyNames +Event::getNonPersistentPropertyNames() const +{ + PropertyNames v; + if (m_nonPersistentProperties) { + for (PropertyMap::const_iterator i = m_nonPersistentProperties->begin(); + i != m_nonPersistentProperties->end(); ++i) { + v.push_back(i->first); + } + } + return v; +} + +void +Event::clearNonPersistentProperties() +{ + if (m_nonPersistentProperties) m_nonPersistentProperties->clear(); +} + +size_t +Event::getStorageSize() const +{ + size_t s = sizeof(Event) + sizeof(EventData) + m_data->m_type.size(); + if (m_data->m_properties) { + for (PropertyMap::const_iterator i = m_data->m_properties->begin(); + i != m_data->m_properties->end(); ++i) { + s += sizeof(i->first); + s += i->second->getStorageSize(); + } + } + if (m_nonPersistentProperties) { + for (PropertyMap::const_iterator i = m_nonPersistentProperties->begin(); + i != m_nonPersistentProperties->end(); ++i) { + s += sizeof(i->first); + s += i->second->getStorageSize(); + } + } + return s; +} + +bool +operator<(const Event &a, const Event &b) +{ + timeT at = a.getAbsoluteTime(); + timeT bt = b.getAbsoluteTime(); + if (at != bt) return at < bt; + else return a.getSubOrdering() < b.getSubOrdering(); +} + +} diff --git a/src/base/Event.h b/src/base/Event.h new file mode 100644 index 0000000..f236681 --- /dev/null +++ b/src/base/Event.h @@ -0,0 +1,584 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _EVENT_H_ +#define _EVENT_H_ + +#include "PropertyMap.h" +#include "Exception.h" + +#include +#include +#ifndef NDEBUG +#include +#include +#endif + + +namespace Rosegarden +{ + +typedef long timeT; + +/** + * The Event class represents an event with some basic attributes and + * an arbitrary number of properties of dynamically-determined name + * and type. + * + * An Event has a type; a duration, often zero for events other than + * notes; an absolute time, the time at which the event begins, which + * is used to order events within a Segment; and a "sub-ordering", used + * to determine an order for events that have the same absolute time + * (for example to ensure that the clef always appears before the key + * signature at the start of a piece). Besides these, an event can + * have any number of properties, which are typed values stored and + * retrieved by name. Properties may be persistent or non-persistent, + * depending on whether they are saved to file with the rest of the + * event data or are considered to be only cached values that can be + * recomputed at will if necessary. + */ + +class Event +{ +public: + class NoData : public Exception { + public: + NoData(std::string property) : + Exception("No data found for property " + property) { } + NoData(std::string property, std::string file, int line) : + Exception("No data found for property " + property, file, line) { } + }; + + class BadType : public Exception { + public: + BadType(std::string property, std::string expected, std::string actl) : + Exception("Bad type for " + property + " (expected " + + expected + ", found " + actl + ")") { } + BadType(std::string property, std::string expected, std::string actual, + std::string file, int line) : + Exception("Bad type for " + property + " (expected " + + expected + ", found " + actual + ")", file, line) { } + }; + + Event(const std::string &type, + timeT absoluteTime, timeT duration = 0, short subOrdering = 0) : + m_data(new EventData(type, absoluteTime, duration, subOrdering)), + m_nonPersistentProperties(0) { } + + Event(const std::string &type, + timeT absoluteTime, timeT duration, short subOrdering, + timeT notationAbsoluteTime, timeT notationDuration) : + m_data(new EventData(type, absoluteTime, duration, subOrdering)), + m_nonPersistentProperties(0) { + setNotationAbsoluteTime(notationAbsoluteTime); + setNotationDuration(notationDuration); + } + + Event(const Event &e) : + m_nonPersistentProperties(0) { share(e); } + + // these ctors can't use default args: default has to be obtained from e + + Event(const Event &e, timeT absoluteTime) : + m_nonPersistentProperties(0) { + share(e); + unshare(); + m_data->m_absoluteTime = absoluteTime; + setNotationAbsoluteTime(absoluteTime); + setNotationDuration(m_data->m_duration); + } + + Event(const Event &e, timeT absoluteTime, timeT duration) : + m_nonPersistentProperties(0) { + share(e); + unshare(); + m_data->m_absoluteTime = absoluteTime; + m_data->m_duration = duration; + setNotationAbsoluteTime(absoluteTime); + setNotationDuration(duration); + } + + Event(const Event &e, timeT absoluteTime, timeT duration, short subOrdering): + m_nonPersistentProperties(0) { + share(e); + unshare(); + m_data->m_absoluteTime = absoluteTime; + m_data->m_duration = duration; + m_data->m_subOrdering = subOrdering; + setNotationAbsoluteTime(absoluteTime); + setNotationDuration(duration); + } + + Event(const Event &e, timeT absoluteTime, timeT duration, short subOrdering, + timeT notationAbsoluteTime) : + m_nonPersistentProperties(0) { + share(e); + unshare(); + m_data->m_absoluteTime = absoluteTime; + m_data->m_duration = duration; + m_data->m_subOrdering = subOrdering; + setNotationAbsoluteTime(notationAbsoluteTime); + setNotationDuration(duration); + } + + Event(const Event &e, timeT absoluteTime, timeT duration, short subOrdering, + timeT notationAbsoluteTime, timeT notationDuration) : + m_nonPersistentProperties(0) { + share(e); + unshare(); + m_data->m_absoluteTime = absoluteTime; + m_data->m_duration = duration; + m_data->m_subOrdering = subOrdering; + setNotationAbsoluteTime(notationAbsoluteTime); + setNotationDuration(notationDuration); + } + + ~Event() { lose(); } + + Event *copyMoving(timeT offset) const { + return new Event(*this, + m_data->m_absoluteTime + offset, + m_data->m_duration, + m_data->m_subOrdering, + getNotationAbsoluteTime() + offset, + getNotationDuration()); + } + + Event &operator=(const Event &e) { + if (&e != this) { lose(); share(e); } + return *this; + } + + friend bool operator<(const Event&, const Event&); + + // Accessors + const std::string &getType() const { return m_data->m_type; } + bool isa(const std::string &t) const { return (m_data->m_type == t); } + timeT getAbsoluteTime() const { return m_data->m_absoluteTime; } + timeT getDuration() const { return m_data->m_duration; } + short getSubOrdering() const { return m_data->m_subOrdering; } + + bool has(const PropertyName &name) const; + + template + typename PropertyDefn

::basic_type get(const PropertyName &name) const; + // throw (NoData, BadType); + + // no throw, returns bool + template + bool get(const PropertyName &name, typename PropertyDefn

::basic_type &val) const; + + template + bool isPersistent(const PropertyName &name) const; + // throw (NoData); + + template + void setPersistence(const PropertyName &name, bool persistent); + // throw (NoData); + + PropertyType getPropertyType(const PropertyName &name) const; + // throw (NoData); + + std::string getPropertyTypeAsString(const PropertyName &name) const; + // throw (NoData); + + std::string getAsString(const PropertyName &name) const; + // throw (NoData); + + template + void set(const PropertyName &name, typename PropertyDefn

::basic_type value, + bool persistent = true); + // throw (BadType); + + // set non-persistent, but only if there's no persistent value already + template + void setMaybe(const PropertyName &name, typename PropertyDefn

::basic_type value); + // throw (BadType); + + template + void setFromString(const PropertyName &name, std::string value, + bool persistent = true); + // throw (BadType); + + void unset(const PropertyName &name); + + timeT getNotationAbsoluteTime() const { return m_data->getNotationTime(); } + timeT getNotationDuration() const { return m_data->getNotationDuration(); } + + typedef std::vector PropertyNames; + PropertyNames getPropertyNames() const; + PropertyNames getPersistentPropertyNames() const; + PropertyNames getNonPersistentPropertyNames() const; + + void clearNonPersistentProperties(); + + struct EventCmp + { + bool operator()(const Event &e1, const Event &e2) const { + return e1 < e2; + } + bool operator()(const Event *e1, const Event *e2) const { + return *e1 < *e2; + } + }; + + struct EventEndCmp + { + bool operator()(const Event &e1, const Event &e2) const { + return e1.getAbsoluteTime() + e1.getDuration() <= + e2.getAbsoluteTime() + e2.getDuration(); + } + bool operator()(const Event *e1, const Event *e2) const { + return e1->getAbsoluteTime() + e1->getDuration() <= + e2->getAbsoluteTime() + e2->getDuration(); + } + }; + + static bool compareEvent2Time(const Event *e, timeT t) { + return e->getAbsoluteTime() < t; + } + + static bool compareTime2Event(timeT t, const Event *e) { + return t < e->getAbsoluteTime(); + } + + // approximate, for debugging and inspection purposes + size_t getStorageSize() const; + + /** + * Get the XML string representing the object. + */ + std::string toXmlString(); + + /** + * Get the XML string representing the object. If the absolute + * time of the event differs from the given absolute time, include + * the difference between the two as a timeOffset attribute. + * If expectedTime == 0, include an absoluteTime attribute instead. + */ + std::string toXmlString(timeT expectedTime); + +#ifndef NDEBUG + void dump(std::ostream&) const; +#else + void dump(std::ostream&) const {} +#endif + static void dumpStats(std::ostream&); + +protected: + // these are for subclasses such as XmlStorableEvent + + Event() : + m_data(new EventData("", 0, 0, 0)), + m_nonPersistentProperties(0) { } + + void setType(const std::string &t) { unshare(); m_data->m_type = t; } + void setAbsoluteTime(timeT t) { unshare(); m_data->m_absoluteTime = t; } + void setDuration(timeT d) { unshare(); m_data->m_duration = d; } + void setSubOrdering(short o) { unshare(); m_data->m_subOrdering = o; } + void setNotationAbsoluteTime(timeT t) { unshare(); m_data->setNotationTime(t); } + void setNotationDuration(timeT d) { unshare(); m_data->setNotationDuration(d); } + +private: + bool operator==(const Event &); // not implemented + + struct EventData // Data that are shared between shallow-copied instances + { + EventData(const std::string &type, + timeT absoluteTime, timeT duration, short subOrdering); + EventData(const std::string &type, + timeT absoluteTime, timeT duration, short subOrdering, + const PropertyMap *properties); + EventData *unshare(); + ~EventData(); + unsigned int m_refCount; + + std::string m_type; + timeT m_absoluteTime; + timeT m_duration; + short m_subOrdering; + + PropertyMap *m_properties; + + // These are properties because we don't care so much about + // raw speed in get/set, but we do care about storage size for + // events that don't have them or that have zero values: + timeT getNotationTime() const; + timeT getNotationDuration() const; + void setNotationTime(timeT t) { + setTime(NotationTime, t, m_absoluteTime); + } + void setNotationDuration(timeT d) { + setTime(NotationDuration, d, m_duration); + } + + private: + EventData(const EventData &); + EventData &operator=(const EventData &); + static PropertyName NotationTime; + static PropertyName NotationDuration; + void setTime(const PropertyName &name, timeT value, timeT deft); + }; + + EventData *m_data; + PropertyMap *m_nonPersistentProperties; // Unique to an instance + + void share(const Event &e) { + m_data = e.m_data; + m_data->m_refCount++; + } + + bool unshare() { // returns true if unshare was necessary + if (m_data->m_refCount > 1) { + m_data = m_data->unshare(); + return true; + } else { + return false; + } + } + + void lose() { + if (--m_data->m_refCount == 0) delete m_data; + delete m_nonPersistentProperties; + m_nonPersistentProperties = 0; + } + + // returned iterator (in i) only valid if return map value is non-zero + PropertyMap *find(const PropertyName &name, PropertyMap::iterator &i); + + const PropertyMap *find(const PropertyName &name, + PropertyMap::const_iterator &i) const { + PropertyMap::iterator j; + PropertyMap *map = const_cast(this)->find(name, j); + i = j; + return map; + } + + PropertyMap::iterator insert(const PropertyPair &pair, bool persistent) { + PropertyMap **map = + (persistent ? &m_data->m_properties : &m_nonPersistentProperties); + if (!*map) *map = new PropertyMap(); + return (*map)->insert(pair).first; + } + +#ifndef NDEBUG + static int m_getCount; + static int m_setCount; + static int m_setMaybeCount; + static int m_hasCount; + static int m_unsetCount; + static clock_t m_lastStats; +#endif +}; + + +template +bool +Event::get(const PropertyName &name, typename PropertyDefn

::basic_type &val) const +{ +#ifndef NDEBUG + ++m_getCount; +#endif + + PropertyMap::const_iterator i; + const PropertyMap *map = find(name, i); + + if (map) { + + PropertyStoreBase *sb = i->second; + if (sb->getType() == P) { + val = (static_cast *>(sb))->getData(); + return true; + } + else { +#ifndef NDEBUG + std::cerr << "Event::get() Error: Attempt to get property \"" << name + << "\" as " << PropertyDefn

::typeName() <<", actual type is " + << sb->getTypeName() << std::endl; +#endif + return false; + } + + } else { + return false; + } +} + + +template +typename PropertyDefn

::basic_type +Event::get(const PropertyName &name) const + // throw (NoData, BadType) +{ +#ifndef NDEBUG + ++m_getCount; +#endif + + PropertyMap::const_iterator i; + const PropertyMap *map = find(name, i); + + if (map) { + + PropertyStoreBase *sb = i->second; + if (sb->getType() == P) + return (static_cast *>(sb))->getData(); + else { + throw BadType(name.getName(), + PropertyDefn

::typeName(), sb->getTypeName(), + __FILE__, __LINE__); + } + + } else { + +#ifndef NDEBUG + std::cerr << "Event::get(): Error dump follows:" << std::endl; + dump(std::cerr); +#endif + throw NoData(name.getName(), __FILE__, __LINE__); + } +} + + +template +bool +Event::isPersistent(const PropertyName &name) const + // throw (NoData) +{ + PropertyMap::const_iterator i; + const PropertyMap *map = find(name, i); + + if (map) { + return (map == m_data->m_properties); + } else { + throw NoData(name.getName(), __FILE__, __LINE__); + } +} + + +template +void +Event::setPersistence(const PropertyName &name, bool persistent) + // throw (NoData) +{ + unshare(); + PropertyMap::iterator i; + PropertyMap *map = find(name, i); + + if (map) { + insert(*i, persistent); + map->erase(i); + } else { + throw NoData(name.getName(), __FILE__, __LINE__); + } +} + + +template +void +Event::set(const PropertyName &name, typename PropertyDefn

::basic_type value, + bool persistent) + // throw (BadType) +{ +#ifndef NDEBUG + ++m_setCount; +#endif + + // this is a little slow, could bear improvement + + unshare(); + PropertyMap::iterator i; + PropertyMap *map = find(name, i); + + if (map) { + bool persistentBefore = (map == m_data->m_properties); + if (persistentBefore != persistent) { + i = insert(*i, persistent); + map->erase(name); + } + + PropertyStoreBase *sb = i->second; + if (sb->getType() == P) { + (static_cast *>(sb))->setData(value); + } else { + throw BadType(name.getName(), + PropertyDefn

::typeName(), sb->getTypeName(), + __FILE__, __LINE__); + } + + } else { + PropertyStoreBase *p = new PropertyStore

(value); + insert(PropertyPair(name, p), persistent); + } +} + + + +// setMaybe<> is actually called rather more frequently than set<>, so +// it makes sense for best performance to implement it separately +// rather than through calls to has, isPersistent and set<> + +template +void +Event::setMaybe(const PropertyName &name, typename PropertyDefn

::basic_type value) + // throw (BadType) +{ +#ifndef NDEBUG + ++m_setMaybeCount; +#endif + + unshare(); + PropertyMap::iterator i; + PropertyMap *map = find(name, i); + + if (map) { + if (map == m_data->m_properties) return; // persistent, so ignore it + + PropertyStoreBase *sb = i->second; + + if (sb->getType() == P) { + (static_cast *>(sb))->setData(value); + } else { + throw BadType(name.getName(), + PropertyDefn

::typeName(), sb->getTypeName(), + __FILE__, __LINE__); + } + } else { + PropertyStoreBase *p = new PropertyStore

(value); + insert(PropertyPair(name, p), false); + } +} + + +template +void +Event::setFromString(const PropertyName &name, std::string value, bool persistent) + // throw (BadType) +{ + set

(name, PropertyDefn

::parse(value), persistent); +} + + +////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/base/Exception.cpp b/src/base/Exception.cpp new file mode 100644 index 0000000..04dad69 --- /dev/null +++ b/src/base/Exception.cpp @@ -0,0 +1,46 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Exception.h" + +#include + +namespace Rosegarden { + +Exception::Exception(std::string message) : + m_message(message) +{ +#ifndef NDEBUG + std::cerr << "WARNING: Rosegarden::Exception: \"" + << message << "\"" << std::endl; +#endif +} + +Exception::Exception(std::string message, std::string file, int line) : + m_message(message) +{ +#ifndef NDEBUG + std::cerr << "WARNING: Rosegarden::Exception: \"" + << message << "\" at " << file << ":" << line << std::endl; +#endif +} + +} diff --git a/src/base/Exception.h b/src/base/Exception.h new file mode 100644 index 0000000..ba39a0f --- /dev/null +++ b/src/base/Exception.h @@ -0,0 +1,47 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _EXCEPTION_H_ +#define _EXCEPTION_H_ + +#include +#include + +namespace Rosegarden { + +class Exception : public virtual std::exception +{ +public: + Exception(std::string message); + Exception(std::string message, std::string file, int line); + + virtual ~Exception() throw () {} + + std::string getMessage() const { return m_message; } + +private: + std::string m_message; +}; + + +} + +#endif diff --git a/src/base/FastVector.h b/src/base/FastVector.h new file mode 100644 index 0000000..0ba8e82 --- /dev/null +++ b/src/base/FastVector.h @@ -0,0 +1,596 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _FAST_VECTOR_H_ +#define _FAST_VECTOR_H_ + +#include +#include /* for malloc, realloc, free */ +#include /* for memmove */ + +#include + + +/** + FastVector is a sequence class with an interface similar to that + of the STL vector, with several nice properties and one nasty one: + + * It allows fast random access, like the STL vector -- although + access is not quite as fast, as a little arithmetic is required. + + * Appending (push_back) and prepending (push_front) are both fast. + + * The worst-case behaviour is repeated random inserts and deletes + of single items, and performance in this case is still as good + as vector where builtin types are stored, and much better where + deep-copied objects are stored. + + * Performance is not as good as vector for very short sequences + (where vector's simple implementation excels), but it's not bad. + + * BUT: To achieve all this, it cheats. Objects are moved around + from place to place in the vector using memmove(), rather than + deep copy. If you store objects with internal pointers, they + will break badly. Storing simple structures will be no problem, + and if you just store pointers to objects you'll be fine, but + it's unwise (for example) to store other containers. + + * One other difference from the STL vector: It uses placement new + with the copy constructor to construct objects, rather than + the default constructor and assignment. Thus the copy + constructor must work on the stored objects, though assignment + doesn't have to. + + Do not use this class if: + + * You do not require random access (operator[]). Use the STL + linked list instead, it'll almost certainly be faster. + + * Your sequence is constructed once at a non-time-critical + moment, and subsequently is only read. Use STL vector, as + it's more standard and lookup is slightly quicker. + + * Your sequence is unlikely to contain more than a dozen objects + which are only appended (push_back) and you do not require + prepend (push_front). Use STL vector, as it's more standard, + simpler and often quicker in this case. + + * You want to pass sequences to other libraries or return them + from library functions. Use a standard container instead. + + * You want to store objects that contain internal pointers or + that do not have a working copy constructor. + + Chris Cannam, 1996-2001 +*/ + +template +class FastVector +{ +public: + typedef T value_type; + typedef long size_type; + typedef long difference_type; + +private: + class iterator_base : public + +#if defined(_STL_1997_) || (__GNUC__ > 2) + std::iterator +#else +#if defined(__STL_USE_NAMESPACES) + std:: +#endif + random_access_iterator +#endif + { + public: + iterator_base() : + m_v(0), m_i(-1) { + } + iterator_base(const iterator_base &i) : + m_v(i.m_v), m_i(i.m_i) { + } + iterator_base &operator=(const iterator_base &i) { + if (&i != this) { m_v = i.m_v; m_i = i.m_i; } + return *this; + } + + iterator_base &operator--() { --m_i; return *this; } + iterator_base operator--(int) { + iterator_base i(*this); + --m_i; + return i; + } + iterator_base &operator++() { ++m_i; return *this; } + iterator_base operator++(int) { + iterator_base i(*this); + ++m_i; + return i; + } + + bool operator==(const iterator_base &i) const { + return (m_v == i.m_v && m_i == i.m_i); + } + + bool operator!=(const iterator_base &i) const { + return (m_v != i.m_v || m_i != i.m_i); + } + + iterator_base &operator+=(FastVector::difference_type i) { + m_i += i; return *this; + } + iterator_base &operator-=(FastVector::difference_type i) { + m_i -= i; return *this; + } + + iterator_base operator+(FastVector::difference_type i) const { + iterator_base n(*this); n += i; return n; + } + iterator_base operator-(FastVector::difference_type i) const { + iterator_base n(*this); n -= i; return n; + } + + typename FastVector::difference_type operator-(const iterator_base &i) const{ + assert(m_v == i.m_v); + return m_i - i.m_i; + } + + protected: + iterator_base(FastVector *v, size_type i) : m_v(v), m_i(i) { } + FastVector *m_v; + size_type m_i; + }; + +public: + // I'm sure these can be simplified + + class iterator : public + iterator_base + { + public: + iterator() : iterator_base() { } + iterator(const iterator_base &i) : iterator_base(i) { } + iterator &operator=(const iterator &i) { + iterator_base::operator=(i); + return *this; + } + + T &operator*() { return iterator_base::m_v->at(iterator_base::m_i); } + T *operator->() { return &(operator*()); } + + const T &operator*() const { return iterator_base::m_v->at(iterator_base::m_i); } + const T *operator->() const { return &(operator*()); } + + protected: + friend class FastVector; + iterator(FastVector *v, size_type i) : iterator_base(v,i) { } + }; + + class reverse_iterator : public + iterator_base + { + public: + reverse_iterator() : iterator_base() { } + reverse_iterator(const iterator_base &i) : iterator_base(i) { } + reverse_iterator &operator=(const reverse_iterator &i) { + iterator_base::operator=(i); + return *this; + } + + T &operator*() { return iterator_base::m_v->at(iterator_base::m_v->size() - iterator_base::m_i - 1); } + T *operator->() { return &(operator*()); } + + const T &operator*() const { return iterator_base::m_v->at(iterator_base::m_v->size() - iterator_base::m_i - 1); } + const T *operator->() const { return &(operator*()); } + + protected: + friend class FastVector; + reverse_iterator(FastVector *v, size_type i) : iterator_base(v,i) { } + }; + + class const_iterator : public + iterator_base + { + public: + const_iterator() : iterator_base() { } + const_iterator(const iterator_base &i) : iterator_base(i) { } + const_iterator &operator=(const const_iterator &i) { + iterator_base::operator=(i); + return *this; + } + + const T &operator*() const { return iterator_base::m_v->at(iterator_base::m_i); } + const T *operator->() const { return &(operator*()); } + + protected: + friend class FastVector; + const_iterator(const FastVector *v, size_type i) : + iterator_base(const_cast *>(v),i) { } + }; + + class const_reverse_iterator : public + iterator_base + { + public: + const_reverse_iterator() : iterator_base() { } + const_reverse_iterator(const iterator_base &i) : iterator_base(i) { } + const_reverse_iterator &operator=(const const_reverse_iterator &i) { + iterator_base::operator=(i); + return *this; + } + + const T &operator*() const { return iterator_base::m_v->at(iterator_base::m_v->size() - iterator_base::m_i - 1); } + const T *operator->() const { return &(operator*()); } + + protected: + friend class FastVector; + const_reverse_iterator(const FastVector *v, size_type i) : + iterator_base(const_cast *>(v),i) { } + }; + +public: + FastVector() : + m_items(0), m_count(0), m_gapStart(-1), + m_gapLength(0), m_size(0) { } + FastVector(const FastVector &); + virtual ~FastVector(); + + template + FastVector(InputIterator first, InputIterator last) : + m_items(0), m_count(0), m_gapStart(-1), + m_gapLength(0), m_size(0) { + insert(begin(), first, last); + } + + FastVector &operator=(const FastVector &); + + virtual iterator begin() { return iterator(this, 0); } + virtual iterator end() { return iterator(this, m_count); } + + virtual const_iterator begin() const { return const_iterator(this, 0); } + virtual const_iterator end() const { return const_iterator(this, m_count); } + + virtual reverse_iterator rbegin() { return reverse_iterator(this, 0); } + virtual reverse_iterator rend() { return reverse_iterator(this, m_count); } + + virtual const_reverse_iterator rbegin() const { return const_reverse_iterator(this, 0); } + virtual const_reverse_iterator rend() const { return const_reverse_iterator(this, m_count); } + + size_type size() const { return m_count; } + bool empty() const { return m_count == 0; } + + /// not all of these are defined yet + void swap(FastVector &v); + bool operator==(const FastVector &) const; + bool operator!=(const FastVector &v) const { return !operator==(v); } + bool operator<(const FastVector &) const; + bool operator>(const FastVector &) const; + bool operator<=(const FastVector &) const; + bool operator>=(const FastVector &) const; + + T& at(size_type index) { + assert(index >= 0 && index < m_count); + return m_items[externalToInternal(index)]; + } + const T& at(size_type index) const { + return (const_cast *>(this))->at(index); + } + + T &operator[](size_type index) { + return at(index); + } + const T &operator[](size_type index) const { + return at(index); + } + + virtual T* array(size_type index, size_type count); + + /** We *guarantee* that push methods etc modify the FastVector + only through a call to insert(size_type, T), and that erase + etc modify it only through a call to remove(size_type). This + is important because subclasses only need to override those + functions to catch all mutations */ + virtual void push_front(const T& item) { insert(0, item); } + virtual void push_back(const T& item) { insert(m_count, item); } + + virtual iterator insert(const iterator &p, const T &t) { + insert(p.m_i, t); + return p; + } + + template + iterator insert(const iterator &p, InputIterator &i, InputIterator &j); + + virtual iterator erase(const iterator &i) { + assert(i.m_v == this); + remove(i.m_i); + return iterator(this, i.m_i); + } + + virtual iterator erase(const iterator &i, const iterator &j); + virtual void clear(); + +protected: + /// basic insert -- all others call this + virtual void insert(size_type index, const T&); + + /// basic remove -- erase(), clear() call this + virtual void remove(size_type index); + +private: + void resize(size_type needed); // needed is internal (i.e. including gap) + + void moveGapTo(size_type index); // index is external + void closeGap() { + if (m_gapStart >= 0) moveGapTo(m_count); + m_gapStart = -1; + } + + size_type bestNewCount(size_type n, size_t) const { + if (m_size == 0) { + if (n < 8) return 8; + else return n; + } else { + // double up each time -- it's faster than just incrementing + size_type s(m_size); + if (s > n*2) return s/2; + while (s <= n) s *= 2; + return s; + } + } + + size_type externalToInternal(size_type index) const { + return ((index < m_gapStart || m_gapStart < 0) ? + index : index + m_gapLength); + } + + size_type minSize() const { return 8; } + size_t minBlock() const { + return minSize() * sizeof(T) > 64 ? minSize() * sizeof(T) : 64; + } + + T* m_items; + size_type m_count; // not counting gap + size_type m_gapStart; // -1 for no gap + size_type m_gapLength; // undefined if no gap + size_type m_size; +}; + + +template +void *operator new(size_t, FastVector *, void *space) +{ + return space; +} + +template +FastVector::FastVector(const FastVector &l) : + m_items(0), m_count(0), m_gapStart(-1), + m_gapLength(0), m_size(0) +{ + resize(l.size()); + for (size_type i = 0; i < l.size(); ++i) push_back(l.at(i)); +} + +template +FastVector::~FastVector() +{ + clear(); + free(static_cast(m_items)); +} + +template +FastVector& FastVector::operator=(const FastVector& l) +{ + if (&l == this) return *this; + + clear(); + + if (l.size() >= m_size) resize(l.size()); + for (size_type i = 0; i < l.size(); ++i) push_back(l.at(i)); + + return *this; +} + +template +void FastVector::moveGapTo(size_type index) +{ + // shift some elements left or right so as to line the gap up with + // the prospective insertion or deletion point. + + assert(m_gapStart >= 0); + + if (m_gapStart < index) { + // need to move some stuff left to fill the gap + memmove(&m_items[m_gapStart], + &m_items[m_gapStart + m_gapLength], + (index - m_gapStart) * sizeof(T)); + + } else if (m_gapStart > index) { + // need to move some stuff right to fill the gap + memmove(&m_items[index + m_gapLength], &m_items[index], + (m_gapStart - index) * sizeof(T)); + } + + m_gapStart = index; +} + +template +void FastVector::resize(size_type needed) +{ + size_type newSize = bestNewCount(needed, sizeof(T)); + + if (m_items) { + m_items = static_cast(realloc(m_items, newSize * sizeof(T))); + } else { + m_items = static_cast(malloc(newSize * sizeof(T))); + } + + m_size = newSize; +} + +template +void FastVector::remove(size_type index) +{ + assert(index >= 0 && index < m_count); + + if (index == m_count - 1) { + // shorten the list without disturbing an existing gap, unless + // the item we're taking was the only one after the gap + m_items[externalToInternal(index)].T::~T(); + if (m_gapStart == index) m_gapStart = -1; + } else { + if (m_gapStart >= 0) { + // moveGapTo shifts the gap around ready for insertion. + // It actually moves the indexed object out of the way, so + // that it's now at the end of the gap. We have to cope. + moveGapTo(index); + m_items[m_gapStart + m_gapLength].T::~T(); + ++m_gapLength; + } else { // no gap, make one + m_gapStart = index; + m_items[m_gapStart].T::~T(); + m_gapLength = 1; + } + } + + if (--m_count == 0) m_gapStart = -1; + if (m_count < m_size/3 && m_size > minSize()) { + closeGap(); + resize(m_count); // recover some memory + } +} + +template +void FastVector::insert(size_type index, const T&t) +{ + assert(index >= 0 && index <= m_count); + + if (index == m_count) { + + // Appending. No need to disturb the gap, if there is one -- + // we'd rather waste a bit of memory than bother closing it up + + if (externalToInternal(m_count) >= m_size || !m_items) { + resize(m_size + 1); + } + + new (this, &m_items[externalToInternal(index)]) T(t); + + } else if (m_gapStart < 0) { + + // Inserting somewhere, when there's no gap we can use. + + if (m_count >= m_size) resize(m_size + 1); + + // I think it's going to be more common to insert elements + // at the same point repeatedly than at random points. + // So, if we can make a gap here ready for more insertions + // *without* exceeding the m_size limit (i.e. if we've got + // slack left over from a previous gap), then let's. But + // not too much -- ideally we'd like some space left for + // appending. Say half. + + if (m_count < m_size-2) { + m_gapStart = index+1; + m_gapLength = (m_size - m_count) / 2; + memmove(&m_items[m_gapStart + m_gapLength], &m_items[index], + (m_count - index) * sizeof(T)); + } else { + memmove(&m_items[index + 1], &m_items[index], + (m_count - index) * sizeof(T)); + } + + new (this, &m_items[index]) T(t); + + } else { + + // There's already a gap, all we have to do is move it (with + // no need to resize) + + if (index != m_gapStart) moveGapTo(index); + new (this, &m_items[m_gapStart]) T(t); + if (--m_gapLength == 0) m_gapStart = -1; + else ++m_gapStart; + } + + ++m_count; +} + +template +template +typename FastVector::iterator FastVector::insert +(const FastVector::iterator &p, InputIterator &i, InputIterator &j) +{ + size_type n = p.m_i; + while (i != j) { + --j; + insert(n, *j); + } + return begin() + n; +} + +template +typename FastVector::iterator FastVector::erase +(const FastVector::iterator &i, const FastVector::iterator &j) +{ + assert(i.m_v == this && j.m_v == this && j.m_i >= i.m_i); + for (size_type k = i.m_i; k < j.m_i; ++k) remove(i.m_i); + return iterator(this, i.m_i); +} + +template +void FastVector::clear() +{ + // Use erase(), which uses remove() -- a subclass that overrides + // remove() will not want to have to provide this method as well + erase(begin(), end()); +} + +template +T* FastVector::array(size_type index, size_type count) +{ + assert(index >= 0 && count > 0 && index + count <= m_count); + + if (m_gapStart < 0 || index + count <= m_gapStart) { + return m_items + index; + } else if (index >= m_gapStart) { + return m_items + index + m_gapLength; + } else { + closeGap(); + return m_items + index; + } +} + +template +bool FastVector::operator==(const FastVector &v) const +{ + if (size() != v.size()) return false; + for (size_type i = 0; i < m_count; ++i) { + if (at(i) != v.at(i)) return false; + } + return true; +} + +#endif + + diff --git a/src/base/Instrument.cpp b/src/base/Instrument.cpp new file mode 100644 index 0000000..add1767 --- /dev/null +++ b/src/base/Instrument.cpp @@ -0,0 +1,645 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 + +#include "Instrument.h" +#include "MidiDevice.h" +#include "AudioPluginInstance.h" +#include "AudioLevel.h" + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + + +namespace Rosegarden +{ + +const unsigned int PluginContainer::PLUGIN_COUNT = 5; + +PluginContainer::PluginContainer(bool havePlugins) +{ + if (havePlugins) { + // Add a number of plugin place holders (unassigned) + for (unsigned int i = 0; i < PLUGIN_COUNT; i++) + addPlugin(new AudioPluginInstance(i)); + } +} + +PluginContainer::~PluginContainer() +{ + clearPlugins(); +} + +void +PluginContainer::addPlugin(AudioPluginInstance *instance) +{ + m_audioPlugins.push_back(instance); +} + +bool +PluginContainer::removePlugin(unsigned int position) +{ + PluginInstanceIterator it = m_audioPlugins.begin(); + + for (; it != m_audioPlugins.end(); it++) + { + if ((*it)->getPosition() == position) + { + delete (*it); + m_audioPlugins.erase(it); + return true; + } + + } + + return false; +} + +void +PluginContainer::clearPlugins() +{ + PluginInstanceIterator it = m_audioPlugins.begin(); + for (; it != m_audioPlugins.end(); it++) + delete (*it); + + m_audioPlugins.erase(m_audioPlugins.begin(), m_audioPlugins.end()); +} + +void +PluginContainer::emptyPlugins() +{ + PluginInstanceIterator it = m_audioPlugins.begin(); + for (; it != m_audioPlugins.end(); it++) + { + (*it)->setAssigned(false); + (*it)->setBypass(false); + (*it)->clearPorts(); + } +} + + +// Get an instance for an index +// +AudioPluginInstance* +PluginContainer::getPlugin(unsigned int position) +{ + PluginInstanceIterator it = m_audioPlugins.begin(); + for (; it != m_audioPlugins.end(); it++) + { + if ((*it)->getPosition() == position) + return *it; + } + + return 0; +} + + +const unsigned int Instrument::SYNTH_PLUGIN_POSITION = 999; + + +Instrument::Instrument(InstrumentId id, + InstrumentType it, + const std::string &name, + Device *device): + PluginContainer(it == Audio || it == SoftSynth), + m_id(id), + m_name(name), + m_type(it), + m_channel(0), + //m_input_channel(-1), + m_transpose(MidiMidValue), + m_pan(MidiMidValue), + m_volume(100), + m_level(0.0), + m_recordLevel(0.0), + m_device(device), + m_sendBankSelect(false), + m_sendProgramChange(false), + m_sendPan(false), + m_sendVolume(false), + m_mappedId(0), + m_audioInput(1000), + m_audioInputChannel(0), + m_audioOutput(0) +{ + if (it == Audio || it == SoftSynth) + { + // In an audio instrument we use the m_channel attribute to + // hold the number of audio channels this Instrument uses - + // not the MIDI channel number. Default is 2 (stereo). + // + m_channel = 2; + + m_pan = 100; // audio pan ranges from -100 to 100 but + // we store within an unsigned char as + // 0 to 200. + } + + if (it == SoftSynth) { + addPlugin(new AudioPluginInstance(SYNTH_PLUGIN_POSITION)); + } +} + +Instrument::Instrument(InstrumentId id, + InstrumentType it, + const std::string &name, + MidiByte channel, + Device *device): + PluginContainer(it == Audio || it == SoftSynth), + m_id(id), + m_name(name), + m_type(it), + m_channel(channel), + //m_input_channel(-1), + m_transpose(MidiMidValue), + m_pan(MidiMidValue), + m_volume(100), + m_level(0.0), + m_recordLevel(0.0), + m_device(device), + m_sendBankSelect(false), + m_sendProgramChange(false), + m_sendPan(false), + m_sendVolume(false), + m_mappedId(0), + m_audioInput(1000), + m_audioInputChannel(0), + m_audioOutput(0) +{ + // Add a number of plugin place holders (unassigned) + // + if (it == Audio || it == SoftSynth) + { + // In an audio instrument we use the m_channel attribute to + // hold the number of audio channels this Instrument uses - + // not the MIDI channel number. Default is 2 (stereo). + // + m_channel = 2; + + m_pan = 100; // audio pan ranges from -100 to 100 but + // we store within an unsigned char as + + } else { +/* + * + * Let's try getting rid of this default behavior, and replacing it with a + * change to the factory autoload instead, because this just doesn't work out + * very well, and it's fiddly trying to sort the overall behavior into something + * less quirky (dmm) + * + // Also defined in Midi.h but we don't use that - not here + // in the clean inner sanctum. + // + const MidiByte MIDI_PERCUSSION_CHANNEL = 9; + const MidiByte MIDI_EXTENDED_PERCUSSION_CHANNEL = 10; + + if (m_channel == MIDI_PERCUSSION_CHANNEL || + m_channel == MIDI_EXTENDED_PERCUSSION_CHANNEL) { + setPercussion(true); + } +*/ + } + + if (it == SoftSynth) { + addPlugin(new AudioPluginInstance(SYNTH_PLUGIN_POSITION)); + } +} + +Instrument::Instrument(const Instrument &ins): + XmlExportable(), + PluginContainer(ins.getType() == Audio || ins.getType() == SoftSynth), + m_id(ins.getId()), + m_name(ins.getName()), + m_type(ins.getType()), + m_channel(ins.getMidiChannel()), + //m_input_channel(ins.getMidiInputChannel()), + m_program(ins.getProgram()), + m_transpose(ins.getMidiTranspose()), + m_pan(ins.getPan()), + m_volume(ins.getVolume()), + m_level(ins.getLevel()), + m_recordLevel(ins.getRecordLevel()), + m_device(ins.getDevice()), + m_sendBankSelect(ins.sendsBankSelect()), + m_sendProgramChange(ins.sendsProgramChange()), + m_sendPan(ins.sendsPan()), + m_sendVolume(ins.sendsVolume()), + m_mappedId(ins.getMappedId()), + m_audioInput(ins.m_audioInput), + m_audioInputChannel(ins.m_audioInputChannel), + m_audioOutput(ins.m_audioOutput) +{ + if (ins.getType() == Audio || ins.getType() == SoftSynth) + { + // In an audio instrument we use the m_channel attribute to + // hold the number of audio channels this Instrument uses - + // not the MIDI channel number. Default is 2 (stereo). + // + m_channel = 2; + } + + if (ins.getType() == SoftSynth) { + addPlugin(new AudioPluginInstance(SYNTH_PLUGIN_POSITION)); + } +} + +Instrument & +Instrument::operator=(const Instrument &ins) +{ + if (&ins == this) return *this; + + m_id = ins.getId(); + m_name = ins.getName(); + m_type = ins.getType(); + m_channel = ins.getMidiChannel(); + //m_input_channel = ins.getMidiInputChannel(); + m_program = ins.getProgram(); + m_transpose = ins.getMidiTranspose(); + m_pan = ins.getPan(); + m_volume = ins.getVolume(); + m_level = ins.getLevel(); + m_recordLevel = ins.getRecordLevel(); + m_device = ins.getDevice(); + m_sendBankSelect = ins.sendsBankSelect(); + m_sendProgramChange = ins.sendsProgramChange(); + m_sendPan = ins.sendsPan(); + m_sendVolume = ins.sendsVolume(); + m_mappedId = ins.getMappedId(); + m_audioInput = ins.m_audioInput; + m_audioInputChannel = ins.m_audioInputChannel; + m_audioOutput = ins.m_audioOutput; + + return *this; +} + + +Instrument::~Instrument() +{ +} + +std::string +Instrument::getPresentationName() const +{ + if (m_type == Audio || m_type == SoftSynth || !m_device) { + return m_name; + } else { + return m_device->getName() + " " + m_name; + } +} + +void +Instrument::setProgramChange(MidiByte program) +{ + m_program = MidiProgram(m_program.getBank(), program); +} + +MidiByte +Instrument::getProgramChange() const +{ + return m_program.getProgram(); +} + +void +Instrument::setMSB(MidiByte msb) +{ + m_program = MidiProgram(MidiBank(m_program.getBank().isPercussion(), + msb, + m_program.getBank().getLSB()), + m_program.getProgram()); +} + +MidiByte +Instrument::getMSB() const +{ + return m_program.getBank().getMSB(); +} + +void +Instrument::setLSB(MidiByte lsb) +{ + m_program = MidiProgram(MidiBank(m_program.getBank().isPercussion(), + m_program.getBank().getMSB(), + lsb), + m_program.getProgram()); +} + +MidiByte +Instrument::getLSB() const +{ + return m_program.getBank().getLSB(); +} + +void +Instrument::setPercussion(bool percussion) +{ + m_program = MidiProgram(MidiBank(percussion, + m_program.getBank().getMSB(), + m_program.getBank().getLSB()), + m_program.getProgram()); +} + +bool +Instrument::isPercussion() const +{ + return m_program.getBank().isPercussion(); +} + +void +Instrument::setAudioInputToBuss(BussId buss, int channel) +{ + m_audioInput = buss; + m_audioInputChannel = channel; +} + +void +Instrument::setAudioInputToRecord(int recordIn, int channel) +{ + m_audioInput = recordIn + 1000; + m_audioInputChannel = channel; +} + +int +Instrument::getAudioInput(bool &isBuss, int &channel) const +{ + channel = m_audioInputChannel; + + if (m_audioInput >= 1000) { + isBuss = false; + return m_audioInput - 1000; + } else { + isBuss = true; + return m_audioInput; + } +} + + +// Implementation of the virtual method to output this class +// as XML. We don't send out the name as it's redundant in +// the file - that is driven from the sequencer. +// +// +std::string +Instrument::toXmlString() +{ + + std::stringstream instrument; + + // We don't send system Instruments out this way - + // only user Instruments. + // + if (m_id < AudioInstrumentBase) + { +#if (__GNUC__ < 3) + instrument << std::ends; +#endif + return instrument.str(); + } + + instrument << " " << std::endl; + + if (m_sendBankSelect) + { + instrument << " " << std::endl; + } + + if (m_sendProgramChange) + { + instrument << " " + << std::endl; + } + + instrument << " " << std::endl; + + instrument << " " << std::endl; + + for (StaticControllerConstIterator it = m_staticControllers.begin(); + it != m_staticControllers.end(); ++it) + { + instrument << " first) + << "\" value=\"" << int(it->second) << "\"/>" << std::endl; + } + + } + else // Audio or SoftSynth + { + + if (m_type == Audio) { + instrument << "audio\">" << std::endl; + } else { + instrument << "softsynth\">" << std::endl; + } + + instrument << " " << std::endl; + + instrument << " " << std::endl; + + instrument << " " << std::endl; + + bool aibuss; + int channel; + int ai = getAudioInput(aibuss, channel); + + instrument << " " << std::endl; + + instrument << " " << std::endl; + + PluginInstanceIterator it = m_audioPlugins.begin(); + for (; it != m_audioPlugins.end(); it++) + { + instrument << (*it)->toXmlString(); + } + } + + instrument << " " << std::endl +#if (__GNUC__ < 3) + << std::endl << std::ends; +#else + << std::endl; +#endif + + return instrument.str(); + +} + + +// Return a program name given a bank select (and whether +// we send it or not) +// +std::string +Instrument::getProgramName() const +{ + if (m_sendProgramChange == false) + return std::string(""); + + MidiProgram program(m_program); + + if (!m_sendBankSelect) + program = MidiProgram(MidiBank(isPercussion(), 0, 0), program.getProgram()); + + return ((dynamic_cast(m_device))->getProgramName(program)); +} + +void +Instrument::setControllerValue(MidiByte controller, MidiByte value) +{ + for (StaticControllerIterator it = m_staticControllers.begin(); + it != m_staticControllers.end(); ++it) + { + if (it->first == controller) + { + it->second = value; + return; + } + } + + m_staticControllers.push_back(std::pair(controller, value)); + +} + +MidiByte +Instrument::getControllerValue(MidiByte controller) const +{ + for (StaticControllerConstIterator it = m_staticControllers.begin(); + it != m_staticControllers.end(); ++it) + { + + if (it->first == controller) + return it->second; + } + + throw std::string(""); +} + +const MidiKeyMapping * +Instrument::getKeyMapping() const +{ + MidiDevice *md = dynamic_cast(m_device); + if (!md) return 0; + + const MidiKeyMapping *mkm = md->getKeyMappingForProgram(m_program); + if (mkm) return mkm; + + if (isPercussion()) { // if any key mapping is available, use it + const KeyMappingList &kml = md->getKeyMappings(); + if (kml.begin() != kml.end()) { + return &(*kml.begin()); + } + } + + return 0; +} + + +Buss::Buss(BussId id) : + PluginContainer(true), + m_id(id), + m_level(0.0), + m_pan(100), + m_mappedId(0) +{ +} + +Buss::~Buss() +{ +} + +std::string +Buss::toXmlString() +{ + std::stringstream buss; + + buss << " " << std::endl; + buss << " " << std::endl; + buss << " " << std::endl; + + PluginInstanceIterator it = m_audioPlugins.begin(); + for (; it != m_audioPlugins.end(); it++) { + buss << (*it)->toXmlString(); + } + + buss << " " << std::endl; + +#if (__GNUC__ < 3) + buss << std::ends; +#endif + + return buss.str(); +} + +std::string +Buss::getName() const +{ + char buffer[20]; + sprintf(buffer, "Submaster %d", m_id); + return buffer; +} + +std::string +Buss::getPresentationName() const +{ + return getName(); +} + +RecordIn::RecordIn() : + m_mappedId(0) +{ +} + +RecordIn::~RecordIn() +{ +} + +std::string +RecordIn::toXmlString() +{ + // We don't actually save these, as they have nothing persistent + // in them. The studio just remembers how many there should be. + return ""; +} + + +} + diff --git a/src/base/Instrument.h b/src/base/Instrument.h new file mode 100644 index 0000000..8c348f0 --- /dev/null +++ b/src/base/Instrument.h @@ -0,0 +1,349 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _INSTRUMENT_H_ +#define _INSTRUMENT_H_ + +#include +#include + +#include "XmlExportable.h" +#include "MidiProgram.h" + +// An Instrument connects a Track (which itself contains +// a list of Segments) to a device that can play that +// Track. +// +// An Instrument is either MIDI or Audio (or whatever else +// we decide to implement). +// +// + +namespace Rosegarden +{ + +// plugins +class AudioPluginInstance; +typedef std::vector::iterator PluginInstanceIterator; + +typedef std::vector > StaticControllers; +typedef std::vector >::iterator StaticControllerIterator; +typedef std::vector >::const_iterator StaticControllerConstIterator; + + +// Instrument number groups +// +const InstrumentId SystemInstrumentBase = 0; +const InstrumentId AudioInstrumentBase = 1000; +const InstrumentId MidiInstrumentBase = 2000; +const InstrumentId SoftSynthInstrumentBase = 10000; + +const unsigned int AudioInstrumentCount = 16; +const unsigned int SoftSynthInstrumentCount = 24; + +const MidiByte MidiMaxValue = 127; +const MidiByte MidiMidValue = 64; +const MidiByte MidiMinValue = 0; + +typedef unsigned int BussId; + +// Predeclare Device +// +class Device; + +class PluginContainer +{ +public: + static const unsigned int PLUGIN_COUNT; // for non-synth plugins + + PluginInstanceIterator beginPlugins() { return m_audioPlugins.begin(); } + PluginInstanceIterator endPlugins() { return m_audioPlugins.end(); } + + // Plugin management + // + void addPlugin(AudioPluginInstance *instance); + bool removePlugin(unsigned int position); + void clearPlugins(); + void emptyPlugins(); // empty the plugins but don't clear them down + + // Get a plugin for this container + // + AudioPluginInstance* getPlugin(unsigned int position); + + virtual unsigned int getId() const = 0; + virtual std::string getName() const = 0; + virtual std::string getPresentationName() const = 0; + +protected: + PluginContainer(bool havePlugins); + virtual ~PluginContainer(); + + std::vector m_audioPlugins; +}; + +class Instrument : public XmlExportable, public PluginContainer +{ +public: + static const unsigned int SYNTH_PLUGIN_POSITION; + + enum InstrumentType { Midi, Audio, SoftSynth }; + + Instrument(InstrumentId id, + InstrumentType it, + const std::string &name, + Device *device); + + Instrument(InstrumentId id, + InstrumentType it, + const std::string &name, + MidiByte channel, + Device *device); + + + // Copy constructor and assignment + // + Instrument(const Instrument &); + Instrument &operator=(const Instrument &); + + ~Instrument(); + + virtual std::string getName() const { return m_name; } + virtual std::string getPresentationName() const; + + void setId(InstrumentId id) { m_id = id; } + InstrumentId getId() const { return m_id; } + + void setName(const std::string &name) { m_name = name; } + InstrumentType getType() const { return m_type; } + + void setType(InstrumentType type) { m_type = type; } + InstrumentType getInstrumentType() { return m_type; } + + + // ---------------- MIDI Controllers ----------------- + // + void setMidiChannel(MidiByte mC) { m_channel = mC; } + MidiByte getMidiChannel() const { return m_channel; } + + //void setMidiInputChannel(char ic) { m_input_channel = ic; } + //char getMidiInputChannel() const { return m_input_channel; } + + void setMidiTranspose(MidiByte mT) { m_transpose = mT; } + MidiByte getMidiTranspose() const { return m_transpose; } + + // Pan is 0-127 for MIDI instruments, and (for some + // unfathomable reason) 0-200 for audio instruments. + // + void setPan(MidiByte pan) { m_pan = pan; } + MidiByte getPan() const { return m_pan; } + + // Volume is 0-127 for MIDI instruments. It's not used for + // audio instruments -- see "level" instead. + // + void setVolume(MidiByte volume) { m_volume = volume; } + MidiByte getVolume() const { return m_volume; } + + void setProgram(const MidiProgram &program) { m_program = program; } + const MidiProgram &getProgram() const { return m_program; } + + void setSendBankSelect(bool value) { m_sendBankSelect = value; } + bool sendsBankSelect() const { return m_sendBankSelect; } + + void setSendProgramChange(bool value) { m_sendProgramChange = value; } + bool sendsProgramChange() const { return m_sendProgramChange; } + + void setSendPan(bool value) { m_sendPan = value; } + bool sendsPan() const { return m_sendPan; } + + void setSendVolume(bool value) { m_sendVolume = value; } + bool sendsVolume() const { return m_sendVolume; } + + void setControllerValue(MidiByte controller, MidiByte value); + MidiByte getControllerValue(MidiByte controller) const; + + // This is retrieved from the reference MidiProgram in the Device + const MidiKeyMapping *getKeyMapping() const; + + // Convenience functions (strictly redundant with get/setProgram): + // + void setProgramChange(MidiByte program); + MidiByte getProgramChange() const; + + void setMSB(MidiByte msb); + MidiByte getMSB() const; + + void setLSB(MidiByte msb); + MidiByte getLSB() const; + + void setPercussion(bool percussion); + bool isPercussion() const; + + // --------------- Audio Controllers ----------------- + // + void setLevel(float dB) { m_level = dB; } + float getLevel() const { return m_level; } + + void setRecordLevel(float dB) { m_recordLevel = dB; } + float getRecordLevel() const { return m_recordLevel; } + + void setAudioChannels(unsigned int ch) { m_channel = MidiByte(ch); } + unsigned int getAudioChannels() const { return (unsigned int)(m_channel); } + + // An audio input can be a buss or a record input. The channel number + // is required for mono instruments, ignored for stereo ones. + void setAudioInputToBuss(BussId buss, int channel = 0); + void setAudioInputToRecord(int recordIn, int channel = 0); + int getAudioInput(bool &isBuss, int &channel) const; + + void setAudioOutput(BussId buss) { m_audioOutput = buss; } + BussId getAudioOutput() const { return m_audioOutput; } + + // Implementation of virtual function + // + virtual std::string toXmlString(); + + // Get and set the parent device + // + Device* getDevice() const { return m_device; } + void setDevice(Device* dev) { m_device = dev; } + + // Return a string describing the current program for + // this Instrument + // + std::string getProgramName() const; + + // MappedId management - should typedef this type once + // we have the energy to shake this all out. + // + int getMappedId() const { return m_mappedId; } + void setMappedId(int id) { m_mappedId = id; } + + StaticControllers& getStaticControllers() { return m_staticControllers; } + +private: + InstrumentId m_id; + std::string m_name; + InstrumentType m_type; + + // Standard MIDI controllers and parameters + // + MidiByte m_channel; + //char m_input_channel; + MidiProgram m_program; + MidiByte m_transpose; + MidiByte m_pan; // required by audio + MidiByte m_volume; + + // Used for Audio volume (dB value) + // + float m_level; + + // Record level for Audio recording (dB value) + // + float m_recordLevel; + + Device *m_device; + + // Do we send at this intrument or do we leave these + // things up to the parent device and God? These are + // directly relatable to GUI elements + // + bool m_sendBankSelect; + bool m_sendProgramChange; + bool m_sendPan; + bool m_sendVolume; + + // Instruments are directly related to faders for volume + // control. Here we can store the remote fader id. + // + int m_mappedId; + + // Which input terminal we're connected to. This is a BussId if + // less than 1000 or a record input number (plus 1000) if >= 1000. + // The channel number is only used for mono instruments. + // + int m_audioInput; + int m_audioInputChannel; + + // Which buss we output to. Zero is always the master. + // + BussId m_audioOutput; + + // A static controller map that can be saved/loaded and queried along with this instrument. + // These values are modified from the IPB - if they appear on the IPB then they are sent + // at playback start time to the sequencer. + // + // + StaticControllers m_staticControllers; +}; + + +class Buss : public XmlExportable, public PluginContainer +{ +public: + Buss(BussId id); + ~Buss(); + + void setId(BussId id) { m_id = id; } + BussId getId() const { return m_id; } + + void setLevel(float dB) { m_level = dB; } + float getLevel() const { return m_level; } + + void setPan(MidiByte pan) { m_pan = pan; } + MidiByte getPan() const { return m_pan; } + + int getMappedId() const { return m_mappedId; } + void setMappedId(int id) { m_mappedId = id; } + + virtual std::string toXmlString(); + virtual std::string getName() const; + virtual std::string getPresentationName() const; + +private: + BussId m_id; + float m_level; + MidiByte m_pan; + int m_mappedId; +}; + + +// audio record input of a sort that can be connected to + +class RecordIn : public XmlExportable +{ +public: + RecordIn(); + ~RecordIn(); + + int getMappedId() const { return m_mappedId; } + void setMappedId(int id) { m_mappedId = id; } + + virtual std::string toXmlString(); + +private: + int m_mappedId; +}; + + +} + +#endif // _INSTRUMENT_H_ diff --git a/src/base/LayoutEngine.cpp b/src/base/LayoutEngine.cpp new file mode 100644 index 0000000..b6b3cf5 --- /dev/null +++ b/src/base/LayoutEngine.cpp @@ -0,0 +1,63 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "LayoutEngine.h" + +namespace Rosegarden +{ + +LayoutEngine::LayoutEngine() : + m_status(0) +{ + // empty +} + +LayoutEngine::~LayoutEngine() +{ + // empty +} + + +HorizontalLayoutEngine::HorizontalLayoutEngine(Composition *c) : + LayoutEngine(), + RulerScale(c) +{ + // empty +} + +HorizontalLayoutEngine::~HorizontalLayoutEngine() +{ + // empty +} + + +VerticalLayoutEngine::VerticalLayoutEngine() : + LayoutEngine() +{ + // empty +} + +VerticalLayoutEngine::~VerticalLayoutEngine() +{ + // empty +} + +} diff --git a/src/base/LayoutEngine.h b/src/base/LayoutEngine.h new file mode 100644 index 0000000..179d119 --- /dev/null +++ b/src/base/LayoutEngine.h @@ -0,0 +1,161 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _LAYOUT_ENGINE_H_ +#define _LAYOUT_ENGINE_H_ + +#include "RulerScale.h" + +namespace Rosegarden { + +class Staff; +class TimeSignature; + + +/** + * Base classes for layout engines. The intention is that + * different sorts of renderers (piano-roll, score etc) can be + * implemented by simply plugging different implementations + * of Staff and LayoutEngine into a single view class. + */ +class LayoutEngine +{ +public: + LayoutEngine(); + virtual ~LayoutEngine(); + + /** + * Resets internal data stores for all staffs + */ + virtual void reset() = 0; + + /** + * Resets internal data stores for a specific staff. + * + * If startTime == endTime, act on the whole staff; otherwise only + * the given section. + */ + virtual void resetStaff(Staff &staff, + timeT startTime = 0, + timeT endTime = 0) = 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. + * + * If startTime == endTime, act on the whole staff; otherwise only + * the given section. + */ + virtual void scanStaff(Staff &staff, + timeT startTime = 0, + timeT endTime = 0) = 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). + * + * If startTime == endTime, act on the whole staff; otherwise only + * the given section. + */ + virtual void finishLayout(timeT startTime = 0, + timeT endTime = 0) = 0; + + unsigned int getStatus() const { return m_status; } + +protected: + unsigned int m_status; +}; + + +class HorizontalLayoutEngine : public LayoutEngine, + public RulerScale +{ +public: + HorizontalLayoutEngine(Composition *c); + virtual ~HorizontalLayoutEngine(); + + /** + * Sets a page width for the layout. + * + * A layout implementation does not have to use this. Some might + * use it (for example) to ensure that bar lines fall precisely at + * the right-hand margin of each page. The computed x-coordinates + * will still require to be wrapped into lines by the staff or + * view implementation, however. + * + * A width of zero indicates no requirement for division into + * pages. + */ + virtual void setPageWidth(double) { /* default: ignore it */ } + + /** + * Returns the number of the first visible bar line on the given + * staff + */ + virtual int getFirstVisibleBarOnStaff(Staff &) { + return getFirstVisibleBar(); + } + + /** + * Returns the number of the last visible bar line on the given + * staff + */ + virtual int getLastVisibleBarOnStaff(Staff &) { + return getLastVisibleBar(); + } + + /** + * Returns true if the specified bar has the correct length + */ + virtual bool isBarCorrectOnStaff(Staff &, int/* barNo */) { + return true; + } + + /** + * 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 &, int /* barNo */, TimeSignature &, double &/* timeSigX */) { + return 0; + } +}; + + + +class VerticalLayoutEngine : public LayoutEngine +{ +public: + VerticalLayoutEngine(); + virtual ~VerticalLayoutEngine(); + + // I don't think we need to add anything here for now +}; + +} + + +#endif diff --git a/src/base/LegatoQuantizer.cpp b/src/base/LegatoQuantizer.cpp new file mode 100644 index 0000000..dcc2458 --- /dev/null +++ b/src/base/LegatoQuantizer.cpp @@ -0,0 +1,141 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "LegatoQuantizer.h" +#include "BaseProperties.h" +#include "NotationTypes.h" +#include "Selection.h" +#include "Composition.h" +#include "Profiler.h" + +#include +#include +#include // for sprintf +#include + +using std::cout; +using std::cerr; +using std::endl; + +namespace Rosegarden +{ + +using namespace BaseProperties; + + +LegatoQuantizer::LegatoQuantizer(timeT unit) : + Quantizer(RawEventData), + m_unit(unit) +{ + if (m_unit < 0) m_unit = Note(Note::Shortest).getDuration(); +} + +LegatoQuantizer::LegatoQuantizer(std::string source, std::string target, timeT unit) : + Quantizer(source, target), + m_unit(unit) +{ + if (m_unit < 0) m_unit = Note(Note::Shortest).getDuration(); +} + +LegatoQuantizer::LegatoQuantizer(const LegatoQuantizer &q) : + Quantizer(q.m_target), + m_unit(q.m_unit) +{ + // nothing else +} + +LegatoQuantizer::~LegatoQuantizer() +{ + // nothing +} + +void +LegatoQuantizer::quantizeRange(Segment *s, + Segment::iterator from, + Segment::iterator to) const +{ + Segment::iterator tmp; + while (from != to) { + quantizeSingle(s, from, tmp); + from = tmp; + if (!s->isBeforeEndMarker(from) || + (s->isBeforeEndMarker(to) && + ((*from)->getAbsoluteTime() >= (*to)->getAbsoluteTime()))) break; + } +} + +void +LegatoQuantizer::quantizeSingle(Segment *s, Segment::iterator i, + Segment::iterator &nexti) const +{ + // Stretch each note out to reach the quantized start time of the + // next note whose quantized start time is greater than or equal + // to the end time of this note after quantization + + timeT t = getFromSource(*i, AbsoluteTimeValue); + timeT d = getFromSource(*i, DurationValue); + + timeT d0(d), t0(t); + + timeT barStart = s->getBarStartForTime(t); + + t -= barStart; + t = quantizeTime(t); + t += barStart; + + nexti = i; + ++nexti; + + for (Segment::iterator j = i; s->isBeforeEndMarker(j); ++j) { + if (!(*j)->isa(Note::EventType)) continue; + + timeT qt = (*j)->getAbsoluteTime(); + qt -= barStart; + qt = quantizeTime(qt); + qt += barStart; + + if (qt >= t + d) { + d = qt - t; + } + if (qt > t) { + break; + } + } + + if (t0 != t || d0 != d) { + setToTarget(s, i, t, d); + nexti = s->findTime(t + d); + } +} + +timeT +LegatoQuantizer::quantizeTime(timeT t) const +{ + if (m_unit != 0) { + timeT low = (t / m_unit) * m_unit; + timeT high = low + m_unit; + t = ((high - t > t - low) ? low : high); + } + return t; +} + +} diff --git a/src/base/LegatoQuantizer.h b/src/base/LegatoQuantizer.h new file mode 100644 index 0000000..645da05 --- /dev/null +++ b/src/base/LegatoQuantizer.h @@ -0,0 +1,64 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 LEGATO_QUANTIZER_H +#define LEGATO_QUANTIZER_H + +#include "Quantizer.h" + +namespace Rosegarden { + +class BasicQuantizer; + +class LegatoQuantizer : public Quantizer +{ +public: + // The default unit is the shortest note type. A unit of + // zero means do no quantization -- unlike for BasicQuantizer + // this does have a purpose, as it still does the legato step + LegatoQuantizer(timeT unit = -1); + LegatoQuantizer(std::string source, std::string target, timeT unit = -1); + LegatoQuantizer(const LegatoQuantizer &); + virtual ~LegatoQuantizer(); + + void setUnit(timeT unit) { m_unit = unit; } + timeT getUnit() const { return m_unit; } + + virtual void quantizeRange(Segment *, + Segment::iterator, + Segment::iterator) const; + +protected: + virtual void quantizeSingle(Segment *, Segment::iterator, + Segment::iterator &) const; + + timeT quantizeTime(timeT) const; + +private: + LegatoQuantizer &operator=(const BasicQuantizer &); // not provided + + timeT m_unit; +}; + +} + +#endif + diff --git a/src/base/Marker.cpp b/src/base/Marker.cpp new file mode 100644 index 0000000..beab5f6 --- /dev/null +++ b/src/base/Marker.cpp @@ -0,0 +1,55 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Marker.h" +#include "misc/Debug.h" + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +namespace Rosegarden +{ + +int Marker::m_sequence = 0; + +std::string +Marker::toXmlString() +{ + std::stringstream marker; + + marker << " " << std::endl; +#if (__GNUC__ < 3) + marker << std::ends; +#endif + + return marker.str(); +} + +} + diff --git a/src/base/Marker.h b/src/base/Marker.h new file mode 100644 index 0000000..624081d --- /dev/null +++ b/src/base/Marker.h @@ -0,0 +1,78 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _MARKER_H_ +#define _MARKER_H_ + +#include + +#include "Event.h" +#include "XmlExportable.h" + +// A Marker is a user defined point in a Composition that can be +// used to define looping points - jump to, make notes at etc. +// +// Not to be confused with Composition or Segment start and +// end markers. Which they probably could be quite easily. +// I probably should've thought the name through a bit better +// really.. +// + +namespace Rosegarden +{ + +class Marker : public XmlExportable +{ +public: + Marker():m_time(0), m_name(std::string("")), + m_description(std::string("")) { m_id = nextSeqVal(); } + + Marker(timeT time, const std::string &name, + const std::string &description): + m_time(time), m_name(name), m_description(description) { m_id = nextSeqVal(); } + + int getID() const { return m_id; } + timeT getTime() const { return m_time; } + std::string getName() const { return m_name; } + std::string getDescription() const { return m_description; } + + void setTime(timeT time) { m_time = time; } + void setName(const std::string &name) { m_name = name; } + void setDescription(const std::string &des) { m_description = des; } + + // export as XML + virtual std::string toXmlString(); + +protected: + + int m_id; + timeT m_time; + std::string m_name; + std::string m_description; + +private: + static int nextSeqVal() { return ++m_sequence; } // assume there won't be concurrency problem + static int m_sequence; +}; + +} + +#endif // _CONTROLPARAMETER_H_ diff --git a/src/base/MidiDevice.cpp b/src/base/MidiDevice.cpp new file mode 100644 index 0000000..cceeb0e --- /dev/null +++ b/src/base/MidiDevice.cpp @@ -0,0 +1,839 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "MidiDevice.h" +#include "MidiTypes.h" +#include "Instrument.h" +#include "ControlParameter.h" + +#include +#include +#include +#include + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + + +namespace Rosegarden +{ + +MidiDevice::MidiDevice(): + Device(0, "Default Midi Device", Device::Midi), + m_metronome(0), + m_direction(Play), + m_variationType(NoVariations), + m_librarian(std::pair("", "")) +{ + generatePresentationList(); + generateDefaultControllers(); + + // create a default Metronome + m_metronome = new MidiMetronome(MidiInstrumentBase + 9); +} + +MidiDevice::MidiDevice(DeviceId id, + const std::string &name, + DeviceDirection dir): + Device(id, name, Device::Midi), + m_metronome(0), + m_direction(dir), + m_variationType(NoVariations), + m_librarian(std::pair("", "")) +{ + generatePresentationList(); + generateDefaultControllers(); + + // create a default Metronome + m_metronome = new MidiMetronome(MidiInstrumentBase + 9); +} + +MidiDevice::MidiDevice(DeviceId id, + const MidiDevice &dev) : + Device(id, dev.getName(), Device::Midi), + m_programList(dev.m_programList), + m_bankList(dev.m_bankList), + m_controlList(dev.m_controlList), + m_metronome(0), + m_direction(dev.getDirection()), + m_variationType(dev.getVariationType()), + m_librarian(dev.getLibrarian()) +{ + // Create and assign a metronome if required + // + if (dev.getMetronome()) { + m_metronome = new MidiMetronome(*dev.getMetronome()); + } + + generatePresentationList(); + generateDefaultControllers(); +} + +MidiDevice::MidiDevice(const MidiDevice &dev) : + Device(dev.getId(), dev.getName(), dev.getType()), + Controllable(), + m_programList(dev.m_programList), + m_bankList(dev.m_bankList), + m_controlList(dev.m_controlList), + m_metronome(0), + m_direction(dev.getDirection()), + m_variationType(dev.getVariationType()), + m_librarian(dev.getLibrarian()) +{ + // Create and assign a metronome if required + // + if (dev.getMetronome()) + { + m_metronome = new MidiMetronome(*dev.getMetronome()); + } + + // Copy the instruments + // + InstrumentList insList = dev.getAllInstruments(); + InstrumentList::iterator iIt = insList.begin(); + for (; iIt != insList.end(); iIt++) + { + Instrument *newInst = new Instrument(**iIt); + newInst->setDevice(this); + m_instruments.push_back(newInst); + } + + // generate presentation instruments + generatePresentationList(); +} + + +MidiDevice & +MidiDevice::operator=(const MidiDevice &dev) +{ + if (&dev == this) return *this; + + m_id = dev.getId(); + m_name = dev.getName(); + m_type = dev.getType(); + m_librarian = dev.getLibrarian(); + + m_programList = dev.getPrograms(); + m_bankList = dev.getBanks(); + m_controlList = dev.getControlParameters(); + m_direction = dev.getDirection(); + m_variationType = dev.getVariationType(); + + // clear down instruments list + m_instruments.clear(); + m_presentationInstrumentList.clear(); + + // Create and assign a metronome if required + // + if (dev.getMetronome()) + { + if (m_metronome) delete m_metronome; + m_metronome = new MidiMetronome(*dev.getMetronome()); + } + else + { + delete m_metronome; + m_metronome = 0; + } + + // Copy the instruments + // + InstrumentList insList = dev.getAllInstruments(); + InstrumentList::iterator iIt = insList.begin(); + for (; iIt != insList.end(); iIt++) + { + Instrument *newInst = new Instrument(**iIt); + newInst->setDevice(this); + m_instruments.push_back(newInst); + } + + // generate presentation instruments + generatePresentationList(); + + return (*this); +} + +MidiDevice::~MidiDevice() +{ + delete m_metronome; + //!!! delete key mappings +} + +void +MidiDevice::generatePresentationList() +{ + // Fill the presentation list for the instruments + // + m_presentationInstrumentList.clear(); + + InstrumentList::iterator it; + for (it = m_instruments.begin(); it != m_instruments.end(); it++) + { + if ((*it)->getId() >= MidiInstrumentBase) { + m_presentationInstrumentList.push_back(*it); + } + } +} + +void +MidiDevice::generateDefaultControllers() +{ + m_controlList.clear(); + + static std::string controls[][9] = { + { "Pan", Rosegarden::Controller::EventType, "", "0", "127", "64", "10", "2", "0" }, + { "Chorus", Rosegarden::Controller::EventType, "", "0", "127", "0", "93", "3", "1" }, + { "Volume", Rosegarden::Controller::EventType, "", "0", "127", "0", "7", "1", "2" }, + { "Reverb", Rosegarden::Controller::EventType, "", "0", "127", "0", "91", "3", "3" }, + { "Sustain", Rosegarden::Controller::EventType, "", "0", "127", "0", "64", "4", "-1" }, + { "Expression", Rosegarden::Controller::EventType, "", "0", "127", "100", "11", "2", "-1" }, + { "Modulation", Rosegarden::Controller::EventType, "", "0", "127", "0", "1", "4", "-1" }, + { "PitchBend", Rosegarden::PitchBend::EventType, "", "0", "16383", "8192", "1", "4", "-1" } + }; + + for (unsigned int i = 0; i < sizeof(controls) / sizeof(controls[0]); ++i) { + + Rosegarden::ControlParameter con(controls[i][0], + controls[i][1], + controls[i][2], + atoi(controls[i][3].c_str()), + atoi(controls[i][4].c_str()), + atoi(controls[i][5].c_str()), + Rosegarden::MidiByte(atoi(controls[i][6].c_str())), + atoi(controls[i][7].c_str()), + atoi(controls[i][8].c_str())); + addControlParameter(con); + } + + +} + +void +MidiDevice::clearBankList() +{ + m_bankList.clear(); +} + +void +MidiDevice::clearProgramList() +{ + m_programList.clear(); +} + +void +MidiDevice::clearControlList() +{ + m_controlList.clear(); +} + +void +MidiDevice::addProgram(const MidiProgram &prog) +{ + // Refuse duplicates + for (ProgramList::const_iterator it = m_programList.begin(); + it != m_programList.end(); ++it) { + if (*it == prog) return; + } + + m_programList.push_back(prog); +} + +void +MidiDevice::addBank(const MidiBank &bank) +{ + m_bankList.push_back(bank); +} + +void +MidiDevice::removeMetronome() +{ + delete m_metronome; + m_metronome = 0; +} + +void +MidiDevice::setMetronome(const MidiMetronome &metronome) +{ + delete m_metronome; + m_metronome = new MidiMetronome(metronome); +} + +BankList +MidiDevice::getBanks(bool percussion) const +{ + BankList banks; + + for (BankList::const_iterator it = m_bankList.begin(); + it != m_bankList.end(); ++it) { + if (it->isPercussion() == percussion) banks.push_back(*it); + } + + return banks; +} + +BankList +MidiDevice::getBanksByMSB(bool percussion, MidiByte msb) const +{ + BankList banks; + + for (BankList::const_iterator it = m_bankList.begin(); + it != m_bankList.end(); ++it) { + if (it->isPercussion() == percussion && it->getMSB() == msb) + banks.push_back(*it); + } + + return banks; +} + +BankList +MidiDevice::getBanksByLSB(bool percussion, MidiByte lsb) const +{ + BankList banks; + + for (BankList::const_iterator it = m_bankList.begin(); + it != m_bankList.end(); ++it) { + if (it->isPercussion() == percussion && it->getLSB() == lsb) + banks.push_back(*it); + } + + return banks; +} + +MidiByteList +MidiDevice::getDistinctMSBs(bool percussion, int lsb) const +{ + std::set msbs; + + for (BankList::const_iterator it = m_bankList.begin(); + it != m_bankList.end(); ++it) { + if (it->isPercussion() == percussion && + (lsb == -1 || it->getLSB() == lsb)) msbs.insert(it->getMSB()); + } + + MidiByteList v; + for (std::set::iterator i = msbs.begin(); i != msbs.end(); ++i) { + v.push_back(*i); + } + + return v; +} + +MidiByteList +MidiDevice::getDistinctLSBs(bool percussion, int msb) const +{ + std::set lsbs; + + for (BankList::const_iterator it = m_bankList.begin(); + it != m_bankList.end(); ++it) { + if (it->isPercussion() == percussion && + (msb == -1 || it->getMSB() == msb)) lsbs.insert(it->getLSB()); + } + + MidiByteList v; + for (std::set::iterator i = lsbs.begin(); i != lsbs.end(); ++i) { + v.push_back(*i); + } + + return v; +} + +ProgramList +MidiDevice::getPrograms(const MidiBank &bank) const +{ + ProgramList programs; + + for (ProgramList::const_iterator it = m_programList.begin(); + it != m_programList.end(); ++it) { + if (it->getBank() == bank) programs.push_back(*it); + } + + return programs; +} + +std::string +MidiDevice::getBankName(const MidiBank &bank) const +{ + for (BankList::const_iterator it = m_bankList.begin(); + it != m_bankList.end(); ++it) { + if (*it == bank) return it->getName(); + } + return ""; +} + +void +MidiDevice::addKeyMapping(const MidiKeyMapping &mapping) +{ + //!!! handle dup names + m_keyMappingList.push_back(mapping); +} + +const MidiKeyMapping * +MidiDevice::getKeyMappingByName(const std::string &name) const +{ + for (KeyMappingList::const_iterator i = m_keyMappingList.begin(); + i != m_keyMappingList.end(); ++i) { + if (i->getName() == name) return &(*i); + } + return 0; +} + +const MidiKeyMapping * +MidiDevice::getKeyMappingForProgram(const MidiProgram &program) const +{ + ProgramList::const_iterator it; + + for (it = m_programList.begin(); it != m_programList.end(); it++) { + if (*it == program) { + std::string kmn = it->getKeyMapping(); + if (kmn == "") return 0; + return getKeyMappingByName(kmn); + } + } + + return 0; +} + +void +MidiDevice::setKeyMappingForProgram(const MidiProgram &program, + std::string mapping) +{ + ProgramList::iterator it; + + for (it = m_programList.begin(); it != m_programList.end(); it++) { + if (*it == program) { + it->setKeyMapping(mapping); + } + } +} + + +std::string +MidiDevice::toXmlString() +{ + std::stringstream midiDevice; + + midiDevice << " " << std::endl << std::endl; + + midiDevice << " " << std::endl; + + if (m_metronome) + { + // Write out the metronome - watch the MidiBytes + // when using the stringstream + // + midiDevice << " getInstrument() << "\" " + << "barpitch=\"" << (int)m_metronome->getBarPitch() << "\" " + << "beatpitch=\"" << (int)m_metronome->getBeatPitch() << "\" " + << "subbeatpitch=\"" << (int)m_metronome->getSubBeatPitch() << "\" " + << "depth=\"" << (int)m_metronome->getDepth() << "\" " + << "barvelocity=\"" << (int)m_metronome->getBarVelocity() << "\" " + << "beatvelocity=\"" << (int)m_metronome->getBeatVelocity() << "\" " + << "subbeatvelocity=\"" << (int)m_metronome->getSubBeatVelocity() + << "\"/>" + << std::endl << std::endl; + } + + // and now bank information + // + BankList::iterator it; + InstrumentList::iterator iit; + ProgramList::iterator pt; + + for (it = m_bankList.begin(); it != m_bankList.end(); it++) + { + midiDevice << " getName()) << "\" " + << "percussion=\"" << (it->isPercussion() ? "true" : "false") << "\" " + << "msb=\"" << (int)it->getMSB() << "\" " + << "lsb=\"" << (int)it->getLSB() << "\">" + << std::endl; + + // Not terribly efficient + // + for (pt = m_programList.begin(); pt != m_programList.end(); pt++) + { + if (pt->getBank() == *it) + { + midiDevice << " getProgram() << "\" " + << "name=\"" << encode(pt->getName()) << "\" "; + if (!pt->getKeyMapping().empty()) { + midiDevice << "keymapping=\"" + << encode(pt->getKeyMapping()) << "\" "; + } + midiDevice << "/>" << std::endl; + } + } + + midiDevice << " " << std::endl << std::endl; + } + + // Now controllers (before Instruments, which can depend on + // Controller colours) + // + midiDevice << " " << std::endl; + ControlList::iterator cIt; + for (cIt = m_controlList.begin(); cIt != m_controlList.end() ; ++cIt) + midiDevice << cIt->toXmlString(); + midiDevice << " " << std::endl << std::endl; + + // Add instruments + // + for (iit = m_instruments.begin(); iit != m_instruments.end(); iit++) + midiDevice << (*iit)->toXmlString(); + + KeyMappingList::iterator kit; + + for (kit = m_keyMappingList.begin(); kit != m_keyMappingList.end(); kit++) + { + midiDevice << " getName()) << "\">\n"; + + for (MidiKeyMapping::KeyNameMap::const_iterator nmi = + kit->getMap().begin(); nmi != kit->getMap().end(); ++nmi) { + midiDevice << " first + << "\" name=\"" << encode(nmi->second) << "\"/>\n"; + } + + midiDevice << " \n"; + } + +#if (__GNUC__ < 3) + midiDevice << " " << std::endl << std::ends; +#else + midiDevice << " " << std::endl; +#endif + + return midiDevice.str(); +} + +// Only copy across non System instruments +// +InstrumentList +MidiDevice::getAllInstruments() const +{ + return m_instruments; +} + +// Omitting special system Instruments +// +InstrumentList +MidiDevice::getPresentationInstruments() const +{ + return m_presentationInstrumentList; +} + +void +MidiDevice::addInstrument(Instrument *instrument) +{ + m_instruments.push_back(instrument); + generatePresentationList(); +} + +std::string +MidiDevice::getProgramName(const MidiProgram &program) const +{ + ProgramList::const_iterator it; + + for (it = m_programList.begin(); it != m_programList.end(); it++) + { + if (*it == program) return it->getName(); + } + + return std::string(""); +} + +void +MidiDevice::replaceBankList(const BankList &bankList) +{ + m_bankList = bankList; +} + +void +MidiDevice::replaceProgramList(const ProgramList &programList) +{ + m_programList = programList; +} + +void +MidiDevice::replaceKeyMappingList(const KeyMappingList &keyMappingList) +{ + m_keyMappingList = keyMappingList; +} + + +// Merge the new bank list in without duplication +// +void +MidiDevice::mergeBankList(const BankList &bankList) +{ + BankList::const_iterator it; + BankList::iterator oIt; + bool clash = false; + + for (it = bankList.begin(); it != bankList.end(); it++) + { + for (oIt = m_bankList.begin(); oIt != m_bankList.end(); oIt++) + { + if (*it == *oIt) + { + clash = true; + break; + } + } + + if (clash == false) + addBank(*it); + else + clash = false; + } + +} + +void +MidiDevice::mergeProgramList(const ProgramList &programList) +{ + ProgramList::const_iterator it; + ProgramList::iterator oIt; + bool clash = false; + + for (it = programList.begin(); it != programList.end(); it++) + { + for (oIt = m_programList.begin(); oIt != m_programList.end(); oIt++) + { + if (*it == *oIt) + { + clash = true; + break; + } + } + + if (clash == false) + addProgram(*it); + else + clash = false; + } +} + +void +MidiDevice::mergeKeyMappingList(const KeyMappingList &keyMappingList) +{ + KeyMappingList::const_iterator it; + KeyMappingList::iterator oIt; + bool clash = false; + + for (it = keyMappingList.begin(); it != keyMappingList.end(); it++) + { + for (oIt = m_keyMappingList.begin(); oIt != m_keyMappingList.end(); oIt++) + { + if (it->getName() == oIt->getName()) + { + clash = true; + break; + } + } + + if (clash == false) + addKeyMapping(*it); + else + clash = false; + } +} + +void +MidiDevice::addControlParameter(const ControlParameter &con) +{ + m_controlList.push_back(con); +} + +void +MidiDevice::addControlParameter(const ControlParameter &con, int index) +{ + ControlList controls; + + // if we're out of range just add the control + if (index >= (int)m_controlList.size()) + { + m_controlList.push_back(con); + return; + } + + // add new controller in at a position + for (int i = 0; i < (int)m_controlList.size(); ++i) + { + if (index == i) controls.push_back(con); + controls.push_back(m_controlList[i]); + } + + m_controlList = controls; +} + + +bool +MidiDevice::removeControlParameter(int index) +{ + ControlList::iterator it = m_controlList.begin(); + int i = 0; + + for (; it != m_controlList.end(); ++it) + { + if (index == i) + { + m_controlList.erase(it); + return true; + } + i++; + } + + return false; +} + +bool +MidiDevice::modifyControlParameter(const ControlParameter &con, int index) +{ + if (index < 0 || index > (int)m_controlList.size()) return false; + m_controlList[index] = con; + return true; +} + +void +MidiDevice::replaceControlParameters(const ControlList &con) +{ + m_controlList = con; +} + + +// Check to see if passed ControlParameter is unique. Either the +// type must be unique or in the case of Controller::EventType the +// ControllerValue must be unique. +// +// Controllers (Control type) +// +// +bool +MidiDevice::isUniqueControlParameter(const ControlParameter &con) const +{ + ControlList::const_iterator it = m_controlList.begin(); + + for (; it != m_controlList.end(); ++it) + { + if (it->getType() == con.getType()) + { + if (it->getType() == Rosegarden::Controller::EventType && + it->getControllerValue() != con.getControllerValue()) + continue; + + return false; + } + + } + + return true; +} + +// Cheat a bit here and remove the VOLUME controller here - just +// so that the MIDIMixer is made a bit easier. +// +ControlList +MidiDevice::getIPBControlParameters() const +{ + ControlList retList; + + Rosegarden::MidiByte MIDI_CONTROLLER_VOLUME = 0x07; + + for (ControlList::const_iterator it = m_controlList.begin(); + it != m_controlList.end(); ++it) + { + if (it->getIPBPosition() != -1 && + it->getControllerValue() != MIDI_CONTROLLER_VOLUME) + retList.push_back(*it); + } + + return retList; +} + + + + +ControlParameter * +MidiDevice::getControlParameter(int index) +{ + if (index >= 0 && ((unsigned int)index) < m_controlList.size()) + return &m_controlList[index]; + + return 0; +} + +const ControlParameter * +MidiDevice::getControlParameter(int index) const +{ + return ((MidiDevice *)this)->getControlParameter(index); +} + +ControlParameter * +MidiDevice::getControlParameter(const std::string &type, Rosegarden::MidiByte controllerValue) +{ + ControlList::iterator it = m_controlList.begin(); + + for (; it != m_controlList.end(); ++it) + { + if (it->getType() == type) + { + // Return matched on type for most events + // + if (type != Rosegarden::Controller::EventType) + return &*it; + + // Also match controller value for Controller events + // + if (it->getControllerValue() == controllerValue) + return &*it; + } + } + + return 0; +} + +const ControlParameter * +MidiDevice::getControlParameter(const std::string &type, Rosegarden::MidiByte controllerValue) const +{ + return ((MidiDevice *)this)->getControlParameter(type, controllerValue); +} + +} + + diff --git a/src/base/MidiDevice.h b/src/base/MidiDevice.h new file mode 100644 index 0000000..0a3c17f --- /dev/null +++ b/src/base/MidiDevice.h @@ -0,0 +1,213 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _MIDIDEVICE_H_ +#define _MIDIDEVICE_H_ + +#include +#include + +#include "Device.h" +#include "Instrument.h" +#include "MidiProgram.h" +#include "ControlParameter.h" +#include "Controllable.h" + +namespace Rosegarden +{ + +typedef std::vector StringList; +typedef std::vector MidiByteList; + +class MidiDevice : public Device, public Controllable +{ +public: + typedef enum + { + Play = 0, + Record = 1 + } DeviceDirection; + + typedef enum + { + NoVariations, + VariationFromLSB, + VariationFromMSB + } VariationType; + + MidiDevice(); + MidiDevice(const MidiDevice &); + MidiDevice(DeviceId id, + const MidiDevice &); + MidiDevice(DeviceId id, + const std::string &name, + DeviceDirection dir); + MidiDevice(DeviceId id, + const std::string &name, + const std::string &label, + DeviceDirection dir); + virtual ~MidiDevice(); + + // Assignment + MidiDevice &operator=(const MidiDevice &); + + // Instrument must be on heap; I take ownership of it + virtual void addInstrument(Instrument*); + + void removeMetronome(); + void setMetronome(const MidiMetronome &); + const MidiMetronome* getMetronome() const { return m_metronome; } + + void addProgram(const MidiProgram &program); + void addBank(const MidiBank &bank); + void addKeyMapping(const MidiKeyMapping &mapping); // I own the result! + + void clearBankList(); + void clearProgramList(); + void clearControlList(); + + const BankList &getBanks() const { return m_bankList; } + BankList getBanks(bool percussion) const; + BankList getBanksByMSB(bool percussion, MidiByte msb) const; + BankList getBanksByLSB(bool percussion, MidiByte lsb) const; + + MidiByteList getDistinctMSBs(bool percussion, int lsb = -1) const; + MidiByteList getDistinctLSBs(bool percussion, int msb = -1) const; + + const ProgramList &getPrograms() const { return m_programList; } + ProgramList getPrograms(const MidiBank &bank) const; + + const KeyMappingList &getKeyMappings() const { return m_keyMappingList; } + const MidiKeyMapping *getKeyMappingByName(const std::string &) const; + const MidiKeyMapping *getKeyMappingForProgram(const MidiProgram &program) const; + void setKeyMappingForProgram(const MidiProgram &program, std::string mapping); + + std::string getBankName(const MidiBank &bank) const; + std::string getProgramName(const MidiProgram &program) const; + + void replaceBankList(const BankList &bank); + void replaceProgramList(const ProgramList &program); + void replaceKeyMappingList(const KeyMappingList &mappings); + + void mergeBankList(const BankList &bank); + void mergeProgramList(const ProgramList &program); + void mergeKeyMappingList(const KeyMappingList &mappings); + + virtual InstrumentList getAllInstruments() const; + virtual InstrumentList getPresentationInstruments() const; + + // Retrieve Librarian details + // + const std::string getLibrarianName() const { return m_librarian.first; } + const std::string getLibrarianEmail() const { return m_librarian.second; } + std::pair getLibrarian() const + { return m_librarian; } + + // Set Librarian details + // + void setLibrarian(const std::string &name, const std::string &email) + { m_librarian = std::pair(name, email); } + + DeviceDirection getDirection() const { return m_direction; } + void setDirection(DeviceDirection dir) { m_direction = dir; } + + VariationType getVariationType() const { return m_variationType; } + void setVariationType(VariationType v) { m_variationType = v; } + + // Controllers - for mapping Controller names to values for use in + // the InstrumentParameterBoxes (IPBs) and Control rulers. + // + ControlList::const_iterator beginControllers() const + { return m_controlList.begin(); } + ControlList::const_iterator endControllers() const + { return m_controlList.end(); } + + // implemented from Controllable interface + // + virtual const ControlList &getControlParameters() const { return m_controlList; } + + // Only those on the IPB list + // + ControlList getIPBControlParameters() const; + + // Access ControlParameters (read/write) + // + virtual ControlParameter *getControlParameter(int index); + virtual const ControlParameter *getControlParameter(int index) const; + virtual ControlParameter *getControlParameter(const std::string &type, MidiByte controllerNumber); + virtual const ControlParameter *getControlParameter(const std::string &type, MidiByte controllerNumber) const; + + // Modify ControlParameters + // + void addControlParameter(const ControlParameter &con); + void addControlParameter(const ControlParameter &con, int index); + bool removeControlParameter(int index); + bool modifyControlParameter(const ControlParameter &con, int index); + + void replaceControlParameters(const ControlList &); + + // Check to see if the passed ControlParameter is unique in + // our ControlParameter list. + // + bool isUniqueControlParameter(const ControlParameter &con) const; + + // Generate some default controllers for the MidiDevice + // + void generateDefaultControllers(); + + virtual std::string toXmlString(); + + // Accessors for recording property + bool isRecording() {return m_recording; } + void setRecording(bool recording) {m_recording = recording;} + +protected: + void generatePresentationList(); + + ProgramList m_programList; + BankList m_bankList; + ControlList m_controlList; + KeyMappingList m_keyMappingList; + MidiMetronome *m_metronome; + + // used when we're presenting the instruments + InstrumentList m_presentationInstrumentList; + + // Is this device Play or Record? + // + DeviceDirection m_direction; + + // Is this device recording? + // + bool m_recording; + + // Should we present LSB or MSB of bank info as a Variation number? + // + VariationType m_variationType; + + // Librarian contact details + // + std::pair m_librarian; // name. email +}; + +} + +#endif // _MIDIDEVICE_H_ diff --git a/src/base/MidiProgram.cpp b/src/base/MidiProgram.cpp new file mode 100644 index 0000000..c026a0a --- /dev/null +++ b/src/base/MidiProgram.cpp @@ -0,0 +1,224 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "MidiProgram.h" + +namespace Rosegarden { + +MidiBank::MidiBank() : + m_percussion(false), m_msb(0), m_lsb(0), m_name() +{ + // nothing else +} + +MidiBank::MidiBank(bool percussion, MidiByte msb, MidiByte lsb, std::string name) : + m_percussion(percussion), m_msb(msb), m_lsb(lsb), m_name(name) +{ + // nothing else +} + +bool +MidiBank::operator==(const MidiBank &b) const +{ + return m_percussion == b.m_percussion && m_msb == b.m_msb && m_lsb == b.m_lsb; +} + +bool +MidiBank::isPercussion() const +{ + return m_percussion; +} + +MidiByte +MidiBank::getMSB() const +{ + return m_msb; +} + +MidiByte +MidiBank::getLSB() const +{ + return m_lsb; +} + +std::string +MidiBank::getName() const +{ + return m_name; +} + +void +MidiBank::setName(std::string name) +{ + m_name = name; +} + + +MidiProgram::MidiProgram() : + m_bank(), m_program(0), m_name() +{ + // nothing else +} + +MidiProgram::MidiProgram(const MidiBank &bank, MidiByte program, std::string name, std::string keyMapping) : + m_bank(bank), m_program(program), m_name(name), m_keyMapping(keyMapping) +{ + // nothing else +} + +bool +MidiProgram::operator==(const MidiProgram &p) const +{ + return m_bank == p.m_bank && m_program == p.m_program; +} + +const MidiBank & +MidiProgram::getBank() const +{ + return m_bank; +} + +MidiByte +MidiProgram::getProgram() const +{ + return m_program; +} + +const std::string & +MidiProgram::getName() const +{ + return m_name; +} + +void +MidiProgram::setName(const std::string &name) +{ + m_name = name; +} + +const std::string & +MidiProgram::getKeyMapping() const +{ + return m_keyMapping; +} + +void +MidiProgram::setKeyMapping(const std::string &keyMapping) +{ + m_keyMapping = keyMapping; +} + +MidiKeyMapping::MidiKeyMapping() : + m_name("") +{ +} + +MidiKeyMapping::MidiKeyMapping(const std::string &name) : + m_name(name) +{ + // nothing else +} + +MidiKeyMapping::MidiKeyMapping(const std::string &name, const KeyNameMap &map) : + m_name(name), + m_map(map) +{ + // nothing else +} + +bool +MidiKeyMapping::operator==(const MidiKeyMapping &m) const +{ + return (m_map == m.m_map); +} + +std::string +MidiKeyMapping::getMapForKeyName(MidiByte pitch) const +{ + KeyNameMap::const_iterator i = m_map.find(pitch); + if (i != m_map.end()) { + return i->second; + } else { + return ""; + } +} + +int +MidiKeyMapping::getOffset(MidiByte pitch) const +{ + int c; + for (KeyNameMap::const_iterator i = m_map.begin(); i != m_map.end(); ++i) { + if (i->first == pitch) return c; + ++c; + } + return -1; +} + +int +MidiKeyMapping::getPitchForOffset(int offset) const +{ + KeyNameMap::const_iterator i = m_map.begin(); + while (i != m_map.end() && offset > 0) { + ++i; --offset; + } + if (i == m_map.end()) return -1; + else return i->first; +} + +int +MidiKeyMapping::getPitchExtent() const +{ + int minPitch = 0, maxPitch = 0; + KeyNameMap::const_iterator mi = m_map.begin(); + if (mi != m_map.end()) { + minPitch = mi->first; + mi = m_map.end(); + --mi; + maxPitch = mi->first; + return maxPitch - minPitch + 1; + } + return maxPitch - minPitch; +} + + + +MidiMetronome::MidiMetronome(InstrumentId instrument, + MidiByte barPitch, + MidiByte beatPitch, + MidiByte subBeatPitch, + int depth, + MidiByte barVely, + MidiByte beatVely, + MidiByte subBeatVely): + m_instrument(instrument), + m_barPitch(barPitch), + m_beatPitch(beatPitch), + m_subBeatPitch(subBeatPitch), + m_depth(depth), + m_barVelocity(barVely), + m_beatVelocity(beatVely), + m_subBeatVelocity(subBeatVely) +{ + // nothing else +} + +} + diff --git a/src/base/MidiProgram.h b/src/base/MidiProgram.h new file mode 100644 index 0000000..e44f631 --- /dev/null +++ b/src/base/MidiProgram.h @@ -0,0 +1,180 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _MIDIBANK_H_ +#define _MIDIBANK_H_ + +#include +#include +#include + +namespace Rosegarden +{ +typedef unsigned char MidiByte; +typedef unsigned int InstrumentId; + +class MidiBank +{ +public: + MidiBank(); + MidiBank(bool percussion, MidiByte msb, MidiByte lsb, std::string name = ""); + + // comparator disregards name + bool operator==(const MidiBank &b) const; + + bool isPercussion() const; + MidiByte getMSB() const; + MidiByte getLSB() const; + std::string getName() const; + + void setName(std::string name); + +private: + bool m_percussion; + MidiByte m_msb; + MidiByte m_lsb; + std::string m_name; +}; + +typedef std::vector BankList; + +class MidiProgram +{ +public: + MidiProgram(); + MidiProgram(const MidiBank &bank, MidiByte program, std::string name = "", + std::string keyMapping = ""); + + // comparator disregards name + bool operator==(const MidiProgram &p) const; + + const MidiBank& getBank() const; + MidiByte getProgram() const; + const std::string &getName() const; + const std::string &getKeyMapping() const; + + void setName(const std::string &name); + void setKeyMapping(const std::string &name); + +private: + MidiBank m_bank; + MidiByte m_program; + std::string m_name; + std::string m_keyMapping; +}; + +typedef std::vector ProgramList; + +class MidiKeyMapping +{ +public: + typedef std::map KeyNameMap; + + MidiKeyMapping(); + MidiKeyMapping(const std::string &name); + MidiKeyMapping(const std::string &name, const KeyNameMap &map); + + bool operator==(const MidiKeyMapping &m) const; + + const std::string &getName() const { return m_name; } + void setName(const std::string &name) { m_name = name; } + + const KeyNameMap &getMap() const { return m_map; } + KeyNameMap &getMap() { return m_map; } + std::string getMapForKeyName(MidiByte pitch) const; + void setMap(const KeyNameMap &map) { m_map = map; } + + // Return 0 if the supplied argument is the lowest pitch in the + // mapping, 1 if it is the second-lowest, etc. Return -1 if it + // is not in the mapping at all. Not instant. + int getOffset(MidiByte pitch) const; + + // Return the offset'th pitch in the mapping. Return -1 if there + // are fewer than offset pitches in the mapping (or offset < 0). + // Not instant. + int getPitchForOffset(int offset) const; + + // Return the difference between the top and bottom pitches + // contained in the map. + // + int getPitchExtent() const; + +private: + std::string m_name; + KeyNameMap m_map; +}; + +typedef std::vector KeyMappingList; + +// A mapped MIDI instrument - a drum track click for example +// +class MidiMetronome +{ +public: + MidiMetronome(InstrumentId instrument, + MidiByte barPitch = 37, + MidiByte beatPitch = 37, + MidiByte subBeatPitch = 37, + int depth = 2, + MidiByte barVely = 120, + MidiByte beatVely = 100, + MidiByte subBeatVely = 80); + + InstrumentId getInstrument() const { return m_instrument; } + MidiByte getBarPitch() const { return m_barPitch; } + MidiByte getBeatPitch() const { return m_beatPitch; } + MidiByte getSubBeatPitch() const { return m_subBeatPitch; } + int getDepth() const { return m_depth; } + MidiByte getBarVelocity() const { return m_barVelocity; } + MidiByte getBeatVelocity() const { return m_beatVelocity; } + MidiByte getSubBeatVelocity() const { return m_subBeatVelocity; } + + void setInstrument(InstrumentId id) { m_instrument = id; } + void setBarPitch(MidiByte pitch) { m_barPitch = pitch; } + void setBeatPitch(MidiByte pitch) { m_beatPitch = pitch; } + void setSubBeatPitch(MidiByte pitch) { m_subBeatPitch = pitch; } + void setDepth(int depth) { m_depth = depth; } + void setBarVelocity(MidiByte barVely) { m_barVelocity = barVely; } + void setBeatVelocity(MidiByte beatVely) { m_beatVelocity = beatVely; } + void setSubBeatVelocity(MidiByte subBeatVely) { m_subBeatVelocity = subBeatVely; } + +private: + InstrumentId m_instrument; + MidiByte m_barPitch; + MidiByte m_beatPitch; + MidiByte m_subBeatPitch; + int m_depth; + MidiByte m_barVelocity; + MidiByte m_beatVelocity; + MidiByte m_subBeatVelocity; +}; + + +// MidiFilter is a bitmask of MappedEvent::MappedEventType. +// Look in sound/MappedEvent.h +// +typedef unsigned int MidiFilter; + + +} + +#endif + diff --git a/src/base/MidiTypes.cpp b/src/base/MidiTypes.cpp new file mode 100644 index 0000000..4118502 --- /dev/null +++ b/src/base/MidiTypes.cpp @@ -0,0 +1,320 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "MidiTypes.h" + +namespace Rosegarden +{ + +static MidiByte getByte(const Event &e, const PropertyName &name) { + long value = -1; + try { + value = e.get(name); + } catch (...) { } + if (value < 0 || value > 255) throw MIDIValueOutOfRange(name.getName()); + return MidiByte(value); +} + +////////////////////////////////////////////////////////////////////// +// PitchBend +////////////////////////////////////////////////////////////////////// + +const std::string PitchBend::EventType = "pitchbend"; +const int PitchBend::EventSubOrdering = -5; + +const PropertyName PitchBend::MSB = "msb"; +const PropertyName PitchBend::LSB = "lsb"; + +PitchBend::PitchBend(Rosegarden::MidiByte msb, + Rosegarden::MidiByte lsb) : + m_msb(msb), + m_lsb(lsb) +{ +} + +PitchBend::PitchBend(const Event &e) +{ + if (e.getType() != EventType) { + throw Event::BadType("PitchBend model event", EventType, e.getType()); + } + m_msb = getByte(e, MSB); + m_lsb = getByte(e, LSB); +} + +PitchBend::~PitchBend() +{ +} + +Event* +PitchBend::getAsEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering); + e->set(MSB, (long)m_msb); + e->set(LSB, (long)m_lsb); + return e; +} + + +////////////////////////////////////////////////////////////////////// +// Controller +////////////////////////////////////////////////////////////////////// + +const std::string Controller::EventType = "controller"; +const int Controller::EventSubOrdering = -5; + +const PropertyName Controller::NUMBER = "number"; +const PropertyName Controller::VALUE = "value"; + +Controller::Controller(Rosegarden::MidiByte number, + Rosegarden::MidiByte value): + m_number(number), + m_value(value) +{ +} + +Controller::Controller(const Event &e) +{ + if (e.getType() != EventType) { + throw Event::BadType("Controller model event", EventType, e.getType()); + } + m_number = getByte(e, NUMBER); + m_value = getByte(e, VALUE); +} + +Controller::~Controller() +{ +} + +Event* +Controller::getAsEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering); + e->set(NUMBER, (long)m_number); + e->set(VALUE, (long)m_value); + return e; +} + + +////////////////////////////////////////////////////////////////////// +// Key Pressure +////////////////////////////////////////////////////////////////////// + +const std::string KeyPressure::EventType = "keypressure"; +const int KeyPressure::EventSubOrdering = -5; + +const PropertyName KeyPressure::PITCH = "pitch"; +const PropertyName KeyPressure::PRESSURE = "pressure"; + +KeyPressure::KeyPressure(Rosegarden::MidiByte pitch, + Rosegarden::MidiByte pressure): + m_pitch(pitch), + m_pressure(pressure) +{ +} + +KeyPressure::KeyPressure(const Event &e) +{ + if (e.getType() != EventType) { + throw Event::BadType("KeyPressure model event", EventType, e.getType()); + } + m_pitch = getByte(e, PITCH); + m_pressure = getByte(e, PRESSURE); +} + +KeyPressure::~KeyPressure() +{ +} + +Event* +KeyPressure::getAsEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering); + e->set(PITCH, (long)m_pitch); + e->set(PRESSURE, (long)m_pressure); + return e; +} + + +////////////////////////////////////////////////////////////////////// +// Channel Pressure +////////////////////////////////////////////////////////////////////// + +const std::string ChannelPressure::EventType = "channelpressure"; +const int ChannelPressure::EventSubOrdering = -5; + +const PropertyName ChannelPressure::PRESSURE = "pressure"; + +ChannelPressure::ChannelPressure(Rosegarden::MidiByte pressure): + m_pressure(pressure) +{ +} + +ChannelPressure::ChannelPressure(const Event &e) +{ + if (e.getType() != EventType) { + throw Event::BadType("ChannelPressure model event", EventType, e.getType()); + } + m_pressure = getByte(e, PRESSURE); +} + +ChannelPressure::~ChannelPressure() +{ +} + +Event* +ChannelPressure::getAsEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering); + e->set(PRESSURE, (long)m_pressure); + return e; +} + + +////////////////////////////////////////////////////////////////////// +// ProgramChange +////////////////////////////////////////////////////////////////////// + +const std::string ProgramChange::EventType = "programchange"; +const int ProgramChange::EventSubOrdering = -5; + +const PropertyName ProgramChange::PROGRAM = "program"; + +ProgramChange::ProgramChange(Rosegarden::MidiByte program): + m_program(program) +{ +} + +ProgramChange::ProgramChange(const Event &e) +{ + if (e.getType() != EventType) { + throw Event::BadType("ProgramChange model event", EventType, e.getType()); + } + m_program = getByte(e, PROGRAM); +} + +ProgramChange::~ProgramChange() +{ +} + +Event* +ProgramChange::getAsEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering); + e->set(PROGRAM, (long)m_program); + return e; +} + + +////////////////////////////////////////////////////////////////////// +// SystemExclusive +////////////////////////////////////////////////////////////////////// + +const std::string SystemExclusive::EventType = "systemexclusive"; +const int SystemExclusive::EventSubOrdering = -5; + +const PropertyName SystemExclusive::DATABLOCK = "datablock"; + +SystemExclusive::SystemExclusive(std::string rawData) : + m_rawData(rawData) +{ +} + +SystemExclusive::SystemExclusive(const Event &e) +{ + if (e.getType() != EventType) { + throw Event::BadType("SystemExclusive model event", EventType, e.getType()); + } + std::string datablock; + e.get(DATABLOCK, datablock); + m_rawData = toRaw(datablock); +} + +SystemExclusive::~SystemExclusive() +{ +} + +Event* +SystemExclusive::getAsEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering); + std::string hex(toHex(m_rawData)); + e->set(DATABLOCK, hex); + return e; +} + +std::string +SystemExclusive::toHex(std::string r) +{ + static char hexchars[] = "0123456789ABCDEF"; + std::string h; + for (unsigned int i = 0; i < r.size(); ++i) { + if (i > 0) h += ' '; + unsigned char b = (unsigned char)r[i]; + h += hexchars[(b / 16) % 16]; + h += hexchars[b % 16]; + } + return h; +} + +std::string +SystemExclusive::toRaw(std::string rh) +{ + std::string r; + std::string h; + + // remove whitespace + for (unsigned int i = 0; i < rh.size(); ++i) { + if (!isspace(rh[i])) h += rh[i]; + } + + for (unsigned int i = 0; i < h.size()/2; ++i) { + unsigned char b = toRawNibble(h[2*i]) * 16 + toRawNibble(h[2*i+1]); + r += b; + } + + return r; +} + +unsigned char +SystemExclusive::toRawNibble(char c) +{ + if (islower(c)) c = toupper(c); + if (isdigit(c)) return c - '0'; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + throw BadEncoding(); +} + +bool +SystemExclusive::isHex(std::string rh) +{ + // arf + try { + std::string r = toRaw(rh); + } catch (BadEncoding) { + return false; + } + return true; +} + + +} + diff --git a/src/base/MidiTypes.h b/src/base/MidiTypes.h new file mode 100644 index 0000000..10416a9 --- /dev/null +++ b/src/base/MidiTypes.h @@ -0,0 +1,224 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _MIDI_TYPES_H_ +#define _MIDI_TYPES_H_ + +#include + +#include "Event.h" +#include "Instrument.h" + +// Internal representation of some very MIDI specific event types +// that fall clearly outside of NotationTypes and still require +// representation. +// + + +namespace Rosegarden +{ + +class MIDIValueOutOfRange : public Exception { +public: + MIDIValueOutOfRange(std::string name) : + Exception("Value of " + name + " out of byte range") { } + MIDIValueOutOfRange(std::string name, std::string file, int line) : + Exception("Value of " + name + " out of byte range", file, line) { } +}; + + +// Rosegarden's internal represetation of MIDI PitchBend +// +class PitchBend +{ +public: + static const std::string EventType; + static const int EventSubOrdering; + + static const PropertyName MSB; + static const PropertyName LSB; + + PitchBend(MidiByte msb, MidiByte lsb); + PitchBend(const Event &); + ~PitchBend(); + + MidiByte getMSB() const { return m_msb; } + MidiByte getLSB() const { return m_lsb; } + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsEvent(timeT absoluteTime) const; + +private: + MidiByte m_msb; + MidiByte m_lsb; +}; + + +// Controller +// + +class Controller +{ +public: + static const std::string EventType; + static const int EventSubOrdering; + + static const PropertyName NUMBER; // controller number + static const PropertyName VALUE; // and value + + Controller(MidiByte number, + MidiByte value); + + Controller(const Event &); + ~Controller(); + + MidiByte getNumber() const { return m_number; } + MidiByte getValue() const { return m_value; } + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsEvent(timeT absoluteTime) const; + +private: + MidiByte m_number; + MidiByte m_value; + +}; + + +// Key pressure +// + +class KeyPressure +{ +public: + static const std::string EventType; + static const int EventSubOrdering; + + static const PropertyName PITCH; + static const PropertyName PRESSURE; + + KeyPressure(MidiByte pitch, MidiByte pressure); + KeyPressure(const Event &event); + ~KeyPressure(); + + MidiByte getPitch() const { return m_pitch; } + MidiByte getPressure() const { return m_pressure; } + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsEvent(timeT absoluteTime) const; + +private: + MidiByte m_pitch; + MidiByte m_pressure; +}; + + +// Channel pressure +// + +class ChannelPressure +{ +public: + static const std::string EventType; + static const int EventSubOrdering; + + static const PropertyName PRESSURE; + + ChannelPressure(MidiByte pressure); + ChannelPressure(const Event &event); + ~ChannelPressure(); + + MidiByte getPressure() const { return m_pressure; } + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsEvent(timeT absoluteTime) const; + +private: + MidiByte m_pressure; +}; + + +// Program Change +// + +class ProgramChange +{ +public: + static const std::string EventType; + static const int EventSubOrdering; + + static const PropertyName PROGRAM; + + ProgramChange(MidiByte program); + ProgramChange(const Event &event); + ~ProgramChange(); + + MidiByte getProgram() const { return m_program; } + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsEvent(timeT absoluteTime) const; + +private: + MidiByte m_program; +}; + + +// System exclusive +// + +class SystemExclusive +{ +public: + static const std::string EventType; + static const int EventSubOrdering; + + struct BadEncoding : public Exception { + BadEncoding() : Exception("Bad SysEx encoding") { } + }; + + static const PropertyName DATABLOCK; + + SystemExclusive(std::string rawData); + SystemExclusive(const Event &event); + ~SystemExclusive(); + + std::string getRawData() const { return m_rawData; } + std::string getHexData() const { return toHex(m_rawData); } + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsEvent(timeT absoluteTime) const; + + static std::string toHex(std::string rawData); + static std::string toRaw(std::string hexData); + static bool isHex(std::string data); + +private: + std::string m_rawData; + static unsigned char toRawNibble(char); +}; + + + +} + + +#endif diff --git a/src/base/NotationQuantizer.cpp b/src/base/NotationQuantizer.cpp new file mode 100644 index 0000000..9e76a94 --- /dev/null +++ b/src/base/NotationQuantizer.cpp @@ -0,0 +1,1205 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "NotationQuantizer.h" +#include "BaseProperties.h" +#include "NotationTypes.h" +#include "Selection.h" +#include "Composition.h" +#include "Sets.h" +#include "Profiler.h" + +#include +#include +#include // for sprintf +#include + +using std::cout; +using std::cerr; +using std::endl; + +//#define DEBUG_NOTATION_QUANTIZER 1 + +namespace Rosegarden { + +using namespace BaseProperties; + +class NotationQuantizer::Impl +{ +public: + Impl(NotationQuantizer *const q) : + m_unit(Note(Note::Demisemiquaver).getDuration()), + m_simplicityFactor(13), + m_maxTuplet(3), + m_articulate(true), + m_q(q), + m_provisionalBase("notationquantizer-provisionalBase"), + m_provisionalAbsTime("notationquantizer-provisionalAbsTime"), + m_provisionalDuration("notationquantizer-provisionalDuration"), + m_provisionalNoteType("notationquantizer-provisionalNoteType"), + m_provisionalScore("notationquantizer-provisionalScore") + { } + + Impl(const Impl &i) : + m_unit(i.m_unit), + m_simplicityFactor(i.m_simplicityFactor), + m_maxTuplet(i.m_maxTuplet), + m_articulate(i.m_articulate), + m_q(i.m_q), + m_provisionalBase(i.m_provisionalBase), + m_provisionalAbsTime(i.m_provisionalAbsTime), + m_provisionalDuration(i.m_provisionalDuration), + m_provisionalNoteType(i.m_provisionalNoteType), + m_provisionalScore(i.m_provisionalScore) + { } + + class ProvisionalQuantizer : public Quantizer { + // This class exists only to pick out the provisional abstime + // and duration values from half-quantized events, so that we + // can treat them using the normal Chord class + public: + ProvisionalQuantizer(Impl *i) : Quantizer("blah", "blahblah"), m_impl(i) { } + virtual timeT getQuantizedDuration(const Event *e) const { + return m_impl->getProvisional((Event *)e, DurationValue); + } + virtual timeT getQuantizedAbsoluteTime(const Event *e) const { + timeT t = m_impl->getProvisional((Event *)e, AbsoluteTimeValue); +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "ProvisionalQuantizer::getQuantizedAbsoluteTime: returning " << t << endl; +#endif + return t; + } + + private: + Impl *m_impl; + }; + + void quantizeRange(Segment *, + Segment::iterator, + Segment::iterator) const; + + void quantizeAbsoluteTime(Segment *, Segment::iterator) const; + long scoreAbsoluteTimeForBase(Segment *, const Segment::iterator &, + int depth, timeT base, timeT sigTime, + timeT t, timeT d, int noteType, + const Segment::iterator &, + const Segment::iterator &, + bool &right) const; + void quantizeDurationProvisional(Segment *, Segment::iterator) const; + void quantizeDuration(Segment *, Chord &) const; + + void scanTupletsInBar(Segment *, + timeT barStart, timeT barDuration, + timeT wholeStart, timeT wholeDuration, + const std::vector &divisions) const; + void scanTupletsAt(Segment *, Segment::iterator, int depth, + timeT base, timeT barStart, + timeT tupletStart, timeT tupletBase) const; + bool isValidTupletAt(Segment *, const Segment::iterator &, + int depth, timeT base, timeT sigTime, + timeT tupletBase) const; + + void setProvisional(Event *, ValueType value, timeT t) const; + timeT getProvisional(Event *, ValueType value) const; + void unsetProvisionalProperties(Event *) const; + + timeT m_unit; + int m_simplicityFactor; + int m_maxTuplet; + bool m_articulate; + bool m_contrapuntal; + +private: + NotationQuantizer *const m_q; + + PropertyName m_provisionalBase; + PropertyName m_provisionalAbsTime; + PropertyName m_provisionalDuration; + PropertyName m_provisionalNoteType; + PropertyName m_provisionalScore; +}; + +NotationQuantizer::NotationQuantizer() : + Quantizer(NotationPrefix), + m_impl(new Impl(this)) +{ + // nothing else +} + +NotationQuantizer::NotationQuantizer(std::string source, std::string target) : + Quantizer(source, target), + m_impl(new Impl(this)) +{ + // nothing else +} + +NotationQuantizer::NotationQuantizer(const NotationQuantizer &q) : + Quantizer(q.m_target), + m_impl(new Impl(*q.m_impl)) +{ + // nothing else +} + +NotationQuantizer::~NotationQuantizer() +{ + delete m_impl; +} + +void +NotationQuantizer::setUnit(timeT unit) +{ + m_impl->m_unit = unit; +} + +timeT +NotationQuantizer::getUnit() const +{ + return m_impl->m_unit; +} + +void +NotationQuantizer::setMaxTuplet(int m) +{ + m_impl->m_maxTuplet = m; +} + +int +NotationQuantizer::getMaxTuplet() const +{ + return m_impl->m_maxTuplet; +} + +void +NotationQuantizer::setSimplicityFactor(int s) +{ + m_impl->m_simplicityFactor = s; +} + +int +NotationQuantizer::getSimplicityFactor() const +{ + return m_impl->m_simplicityFactor; +} + +void +NotationQuantizer::setContrapuntal(bool c) +{ + m_impl->m_contrapuntal = c; +} + +bool +NotationQuantizer::getContrapuntal() const +{ + return m_impl->m_contrapuntal; +} + +void +NotationQuantizer::setArticulate(bool a) +{ + m_impl->m_articulate = a; +} + +bool +NotationQuantizer::getArticulate() const +{ + return m_impl->m_articulate; +} + +void +NotationQuantizer::Impl::setProvisional(Event *e, ValueType v, timeT t) const +{ + if (v == AbsoluteTimeValue) { + e->setMaybe(m_provisionalAbsTime, t); + } else { + e->setMaybe(m_provisionalDuration, t); + } +} + +timeT +NotationQuantizer::Impl::getProvisional(Event *e, ValueType v) const +{ + timeT t; + if (v == AbsoluteTimeValue) { + t = e->getAbsoluteTime(); + e->get(m_provisionalAbsTime, t); + } else { + t = e->getDuration(); + e->get(m_provisionalDuration, t); + } + return t; +} + +void +NotationQuantizer::Impl::unsetProvisionalProperties(Event *e) const +{ + e->unset(m_provisionalBase); + e->unset(m_provisionalAbsTime); + e->unset(m_provisionalDuration); + e->unset(m_provisionalNoteType); + e->unset(m_provisionalScore); +} + +void +NotationQuantizer::Impl::quantizeAbsoluteTime(Segment *s, Segment::iterator i) const +{ + Profiler profiler("NotationQuantizer::Impl::quantizeAbsoluteTime"); + + Composition *comp = s->getComposition(); + + TimeSignature timeSig; + timeT t = m_q->getFromSource(*i, AbsoluteTimeValue); + timeT sigTime = comp->getTimeSignatureAt(t, timeSig); + + timeT d = getProvisional(*i, DurationValue); + int noteType = Note::getNearestNote(d).getNoteType(); + (*i)->setMaybe(m_provisionalNoteType, noteType); + + int maxDepth = 8 - noteType; + if (maxDepth < 4) maxDepth = 4; + std::vector divisions; + timeSig.getDivisions(maxDepth, divisions); + if (timeSig == TimeSignature()) // special case for 4/4 + divisions[0] = 2; + + // At each depth of beat subdivision, we find the closest match + // and assign it a score according to distance and depth. The + // calculation for the score should accord "better" scores to + // shorter distance and lower depth, but it should avoid giving + // a "perfect" score to any combination of distance and depth + // except where both are 0. Also, the effective depth is + // 2 more than the value of our depth counter, which counts + // from 0 at a point where the effective depth is already 1. + + timeT base = timeSig.getBarDuration(); + + timeT bestBase = -2; + long bestScore = 0; + bool bestRight = false; + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "quantizeAbsoluteTime: t is " << t << ", d is " << d << endl; +#endif + + // scoreAbsoluteTimeForBase wants to know the previous starting + // note (N) and the previous starting note that ends (roughly) + // before this one starts (N'). Much more efficient to calculate + // them once now before the loop. + + static timeT shortTime = Note(Note::Shortest).getDuration(); + + Segment::iterator j(i); + Segment::iterator n(s->end()), nprime(s->end()); + for (;;) { + if (j == s->begin()) break; + --j; + if ((*j)->isa(Note::EventType)) { + if (n == s->end()) n = j; + if ((*j)->getAbsoluteTime() + (*j)->getDuration() + shortTime/2 + <= (*i)->getAbsoluteTime()) { + nprime = j; + break; + } + } + } + +#ifdef DEBUG_NOTATION_QUANTIZER + if (n != s->end() && n != nprime) { + cout << "found n (distinct from nprime) at " << (*n)->getAbsoluteTime() << endl; + } + if (nprime != s->end()) { + cout << "found nprime at " << (*nprime)->getAbsoluteTime() + << ", duration " << (*nprime)->getDuration() << endl; + } +#endif + + for (int depth = 0; depth < maxDepth; ++depth) { + + base /= divisions[depth]; + if (base < m_unit) break; + bool right = false; + long score = scoreAbsoluteTimeForBase(s, i, depth, base, sigTime, + t, d, noteType, n, nprime, right); + + if (depth == 0 || score < bestScore) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << " [*]"; +#endif + bestBase = base; + bestScore = score; + bestRight = right; + } + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << endl; +#endif + } + + if (bestBase == -2) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "Quantizer::quantizeAbsoluteTime: weirdness: no snap found" << endl; +#endif + } else { + // we need to snap relative to the time sig, not relative + // to the start of the whole composition + t -= sigTime; + + t = (t / bestBase) * bestBase; + if (bestRight) t += bestBase; + +/* + timeT low = (t / bestBase) * bestBase; + timeT high = low + bestBase; + t = ((high - t > t - low) ? low : high); +*/ + + t += sigTime; + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "snap base is " << bestBase << ", snapped to " << t << endl; +#endif + } + + setProvisional(*i, AbsoluteTimeValue, t); + (*i)->setMaybe(m_provisionalBase, bestBase); + (*i)->setMaybe(m_provisionalScore, bestScore); +} + +long +NotationQuantizer::Impl::scoreAbsoluteTimeForBase(Segment *s, + const Segment::iterator & /* i */, + int depth, + timeT base, + timeT sigTime, + timeT t, + timeT d, + int noteType, + const Segment::iterator &n, + const Segment::iterator &nprime, + bool &wantRight) + const +{ + Profiler profiler("NotationQuantizer::Impl::scoreAbsoluteTimeForBase"); + + // Lower score is better. + + static timeT shortTime = Note(Note::Shortest).getDuration(); + + double simplicityFactor(m_simplicityFactor); + simplicityFactor -= Note::Crotchet - noteType; + if (simplicityFactor < 10) simplicityFactor = 10; + + double effectiveDepth = pow(depth + 2, simplicityFactor / 10); + + //!!! use velocity to adjust the effective depth as well? -- louder + // notes are more likely to be on big boundaries. Actually, perhaps + // introduce a generally-useful "salience" score a la Dixon et al + + long leftScore = 0; + + for (int ri = 0; ri < 2; ++ri) { + + bool right = (ri == 1); + + long distance = (t - sigTime) % base; + if (right) distance = base - distance; + long score = long((distance + shortTime / 2) * effectiveDepth); + + double penalty1 = 1.0; + + // seriously penalise moving a note beyond its own end time + if (d > 0 && right && distance >= d * 0.9) { + penalty1 = double(distance) / d + 0.5; + } + + double penalty2 = 1.0; + + // Examine the previous starting note (N), and the previous + // starting note that ends before this one starts (N'). + + // We should penalise moving this note to before the performed end + // of N' and seriously penalise moving it to the same quantized + // start time as N' -- but we should encourage moving it to the + // same time as the provisional end of N', or to the same start + // time as N if N != N'. + + if (!right) { + if (n != s->end()) { + if (n != nprime) { + timeT nt = getProvisional(*n, AbsoluteTimeValue); + if (t - distance == nt) penalty2 = penalty2 * 2 / 3; + } + if (nprime != s->end()) { + timeT npt = getProvisional(*nprime, AbsoluteTimeValue); + timeT npd = getProvisional(*nprime, DurationValue); + if (t - distance <= npt) penalty2 *= 4; + else if (t - distance < npt + npd) penalty2 *= 2; + else if (t - distance == npt + npd) penalty2 = penalty2 * 2 / 3; + } + } + } + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << " depth/eff/dist/t/score/pen1/pen2/res: " << depth << "/" << effectiveDepth << "/" << distance << "/" << (right ? t + distance : t - distance) << "/" << score << "/" << penalty1 << "/" << penalty2 << "/" << (score * penalty1 * penalty2); + if (right) cout << " -> "; + else cout << " <- "; + if (ri == 0) cout << endl; +#endif + + score = long(score * penalty1); + score = long(score * penalty2); + + if (ri == 0) { + leftScore = score; + } else { + if (score < leftScore) { + wantRight = true; + return score; + } else { + wantRight = false; + return leftScore; + } + } + } + + return leftScore; +} + +void +NotationQuantizer::Impl::quantizeDurationProvisional(Segment *, Segment::iterator i) + const +{ + Profiler profiler("NotationQuantizer::Impl::quantizeDurationProvisional"); + + // Calculate a first guess at the likely notation duration based + // only on its performed duration, without considering start time. + + timeT d = m_q->getFromSource(*i, DurationValue); + if (d == 0) { + setProvisional(*i, DurationValue, d); + return; + } + + Note shortNote = Note::getNearestNote(d, 2); + + timeT shortTime = shortNote.getDuration(); + timeT time = shortTime; + + if (shortTime != d) { + + Note longNote(shortNote); + + if ((shortNote.getDots() > 0 || + shortNote.getNoteType() == Note::Shortest)) { // can't dot that + + if (shortNote.getNoteType() < Note::Longest) { + longNote = Note(shortNote.getNoteType() + 1, 0); + } + + } else { + longNote = Note(shortNote.getNoteType(), 1); + } + + timeT longTime = longNote.getDuration(); + + // we should prefer to round up to a note with fewer dots rather + // than down to one with more + + //!!! except in dotted time, etc -- we really want this to work on a + // similar attraction-to-grid basis to the abstime quantization + + if ((longNote.getDots() + 1) * (longTime - d) < + (shortNote.getDots() + 1) * (d - shortTime)) { + time = longTime; + } + } + + setProvisional(*i, DurationValue, time); + + if ((*i)->has(BEAMED_GROUP_TUPLET_BASE)) { + // We're going to recalculate these, and use our own results + (*i)->unset(BEAMED_GROUP_ID); + (*i)->unset(BEAMED_GROUP_TYPE); + (*i)->unset(BEAMED_GROUP_TUPLET_BASE); + (*i)->unset(BEAMED_GROUP_TUPLED_COUNT); + (*i)->unset(BEAMED_GROUP_UNTUPLED_COUNT); +//!!! (*i)->unset(TUPLET_NOMINAL_DURATION); + } +} + +void +NotationQuantizer::Impl::quantizeDuration(Segment *s, Chord &c) const +{ + static int totalFracCount = 0; + static float totalFrac = 0; + + Profiler profiler("NotationQuantizer::Impl::quantizeDuration"); + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "quantizeDuration: chord has " << c.size() << " notes" << endl; +#endif + + Composition *comp = s->getComposition(); + + TimeSignature timeSig; +// timeT t = m_q->getFromSource(*c.getInitialElement(), AbsoluteTimeValue); +// timeT sigTime = comp->getTimeSignatureAt(t, timeSig); + + timeT d = getProvisional(*c.getInitialElement(), DurationValue); + int noteType = Note::getNearestNote(d).getNoteType(); + int maxDepth = 8 - noteType; + if (maxDepth < 4) maxDepth = 4; + std::vector divisions; + timeSig.getDivisions(maxDepth, divisions); + + Segment::iterator nextNote = c.getNextNote(); + timeT nextNoteTime = + (s->isBeforeEndMarker(nextNote) ? + getProvisional(*nextNote, AbsoluteTimeValue) : + s->getEndMarkerTime()); + + timeT nonContrapuntalDuration = 0; + + for (Chord::iterator ci = c.begin(); ci != c.end(); ++ci) { + + if (!(**ci)->isa(Note::EventType)) continue; + if ((**ci)->has(m_provisionalDuration) && + (**ci)->has(BEAMED_GROUP_TUPLET_BASE)) { + // dealt with already in tuplet code, we'd only mess it up here +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "not recalculating duration for tuplet" << endl; +#endif + continue; + } + + timeT ud = 0; + + if (!m_contrapuntal) { + // if not contrapuntal, give all notes in chord equal duration + if (nonContrapuntalDuration > 0) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "setting duration trivially to " << nonContrapuntalDuration << endl; +#endif + setProvisional(**ci, DurationValue, nonContrapuntalDuration); + continue; + } else { + // establish whose duration to use, then set it at the + // bottom after it's been quantized + Segment::iterator li = c.getLongestElement(); + if (li != s->end()) ud = m_q->getFromSource(*li, DurationValue); + else ud = m_q->getFromSource(**ci, DurationValue); + } + } else { + ud = m_q->getFromSource(**ci, DurationValue); + } + + timeT qt = getProvisional(**ci, AbsoluteTimeValue); + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "note at time " << (**ci)->getAbsoluteTime() << " (provisional time " << qt << ")" << endl; +#endif + + timeT base = timeSig.getBarDuration(); + std::pair bases; + for (int depth = 0; depth < maxDepth; ++depth) { + if (base >= ud) { + bases = std::pair(base / divisions[depth], base); + } + base /= divisions[depth]; + } + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "duration is " << ud << ", probably between " + << bases.first << " and " << bases.second << endl; +#endif + + timeT qd = getProvisional(**ci, DurationValue); + + timeT spaceAvailable = nextNoteTime - qt; + + if (spaceAvailable > 0) { + float frac = float(ud) / float(spaceAvailable); + totalFrac += frac; + totalFracCount += 1; + } + + if (!m_contrapuntal && qd > spaceAvailable) { + + qd = Note::getNearestNote(spaceAvailable).getDuration(); + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "non-contrapuntal segment, rounded duration down to " + << qd << " (as only " << spaceAvailable << " available)" + << endl; +#endif + + } else { + + //!!! Note longer than the longest note we have. Deal with + //this -- how? Quantize the end time? Split the note? + //(Prefer to do that in a separate phase later if requested.) + //Leave it as it is? (Yes, for now.) + if (bases.first == 0) return; + + timeT absTimeBase = bases.first; + (**ci)->get(m_provisionalBase, absTimeBase); + + spaceAvailable = std::min(spaceAvailable, + comp->getBarEndForTime(qt) - qt); + + // We have a really good possibility of staccato if we have a + // note on a boundary whose base is double the note duration + // and there's nothing else until the next boundary and we're + // shorter than about a quaver (i.e. the base is a quaver or + // less) + + if (qd*2 <= absTimeBase && (qd*8/3) >= absTimeBase && + bases.second == absTimeBase) { + + if (nextNoteTime >= qt + bases.second) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "We rounded to " << qd + << " but we're on " << absTimeBase << " absTimeBase" + << " and the next base is " << bases.second + << " and we have room for it, so" + << " rounding up again" << endl; +#endif + qd = bases.second; + } + + } else { + + // Alternatively, if we rounded down but there's space to + // round up, consider doing so + + //!!! mark staccato if necessary, and take existing marks into account + + Note note(Note::getNearestNote(qd)); + + if (qd < ud || (qd == ud && note.getDots() == 2)) { + + if (note.getNoteType() < Note::Longest) { + + if (bases.second <= spaceAvailable) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "We rounded down to " << qd + << " but have room for " << bases.second + << ", rounding up again" << endl; +#endif + qd = bases.second; + } else { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "We rounded down to " << qd + << "; can't fit " << bases.second << endl; +#endif + } + } + } + } + } + + setProvisional(**ci, DurationValue, qd); + if (!m_contrapuntal) nonContrapuntalDuration = qd; + } + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "totalFrac " << totalFrac << ", totalFracCount " << totalFracCount << ", avg " << (totalFracCount > 0 ? (totalFrac / totalFracCount) : 0) << endl; +#endif +} + + +void +NotationQuantizer::Impl::scanTupletsInBar(Segment *s, + timeT barStart, + timeT barDuration, + timeT wholeStart, + timeT wholeEnd, + const std::vector &divisions) const +{ + Profiler profiler("NotationQuantizer::Impl::scanTupletsInBar"); + + //!!! need to further constrain the area scanned so as to cope with + // partial bars + + timeT base = barDuration; + + for (int depth = -1; depth < int(divisions.size()) - 2; ++depth) { + + if (depth >= 0) base /= divisions[depth]; + if (base <= Note(Note::Semiquaver).getDuration()) break; + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "\nscanTupletsInBar: trying at depth " << depth << " (base " << base << ")" << endl; +#endif + + // check for triplets if our next divisor is 2 and the following + // one is not 3 + + if (divisions[depth+1] != 2 || divisions[depth+2] == 3) continue; + + timeT tupletBase = base / 3; + timeT tupletStart = barStart; + + while (tupletStart < barStart + barDuration) { + + timeT tupletEnd = tupletStart + base; + if (tupletStart < wholeStart || tupletEnd > wholeEnd) { + tupletStart = tupletEnd; + continue; + } + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "scanTupletsInBar: testing " << tupletStart << "," << base << " at tuplet base " << tupletBase << endl; +#endif + + // find first note within a certain distance whose start time + // quantized to tupletStart or greater + Segment::iterator j = s->findTime(tupletStart - tupletBase / 3); + timeT jTime = tupletEnd; + + while (s->isBeforeEndMarker(j) && + (!(*j)->isa(Note::EventType) || + !(*j)->get(m_provisionalAbsTime, jTime) || + jTime < tupletStart)) { + if ((*j)->getAbsoluteTime() > tupletEnd + tupletBase / 3) { + break; + } + ++j; + } + + if (jTime >= tupletEnd) { // nothing to make tuplets of +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "scanTupletsInBar: nothing here" << endl; +#endif + tupletStart = tupletEnd; + continue; + } + + scanTupletsAt(s, j, depth+1, base, barStart, + tupletStart, tupletBase); + + tupletStart = tupletEnd; + } + } +} + + +void +NotationQuantizer::Impl::scanTupletsAt(Segment *s, + Segment::iterator i, + int depth, + timeT base, + timeT sigTime, + timeT tupletStart, + timeT tupletBase) const +{ + Profiler profiler("NotationQuantizer::Impl::scanTupletsAt"); + + Segment::iterator j = i; + timeT tupletEnd = tupletStart + base; + timeT jTime = tupletEnd; + + std::vector candidates; + int count = 0; + + while (s->isBeforeEndMarker(j) && + ((*j)->isa(Note::EventRestType) || + ((*j)->get(m_provisionalAbsTime, jTime) && + jTime < tupletEnd))) { + + if (!(*j)->isa(Note::EventType)) { ++j; continue; } + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "scanTupletsAt time " << jTime << " (unquantized " + << (*j)->getAbsoluteTime() << "), found note" << endl; +#endif + + // reject any group containing anything already a tuplet + if ((*j)->has(BEAMED_GROUP_TUPLET_BASE)) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "already made tuplet here" << endl; +#endif + return; + } + + timeT originalBase; + + if (!(*j)->get(m_provisionalBase, originalBase)) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "some notes not provisionally quantized, no good" << endl; +#endif + return; + } + + if (originalBase == base) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "accepting note at original base" << endl; +#endif + candidates.push_back(*j); + } else if (((jTime - sigTime) % base) == 0) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "accepting note that happens to lie on original base" << endl; +#endif + candidates.push_back(*j); + } else { + + // This is a note that did not quantize to the original base + // (the first note in the tuplet would have, but we can't tell + // anything from that). Reject the entire group if it fails + // any of the likelihood tests for tuplets. + + if (!isValidTupletAt(s, j, depth, base, sigTime, tupletBase)) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "no good" << endl; +#endif + return; + } + + candidates.push_back(*j); + ++count; + } + + ++j; + } + + // must have at least one note that is not already quantized to the + // original base + if (count < 1) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "scanTupletsAt: found no note not already quantized to " << base << endl; +#endif + return; + } + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "scanTupletsAt: Tuplet group of duration " << base << " starting at " << tupletStart << endl; +#endif + + // Woo-hoo! It looks good. + + int groupId = s->getNextId(); + std::map multiples; + + for (std::vector::iterator ei = candidates.begin(); + ei != candidates.end(); ++ei) { + + //!!! Interesting -- we can't modify rests here, but Segment's + // normalizeRests won't insert the correct sort of rest for us... + // what to do? + //!!! insert a tupleted rest, and prevent Segment::normalizeRests + // from messing about with it + if (!(*ei)->isa(Note::EventType)) continue; + (*ei)->set(BEAMED_GROUP_TYPE, GROUP_TYPE_TUPLED); + + //!!! This is too easy, because we rejected any notes of + //durations not conforming to a single multiple of the + //tupletBase in isValidTupletAt + + (*ei)->set(BEAMED_GROUP_ID, groupId); + (*ei)->set(BEAMED_GROUP_TUPLET_BASE, base/2); //!!! wrong if tuplet count != 3 + (*ei)->set(BEAMED_GROUP_TUPLED_COUNT, 2); //!!! as above + (*ei)->set(BEAMED_GROUP_UNTUPLED_COUNT, base/tupletBase); + + timeT t = (*ei)->getAbsoluteTime(); + t -= tupletStart; + timeT low = (t / tupletBase) * tupletBase; + timeT high = low + tupletBase; + t = ((high - t > t - low) ? low : high); + + multiples[t / tupletBase] = true; + + t += tupletStart; + + setProvisional(*ei, AbsoluteTimeValue, t); + setProvisional(*ei, DurationValue, tupletBase); + } + + // fill in with tupleted rests + + for (int m = 0; m < base / tupletBase; ++m) { + + if (multiples[m]) continue; + + timeT absTime = tupletStart + m * tupletBase; + timeT duration = tupletBase; +//!!! while (multiples[++m]) duration += tupletBase; + + Event *rest = new Event(Note::EventRestType, absTime, duration); + + rest->set(BEAMED_GROUP_TYPE, GROUP_TYPE_TUPLED); + rest->set(BEAMED_GROUP_ID, groupId); + rest->set(BEAMED_GROUP_TUPLET_BASE, base/2); //!!! wrong if tuplet count != 3 + rest->set(BEAMED_GROUP_TUPLED_COUNT, 2); //!!! as above + rest->set(BEAMED_GROUP_UNTUPLED_COUNT, base/tupletBase); + + m_q->m_toInsert.push_back(rest); + } +} + +bool +NotationQuantizer::Impl::isValidTupletAt(Segment *s, + const Segment::iterator &i, + int depth, + timeT /* base */, + timeT sigTime, + timeT tupletBase) const +{ + Profiler profiler("NotationQuantizer::Impl::isValidTupletAt"); + + //!!! This is basically wrong; we need to be able to deal with groups + // that contain e.g. a crotchet and a quaver, tripleted. + + timeT ud = m_q->getFromSource(*i, DurationValue); + + if (ud > (tupletBase * 5 / 4)) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "\nNotationQuantizer::isValidTupletAt: note too long at " + << (*i)->getDuration() << " (tupletBase is " << tupletBase << ")" + << endl; +#endif + return false; // too long + } + + //!!! This bit is a cop-out. It means we reject anything that looks + // like it's going to have rests in it. Bah. + if (ud <= (tupletBase * 3 / 8)) { +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "\nNotationQuantizer::isValidTupletAt: note too short at " + << (*i)->getDuration() << " (tupletBase is " << tupletBase << ")" + << endl; +#endif + return false; + } + + long score = 0; + if (!(*i)->get(m_provisionalScore, score)) return false; + + timeT t = m_q->getFromSource(*i, AbsoluteTimeValue); + timeT d = getProvisional(*i, DurationValue); + int noteType = (*i)->get(m_provisionalNoteType); + + //!!! not as complete as the calculation we do in the original scoring + bool dummy; + long tupletScore = scoreAbsoluteTimeForBase + (s, i, depth, tupletBase, sigTime, t, d, noteType, s->end(), s->end(), dummy); +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "\nNotationQuantizer::isValidTupletAt: score " << score + << " vs tupletScore " << tupletScore << endl; +#endif + return (tupletScore < score); +} + + +void +NotationQuantizer::quantizeRange(Segment *s, + Segment::iterator from, + Segment::iterator to) const +{ + m_impl->quantizeRange(s, from, to); +} + +void +NotationQuantizer::Impl::quantizeRange(Segment *s, + Segment::iterator from, + Segment::iterator to) const +{ + Profiler *profiler = new Profiler("NotationQuantizer::Impl::quantizeRange"); + + clock_t start = clock(); + int events = 0, notes = 0, passes = 0; + int setGood = 0, setBad = 0; + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "NotationQuantizer::Impl::quantizeRange: from time " + << (from == s->end() ? -1 : (*from)->getAbsoluteTime()) + << " to " + << (to == s->end() ? -1 : (*to)->getAbsoluteTime()) + << endl; +#endif + + timeT segmentEndTime = s->getEndMarkerTime(); + + // This process does several passes over the data. It's assumed + // that this is not going to be invoked in any really time-critical + // place. + + // Calculate absolute times on the first pass, so that we know + // which things are chords. We need to assign absolute times to + // all events, but we only need do durations for notes. + + PropertyName provisionalBase("notationquantizer-provisionalBase"); + + // We don't use setToTarget until we have our final values ready, + // as it erases and replaces the events. Just set the properties. + + // Set a provisional duration to each note first + + for (Segment::iterator i = from; i != to; ++i) { + + ++events; + if ((*i)->isa(Note::EventRestType)) continue; + if ((*i)->isa(Note::EventType)) ++notes; + quantizeDurationProvisional(s, i); + } + ++passes; + + // now do the absolute-time calculation + + timeT wholeStart = 0, wholeEnd = 0; + + Segment::iterator i = from; + + for (Segment::iterator nexti = i; i != to; i = nexti) { + + ++nexti; + + if ((*i)->isa(Note::EventRestType)) { + if (i == from) ++from; + s->erase(i); + continue; + } + + quantizeAbsoluteTime(s, i); + + timeT t0 = (*i)->get(m_provisionalAbsTime); + timeT t1 = (*i)->get(m_provisionalDuration) + t0; + if (wholeStart == wholeEnd) { + wholeStart = t0; + wholeEnd = t1; + } else if (t1 > wholeEnd) { + wholeEnd = t1; + } + } + ++passes; + + // now we've grouped into chords, look for tuplets next + + Composition *comp = s->getComposition(); + + if (m_maxTuplet >= 2) { + + std::vector divisions; + comp->getTimeSignatureAt(wholeStart).getDivisions(7, divisions); + + for (int barNo = comp->getBarNumber(wholeStart); + barNo <= comp->getBarNumber(wholeEnd); ++barNo) { + + bool isNew = false; + TimeSignature timeSig = comp->getTimeSignatureInBar(barNo, isNew); + if (isNew) timeSig.getDivisions(7, divisions); + scanTupletsInBar(s, comp->getBarStart(barNo), + timeSig.getBarDuration(), + wholeStart, wholeEnd, divisions); + } + ++passes; + } + + ProvisionalQuantizer provisionalQuantizer((Impl *)this); + + for (i = from; i != to; ++i) { + + if (!(*i)->isa(Note::EventType)) continue; + + // could potentially supply clef and key here, but at the + // moment Chord doesn't do anything with them (unlike + // NotationChord) and we don't have any really clever + // ideas for how to use them here anyway +// Chord c(*s, i, m_q); + Chord c(*s, i, &provisionalQuantizer); + + quantizeDuration(s, c); + + bool ended = false; + for (Segment::iterator ci = c.getInitialElement(); + s->isBeforeEndMarker(ci); ++ci) { + if (ci == to) ended = true; + if (ci == c.getFinalElement()) break; + } + if (ended) break; + + i = c.getFinalElement(); + } + ++passes; + + // staccato (we now do slurs separately, in SegmentNotationHelper::autoSlur) + + if (m_articulate) { + + for (i = from; i != to; ++i) { + + if (!(*i)->isa(Note::EventType)) continue; + + timeT qd = getProvisional(*i, DurationValue); + timeT ud = m_q->getFromSource(*i, DurationValue); + + if (ud < (qd * 3 / 4) && + qd <= Note(Note::Crotchet).getDuration()) { + Marks::addMark(**i, Marks::Staccato, true); + } else if (ud > qd) { + Marks::addMark(**i, Marks::Tenuto, true); + } + } + ++passes; + } + + i = from; + + for (Segment::iterator nexti = i; i != to; i = nexti) { + + ++nexti; + + if ((*i)->isa(Note::EventRestType)) continue; + + timeT t = getProvisional(*i, AbsoluteTimeValue); + timeT d = getProvisional(*i, DurationValue); + + unsetProvisionalProperties(*i); + + if ((*i)->getAbsoluteTime() == t && + (*i)->getDuration() == d) ++setBad; + else ++setGood; + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "Setting to target at " << t << "," << d << endl; +#endif + + m_q->setToTarget(s, i, t, d); + } + ++passes; +/* + cerr << "NotationQuantizer: " << events << " events (" + << notes << " notes), " << passes << " passes, " + << setGood << " good sets, " << setBad << " bad sets, " + << ((clock() - start) * 1000 / CLOCKS_PER_SEC) << "ms elapsed" + << endl; +*/ + if (s->getEndTime() < segmentEndTime) { + s->setEndMarkerTime(segmentEndTime); + } + + delete profiler; // on heap so it updates before the next line: + Profiles::getInstance()->dump(); + +} + + +} + diff --git a/src/base/NotationQuantizer.h b/src/base/NotationQuantizer.h new file mode 100644 index 0000000..87b0d72 --- /dev/null +++ b/src/base/NotationQuantizer.h @@ -0,0 +1,93 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 NOTATION_QUANTIZER_H_ +#define NOTATION_QUANTIZER_H_ + +#include "Quantizer.h" + +namespace Rosegarden { + +class NotationQuantizer : public Quantizer +{ +public: + NotationQuantizer(); + NotationQuantizer(std::string source, std::string target); + NotationQuantizer(const NotationQuantizer &); + ~NotationQuantizer(); + + /** + * Set the absolute time minimum unit. Default is demisemiquaver. + */ + void setUnit(timeT); + timeT getUnit() const; + + /** + * Set the simplicity factor. This controls the relative "pull" + * towards larger units and more obvious beats in placing notes. + * The value 10 means no pull to larger units, lower values mean + * an active pull away from them. Default is 13. + */ + void setSimplicityFactor(int); + int getSimplicityFactor() const; + + /** + * Set the maximum size of tuplet group. 2 = two-in-the-time-of-three + * groupings, 3 = triplets, etc. Default is 3. Set <2 to switch off + * tuplets altogether. + */ + void setMaxTuplet(int); + int getMaxTuplet() const; + + /** + * Set whether we assume the music may be contrapuntal -- that is, + * may have notes that overlap rather than simply a sequence of + * individual notes and chords. + */ + void setContrapuntal(bool); + bool getContrapuntal() const; + + /** + * Set whether to add articulations (staccato, tenuto, slurs). + * Default is true. Doesn't affect quantization, only the marks + * that are added to quantized notes. + */ + void setArticulate(bool); + bool getArticulate() const; + +protected: + virtual void quantizeRange(Segment *, + Segment::iterator, + Segment::iterator) const; + +protected: + // avoid having to rebuild absolutely everything each time we + // tweak the implementation + class Impl; + Impl *m_impl; + +private: + NotationQuantizer &operator=(const NotationQuantizer &); // not provided +}; + +} + +#endif diff --git a/src/base/NotationRules.h b/src/base/NotationRules.h new file mode 100644 index 0000000..a745afa --- /dev/null +++ b/src/base/NotationRules.h @@ -0,0 +1,133 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _NOTATION_RULES_H_ +#define _NOTATION_RULES_H_ + + +/** + * Common major and minor scales. + * + * For example, sixth note in 12-basis on Cmajor scale: + * scale_Cmajor[5] = 9 + */ +static int scale_Cmajor[] = { 0, 2, 4, 5, 7, 9, 11 }; +static int scale_Cminor[] = { 0, 2, 3, 5, 7, 8, 10 }; +static int scale_Cminor_harmonic[] = { 0, 2, 3, 5, 7, 8, 11 }; +/** + * Steps of common major and minor scales. + * + * For example, get accidental in 12-basis on Cmajor scale: + * 10 - scale_Cmajor[steps_Cmajor[10]] = 10 - 9 = +1 + */ +static int steps_Cmajor[] = { 0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6 }; +static int steps_Cminor[] = { 0, 0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6 }; +static int steps_Cminor_harmonic[] = { 0, 0, 1, 2, 2, 3, 3, 4, 5, 5, 5, 6 }; +/** + * Same as previosly, but the use of accidentals is explicitly written. + * + * For example, get accidental in 12-basis on Cmajor scale: + * 10 - scale_Cmajor[steps_Cmajor_with_sharps[10]] = 10 - 9 = +1 + * 10 - scale_Cmajor[steps_Cmajor_with_flats[10]] = 10 - 11 = -1 + */ +static int steps_Cmajor_with_sharps[] = { 0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6 }; +static int steps_Cmajor_with_flats[] = { 0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6 }; + +namespace Rosegarden +{ + +/* + * NotationRules.h + * + * This file contains the model for rules which are used in notation decisions. + * + */ + +class NotationRules +{ +public: + NotationRules() { }; + ~NotationRules() { }; + + /** + * If a single note is above the middle line, the preferred direction is up. + * + * If a single note is on the middle line, the preferred direction is down. + * + * If a single note is below the middle line, the preferred direction is down. + */ + bool isStemUp(int heightOnStaff) { return heightOnStaff < 4; } + + /** + * If the highest note in a chord is more distant from the middle + * line than the lowest note in a chord, the preferred direction is down. + * + * If the extreme notes in a chord are an equal distance from the + * middle line, the preferred direction is down. + * + * If the lowest note in a chord is more distant from the middle + * line than the highest note in a chord, the preferred direction is up. + */ + bool isStemUp(int highestHeightOnStaff, int lowestHeightOnStaff) { + return (highestHeightOnStaff + lowestHeightOnStaff) < 2*4; + } + + /** + * If majority of notes are below the middle line, + * the preferred direction is up. + * + * If notes are equally distributed around the middle line, + * the preferred direction is down. + * + * If majority of notes are above the middle line, + * the preferred direction is down. + */ + bool isBeamAboveWeighted(int weightAbove, int weightBelow) { + return weightBelow > weightAbove; + } + + /** + * If the highest note in a group is more distant from the middle + * line than the lowest note in a group, the preferred direction is down. + * + * If the extreme notes in a group are an equal distance from the + * middle line, the preferred direction is down. + * + * If the lowest note in a group is more distant from the middle + * line than the highest note in a group, the preferred direction is up. + */ + bool isBeamAbove(int highestHeightOnStaff, int lowestHeightOnStaff) { + return (highestHeightOnStaff + lowestHeightOnStaff) < 2*4; + } + bool isBeamAbove(int highestHeightOnStaff, int lowestHeightOnStaff, + int weightAbove, int weightBelow) { + if (highestHeightOnStaff + lowestHeightOnStaff == 2*4) { + return isBeamAboveWeighted(weightAbove,weightBelow); + } else { + return isBeamAbove(highestHeightOnStaff,lowestHeightOnStaff); + } + } +}; + +} + +#endif diff --git a/src/base/NotationTypes.cpp b/src/base/NotationTypes.cpp new file mode 100644 index 0000000..ceddf79 --- /dev/null +++ b/src/base/NotationTypes.cpp @@ -0,0 +1,2436 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 // needed for sprintf() +#include "NotationRules.h" +#include "NotationTypes.h" +#include "BaseProperties.h" +#include +#include // for atoi +#include // for SHRT_MIN +#include + +#if (__GNUC__ < 3) +#include +#else +#include +#endif + +//dmm This will make everything excruciatingly slow if defined: +//#define DEBUG_PITCH + +namespace Rosegarden +{ +using std::string; +using std::vector; +using std::cout; +using std::cerr; +using std::endl; + +// This is the fundamental definition of the resolution used throughout. +// It must be a multiple of 16, and should ideally be a multiple of 96. +static const timeT basePPQ = 960; + +const int MIN_SUBORDERING = SHRT_MIN; + +namespace Accidentals +{ + /** + * NoAccidental means the accidental will be inferred + * based on the performance pitch and current key at the + * location of the note. + */ + const Accidental NoAccidental = "no-accidental"; + + const Accidental Sharp = "sharp"; + const Accidental Flat = "flat"; + const Accidental Natural = "natural"; + const Accidental DoubleSharp = "double-sharp"; + const Accidental DoubleFlat = "double-flat"; + + AccidentalList getStandardAccidentals() { + + static Accidental a[] = { + NoAccidental, Sharp, Flat, Natural, DoubleSharp, DoubleFlat + }; + + static AccidentalList v; + if (v.size() == 0) { + for (unsigned int i = 0; i < sizeof(a)/sizeof(a[0]); ++i) + v.push_back(a[i]); + } + return v; + } + + int getPitchOffset(const Accidental &acc) { + if (acc == DoubleSharp) return 2; + else if (acc == Sharp) return 1; + else if (acc == Flat) return -1; + else if (acc == DoubleFlat) return -2; + else return 0; + } + + Accidental getAccidental(int pitchChange) { + if (pitchChange == -2) return DoubleFlat; + if (pitchChange == -1) return Flat; + // Yielding 'Natural' will add a natural-sign even if not needed, so for now + // just return NoAccidental + if (pitchChange == 0) return NoAccidental; + if (pitchChange == 1) return Sharp; + if (pitchChange == 2) return DoubleSharp; + + // if we're getting into triple flats/sharps, we're probably atonal + // and don't case if the accidental is simplified + return NoAccidental; + } +} + +using namespace Accidentals; + + +namespace Marks +{ + const Mark NoMark = "no-mark"; + const Mark Accent = "accent"; + const Mark Tenuto = "tenuto"; + const Mark Staccato = "staccato"; + const Mark Staccatissimo = "staccatissimo"; + const Mark Marcato = "marcato"; + const Mark Sforzando = getTextMark("sf"); + const Mark Rinforzando = getTextMark("rf"); + const Mark Trill = "trill"; + const Mark LongTrill = "long-trill"; + const Mark TrillLine = "trill-line"; + const Mark Turn = "turn"; + const Mark Pause = "pause"; + const Mark UpBow = "up-bow"; + const Mark DownBow = "down-bow"; + + const Mark Mordent = "mordent"; + const Mark MordentInverted = "mordent-inverted"; + const Mark MordentLong = "mordent-long"; + const Mark MordentLongInverted = "mordent-long-inverted"; + + string getTextMark(string text) { + return string("text_") + text; + } + + bool isTextMark(Mark mark) { + return string(mark).substr(0, 5) == "text_"; + } + + string getTextFromMark(Mark mark) { + if (!isTextMark(mark)) return string(); + else return string(mark).substr(5); + } + + string getFingeringMark(string fingering) { + return string("finger_") + fingering; + } + + bool isFingeringMark(Mark mark) { + return string(mark).substr(0, 7) == "finger_"; + } + + string getFingeringFromMark(Mark mark) { + if (!isFingeringMark(mark)) return string(); + else return string(mark).substr(7); + } + + int getMarkCount(const Event &e) { + long markCount = 0; + e.get(BaseProperties::MARK_COUNT, markCount); + return markCount; + } + + std::vector getMarks(const Event &e) { + + std::vector marks; + + long markCount = 0; + e.get(BaseProperties::MARK_COUNT, markCount); + if (markCount == 0) return marks; + + for (long j = 0; j < markCount; ++j) { + + Mark mark(Marks::NoMark); + (void)e.get(BaseProperties::getMarkPropertyName(j), mark); + + marks.push_back(mark); + } + + return marks; + } + + Mark getFingeringMark(const Event &e) { + + long markCount = 0; + e.get(BaseProperties::MARK_COUNT, markCount); + if (markCount == 0) return NoMark; + + for (long j = 0; j < markCount; ++j) { + + Mark mark(Marks::NoMark); + (void)e.get(BaseProperties::getMarkPropertyName(j), mark); + + if (isFingeringMark(mark)) return mark; + } + + return NoMark; + } + + void addMark(Event &e, const Mark &mark, bool unique) { + if (unique && hasMark(e, mark)) return; + + long markCount = 0; + e.get(BaseProperties::MARK_COUNT, markCount); + e.set(BaseProperties::MARK_COUNT, markCount + 1); + + PropertyName markProperty = BaseProperties::getMarkPropertyName(markCount); + e.set(markProperty, mark); + } + + bool removeMark(Event &e, const Mark &mark) { + + long markCount = 0; + e.get(BaseProperties::MARK_COUNT, markCount); + + for (long j = 0; j < markCount; ++j) { + PropertyName pn(BaseProperties::getMarkPropertyName(j)); + std::string m; + if (e.get(pn, m) && m == mark) { + e.unset(pn); + while (j < markCount - 1) { + PropertyName npn(BaseProperties::getMarkPropertyName(j+1)); + if (e.get(npn, m)) { + e.set( pn, m); + } + pn = npn; + ++j; + } + e.set(BaseProperties::MARK_COUNT, markCount - 1); + return true; + } + } + + return false; + } + + bool hasMark(const Event &e, const Mark &mark) { + long markCount = 0; + e.get(BaseProperties::MARK_COUNT, markCount); + + for (long j = 0; j < markCount; ++j) { + std::string m; + if (e.get(BaseProperties::getMarkPropertyName(j), m) && m == mark) { + return true; + } + } + + return false; + } + + std::vector getStandardMarks() { + + static Mark a[] = { + NoMark, Accent, Tenuto, Staccato, Staccatissimo, Marcato, + Sforzando, Rinforzando, Trill, LongTrill, TrillLine, + Turn, Pause, UpBow, DownBow, + Mordent, MordentInverted, MordentLong, MordentLongInverted + }; + + static std::vector v; + if (v.size() == 0) { + for (unsigned int i = 0; i < sizeof(a)/sizeof(a[0]); ++i) + v.push_back(a[i]); + } + return v; + } + +} + +using namespace Marks; + + +////////////////////////////////////////////////////////////////////// +// Clef +////////////////////////////////////////////////////////////////////// + +const string Clef::EventType = "clefchange"; +const int Clef::EventSubOrdering = -250; +const PropertyName Clef::ClefPropertyName = "clef"; +const PropertyName Clef::OctaveOffsetPropertyName = "octaveoffset"; +const string Clef::Treble = "treble"; +const string Clef::French = "french"; +const string Clef::Soprano = "soprano"; +const string Clef::Mezzosoprano = "mezzosoprano"; +const string Clef::Alto = "alto"; +const string Clef::Tenor = "tenor"; +const string Clef::Baritone = "baritone"; +const string Clef::Varbaritone = "varbaritone"; +const string Clef::Bass = "bass"; +const string Clef::Subbass = "subbass"; + +const Clef Clef::DefaultClef = Clef("treble"); + +Clef::Clef(const Event &e) : + m_clef(DefaultClef.m_clef), + m_octaveOffset(0) +{ + if (e.getType() != EventType) { + std::cerr << Event::BadType + ("Clef model event", EventType, e.getType()).getMessage() + << std::endl; + return; + } + + std::string s; + e.get(ClefPropertyName, s); + + if (s != Treble && s != Soprano && s != French && s != Mezzosoprano && s != Alto && s != Tenor && s != Baritone && s != Bass && s != Varbaritone && s != Subbass) { + std::cerr << BadClefName("No such clef as \"" + s + "\"").getMessage() + << std::endl; + return; + } + + long octaveOffset = 0; + (void)e.get(OctaveOffsetPropertyName, octaveOffset); + + m_clef = s; + m_octaveOffset = octaveOffset; +} + +Clef::Clef(const std::string &s, int octaveOffset) + // throw (BadClefName) +{ + if (s != Treble && s != Soprano && s != French && s != Mezzosoprano && s != Alto && s != Tenor && s != Baritone && s != Bass && s != Varbaritone && s != Subbass) { + throw BadClefName("No such clef as \"" + s + "\""); + } + m_clef = s; + m_octaveOffset = octaveOffset; +} + +Clef &Clef::operator=(const Clef &c) +{ + if (this != &c) { + m_clef = c.m_clef; + m_octaveOffset = c.m_octaveOffset; + } + return *this; +} + +bool Clef::isValid(const Event &e) +{ + if (e.getType() != EventType) return false; + + std::string s; + e.get(ClefPropertyName, s); + if (s != Treble && s != Soprano && s != French && s != Mezzosoprano && s != Alto && s != Tenor && s != Baritone && s != Bass && s != Varbaritone && s != Subbass) return false; + + return true; +} + +int Clef::getTranspose() const +{ +//!!! plus or minus? + return getOctave() * 12 - getPitchOffset(); +} + +int Clef::getOctave() const +{ + if (m_clef == Treble || m_clef == French) return 0 + m_octaveOffset; + else if (m_clef == Bass || m_clef == Varbaritone || m_clef == Subbass) return -2 + m_octaveOffset; + else return -1 + m_octaveOffset; +} + +int Clef::getPitchOffset() const +{ + if (m_clef == Treble) return 0; + else if (m_clef == French) return -2; + else if (m_clef == Soprano) return -5; + else if (m_clef == Mezzosoprano) return -3; + else if (m_clef == Alto) return -1; + else if (m_clef == Tenor) return 1; + else if (m_clef == Baritone) return 3; + else if (m_clef == Varbaritone) return -4; + else if (m_clef == Bass) return -2; + else if (m_clef == Subbass) return 0; + else return -2; +} + +int Clef::getAxisHeight() const +{ + if (m_clef == Treble) return 2; + else if (m_clef == French) return 0; + else if (m_clef == Soprano) return 0; + else if (m_clef == Mezzosoprano) return 2; + else if (m_clef == Alto) return 4; + else if (m_clef == Tenor) return 6; + else if (m_clef == Baritone) return 8; + else if (m_clef == Varbaritone) return 4; + else if (m_clef == Bass) return 6; + else if (m_clef == Subbass) return 8; + else return 6; +} + +Clef::ClefList +Clef::getClefs() +{ + ClefList clefs; + clefs.push_back(Clef(Bass)); + clefs.push_back(Clef(Varbaritone)); + clefs.push_back(Clef(Subbass)); + clefs.push_back(Clef(Baritone)); + clefs.push_back(Clef(Tenor)); + clefs.push_back(Clef(Alto)); + clefs.push_back(Clef(Mezzosoprano)); + clefs.push_back(Clef(Soprano)); + clefs.push_back(Clef(French)); + clefs.push_back(Clef(Treble)); + return clefs; +} + +Event *Clef::getAsEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering); + e->set(ClefPropertyName, m_clef); + e->set(OctaveOffsetPropertyName, m_octaveOffset); + return e; +} + + +////////////////////////////////////////////////////////////////////// +// Key +////////////////////////////////////////////////////////////////////// + +Key::KeyDetailMap Key::m_keyDetailMap = Key::KeyDetailMap(); + +const string Key::EventType = "keychange"; +const int Key::EventSubOrdering = -200; +const PropertyName Key::KeyPropertyName = "key"; +const Key Key::DefaultKey = Key("C major"); + +Key::Key() : + m_name(DefaultKey.m_name), + m_accidentalHeights(0) +{ + checkMap(); +} + + +Key::Key(const Event &e) : + m_name(""), + m_accidentalHeights(0) +{ + checkMap(); + if (e.getType() != EventType) { + std::cerr << Event::BadType + ("Key model event", EventType, e.getType()).getMessage() + << std::endl; + return; + } + e.get(KeyPropertyName, m_name); + if (m_keyDetailMap.find(m_name) == m_keyDetailMap.end()) { + std::cerr << BadKeyName + ("No such key as \"" + m_name + "\"").getMessage() << std::endl; + return; + } +} + +Key::Key(const std::string &name) : + m_name(name), + m_accidentalHeights(0) +{ + checkMap(); + if (m_keyDetailMap.find(m_name) == m_keyDetailMap.end()) { + throw BadKeyName("No such key as \"" + m_name + "\""); + } +} + +Key::Key(int accidentalCount, bool isSharp, bool isMinor) : + m_accidentalHeights(0) +{ + checkMap(); + for (KeyDetailMap::const_iterator i = m_keyDetailMap.begin(); + i != m_keyDetailMap.end(); ++i) { + if ((*i).second.m_sharpCount == accidentalCount && + (*i).second.m_minor == isMinor && + ((*i).second.m_sharps == isSharp || + (*i).second.m_sharpCount == 0)) { + m_name = (*i).first; + return; + } + } + +#if (__GNUC__ < 3) + std::ostrstream os; +#else + std::ostringstream os; +#endif + + os << "No " << (isMinor ? "minor" : "major") << " key with " + << accidentalCount << (isSharp ? " sharp(s)" : " flat(s)"); + +#if (__GNUC__ < 3) + os << std::ends; +#endif + + throw BadKeySpec(os.str()); +} + +// Unfortunately this is ambiguous -- e.g. B major / Cb major. +// We need an isSharp argument, but we already have a constructor +// with that signature. Not quite sure what's the best solution. + +Key::Key(int tonicPitch, bool isMinor) : + m_accidentalHeights(0) +{ + checkMap(); + for (KeyDetailMap::const_iterator i = m_keyDetailMap.begin(); + i != m_keyDetailMap.end(); ++i) { + if ((*i).second.m_tonicPitch == tonicPitch && + (*i).second.m_minor == isMinor) { + m_name = (*i).first; + return; + } + } + +#if (__GNUC__ < 3) + std::ostrstream os; +#else + std::ostringstream os; +#endif + + os << "No " << (isMinor ? "minor" : "major") << " key with tonic pitch " + << tonicPitch; + +#if (__GNUC__ < 3) + os << std::ends; +#endif + + throw BadKeySpec(os.str()); +} + + +Key::Key(const Key &kc) : + m_name(kc.m_name), + m_accidentalHeights(0) +{ +} + +Key& Key::operator=(const Key &kc) +{ + m_name = kc.m_name; + m_accidentalHeights = 0; + return *this; +} + +bool Key::isValid(const Event &e) +{ + if (e.getType() != EventType) return false; + std::string name; + e.get(KeyPropertyName, name); + if (m_keyDetailMap.find(name) == m_keyDetailMap.end()) return false; + return true; +} + +Key::KeyList Key::getKeys(bool minor) +{ + checkMap(); + KeyList result; + for (KeyDetailMap::const_iterator i = m_keyDetailMap.begin(); + i != m_keyDetailMap.end(); ++i) { + if ((*i).second.m_minor == minor) { + result.push_back(Key((*i).first)); + } + } + return result; +} + +Key::Key Key::transpose(int pitchDelta, int heightDelta) +{ + Pitch tonic(getTonicPitch()); + Pitch newTonic = tonic.transpose(*this, pitchDelta, heightDelta); + int newTonicPitch = (newTonic.getPerformancePitch() % 12 + 12) % 12; + return Key (newTonicPitch, isMinor()); +} + +Accidental Key::getAccidentalAtHeight(int height, const Clef &clef) const +{ + checkAccidentalHeights(); + height = canonicalHeight(height); + for (unsigned int i = 0; i < m_accidentalHeights->size(); ++i) { + if (height ==static_cast(canonicalHeight((*m_accidentalHeights)[i] + + clef.getPitchOffset()))) { + return isSharp() ? Sharp : Flat; + } + } + return NoAccidental; +} + +Accidental Key::getAccidentalForStep(int step) const +{ + if (isMinor()) { + step = (step + 5) % 7; + } + + int accidentalCount = getAccidentalCount(); + + if (accidentalCount == 0) { + return NoAccidental; + } + + bool sharp = isSharp(); + + int currentAccidentalPosition = sharp ? 6 : 3; + + for (int i = 1; i <= accidentalCount; i++) { + if (step == currentAccidentalPosition) { + return sharp ? Sharp : Flat; + } + + currentAccidentalPosition = + (currentAccidentalPosition + (sharp ? 3 : 4)) % 7; + } + + return NoAccidental; +} + +vector Key::getAccidentalHeights(const Clef &clef) const +{ + // staff positions of accidentals + checkAccidentalHeights(); + vector v(*m_accidentalHeights); + int offset = clef.getPitchOffset(); + + for (unsigned int i = 0; i < v.size(); ++i) { + v[i] += offset; + if (offset > 0) + if (v[i] > 8) v[i] -= 7; + } + return v; +} + +void Key::checkAccidentalHeights() const +{ + if (m_accidentalHeights) return; + m_accidentalHeights = new vector; + + bool sharp = isSharp(); + int accidentals = getAccidentalCount(); + int height = sharp ? 8 : 4; + + for (int i = 0; i < accidentals; ++i) { + m_accidentalHeights->push_back(height); + if (sharp) { height -= 3; if (height < 3) height += 7; } + else { height += 3; if (height > 7) height -= 7; } + } +} + +int Key::convertFrom(int p, const Key &previousKey, + const Accidental &explicitAccidental) const +{ + Pitch pitch(p, explicitAccidental); + int height = pitch.getHeightOnStaff(Clef(), previousKey); + Pitch newPitch(height, Clef(), *this, explicitAccidental); + return newPitch.getPerformancePitch(); +} + +int Key::transposeFrom(int pitch, const Key &previousKey) const +{ + int delta = getTonicPitch() - previousKey.getTonicPitch(); + if (delta > 6) delta -= 12; + if (delta < -6) delta += 12; + return pitch + delta; +} + +Event *Key::getAsEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering); + e->set(KeyPropertyName, m_name); + return e; +} + + +void Key::checkMap() { + if (!m_keyDetailMap.empty()) return; + + m_keyDetailMap["A major" ] = KeyDetails(true, false, 3, "F# minor", "A maj / F# min", 9); + m_keyDetailMap["F# minor"] = KeyDetails(true, true, 3, "A major", "A maj / F# min", 6); + m_keyDetailMap["Ab major"] = KeyDetails(false, false, 4, "F minor", "Ab maj / F min", 8); + m_keyDetailMap["F minor" ] = KeyDetails(false, true, 4, "Ab major", "Ab maj / F min", 5); + m_keyDetailMap["B major" ] = KeyDetails(true, false, 5, "G# minor", "B maj / G# min", 11); + m_keyDetailMap["G# minor"] = KeyDetails(true, true, 5, "B major", "B maj / G# min", 8); + m_keyDetailMap["Bb major"] = KeyDetails(false, false, 2, "G minor", "Bb maj / G min", 10); + m_keyDetailMap["G minor" ] = KeyDetails(false, true, 2, "Bb major", "Bb maj / G min", 7); + m_keyDetailMap["C major" ] = KeyDetails(true, false, 0, "A minor", "C maj / A min", 0); + m_keyDetailMap["A minor" ] = KeyDetails(false, true, 0, "C major", "C maj / A min", 9); + m_keyDetailMap["Cb major"] = KeyDetails(false, false, 7, "Ab minor", "Cb maj / Ab min", 11); + m_keyDetailMap["Ab minor"] = KeyDetails(false, true, 7, "Cb major", "Cb maj / Ab min", 8); + m_keyDetailMap["C# major"] = KeyDetails(true, false, 7, "A# minor", "C# maj / A# min", 1); + m_keyDetailMap["A# minor"] = KeyDetails(true, true, 7, "C# major", "C# maj / A# min", 10); + m_keyDetailMap["D major" ] = KeyDetails(true, false, 2, "B minor", "D maj / B min", 2); + m_keyDetailMap["B minor" ] = KeyDetails(true, true, 2, "D major", "D maj / B min", 11); + m_keyDetailMap["Db major"] = KeyDetails(false, false, 5, "Bb minor", "Db maj / Bb min", 1); + m_keyDetailMap["Bb minor"] = KeyDetails(false, true, 5, "Db major", "Db maj / Bb min", 10); + m_keyDetailMap["E major" ] = KeyDetails(true, false, 4, "C# minor", "E maj / C# min", 4); + m_keyDetailMap["C# minor"] = KeyDetails(true, true, 4, "E major", "E maj / C# min", 1); + m_keyDetailMap["Eb major"] = KeyDetails(false, false, 3, "C minor", "Eb maj / C min", 3); + m_keyDetailMap["C minor" ] = KeyDetails(false, true, 3, "Eb major", "Eb maj / C min", 0); + m_keyDetailMap["F major" ] = KeyDetails(false, false, 1, "D minor", "F maj / D min", 5); + m_keyDetailMap["D minor" ] = KeyDetails(false, true, 1, "F major", "F maj / D min", 2); + m_keyDetailMap["F# major"] = KeyDetails(true, false, 6, "D# minor", "F# maj / D# min", 6); + m_keyDetailMap["D# minor"] = KeyDetails(true, true, 6, "F# major", "F# maj / D# min", 3); + m_keyDetailMap["G major" ] = KeyDetails(true, false, 1, "E minor", "G maj / E min", 7); + m_keyDetailMap["E minor" ] = KeyDetails(true, true, 1, "G major", "G maj / E min", 4); + m_keyDetailMap["Gb major"] = KeyDetails(false, false, 6, "Eb minor", "Gb maj / Eb min", 6); + m_keyDetailMap["Eb minor"] = KeyDetails(false, true, 6, "Gb major", "Gb maj / Eb min", 3); +} + + +Key::KeyDetails::KeyDetails() + : m_sharps(false), m_minor(false), m_sharpCount(0), + m_equivalence(""), m_rg2name(""), m_tonicPitch(0) +{ +} + +Key::KeyDetails::KeyDetails(bool sharps, bool minor, int sharpCount, + std::string equivalence, std::string rg2name, + int tonicPitch) + : m_sharps(sharps), m_minor(minor), m_sharpCount(sharpCount), + m_equivalence(equivalence), m_rg2name(rg2name), m_tonicPitch(tonicPitch) +{ +} + +Key::KeyDetails::KeyDetails(const Key::KeyDetails &d) + : m_sharps(d.m_sharps), m_minor(d.m_minor), + m_sharpCount(d.m_sharpCount), m_equivalence(d.m_equivalence), + m_rg2name(d.m_rg2name), m_tonicPitch(d.m_tonicPitch) +{ +} + +Key::KeyDetails& Key::KeyDetails::operator=(const Key::KeyDetails &d) +{ + if (&d == this) return *this; + m_sharps = d.m_sharps; m_minor = d.m_minor; + m_sharpCount = d.m_sharpCount; m_equivalence = d.m_equivalence; + m_rg2name = d.m_rg2name; m_tonicPitch = d.m_tonicPitch; + return *this; +} + +////////////////////////////////////////////////////////////////////// +// Indication +////////////////////////////////////////////////////////////////////// + +const std::string Indication::EventType = "indication"; +const int Indication::EventSubOrdering = -50; +const PropertyName Indication::IndicationTypePropertyName = "indicationtype"; +//const PropertyName Indication::IndicationDurationPropertyName = "indicationduration"; +static const PropertyName IndicationDurationPropertyName = "indicationduration";//!!! + +const std::string Indication::Slur = "slur"; +const std::string Indication::PhrasingSlur = "phrasingslur"; +const std::string Indication::Crescendo = "crescendo"; +const std::string Indication::Decrescendo = "decrescendo"; +const std::string Indication::Glissando = "glissando"; +const std::string Indication::QuindicesimaUp = "ottava2up"; +const std::string Indication::OttavaUp = "ottavaup"; +const std::string Indication::OttavaDown = "ottavadown"; +const std::string Indication::QuindicesimaDown = "ottava2down"; + +Indication::Indication(const Event &e) +{ + if (e.getType() != EventType) { + throw Event::BadType("Indication model event", EventType, e.getType()); + } + std::string s; + e.get(IndicationTypePropertyName, s); + if (!isValid(s)) { + throw BadIndicationName("No such indication as \"" + s + "\""); + } + m_indicationType = s; + + m_duration = e.getDuration(); + if (m_duration == 0) { + e.get(IndicationDurationPropertyName, m_duration); // obsolete property + } +} + +Indication::Indication(const std::string &s, timeT indicationDuration) +{ + if (!isValid(s)) { + throw BadIndicationName("No such indication as \"" + s + "\""); + } + m_indicationType = s; + m_duration = indicationDuration; +} + +Indication & +Indication::operator=(const Indication &m) +{ + if (&m != this) { + m_indicationType = m.m_indicationType; + m_duration = m.m_duration; + } + return *this; +} + +Event * +Indication::getAsEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventType, absoluteTime, m_duration, EventSubOrdering); + e->set(IndicationTypePropertyName, m_indicationType); + + // Set this obsolete property as well, as otherwise we could actually + // crash earlier versions of RG by loading files exported from this one! + e->set(IndicationDurationPropertyName, m_duration); + + return e; +} + +bool +Indication::isValid(const std::string &s) const +{ + return + (s == Slur || s == PhrasingSlur || + s == Crescendo || s == Decrescendo || + s == Glissando || + s == QuindicesimaUp || s == OttavaUp || + s == OttavaDown || s == QuindicesimaDown); +} + + + +////////////////////////////////////////////////////////////////////// +// Text +////////////////////////////////////////////////////////////////////// + +const std::string Text::EventType = "text"; +const int Text::EventSubOrdering = -70; +const PropertyName Text::TextPropertyName = "text"; +const PropertyName Text::TextTypePropertyName = "type"; +const PropertyName Text::LyricVersePropertyName = "verse"; + +// text styles +const std::string Text::UnspecifiedType = "unspecified"; +const std::string Text::StaffName = "staffname"; +const std::string Text::ChordName = "chordname"; +const std::string Text::KeyName = "keyname"; +const std::string Text::Dynamic = "dynamic"; +const std::string Text::Lyric = "lyric"; +const std::string Text::Chord = "chord"; +const std::string Text::Direction = "direction"; +const std::string Text::LocalDirection = "local_direction"; +const std::string Text::Tempo = "tempo"; +const std::string Text::LocalTempo = "local_tempo"; +const std::string Text::Annotation = "annotation"; +const std::string Text::LilyPondDirective = "lilypond_directive"; + +// special LilyPond directives +const std::string Text::Segno = "Segno"; +const std::string Text::Coda = "Coda"; +const std::string Text::Alternate1 = "Alt1 ->"; +const std::string Text::Alternate2 = "Alt2 ->"; +const std::string Text::BarDouble = "|| ->"; +const std::string Text::BarEnd = "|. ->"; +const std::string Text::BarDot = ": ->"; +const std::string Text::Gliss = "Gliss."; +const std::string Text::Arpeggio = "Arp."; +//const std::string Text::ArpeggioUp = "Arp.^"; +//const std::string Text::ArpeggioDn = "Arp._"; +const std::string Text::Tiny = "tiny ->"; +const std::string Text::Small = "small ->"; +const std::string Text::NormalSize = "norm. ->"; + +Text::Text(const Event &e) : + m_verse(0) +{ + if (e.getType() != EventType) { + throw Event::BadType("Text model event", EventType, e.getType()); + } + + m_text = ""; + m_type = Text::UnspecifiedType; + + e.get(TextPropertyName, m_text); + e.get(TextTypePropertyName, m_type); + e.get(LyricVersePropertyName, m_verse); +} + +Text::Text(const std::string &s, const std::string &type) : + m_text(s), + m_type(type), + m_verse(0) +{ + // nothing else +} + +Text::Text(const Text &t) : + m_text(t.m_text), + m_type(t.m_type), + m_verse(t.m_verse) +{ + // nothing else +} + +Text & +Text::operator=(const Text &t) +{ + if (&t != this) { + m_text = t.m_text; + m_type = t.m_type; + m_verse = t.m_verse; + } + return *this; +} + +Text::~Text() +{ + // nothing +} + +bool +Text::isTextOfType(Event *e, std::string type) +{ + return (e->isa(EventType) && + e->has(TextTypePropertyName) && + e->get(TextTypePropertyName) == type); +} + +std::vector +Text::getUserStyles() +{ + std::vector v; + + v.push_back(Dynamic); + v.push_back(Direction); + v.push_back(LocalDirection); + v.push_back(Tempo); + v.push_back(LocalTempo); + v.push_back(Chord); + v.push_back(Lyric); + v.push_back(Annotation); + v.push_back(LilyPondDirective); + + return v; +} + +std::vector +Text::getLilyPondDirectives() +{ + std::vector v; + + v.push_back(Alternate1); + v.push_back(Alternate2); + v.push_back(Segno); + v.push_back(Coda); + v.push_back(BarDouble); + v.push_back(BarEnd); + v.push_back(BarDot); + v.push_back(Gliss); + v.push_back(Arpeggio); +// v.push_back(ArpeggioUp); +// v.push_back(ArpeggioDn); + v.push_back(Tiny); + v.push_back(Small); + v.push_back(NormalSize); + + return v; +} + +Event * +Text::getAsEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering); + e->set(TextPropertyName, m_text); + e->set(TextTypePropertyName, m_type); + if (m_type == Lyric) e->set(LyricVersePropertyName, m_verse); + return e; +} + +bool +pitchInKey(int pitch, const Key& key) +{ + int pitchOffset = (pitch - key.getTonicPitch() + 12) % 12; + + static int pitchInMajor[] = + { true, false, true, false, true, true, false, true, false, true, false, true }; + static int pitchInMinor[] = + { true, false, true, true, false, true, false, true, true, false, true, false }; + + if (key.isMinor()) { + return pitchInMinor[pitchOffset]; + } + else { + return pitchInMajor[pitchOffset]; + } +} + +/** + * @param pitch in the range 0..11 (C..B) + * + * @author Arnout Engelen + */ +Accidental +resolveNoAccidental(int pitch, + const Key &key, + NoAccidentalStrategy noAccidentalStrategy) +{ + Accidental outputAccidental = ""; + + // Find out the accidental to use, based on the strategy specified + switch (noAccidentalStrategy) { + case UseKeySharpness: + noAccidentalStrategy = + key.isSharp() ? UseSharps : UseFlats; + // fall though + case UseFlats: + // shares code with UseSharps + case UseSharps: + if (pitchInKey(pitch, key)) { + outputAccidental = NoAccidental; + } + else { + if (noAccidentalStrategy == UseSharps) { + outputAccidental = Sharp; + } + else { + outputAccidental = Flat; + } + } + break; + case UseKey: + // the distance of the pitch from the tonic of the current + // key + int pitchOffset = (pitch - key.getTonicPitch() + 12) % 12; + // 0: major, 1: minor + int minor = key.isMinor(); + static int pitchToHeight[2][12] = + { + { 0, 0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6 }, + // a ., b, c, ., d, ., e, f, ., g, . + { 0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6 } + }; + + // map pitchOffset to the extra correction, on top of any + // accidentals in the key. Example: in F major, with a pitchOffset + // of 6, the resulting height would be 3 (Bb) and the correction + // would be +1, so the resulting note would be B-natural + static int pitchToCorrection[2][12] = + { + { 0, +1, 0, -1, 0, 0, +1, 0, -1, 0, -1, 0 }, + { 0, -1, 0, 0, +1, 0, -1, 0, 0, +1, 0, +1 } + }; + + int correction = pitchToCorrection[minor][pitchOffset]; + + // Get the accidental normally associated with this height in this + // key. + Accidental normalAccidental = key.getAccidentalForStep(pitchToHeight[minor][pitchOffset]); + + // Apply the pitchCorrection and get the outputAccidental + outputAccidental = Accidentals::getAccidental( + getPitchOffset(normalAccidental) + correction); + + } + + return outputAccidental; +} + +/** + * @param pitch in the range 0..11 (C..B) + * + * @author Michael McIntyre + */ +void +resolveSpecifiedAccidental(int pitch, + const Clef &clef, + const Key &key, + int &height, + int &octave, + Accidental &inputAccidental, + Accidental &outputAccidental) +{ + // 4. Get info from the Key + long accidentalCount = key.getAccidentalCount(); + bool keyIsSharp = key.isSharp(), keyIsFlat = !keyIsSharp; + + // Calculate the flags needed for resolving accidentals against the key. + // First we initialize them false... + bool keyHasSharpC = false, keyHasSharpD = false, keyHasSharpE = false, + keyHasSharpF = false, keyHasSharpG = false, keyHasSharpA = false, + keyHasSharpB = false, keyHasFlatC = false, keyHasFlatD = false, + keyHasFlatE = false, keyHasFlatF = false, keyHasFlatG = false, + keyHasFlatA = false, keyHasFlatB = false; + + // Then we use "trip points" based on the flat/sharp state of the key and + // its number of accidentals to set the flags: + if (keyIsSharp) { + switch (accidentalCount) { + case 7: keyHasSharpB = true; + case 6: keyHasSharpE = true; + case 5: keyHasSharpA = true; + case 4: keyHasSharpD = true; + case 3: keyHasSharpG = true; + case 2: keyHasSharpC = true; + case 1: keyHasSharpF = true; + } + } else { + switch (accidentalCount) { + case 7: keyHasFlatF = true; + case 6: keyHasFlatC = true; + case 5: keyHasFlatG = true; + case 4: keyHasFlatD = true; + case 3: keyHasFlatA = true; + case 2: keyHasFlatE = true; + case 1: keyHasFlatB = true; + } + } + + + // 5. Determine height on staff and accidental note should display with for key... + // + // Every position on the staff is one of six accidental states: + // + // Natural, Sharp, Flat, DoubleSharp, DoubleFlat, NoAccidental + // + // DoubleSharp and DoubleFlat are always user-specified accidentals, so + // they are always used to decide how to draw the note, and they are + // always passed along unchanged. + // + // The Natural state indicates that a note is or might be going against + // the key. Since the Natural state will always be attached to a plain + // pitch that can never resolve to a "black key" note, it is not necessary + // to handle this case differently unless the key has "white key" notes + // that are supposed to take accidentals for the key. (eg. Cb Gb B C# major) + // For most keys we treat it the same as a NoAccidental, and use the key + // to decide where to draw the note, and what accidental to return. + // + // The Sharp and Flat states indicate that a user has specified an + // accidental for the note, and it might be "out of key." We check to see + // if that's the case. If the note is "in key" then the extra accidental + // property is removed, and we return NoAccidental. If the note is "out of + // key" then the Sharp or Flat is used to decide where to draw the note, and + // the accidental is passed along unchanged. (Incomplete? Will a failure + // to always pass along the accidental cause strange behavior if a user + // specifies an explicit Bb in key of F and then transposes to G, wishing + // the Bb to remain an explicit Bb? If someone complains, I'll know where + // to look.) + // + // The NoAccidental state is a default state. We have nothing else upon + // which to base a decision in this case, so we make the best decisions + // possible using only the pitch and key. Notes that are "in key" pass on + // with NoAccidental preserved, otherwise we return an appropriate + // accidental for the key. + + // We calculate height on a virtual staff, and then make necessary adjustments to + // translate them onto a particular Clef later on... + // + // ---------F--------- Staff Height Note(semitone) for each of five states: + // E + // ---------D--------- Natural| Sharp | Flat |DblSharp| DblFlat + // C | | | | + // ---------B--------- height 4 B(11) | B#( 0) | Bb(10) | Bx( 1) | Bbb( 9) + // A height 3 A( 9) | A#(10) | Ab( 8) | Ax(11) | Abb( 7) + // ---------G--------- height 2 G( 7) | G#( 8) | Gb( 6) | Gx( 9) | Gbb( 5) + // F height 1 F( 5) | F#( 6) | Fb( 4) | Fx( 7) | Fbb( 3) + // ---------E--------- height 0 E( 4) | E#( 5) | Eb( 3) | Ex( 6) | Ebb( 2) + // D height -1 D( 2) | D#( 3) | Db( 1) | Dx( 4) | Dbb( 0) + // ---C---- height -2 C( 0) | C#( 1) | Cb(11) | Cx( 2) | Cbb(10) + + + // use these constants instead of numeric literals in order to reduce the + // chance of making incorrect height assignments... + const int C = -2, D = -1, E = 0, F = 1, G = 2, A = 3, B = 4; + + // Here we do the actual work of making all the decisions explained above. + switch (pitch) { + case 0 : + if (inputAccidental == Sharp || // B# + (inputAccidental == NoAccidental && keyHasSharpB)) { + height = B; + octave--; + outputAccidental = (keyHasSharpB) ? NoAccidental : Sharp; + } else if (inputAccidental == DoubleFlat) { // Dbb + height = D; + outputAccidental = DoubleFlat; + } else { + height = C; // C or C-Natural + outputAccidental = (keyHasFlatC || keyHasSharpC || + (keyHasSharpB && + inputAccidental == Natural)) ? Natural : NoAccidental; + } + break; + case 1 : + if (inputAccidental == Sharp || // C# + (inputAccidental == NoAccidental && keyIsSharp)) { + height = C; + outputAccidental = (keyHasSharpC) ? NoAccidental : Sharp; + } else if (inputAccidental == Flat || // Db + (inputAccidental == NoAccidental && keyIsFlat)) { + height = D; + outputAccidental = (keyHasFlatD) ? NoAccidental : Flat; + } else if (inputAccidental == DoubleSharp) { // Bx + height = B; + octave--; + outputAccidental = DoubleSharp; + } + break; + case 2 : + if (inputAccidental == DoubleSharp) { // Cx + height = C; + outputAccidental = DoubleSharp; + } else if (inputAccidental == DoubleFlat) { // Ebb + height = E; + outputAccidental = DoubleFlat; + } else { // D or D-Natural + height = D; + outputAccidental = (keyHasSharpD || keyHasFlatD) ? Natural : NoAccidental; + } + break; + case 3 : + if (inputAccidental == Sharp || // D# + (inputAccidental == NoAccidental && keyIsSharp)) { + height = D; + outputAccidental = (keyHasSharpD) ? NoAccidental : Sharp; + } else if (inputAccidental == Flat || // Eb + (inputAccidental == NoAccidental && keyIsFlat)) { + height = E; + outputAccidental = (keyHasFlatE) ? NoAccidental : Flat; + } else if (inputAccidental == DoubleFlat) { // Fbb + height = F; + outputAccidental = DoubleFlat; + } + break; + case 4 : + if (inputAccidental == Flat || // Fb + (inputAccidental == NoAccidental && keyHasFlatF)) { + height = F; + outputAccidental = (keyHasFlatF) ? NoAccidental : Flat; + } else if (inputAccidental == DoubleSharp) { // Dx + height = D; + outputAccidental = DoubleSharp; + } else { // E or E-Natural + height = E; + outputAccidental = (keyHasSharpE || keyHasFlatE || + (keyHasFlatF && inputAccidental==Natural)) ? + Natural : NoAccidental; + } + break; + case 5 : + if (inputAccidental == Sharp || // E# + (inputAccidental == NoAccidental && keyHasSharpE)) { + height = E; + outputAccidental = (keyHasSharpE) ? NoAccidental : Sharp; + } else if (inputAccidental == DoubleFlat) { // Gbb + height = G; + outputAccidental = DoubleFlat; + } else { // F or F-Natural + height = F; + outputAccidental = (keyHasSharpF || keyHasFlatF || + (keyHasSharpE && inputAccidental==Natural))? + Natural : NoAccidental; + } + break; + case 6 : + if (inputAccidental == Sharp || + (inputAccidental == NoAccidental && keyIsSharp)) { // F# + height = F; + outputAccidental = (keyHasSharpF) ? NoAccidental : Sharp; + } else if (inputAccidental == Flat || // Gb + (inputAccidental == NoAccidental && keyIsFlat)) { + height = G; + outputAccidental = (keyHasFlatG) ? NoAccidental : Flat; + } else if (inputAccidental == DoubleSharp) { // Ex + height = E; + outputAccidental = DoubleSharp; + } + break; + case 7 : + if (inputAccidental == DoubleSharp) { // Fx + height = F; + outputAccidental = DoubleSharp; + } else if (inputAccidental == DoubleFlat) { // Abb + height = A; + outputAccidental = DoubleFlat; + } else { // G or G-Natural + height = G; + outputAccidental = (keyHasSharpG || keyHasFlatG) ? Natural : NoAccidental; + } + break; + case 8 : + if (inputAccidental == Sharp || + (inputAccidental == NoAccidental && keyIsSharp)) { // G# + height = G; + outputAccidental = (keyHasSharpG) ? NoAccidental : Sharp; + } else if (inputAccidental == Flat || // Ab + (inputAccidental == NoAccidental && keyIsFlat)) { + height = A; + outputAccidental = (keyHasFlatA) ? NoAccidental : Flat; + } + break; + case 9 : + if (inputAccidental == DoubleSharp) { // Gx + height = G; + outputAccidental = DoubleSharp; + } else if (inputAccidental == DoubleFlat) { // Bbb + height = B; + outputAccidental = DoubleFlat; + } else { // A or A-Natural + height = A; + outputAccidental = (keyHasSharpA || keyHasFlatA) ? Natural : NoAccidental; + } + break; + case 10: + if (inputAccidental == DoubleFlat) { // Cbb + height = C; + octave++; // tweak B/C divide + outputAccidental = DoubleFlat; + } else if (inputAccidental == Sharp || // A# + (inputAccidental == NoAccidental && keyIsSharp)) { + height = A; + outputAccidental = (keyHasSharpA) ? NoAccidental : Sharp; + } else if (inputAccidental == Flat || // Bb + (inputAccidental == NoAccidental && keyIsFlat)) { + height = B; + outputAccidental = (keyHasFlatB) ? NoAccidental : Flat; + } + break; + case 11: + if (inputAccidental == DoubleSharp) { // Ax + height = A; + outputAccidental = DoubleSharp; + } else if (inputAccidental == Flat || // Cb + (inputAccidental == NoAccidental && keyHasFlatC)) { + height = C; + octave++; // tweak B/C divide + outputAccidental = (keyHasFlatC) ? NoAccidental : Flat; + } else { // B or B-Natural + height = B; + outputAccidental = (keyHasSharpB || keyHasFlatB || + (keyHasFlatC && inputAccidental==Natural)) ? + Natural : NoAccidental; + } + } + + if (outputAccidental == NoAccidental && inputAccidental == Natural) { + outputAccidental = Natural; + } + +} + +bool +Pitch::validAccidental() const +{ +// std::cout << "Checking whether accidental is valid " << std::endl; + if (m_accidental == NoAccidental) + { + return true; + } + int naturalPitch = (m_pitch - + Accidentals::getPitchOffset(m_accidental) + 12) % 12; + switch(naturalPitch) + { + case 0: //C + return true; + case 1: + return false; + case 2: //D + return true; + case 3: + return false; + case 4: //E + return true; + case 5: //F + return true; + case 6: + return false; + case 7: //G + return true; + case 8: + return false; + case 9: //A + return true; + case 10: + return false; + case 11: //B + return true; + }; + std::cout << "Internal error in validAccidental" << std::endl; + return false; +} + +Event * +Pitch::getAsNoteEvent(timeT absoluteTime, timeT duration) const +{ + Event *e = new Event(Note::EventType, absoluteTime, duration); + e->set(BaseProperties::PITCH, m_pitch); + e->set(BaseProperties::ACCIDENTAL, m_accidental); + return e; +} + +/** + * Converts performance pitch to height on staff + correct accidentals + * for current key. + * + * This method takes a Clef, Key, Accidental and raw performance pitch, then + * applies this information to return a height on staff value and an + * accidental state. The pitch itself contains a lot of information, but we + * need to use the Key and user-specified Accidental to make an accurate + * decision just where to put it on the staff, and what accidental it should + * display for (or against) the key. + * + * This function originally written by Chris Cannam for Rosegarden 2.1 + * Entirely rewritten by Chris Cannam for Rosegarden 4 + * Entirely rewritten by Hans Kieserman + * Entirely rewritten by Michael McIntyre + * This version by Michael McIntyre + * Resolving the accidental was refactored out by Arnout Engelen + */ +void +Pitch::rawPitchToDisplayPitch(int rawpitch, + const Clef &clef, + const Key &key, + int &height, + Accidental &accidental, + NoAccidentalStrategy noAccidentalStrategy) +{ + + // 1. Calculate the octave (for later): + int octave = rawpitch / 12; + + // 2. Set initial height to 0 + height = 0; + + // 3. Calculate raw semitone number, yielding a value between 0 (C) and + // 11 (B) + int pitch = rawpitch % 12; + + // clear the in-coming accidental so we can trap any failure to re-set + // it on the way out: + Accidental userAccidental = accidental; + accidental = ""; + + if (userAccidental == NoAccidental || !Pitch(rawpitch, userAccidental).validAccidental()) + { + userAccidental = resolveNoAccidental(pitch, key, noAccidentalStrategy); + //std::cout << "Chose accidental " << userAccidental << " for pitch " << pitch << + // " in key " << key.getName() << std::endl; + } + //else + //{ + // std::cout << "Accidental was specified, as " << userAccidental << std::endl; + //} + + resolveSpecifiedAccidental(pitch, clef, key, height, octave, userAccidental, accidental); + + // Failsafe... If this ever executes, there's trouble to fix... +// WIP - DMM - munged up to explore #937389, which is temporarily deferred, +// owing to its non-critical nature, having been hacked around in the LilyPond +// code +#ifndef DEBUG_PITCH + if (accidental == "") { + std::cerr << "Pitch::rawPitchToDisplayPitch(): error! returning null accidental for:" +#else + std::cerr << "Pitch::rawPitchToDisplayPitch(): calculating: " +#endif + << std::endl << "pitch: " << rawpitch << " (" << pitch << " in oct " + << octave << ") userAcc: " << userAccidental + << " clef: " << clef.getClefType() << " key: " << key.getName() << std::endl; +#ifndef DEBUG_PITCH + } +#endif + + + // 6. "Recenter" height in case it's been changed: + height = ((height + 2) % 7) - 2; + + height += (octave - 5) * 7; + height += clef.getPitchOffset(); + + + // 7. Transpose up or down for the clef: + height -= 7 * clef.getOctave(); +} + +void +Pitch::displayPitchToRawPitch(int height, + Accidental accidental, + const Clef &clef, + const Key &key, + int &pitch, + bool ignoreOffset) +{ + int octave = 5; + + // 1. Ask Key for accidental if necessary + if (accidental == NoAccidental) { + accidental = key.getAccidentalAtHeight(height, clef); + } + + // 2. Get pitch and correct octave + + if (!ignoreOffset) height -= clef.getPitchOffset(); + + while (height < 0) { octave -= 1; height += 7; } + while (height >= 7) { octave += 1; height -= 7; } + + if (height > 4) ++octave; + + // Height is now relative to treble clef lines + switch (height) { + + case 0: pitch = 4; break; /* bottom line, treble clef: E */ + case 1: pitch = 5; break; /* F */ + case 2: pitch = 7; break; /* G */ + case 3: pitch = 9; break; /* A, in next octave */ + case 4: pitch = 11; break; /* B, likewise*/ + case 5: pitch = 0; break; /* C, moved up an octave (see above) */ + case 6: pitch = 2; break; /* D, likewise */ + } + // Pitch is now "natural"-ized note at given height + + // 3. Adjust pitch for accidental + + if (accidental != NoAccidental && + accidental != Natural) { + if (accidental == Sharp) { pitch++; } + else if (accidental == Flat) { pitch--; } + else if (accidental == DoubleSharp) { pitch += 2; } + else if (accidental == DoubleFlat) { pitch -= 2; } + } + + // 4. Adjust for clef + octave += clef.getOctave(); + + pitch += 12 * octave; +} + + + +Pitch::Pitch(const Event &e) : + // throw (Event::NoData) + m_accidental(NoAccidental) +{ + m_pitch = e.get(BaseProperties::PITCH); + e.get(BaseProperties::ACCIDENTAL, m_accidental); +} + +Pitch::Pitch(int performancePitch, const Accidental &explicitAccidental) : + m_pitch(performancePitch), + m_accidental(explicitAccidental) +{ + // nothing +} + +Pitch::Pitch(int pitchInOctave, int octave, + const Accidental &explicitAccidental, int octaveBase) : + m_pitch((octave - octaveBase) * 12 + pitchInOctave), + m_accidental(explicitAccidental) +{ + // nothing else +} + +Pitch::Pitch(int noteInScale, int octave, const Key &key, + const Accidental &explicitAccidental, int octaveBase) : + m_pitch(0), + m_accidental(explicitAccidental) +{ + m_pitch = (key.getTonicPitch()); + m_pitch = (octave - octaveBase) * 12 + m_pitch % 12; + + if (key.isMinor()) m_pitch += scale_Cminor_harmonic[noteInScale]; + else m_pitch += scale_Cmajor[noteInScale]; + + m_pitch += Accidentals::getPitchOffset(m_accidental); +} + +Pitch::Pitch(int noteInCMajor, int octave, int pitch, + int octaveBase) : + m_pitch(pitch) +{ + int natural = (octave - octaveBase) * 12 + scale_Cmajor[noteInCMajor]; + m_accidental = Accidentals::getAccidental(pitch - natural); +} + + +Pitch::Pitch(char noteName, int octave, const Key &key, + const Accidental &explicitAccidental, int octaveBase) : + m_pitch(0), + m_accidental(explicitAccidental) +{ + int height = getIndexForNote(noteName) - 2; + displayPitchToRawPitch(height, explicitAccidental, + Clef(), key, m_pitch); + + // we now have the pitch within octave 5 (C == 60) -- though it + // might have spilled over at either end + if (m_pitch < 60) --octave; + if (m_pitch > 71) ++octave; + m_pitch = (octave - octaveBase) * 12 + m_pitch % 12; +} + +Pitch::Pitch(int heightOnStaff, const Clef &clef, const Key &key, + const Accidental &explicitAccidental) : + m_pitch(0), + m_accidental(explicitAccidental) +{ + displayPitchToRawPitch + (heightOnStaff, explicitAccidental, clef, key, m_pitch); +} + +Pitch::Pitch(const Pitch &p) : + m_pitch(p.m_pitch), + m_accidental(p.m_accidental) +{ + // nothing else +} + +Pitch & +Pitch::operator=(const Pitch &p) +{ + if (&p != this) { + m_pitch = p.m_pitch; + m_accidental = p.m_accidental; + } + return *this; +} + +int +Pitch::getPerformancePitch() const +{ + return m_pitch; +} + +Accidental +Pitch::getAccidental(bool useSharps) const +{ + return getDisplayAccidental(Key("C major"), + useSharps ? UseSharps : UseFlats); +} + +Accidental +Pitch::getAccidental(const Key &key) const +{ + if (m_accidental == NoAccidental || !validAccidental()) + { + Accidental retval = resolveNoAccidental(m_pitch, key, UseKey); + //std::cout << "Resolved No/invalid accidental: chose " << retval << std::endl; + return retval; + } + else + { + //std::cout << "Returning specified accidental" << std::endl; + return m_accidental; + } +} + +Accidental +Pitch::getDisplayAccidental(const Key &key) const +{ + return getDisplayAccidental(key, UseKey); +} + +Accidental +Pitch::getDisplayAccidental(const Key &key, NoAccidentalStrategy noAccidentalStrategy) const +{ + int heightOnStaff; + Accidental accidental(m_accidental); + rawPitchToDisplayPitch(m_pitch, Clef(), key, heightOnStaff, accidental, noAccidentalStrategy); + return accidental; +} + +int +Pitch::getNoteInScale(const Key &key) const +{ + int p = m_pitch; + p -= key.getTonicPitch(); + p -= Accidentals::getPitchOffset(getDisplayAccidental(key)); + p += 24; // in case these calculations made it -ve + p %= 12; + + if (key.isMinor()) return steps_Cminor_harmonic[p]; + else return steps_Cmajor[p]; +} + +char +Pitch::getNoteName(const Key &key) const +{ + int index = (getHeightOnStaff(Clef(Clef::Treble), key) + 72) % 7; + return getNoteForIndex(index); +} + +int +Pitch::getHeightOnStaff(const Clef &clef, const Key &key) const +{ + int heightOnStaff; + Accidental accidental(m_accidental); + rawPitchToDisplayPitch(m_pitch, clef, key, heightOnStaff, accidental, UseKey); + return heightOnStaff; +} + +int +Pitch::getHeightOnStaff(const Clef &clef, bool useSharps) const +{ + int heightOnStaff; + Accidental accidental(m_accidental); + rawPitchToDisplayPitch(m_pitch, clef, Key("C major"), heightOnStaff, accidental, + useSharps ? UseSharps : UseFlats); + return heightOnStaff; +} + +int +Pitch::getOctave(int octaveBase) const +{ + return m_pitch / 12 + octaveBase; +} + +int +Pitch::getPitchInOctave() const +{ + return m_pitch % 12; +} + +bool +Pitch::isDiatonicInKey(const Key &key) const +{ + if (getDisplayAccidental(key) == Accidentals::NoAccidental) return true; + + // ### as used in the chord identifiers, this calls chords built on + // the raised sixth step diatonic -- may be correct, but it's + // misleading, as we're really looking for whether chords are + // often built on given tone + + if (key.isMinor()) { + int stepsFromTonic = ((m_pitch - key.getTonicPitch() + 12) % 12); + if (stepsFromTonic == 9 || stepsFromTonic == 11) return true; + } + + return false; +} + +std::string +Pitch::getAsString(bool useSharps, bool inclOctave, int octaveBase) const +{ + Accidental acc = getAccidental(useSharps); + + std::string s; + s += getNoteName(useSharps ? Key("C major") : Key("A minor")); + + if (acc == Accidentals::Sharp) s += "#"; + else if (acc == Accidentals::Flat) s += "b"; + + if (!inclOctave) return s; + + char tmp[10]; + sprintf(tmp, "%s%d", s.c_str(), getOctave(octaveBase)); + return std::string(tmp); +} + +int +Pitch::getIndexForNote(char noteName) +{ + if (islower(noteName)) noteName = toupper(noteName); + if (noteName < 'C') { + if (noteName < 'A') return 0; // error, really + else return noteName - 'A' + 5; + } else { + if (noteName > 'G') return 0; // error, really + else return noteName - 'C'; + } +} + +char +Pitch::getNoteForIndex(int index) +{ + if (index < 0 || index > 6) return 'C'; // error, really + return "CDEFGAB"[index]; +} + +int +Pitch::getPerformancePitchFromRG21Pitch(int heightOnStaff, + const Accidental &accidental, + const Clef &clef, + const Key &) +{ + // Rosegarden 2.1 pitches are a bit weird; see + // docs/data_struct/units.txt + + // We pass the accidental and clef, a faked key of C major, and a + // flag telling displayPitchToRawPitch to ignore the clef offset + // and take only its octave into account + + int p = 0; + displayPitchToRawPitch(heightOnStaff, accidental, clef, Key(), p, true); + return p; +} + +Pitch Pitch::transpose(const Key &key, int pitchDelta, int heightDelta) +{ + // get old accidental + Accidental oldAccidental = getAccidental(key); + + // get old step + // TODO: maybe we should write an oldPitchObj.getOctave(0, key) that takes into account accidentals + // properly (e.g. yielding '0' instead of '1' for B#0). For now workaround here. + Pitch oldPitchWithoutAccidental(getPerformancePitch() - Accidentals::getPitchOffset(oldAccidental), Natural); + Key cmaj = Key(); + int oldStep = oldPitchWithoutAccidental.getNoteInScale(cmaj) + oldPitchWithoutAccidental.getOctave(0) * 7; + + // calculate new pitch and step + int newPitch = getPerformancePitch() + pitchDelta; + int newStep = oldStep + heightDelta; + + // could happen for example when transposing the tonic of a key downwards + if (newStep < 0 || newPitch < 0) { + newStep += 7; + newPitch += 12; + } + + // should not happen + if (newStep < 0 || newPitch < 0) { + std::cerr << "Internal error in NotationTypes, Pitch::transpose()" + << std::endl; + } + + // calculate new accidental for step + int pitchWithoutAccidental = ((newStep / 7) * 12 + scale_Cmajor[newStep % 7]); + int newAccidentalOffset = newPitch - pitchWithoutAccidental; + + // construct pitch-object to return + Pitch newPitchObj(newPitch, Accidentals::getAccidental(newAccidentalOffset)); + return newPitchObj; +} + +////////////////////////////////////////////////////////////////////// +// Note +////////////////////////////////////////////////////////////////////// + +const string Note::EventType = "note"; +const string Note::EventRestType = "rest"; +const int Note::EventRestSubOrdering = 10; + +const timeT Note::m_shortestTime = basePPQ / 16; + +Note& Note::operator=(const Note &n) +{ + if (&n == this) return *this; + m_type = n.m_type; + m_dots = n.m_dots; + return *this; +} + +timeT Note::getDurationAux() const +{ + int duration = m_shortestTime * (1 << m_type); + int extra = duration / 2; + for (int dots = m_dots; dots > 0; --dots) { + duration += extra; + extra /= 2; + } + return duration; +} + + +Note Note::getNearestNote(timeT duration, int maxDots) +{ + int tag = Shortest - 1; + timeT d(duration / m_shortestTime); + while (d > 0) { ++tag; d /= 2; } + +// cout << "Note::getNearestNote: duration " << duration << +// " leading to tag " << tag << endl; + if (tag < Shortest) return Note(Shortest); + if (tag > Longest) return Note(Longest, maxDots); + + timeT prospective = Note(tag, 0).getDuration(); + int dots = 0; + timeT extra = prospective / 2; + + while (dots <= maxDots && + dots <= tag) { // avoid TooManyDots exception from Note ctor + prospective += extra; + if (prospective > duration) return Note(tag, dots); + extra /= 2; + ++dots; +// cout << "added another dot okay" << endl; + } + + if (tag < Longest) return Note(tag + 1, 0); + else return Note(tag, std::max(maxDots, tag)); +} + +Event *Note::getAsNoteEvent(timeT absoluteTime, int pitch) const +{ + Event *e = new Event(EventType, absoluteTime, getDuration()); + e->set(BaseProperties::PITCH, pitch); + return e; +} + +Event *Note::getAsRestEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventRestType, absoluteTime, getDuration()); + return e; +} + + + +////////////////////////////////////////////////////////////////////// +// TimeSignature +////////////////////////////////////////////////////////////////////// + +const string TimeSignature::EventType = "timesignature"; +const int TimeSignature::EventSubOrdering = -150; +const PropertyName TimeSignature::NumeratorPropertyName = "numerator"; +const PropertyName TimeSignature::DenominatorPropertyName = "denominator"; +const PropertyName TimeSignature::ShowAsCommonTimePropertyName = "common"; +const PropertyName TimeSignature::IsHiddenPropertyName = "hidden"; +const PropertyName TimeSignature::HasHiddenBarsPropertyName = "hiddenbars"; +const TimeSignature TimeSignature::DefaultTimeSignature = TimeSignature(4, 4); + +TimeSignature::TimeSignature(int numerator, int denominator, + bool preferCommon, bool hidden, bool hiddenBars) + // throw (BadTimeSignature) + : m_numerator(numerator), m_denominator(denominator), + m_common(preferCommon && + (m_denominator == m_numerator && + (m_numerator == 2 || m_numerator == 4))), + m_hidden(hidden), + m_hiddenBars(hiddenBars) +{ + if (numerator < 1 || denominator < 1) { + throw BadTimeSignature("Numerator and denominator must be positive"); + } +} + +TimeSignature::TimeSignature(const Event &e) + // throw (Event::NoData, Event::BadType, BadTimeSignature) +{ + if (e.getType() != EventType) { + throw Event::BadType("TimeSignature model event", EventType, e.getType()); + } + m_numerator = 4; + m_denominator = 4; + + if (e.has(NumeratorPropertyName)) { + m_numerator = e.get(NumeratorPropertyName); + } + + if (e.has(DenominatorPropertyName)) { + m_denominator = e.get(DenominatorPropertyName); + } + + m_common = false; + e.get(ShowAsCommonTimePropertyName, m_common); + + m_hidden = false; + e.get(IsHiddenPropertyName, m_hidden); + + m_hiddenBars = false; + e.get(HasHiddenBarsPropertyName, m_hiddenBars); + + if (m_numerator < 1 || m_denominator < 1) { + throw BadTimeSignature("Numerator and denominator must be positive"); + } +} + +TimeSignature& TimeSignature::operator=(const TimeSignature &ts) +{ + if (&ts == this) return *this; + m_numerator = ts.m_numerator; + m_denominator = ts.m_denominator; + m_common = ts.m_common; + m_hidden = ts.m_hidden; + m_hiddenBars = ts.m_hiddenBars; + return *this; +} + +timeT TimeSignature::getBarDuration() const +{ + setInternalDurations(); + return m_barDuration; +} + +timeT TimeSignature::getBeatDuration() const +{ + setInternalDurations(); + return m_beatDuration; +} + +timeT TimeSignature::getUnitDuration() const +{ + return m_crotchetTime * 4 / m_denominator; +} + +Note::Type TimeSignature::getUnit() const +{ + int c, d; + for (c = 0, d = m_denominator; d > 1; d /= 2) ++c; + return Note::Semibreve - c; +} + +bool TimeSignature::isDotted() const +{ + setInternalDurations(); + return m_dotted; +} + +Event *TimeSignature::getAsEvent(timeT absoluteTime) const +{ + Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering); + e->set(NumeratorPropertyName, m_numerator); + e->set(DenominatorPropertyName, m_denominator); + e->set(ShowAsCommonTimePropertyName, m_common); + e->set(IsHiddenPropertyName, m_hidden); + e->set(HasHiddenBarsPropertyName, m_hiddenBars); + return e; +} + +// This doesn't consider subdivisions of the bar larger than a beat in +// any time other than 4/4, but it should handle the usual time signatures +// correctly (compound time included). + +void TimeSignature::getDurationListForInterval(DurationList &dlist, + timeT duration, + timeT startOffset) const +{ + setInternalDurations(); + + timeT offset = startOffset; + timeT durationRemaining = duration; + + while (durationRemaining > 0) { + + // Everything in this loop is of the form, "if we're on a + // [unit] boundary and there's a [unit] of space left to fill, + // insert a [unit] of time." + + // See if we can insert a bar of time. + + if (offset % m_barDuration == 0 + && durationRemaining >= m_barDuration) { + + getDurationListForBar(dlist); + durationRemaining -= m_barDuration, + offset += m_barDuration; + + } + + // If that fails and we're in 4/4 time, see if we can insert a + // half-bar of time. + + //_else_ if! + else if (m_numerator == 4 && m_denominator == 4 + && offset % (m_barDuration/2) == 0 + && durationRemaining >= m_barDuration/2) { + + dlist.push_back(m_barDuration/2); + durationRemaining -= m_barDuration/2; + offset += m_barDuration; + + } + + // If that fails, see if we can insert a beat of time. + + else if (offset % m_beatDuration == 0 + && durationRemaining >= m_beatDuration) { + + dlist.push_back(m_beatDuration); + durationRemaining -= m_beatDuration; + offset += m_beatDuration; + + } + + // If that fails, see if we can insert a beat-division of time + // (half the beat in simple time, a third of the beat in compound + // time) + + else if (offset % m_beatDivisionDuration == 0 + && durationRemaining >= m_beatDivisionDuration) { + + dlist.push_back(m_beatDivisionDuration); + durationRemaining -= m_beatDivisionDuration; + offset += m_beatDivisionDuration; + + } + + // cc: In practice, if the time we have remaining is shorter + // than our shortest note then we should just insert a single + // unit of the correct time; we won't be able to do anything + // useful with any shorter units anyway. + + else if (durationRemaining <= Note(Note::Shortest).getDuration()) { + + dlist.push_back(durationRemaining); + offset += durationRemaining; + durationRemaining = 0; + + } + + // If that fails, keep halving the beat division until we + // find something to insert. (This could be part of the beat-division + // case; it's only in its own place for clarity.) + + else { + + timeT currentDuration = m_beatDivisionDuration; + + while ( !(offset % currentDuration == 0 + && durationRemaining >= currentDuration) ) { + + if (currentDuration <= Note(Note::Shortest).getDuration()) { + + // okay, this isn't working. If our duration takes + // us past the next beat boundary, fill with an exact + // rest duration to there and then continue --cc + + timeT toNextBeat = + m_beatDuration - (offset % m_beatDuration); + + if (durationRemaining > toNextBeat) { + currentDuration = toNextBeat; + } else { + currentDuration = durationRemaining; + } + break; + } + + currentDuration /= 2; + } + + dlist.push_back(currentDuration); + durationRemaining -= currentDuration; + offset += currentDuration; + + } + + } + +} + +void TimeSignature::getDurationListForBar(DurationList &dlist) const +{ + + // If the bar's length can be represented with one long symbol, do it. + // Otherwise, represent it as individual beats. + + if (m_barDuration == m_crotchetTime || + m_barDuration == m_crotchetTime * 2 || + m_barDuration == m_crotchetTime * 4 || + m_barDuration == m_crotchetTime * 8 || + m_barDuration == m_dottedCrotchetTime || + m_barDuration == m_dottedCrotchetTime * 2 || + m_barDuration == m_dottedCrotchetTime * 4 || + m_barDuration == m_dottedCrotchetTime * 8) { + + dlist.push_back(getBarDuration()); + + } else { + + for (int i = 0; i < getBeatsPerBar(); ++i) { + dlist.push_back(getBeatDuration()); + } + + } + +} + +int TimeSignature::getEmphasisForTime(timeT offset) +{ + setInternalDurations(); + + if (offset % m_barDuration == 0) + return 4; + else if (m_numerator == 4 && m_denominator == 4 && + offset % (m_barDuration/2) == 0) + return 3; + else if (offset % m_beatDuration == 0) + return 2; + else if (offset % m_beatDivisionDuration == 0) + return 1; + else + return 0; +} + + +void TimeSignature::getDivisions(int depth, std::vector &divisions) const +{ + divisions.clear(); + + if (depth <= 0) return; + timeT base = getBarDuration(); // calls setInternalDurations +/* + if (m_numerator == 4 && m_denominator == 4) { + divisions.push_back(2); + base /= 2; + --depth; + } +*/ + if (depth <= 0) return; + + divisions.push_back(base / m_beatDuration); + base = m_beatDuration; + --depth; + + if (depth <= 0) return; + + if (m_dotted) divisions.push_back(3); + else divisions.push_back(2); + --depth; + + while (depth > 0) { + divisions.push_back(2); + --depth; + } + + return; +} + + +void TimeSignature::setInternalDurations() const +{ + int unitLength = m_crotchetTime * 4 / m_denominator; + + m_barDuration = m_numerator * unitLength; + + // Is 3/8 dotted time? This will report that it isn't, because of + // the check for m_numerator > 3 -- but otherwise we'd get a false + // positive with 3/4 + + // [rf] That's an acceptable answer, according to my theory book. In + // practice, you can say it's dotted time iff it has 6, 9, or 12 on top. + + m_dotted = (m_numerator % 3 == 0 && + m_numerator > 3 && + m_barDuration >= m_dottedCrotchetTime); + + if (m_dotted) { + m_beatDuration = unitLength * 3; + m_beatDivisionDuration = unitLength; + } + else { + m_beatDuration = unitLength; + m_beatDivisionDuration = unitLength / 2; + } + +} + +const timeT TimeSignature::m_crotchetTime = basePPQ; +const timeT TimeSignature::m_dottedCrotchetTime = basePPQ + basePPQ/2; + + + +////////////////////////////////////////////////////////////////////// +// AccidentalTable +////////////////////////////////////////////////////////////////////// + +AccidentalTable::AccidentalTable(const Key &key, const Clef &clef, + OctaveType octaves, BarResetType barReset) : + m_key(key), m_clef(clef), + m_octaves(octaves), m_barReset(barReset) +{ + // nothing else +} + +AccidentalTable::AccidentalTable(const AccidentalTable &t) : + m_key(t.m_key), m_clef(t.m_clef), + m_octaves(t.m_octaves), m_barReset(t.m_barReset), + m_accidentals(t.m_accidentals), + m_canonicalAccidentals(t.m_canonicalAccidentals), + m_newAccidentals(t.m_newAccidentals), + m_newCanonicalAccidentals(t.m_newCanonicalAccidentals) +{ + // nothing else +} + +AccidentalTable & +AccidentalTable::operator=(const AccidentalTable &t) +{ + if (&t != this) { + m_key = t.m_key; + m_clef = t.m_clef; + m_octaves = t.m_octaves; + m_barReset = t.m_barReset; + m_accidentals = t.m_accidentals; + m_canonicalAccidentals = t.m_canonicalAccidentals; + m_newAccidentals = t.m_newAccidentals; + m_newCanonicalAccidentals = t.m_newCanonicalAccidentals; + } + return *this; +} + +Accidental +AccidentalTable::processDisplayAccidental(const Accidental &acc0, int height, + bool &cautionary) +{ + Accidental acc = acc0; + + int canonicalHeight = Key::canonicalHeight(height); + Accidental keyAcc = m_key.getAccidentalAtHeight(canonicalHeight, m_clef); + + Accidental normalAcc = NoAccidental; + Accidental canonicalAcc = NoAccidental; + Accidental prevBarAcc = NoAccidental; + + if (m_octaves == OctavesEquivalent || + m_octaves == OctavesCautionary) { + + AccidentalMap::iterator i = m_canonicalAccidentals.find(canonicalHeight); + if (i != m_canonicalAccidentals.end() && !i->second.previousBar) { + canonicalAcc = i->second.accidental; + } + } + + if (m_octaves == OctavesEquivalent) { + normalAcc = canonicalAcc; + } else { + AccidentalMap::iterator i = m_accidentals.find(height); + if (i != m_accidentals.end() && !i->second.previousBar) { + normalAcc = i->second.accidental; + } + } + + if (m_barReset != BarResetNone) { + AccidentalMap::iterator i = m_accidentals.find(height); + if (i != m_accidentals.end() && i->second.previousBar) { + prevBarAcc = i->second.accidental; + } + } + +// std::cerr << "AccidentalTable::processDisplayAccidental: acc " << acc0 << ", h " << height << ", caut " << cautionary << ", ch " << canonicalHeight << ", keyacc " << keyAcc << " canacc " << canonicalAcc << " noracc " << normalAcc << " oct " << m_octaves << " barReset = " << m_barReset << " pbacc " << prevBarAcc << std::endl; + + if (acc == NoAccidental) acc = keyAcc; + + if (m_octaves == OctavesIndependent || + m_octaves == OctavesEquivalent) { + + if (normalAcc == NoAccidental) { + normalAcc = keyAcc; + } + + if (acc == normalAcc) { + if (!cautionary) acc = NoAccidental; + } else if (acc == NoAccidental) { + if (normalAcc != Natural) { + acc = Natural; + } + } + + } else { + + if (normalAcc != NoAccidental) { + if (acc != normalAcc) { + if (acc == NoAccidental) { + if (normalAcc != Natural) { + acc = Natural; + } + } + } else { // normalAcc != NoAccidental, acc == normalAcc + if (canonicalAcc != NoAccidental && canonicalAcc != normalAcc) { + cautionary = true; + } else { // canonicalAcc == NoAccidental || canonicalAcc == normalAcc + if (!cautionary) { + acc = NoAccidental; + } + } + } + } else { // normalAcc == NoAccidental + if (acc != keyAcc && keyAcc != Natural) { + if (acc == NoAccidental) { + acc = Natural; + } + } else { // normalAcc == NoAccidental, acc == keyAcc + if (canonicalAcc != NoAccidental && canonicalAcc != keyAcc) { + cautionary = true; + if (acc == NoAccidental) { + acc = Natural; + } + } else { // canonicalAcc == NoAccidental || canonicalAcc == keyAcc + if (!cautionary) { + acc = NoAccidental; + } + } + } + } + } + + if (m_barReset != BarResetNone) { + if (acc == NoAccidental) { + if (prevBarAcc != NoAccidental && + prevBarAcc != keyAcc && + !(prevBarAcc == Natural && keyAcc == NoAccidental)) { + cautionary = (m_barReset == BarResetCautionary); + if (keyAcc == NoAccidental) { + acc = Natural; + } else { + acc = keyAcc; + } + } + } + } + + if (acc != NoAccidental) { + m_newAccidentals[height] = AccidentalRec(acc, false); + m_newCanonicalAccidentals[canonicalHeight] = AccidentalRec(acc, false); + } + + return acc; +} + +void +AccidentalTable::update() +{ + m_accidentals = m_newAccidentals; + m_canonicalAccidentals = m_newCanonicalAccidentals; +} + +void +AccidentalTable::newBar() +{ + for (AccidentalMap::iterator i = m_accidentals.begin(); + i != m_accidentals.end(); ) { + + if (i->second.previousBar) { + AccidentalMap::iterator j = i; + ++j; + m_accidentals.erase(i); + i = j; + } else { + i->second.previousBar = true; + ++i; + } + } + + m_canonicalAccidentals.clear(); + + m_newAccidentals = m_accidentals; + m_newCanonicalAccidentals.clear(); +} + +void +AccidentalTable::newClef(const Clef &clef) +{ + m_clef = clef; +} + + +} // close namespace diff --git a/src/base/NotationTypes.h b/src/base/NotationTypes.h new file mode 100644 index 0000000..9133983 --- /dev/null +++ b/src/base/NotationTypes.h @@ -0,0 +1,1342 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _NOTATION_TYPES_H_ +#define _NOTATION_TYPES_H_ + +#include +#include + +#include "Event.h" +#include "Instrument.h" + +/* + * NotationTypes.h + * + * This file contains definitions of several classes to assist in + * creating and manipulating certain event types. The classes are: + * + * Accidental + * Clef + * Key + * Indication + * Pitch + * Note + * TimeSignature + * AccidentalTable + * + * The classes in this file are _not_ actually used for storing + * events. Events are always stored in Event objects (see Event.h). + * + * These classes are usually constructed on-the-fly when a particular + * operation specific to a single sort of event is required, and + * usually destroyed as soon as they go out of scope. The most common + * usages are for creating events (create an instance of one of these + * classes with the data you require, then call getAsEvent on it), for + * doing notation-related calculations from existing events (such as + * the bar duration of a time signature), and for doing calculations + * that are independent of any particular instance of an event (such + * as the Note methods that calculate duration-related values without + * reference to any specific pitch or other note-event properties; or + * everything in Pitch). + * + * This file also defines the event types and standard property names + * for the basic events. + */ + +namespace Rosegarden +{ + +extern const int MIN_SUBORDERING; + +typedef std::list DurationList; + + +/** + * Accidentals are stored in the event as string properties, purely + * for clarity. (They aren't manipulated _all_ that often, so this + * probably isn't a great inefficiency.) Originally we used an enum + * for the Accidental type with conversion functions to and from + * strings, but making Accidental a string seems simpler. + */ + +typedef std::string Accidental; + +namespace Accidentals +{ + extern const Accidental NoAccidental; + extern const Accidental Sharp; + extern const Accidental Flat; + extern const Accidental Natural; + extern const Accidental DoubleSharp; + extern const Accidental DoubleFlat; + + typedef std::vector AccidentalList; + + /** + * When no accidental is specified for a pitch, there are several + * strategies to determine what accidental to display for an + * out-of-key pitch + */ + enum NoAccidentalStrategy { + /** always use sharps */ + UseSharps, + /** always use flats */ + UseFlats, + /** always use sharps or always use flats depending on of what + * type of accidentals the current key is made up */ + UseKeySharpness, + /** use the most likely accidental for this key */ + UseKey + }; + + /** + * Get the predefined accidentals (i.e. the ones listed above) + * in their defined order. + */ + extern AccidentalList getStandardAccidentals(); + + /** + * Get the change in pitch resulting from an accidental: -1 for + * flat, 2 for double-sharp, 0 for natural or NoAccidental etc. + * This is not as useful as it may seem, as in reality the + * effect of an accidental depends on the key as well -- see + * the Key and Pitch classes. + */ + extern int getPitchOffset(const Accidental &accidental); + + + /** + * Get the Accidental corresponding to a change in pitch: flat + * for -1, double-sharp for 2, natural for 0 etc. + * + * Useful for tying to code that represents accidentals by + * their pitch change. + */ + extern Accidental getAccidental(int pitchChange); +} + + +/** + * Marks, like Accidentals, are stored in the event as string properties. + */ + +typedef std::string Mark; + +namespace Marks //!!! This would be better as a class, these days +{ + extern const Mark NoMark; // " " + + extern const Mark Accent; // ">" + extern const Mark Tenuto; // "-" ("legato" in RG2.1) + extern const Mark Staccato; // "." + extern const Mark Staccatissimo; // "'" + extern const Mark Marcato; // "^" + extern const Mark Sforzando; // "sf" + extern const Mark Rinforzando; // "rf" + + extern const Mark Trill; // "tr" + extern const Mark LongTrill; // with wiggly line + extern const Mark TrillLine; // line on its own + extern const Mark Turn; // "~" + + extern const Mark Pause; // aka "fermata" + + extern const Mark UpBow; // "v" + extern const Mark DownBow; // a square with the bottom side missing + + extern const Mark Mordent; + extern const Mark MordentInverted; + extern const Mark MordentLong; + extern const Mark MordentLongInverted; + + /** + * Given a string, return a mark that will be recognised as a + * text mark containing that string. For example, the Sforzando + * mark is actually defined as getTextMark("sf"). + */ + extern Mark getTextMark(std::string text); + + /** + * Return true if the given mark is a text mark. + */ + extern bool isTextMark(Mark mark); + + /** + * Extract the string from a text mark. + */ + extern std::string getTextFromMark(Mark mark); + + /** + * Given a string, return a mark that will be recognised as a + * fingering mark containing that string. (We use a string + * instead of a number to permit "fingering" marks containing + * labels like "+".) + */ + extern Mark getFingeringMark(std::string fingering); + + /** + * Return true if the given mark is a fingering mark. + */ + extern bool isFingeringMark(Mark mark); + + /** + * Extract the string from a fingering mark. + */ + extern std::string getFingeringFromMark(Mark mark); + + /** + * Extract the number of marks from an event. + */ + extern int getMarkCount(const Event &e); + + /** + * Extract the marks from an event. + */ + extern std::vector getMarks(const Event &e); + + /** + * Return the first fingering mark on an event (or NoMark, if none). + */ + extern Mark getFingeringMark(const Event &e); + + /** + * Add a mark to an event. If unique is true, add the mark only + * if the event does not already have it (otherwise permit + * multiple identical marks). + */ + extern void addMark(Event &e, const Mark &mark, bool unique); + + /** + * Remove a mark from an event. Returns true if the mark was + * there to remove. If the mark was not unique, removes only + * the first instance of it. + */ + extern bool removeMark(Event &e, const Mark &mark); + + /** + * Returns true if the event has the given mark. + */ + extern bool hasMark(const Event &e, const Mark &mark); + + /** + * Get the predefined marks (i.e. the ones listed above) in their + * defined order. + */ + extern std::vector getStandardMarks(); +} + + +/** + * Clefs are represented as one of a set of standard strings, stored + * within a clef Event. The Clef class defines those standards and + * provides a few bits of information about the clefs. + */ + +class Clef +{ +public: + static const std::string EventType; + static const int EventSubOrdering; + static const PropertyName ClefPropertyName; + static const PropertyName OctaveOffsetPropertyName; + static const Clef DefaultClef; + typedef Exception BadClefName; + + static const std::string Treble; + static const std::string French; + static const std::string Soprano; + static const std::string Mezzosoprano; + static const std::string Alto; + static const std::string Tenor; + static const std::string Baritone; + static const std::string Varbaritone; + static const std::string Bass; + static const std::string Subbass; + + /** + * Construct the default clef (treble). + */ + Clef() : m_clef(DefaultClef.m_clef), m_octaveOffset(0) { } + + /** + * Construct a Clef from the clef data in the given event. If the + * event is not of clef type or contains insufficient data, this + * returns the default clef (with a warning). You should normally + * test Clef::isValid() to catch that before construction. + */ + Clef(const Event &e); + + /** + * Construct a Clef from the given data. Throws a BadClefName + * exception if the given string does not match one of the above + * clef name constants. + */ + Clef(const std::string &s, int octaveOffset = 0); + + Clef(const Clef &c) : m_clef(c.m_clef), m_octaveOffset(c.m_octaveOffset) { + } + + Clef &operator=(const Clef &c); + + bool operator==(const Clef &c) const { + return c.m_clef == m_clef && c.m_octaveOffset == m_octaveOffset; + } + + bool operator!=(const Clef &c) const { + return !(c == *this); + } + + ~Clef() { } + + /** + * Test whether the given event is a valid Clef event. + */ + static bool isValid(const Event &e); + + /** + * Return the basic clef type (Treble, French, Soprano, Mezzosoprano, Alto, Tenor, Baritone, Varbaritone, Bass, Subbass) + */ + std::string getClefType() const { return m_clef; } + + /** + * Return any additional octave offset, that is, return 1 for + * a clef shifted an 8ve up, etc + */ + int getOctaveOffset() const { return m_octaveOffset; } + + /** + * Return the number of semitones a pitch in the treble clef would + * have to be lowered by in order to be drawn with the same height + * and accidental in this clef + */ + int getTranspose() const; + + /** + * Return the octave component of getTranspose(), i.e. the number + * of octaves difference in pitch between this clef and the treble + */ + int getOctave() const; + + /** + * Return the intra-octave component of getTranspose(), i.e. the + * number of semitones this clef is distinct in pitch from the treble + * besides the difference in octaves + */ + int getPitchOffset() const; + + /** + * Return the height-on-staff (in Pitch terminology) + * of the clef's axis -- the line around which the clef is drawn. + */ + int getAxisHeight() const; + + typedef std::vector ClefList; + + /** + * Return all the clefs, in ascending order of pitch + */ + static ClefList getClefs(); + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsEvent(timeT absoluteTime) const; + +private: + std::string m_clef; + int m_octaveOffset; +}; + +/** + * All we store in a key Event is the name of the key. A Key object + * can be constructed from such an Event or just from its name, and + * will return all the properties of the key. The Key class also + * provides some useful mechanisms for getting information about and + * transposing between keys. + */ + +class Key +{ +public: + static const std::string EventType; + static const int EventSubOrdering; + static const PropertyName KeyPropertyName; + static const Key DefaultKey; + typedef Exception BadKeyName; + typedef Exception BadKeySpec; + + /** + * Construct the default key (C major). + */ + Key(); + + /** + * Construct a Key from the key data in the given event. If the + * event is not of key type or contains insufficient data, this + * returns the default key (with a warning). You should normally + * test Key::isValid() to catch that before construction. + */ + Key(const Event &e); + + /** + * Construct the named key. Throws a BadKeyName exception if the + * given string does not match one of the known key names. + */ + Key(const std::string &name); + + /** + * Construct a key from signature and mode. May throw a + * BadKeySpec exception. + */ + Key(int accidentalCount, bool isSharp, bool isMinor); + + /** + * Construct the key with the given tonic and mode. (Ambiguous.) + * May throw a BadKeySpec exception. + */ + Key(int tonicPitch, bool isMinor); + + Key(const Key &kc); + + ~Key() { + delete m_accidentalHeights; + } + + Key &operator=(const Key &kc); + + bool operator==(const Key &k) const { + return k.m_name == m_name; + } + + bool operator!=(const Key &k) const { + return !(k == *this); + } + + /** + * Test whether the given event is a valid Key event. + */ + static bool isValid(const Event &e); + + /** + * Return true if this is a minor key. Unlike in RG2.1, + * we distinguish between major and minor keys with the + * same signature. + */ + bool isMinor() const { + return m_keyDetailMap[m_name].m_minor; + } + + /** + * Return true if this key's signature is made up of + * sharps, false if flats. + */ + bool isSharp() const { + return m_keyDetailMap[m_name].m_sharps; + } + + /** + * Return the pitch of the tonic note in this key, as a + * MIDI (or RG4) pitch modulo 12 (i.e. in the range 0-11). + * This is the pitch of the note named in the key's name, + * e.g. 0 for the C in C major. + */ + int getTonicPitch() const { + return m_keyDetailMap[m_name].m_tonicPitch; + } + + /** + * Return the number of sharps or flats in the key's signature. + */ + int getAccidentalCount() const { + return m_keyDetailMap[m_name].m_sharpCount; + } + + /** + * Return the key with the same signature but different + * major/minor mode. For example if called on C major, + * returns A minor. + */ + Key getEquivalent() const { + return Key(m_keyDetailMap[m_name].m_equivalence); + } + + /** + * Return the name of the key, in a human-readable form + * also suitable for passing to the Key constructor. + */ + std::string getName() const { + return m_name; + } + + /** + * Return the name of the key, in the form used by RG2.1. + */ + std::string getRosegarden2Name() const { + return m_keyDetailMap[m_name].m_rg2name; + } + + /** + * Return the accidental at the given height-on-staff + * (in Pitch terminology) in the given clef. + */ + Accidental getAccidentalAtHeight(int height, const Clef &clef) const; + + /** + * Return the accidental for the the given number of steps + * from the tonic. For example: for F major, step '3' is the + * Bb, so getAccidentalForStep(3) will yield a Flat. + */ + Accidental getAccidentalForStep(int steps) const; + + /** + * Return the heights-on-staff (in Pitch + * terminology) of all accidentals in the key's signature, + * in the given clef. + */ + std::vector getAccidentalHeights(const Clef &clef) const; + + /** + * Return the result of applying this key to the given + * pitch, that is, modifying the pitch so that it has the + * same status in terms of accidentals as it had when + * found in the given previous key. + */ + int convertFrom(int pitch, const Key &previousKey, + const Accidental &explicitAccidental = + Accidentals::NoAccidental) const; + + /** + * Return the result of transposing the given pitch into + * this key, that is, modifying the pitch by the difference + * between the tonic pitches of this and the given previous + * key. + */ + int transposeFrom(int pitch, const Key &previousKey) const; + + /** + * Reduce a height-on-staff to a single octave, so that it + * can be compared against the accidental heights returned + * by the preceding method. + */ + static inline unsigned int canonicalHeight(int height) { + return (height > 0) ? (height % 7) : ((7 - (-height % 7)) % 7); + } + + typedef std::vector KeyList; + + /** + * Return all the keys in the given major/minor mode, in + * no particular order. + */ + static KeyList getKeys(bool minor = false); + + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsEvent(timeT absoluteTime) const; + + /** + * Transpose this key by the specified interval given in pitch and steps + * + * For example: transposing F major by a major triad (4,2) yields + * A major. + */ + Key transpose(int pitchDelta, int heightDelta); + +private: + std::string m_name; + mutable std::vector *m_accidentalHeights; + + struct KeyDetails { + bool m_sharps; + bool m_minor; + int m_sharpCount; + std::string m_equivalence; + std::string m_rg2name; + int m_tonicPitch; + + KeyDetails(); // ctor needed in order to live in a map + + KeyDetails(bool sharps, bool minor, int sharpCount, + std::string equivalence, std::string rg2name, + int m_tonicPitch); + + KeyDetails(const KeyDetails &d); + + KeyDetails &operator=(const KeyDetails &d); + }; + + + typedef std::map KeyDetailMap; + static KeyDetailMap m_keyDetailMap; + static void checkMap(); + void checkAccidentalHeights() const; + +}; + + +/** + * Indication is a collective name for graphical marks that span a + * series of events, such as slurs, dynamic marks etc. These are + * stored in indication Events with a type and duration. The + * Indication class gives a basic set of indication types. + */ + +class Indication +{ +public: + static const std::string EventType; + static const int EventSubOrdering; + static const PropertyName IndicationTypePropertyName; + typedef Exception BadIndicationName; + + static const std::string Slur; + static const std::string PhrasingSlur; + static const std::string Crescendo; + static const std::string Decrescendo; + static const std::string Glissando; + + static const std::string QuindicesimaUp; + static const std::string OttavaUp; + static const std::string OttavaDown; + static const std::string QuindicesimaDown; + + Indication(const Event &e) + /* throw (Event::NoData, Event::BadType) */; + Indication(const std::string &s, timeT indicationDuration) + /* throw (BadIndicationName) */; + + Indication(const Indication &m) : m_indicationType(m.m_indicationType), + m_duration(m.m_duration) { } + + Indication &operator=(const Indication &m); + + ~Indication() { } + + std::string getIndicationType() const { return m_indicationType; } + timeT getIndicationDuration() const { return m_duration; } + + bool isOttavaType() const { + return + m_indicationType == QuindicesimaUp || + m_indicationType == OttavaUp || + m_indicationType == OttavaDown || + m_indicationType == QuindicesimaDown; + } + + int getOttavaShift() const { + return (m_indicationType == QuindicesimaUp ? 2 : + m_indicationType == OttavaUp ? 1 : + m_indicationType == OttavaDown ? -1 : + m_indicationType == QuindicesimaDown ? -2 : 0); + } + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsEvent(timeT absoluteTime) const; + +private: + bool isValid(const std::string &s) const; + + std::string m_indicationType; + timeT m_duration; +}; + + + +/** + * Definitions for use in the text Event type. + */ + +class Text +{ +public: + static const std::string EventType; + static const int EventSubOrdering; + static const PropertyName TextPropertyName; + static const PropertyName TextTypePropertyName; + static const PropertyName LyricVersePropertyName; + + /** + * Text styles + */ + static const std::string UnspecifiedType; + static const std::string StaffName; + static const std::string ChordName; + static const std::string KeyName; + static const std::string Lyric; + static const std::string Chord; + static const std::string Dynamic; + static const std::string Direction; + static const std::string LocalDirection; + static const std::string Tempo; + static const std::string LocalTempo; + static const std::string Annotation; + static const std::string LilyPondDirective; + + /** + * Special LilyPond directives + */ + static const std::string Segno; // print segno here + static const std::string Coda; // print coda sign here + static const std::string Alternate1; // first alternative ending + static const std::string Alternate2; // second alternative ending + static const std::string BarDouble; // next barline is double + static const std::string BarEnd; // next barline is final double + static const std::string BarDot; // next barline is dotted + static const std::string Gliss; // \glissando on this note (to next note) + static const std::string Arpeggio; // \arpeggio on this chord +// static const std::string ArpeggioUp; // \ArpeggioUp on this chord +// static const std::string ArpeggioDn; // \ArpeggioDown on this chord + static const std::string Tiny; // begin \tiny font section + static const std::string Small; // begin \small font section + static const std::string NormalSize; // begin \normalsize font section + + Text(const Event &e) + /* throw (Event::NoData, Event::BadType) */; + Text(const std::string &text, + const std::string &textType = UnspecifiedType); + Text(const Text &); + Text &operator=(const Text &); + ~Text(); + + std::string getText() const { return m_text; } + std::string getTextType() const { return m_type; } + + int getVerse() const { return m_verse; } // only relevant for lyrics + void setVerse(int verse) { m_verse = verse; } + + static bool isTextOfType(Event *, std::string type); + + /** + * Return those text types that the user should be allowed to + * specify directly and visually + */ + static std::vector getUserStyles(); + + /** + * Return a list of available special LilyPond directives + */ + static std::vector getLilyPondDirectives(); + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsEvent(timeT absoluteTime) const; + +private: + std::string m_text; + std::string m_type; + long m_verse; +}; + + + +/** + * Pitch stores a note's pitch and provides information about it in + * various different ways, notably in terms of the position of the + * note on the staff and its associated accidental. + * + * (See docs/discussion/units.txt for explanation of pitch units.) + * + * This completely replaces the older NotationDisplayPitch class. + */ + +class Pitch +{ +public: + /** + * Construct a Pitch object based on the given Event, which must + * have a BaseProperties::PITCH property. If the property is + * absent, NoData is thrown. The BaseProperties::ACCIDENTAL + * property will also be used if present. + */ + Pitch(const Event &e) + /* throw Event::NoData */; + + /** + * Construct a Pitch object based on the given performance (MIDI) pitch. + */ + Pitch(int performancePitch, + const Accidental &explicitAccidental = Accidentals::NoAccidental); + + /** + * Construct a Pitch based on octave and pitch in octave. The + * lowest permissible octave number is octaveBase, and middle C is + * in octave octaveBase + 5. pitchInOctave must be in the range + * 0-11 where 0 is C, 1 is C sharp, etc. + */ + Pitch(int pitchInOctave, int octave, + const Accidental &explicitAccidental = Accidentals::NoAccidental, + int octaveBase = -2); + + /** + * Construct a Pitch based on octave and note in scale. The + * lowest permissible octave number is octaveBase, and middle C is + * in octave octaveBase + 5. The octave supplied should be that + * of the root note in the given key, which may be in a different + * MIDI octave from the resulting pitch (as MIDI octaves always + * begin at C). noteInScale must be in the range 0-6 where 0 is + * the root of the key and so on. The accidental is relative to + * noteInScale: if there is an accidental in the key for this note + * already, explicitAccidental will be "added" to it. + * + * For minor keys, the harmonic scale is used. + */ + Pitch(int noteInScale, int octave, const Key &key, + const Accidental &explicitAccidental = Accidentals::NoAccidental, + int octaveBase = -2); + + /** + * Construct a Pitch based on (MIDI) octave, note in the C major scale and + * performance pitch. The accidental is calculated based on these + * properties. + */ + Pitch(int noteInCMajor, int octave, int pitch, + int octaveBase = -2); + + /** + * Construct a Pitch based on octave and note name. The lowest + * permissible octave number is octaveBase, and middle C is in + * octave octaveBase + 5. noteName must be a character in the + * range [CDEFGAB] or lower-case equivalents. The key is supplied + * so that we know how to interpret the NoAccidental case. + */ + Pitch(char noteName, int octave, const Key &key, + const Accidental &explicitAccidental = Accidentals::NoAccidental, + int octaveBase = -2); + + /** + * Construct a Pitch corresponding a staff line or space on a + * classical 5-line staff. The bottom staff line has height 0, + * the top has height 8, and both positive and negative values are + * permissible. + */ + Pitch(int heightOnStaff, const Clef &clef, const Key &key, + const Accidental &explicitAccidental = Accidentals::NoAccidental); + + Pitch(const Pitch &); + Pitch &operator=(const Pitch &); + + /** + * Return the MIDI pitch for this Pitch object. + */ + int getPerformancePitch() const; + + /** + * Return the accidental for this pitch using a bool to prefer sharps over + * flats if there is any doubt. This is the accidental + * that would be used to display this pitch outside of the context + * of any key; that is, it may duplicate an accidental actually in + * the current key. This should not be used if you need to get an + * explicit accidental returned for E#, Fb, B# or Cb. + * + * This version of the function exists to avoid breaking old code. + */ + Accidental getAccidental(bool useSharps) const; + + /** + * Return the accidental for this pitch, using a key. This should be used + * if you need an explicit accidental returned for E#, Fb, B# or Cb, which + * can't be resolved correctly without knowing that their key requires + * them to take an accidental. The provided key will also be used to + * determine whether to prefer sharps over flats. + */ + Accidental getAccidental(const Key &key) const; + + /** + * Return the accidental that should be used to display this pitch + * in a given key. For example, if the pitch is F-sharp in a key + * in which F has a sharp, NoAccidental will be returned. (This + * is in contrast to getAccidental, which would return Sharp.) + * This obviously can't take into account things like which + * accidentals have already been displayed in the bar, etc. + */ + Accidental getDisplayAccidental(const Key &key) const; + + /** + * Return the accidental that should be used to display this pitch + * in a given key, using the given strategy to resolve pitches where + * an accidental is needed but not specified. + */ + Accidental getDisplayAccidental(const Key &key, Accidentals::NoAccidentalStrategy) const; + + /** + * Return the position in the scale for this pitch, as a number in + * the range 0 to 6 where 0 is the root of the key. + */ + int getNoteInScale(const Key &key) const; + + /** + * Return the note name for this pitch, as a single character in + * the range A to G. (This is a reference value that should not + * normally be shown directly to the user, for i18n reasons.) + */ + char getNoteName(const Key &key) const; + + /** + * Return the height at which this pitch should display on a + * conventional 5-line staff. 0 is the bottom line, 1 the first + * space, etc., so for example middle-C in the treble clef would + * return -2. + * + * Chooses the most likely accidental for this pitch in this key. + */ + int getHeightOnStaff(const Clef &clef, const Key &key) const; + + /** + * Return the height at which this pitch should display on a + * conventional 5-line staff. 0 is the bottom line, 1 the first + * space, etc., so for example middle-C in the treble clef would + * return -2. + * + * Chooses the accidental specified by the 'useSharps' parameter + */ + int getHeightOnStaff(const Clef &clef, bool useSharps) const; + + /** + * Return the octave containing this pitch. The octaveBase argument + * specifies the octave containing MIDI pitch 0; middle-C is in octave + * octaveBase + 5. + */ + int getOctave(int octaveBase = -2) const; + + /** + * Return the pitch within the octave, in the range 0 to 11. + */ + int getPitchInOctave() const; + + /** + * Return whether this pitch is diatonic in the given key. + */ + bool isDiatonicInKey(const Key &key) const; + + /** + * Return a reference name for this pitch. (C4, Bb2, etc...) + * according to http://www.harmony-central.com/MIDI/Doc/table2.html + * + * Note that this does not take into account the stored accidental + * -- this string is purely an encoding of the MIDI pitch, with + * the accidental in the string selected according to the + * useSharps flag (which may be expected to have come from a call + * to Key::isSharp). + * + * If inclOctave is false, this will return C, Bb, etc. + */ + std::string getAsString(bool useSharps, + bool inclOctave = true, + int octaveBase = -2) const; + + /** + * Return a number 0-6 corresponding to the given note name, which + * must be in the range [CDEFGAB] or lower-case equivalents. The + * return value is in the range 0-6 with 0 for C, 1 for D etc. + */ + static int getIndexForNote(char noteName); + + /** + * Return a note name corresponding to the given note index, which + * must be in the range 0-6 with 0 for C, 1 for D etc. + */ + static char getNoteForIndex(int index); + + /** + * Calculate and return the performance (MIDI) pitch corresponding + * to the stored height and accidental, interpreting them as + * Rosegarden-2.1-style values (for backward compatibility use), + * in the given clef and key + */ + static int getPerformancePitchFromRG21Pitch(int heightOnStaff, + const Accidental &accidental, + const Clef &clef, + const Key &key); + + /** + * return the result of transposing the given pitch by the + * specified interval in the given key. The key is left unchanged, + * only the pitch is transposed. + */ + Pitch transpose(const Key &key, int pitchDelta, int heightDelta); + + /** + * checks whether the accidental specified for this pitch (if any) + * is valid - for example, a Sharp for pitch 11 is invalid, as + * it's between A# and B#. + */ + bool validAccidental() const; + + /** + * Returned event is on heap; caller takes responsibility for ownership + */ + Event *getAsNoteEvent(timeT absoluteTime, timeT duration) const; + +private: + int m_pitch; + Accidental m_accidental; + + static void rawPitchToDisplayPitch + (int, const Clef &, const Key &, int &, Accidental &, + Accidentals::NoAccidentalStrategy); + + static void displayPitchToRawPitch + (int, Accidental, const Clef &, const Key &, + int &, bool ignoreOffset = false); +}; + + + +class TimeSignature; + + +/** + * The Note class represents note durations only, not pitch or + * accidental; it's therefore just as relevant to rest events as to + * note events. You can construct one of these from either. + */ + +class Note +{ +public: + static const std::string EventType; + static const std::string EventRestType; + static const int EventRestSubOrdering; + + typedef int Type; // not an enum, too much arithmetic at stake + + // define both sorts of names; some people prefer the American + // names, but I just can't remember which of them is which + + static const Type + + SixtyFourthNote = 0, + ThirtySecondNote = 1, + SixteenthNote = 2, + EighthNote = 3, + QuarterNote = 4, + HalfNote = 5, + WholeNote = 6, + DoubleWholeNote = 7, + + Hemidemisemiquaver = 0, + Demisemiquaver = 1, + Semiquaver = 2, + Quaver = 3, + Crotchet = 4, + Minim = 5, + Semibreve = 6, + Breve = 7, + + Shortest = 0, + Longest = 7; + + + /** + * Create a Note object of the given type, representing a + * particular sort of duration. Note objects are strictly + * durational; they don't represent pitch, and may be as + * relevant to rests as actual notes. + */ + Note(Type type, int dots = 0) : + m_type(type < Shortest ? Shortest : + type > Longest ? Longest : + type), + m_dots(dots) { } + + Note(const Note &n) : m_type(n.m_type), m_dots(n.m_dots) { } + ~Note() { } + + Note &operator=(const Note &n); + + Type getNoteType() const { return m_type; } + int getDots() const { return m_dots; } + + /** + * Return the duration of this note type. + */ + timeT getDuration() const { + return m_dots ? getDurationAux() : (m_shortestTime * (1 << m_type)); + } + + /** + * Return the Note whose duration is closest to (but shorter than or + * equal to) the given duration, permitting at most maxDots dots. + */ + static Note getNearestNote(timeT duration, int maxDots = 2); + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsNoteEvent(timeT absoluteTime, int pitch) const; + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsRestEvent(timeT absoluteTime) const; + + +private: + Type m_type; + int m_dots; + + timeT getDurationAux() const; + + // a time & effort saving device; if changing this, change + // TimeSignature::m_crotchetTime etc too + static const timeT m_shortestTime; +}; + + + +/** + * TimeSignature contains arithmetic methods relevant to time + * signatures and bar durations, including code for splitting long + * rest intervals into bite-sized chunks. Although there is a time + * signature Event type, these Events don't appear in regular Segments + * but only in the Composition's reference segment. + */ + +class TimeSignature +{ +public: + static const TimeSignature DefaultTimeSignature; + typedef Exception BadTimeSignature; + + TimeSignature() : + m_numerator(DefaultTimeSignature.m_numerator), + m_denominator(DefaultTimeSignature.m_denominator), + m_common(false), m_hidden(false), m_hiddenBars(false) { } + + /** + * Construct a TimeSignature object describing a time signature + * with the given numerator and denominator. If preferCommon is + * true and the time signature is a common or cut-common time, the + * constructed object will return true for isCommon; if hidden is + * true, the time signature is intended not to be displayed and + * isHidden will return true; if hiddenBars is true, the bar lines + * between this time signature and the next will not be shown. + */ + TimeSignature(int numerator, int denominator, + bool preferCommon = false, + bool hidden = false, + bool hiddenBars = false) + /* throw (BadTimeSignature) */; + + TimeSignature(const TimeSignature &ts) : + m_numerator(ts.m_numerator), + m_denominator(ts.m_denominator), + m_common(ts.m_common), + m_hidden(ts.m_hidden), + m_hiddenBars(ts.m_hiddenBars) { } + + ~TimeSignature() { } + + TimeSignature &operator=(const TimeSignature &ts); + + bool operator==(const TimeSignature &ts) const { + return ts.m_numerator == m_numerator && ts.m_denominator == m_denominator; + } + bool operator!=(const TimeSignature &ts) const { + return !operator==(ts); + } + + int getNumerator() const { return m_numerator; } + int getDenominator() const { return m_denominator; } + + bool isCommon() const { return m_common; } + bool isHidden() const { return m_hidden; } + bool hasHiddenBars() const { return m_hiddenBars; } + + timeT getBarDuration() const; + + /** + * Return the unit of the time signature. This is the note + * implied by the denominator. For example, the unit of 4/4 time + * is the crotchet, and that of 6/8 is the quaver. (The numerator + * of the time signature gives the number of units per bar.) + */ + Note::Type getUnit() const; + + /** + * Return the duration of the unit of the time signature. + * See also getUnit(). In most cases getBeatDuration() gives + * a more meaningful value. + */ + timeT getUnitDuration() const; + + /** + * Return true if this time signature indicates dotted time. + */ + bool isDotted() const; + + /** + * Return the duration of the beat of the time signature. For + * example, the beat of 4/4 time is the crotchet, the same as its + * unit, but that of 6/8 is the dotted crotchet (there are only + * two beats in a 6/8 bar). The beat therefore depends on whether + * the signature indicates dotted or undotted time. + */ + timeT getBeatDuration() const; + + /** + * Return the number of beats in a complete bar. + */ + int getBeatsPerBar() const { + return getBarDuration() / getBeatDuration(); + } + + /** + * Get the "optimal" list of rest durations to make up a bar in + * this time signature. + */ + void getDurationListForBar(DurationList &dlist) const; + + /** + * Get the "optimal" list of rest durations to make up a time + * interval of the given total duration, starting at the given + * offset after the start of a bar, assuming that the interval + * is entirely in this time signature. + */ + void getDurationListForInterval(DurationList &dlist, + timeT intervalDuration, + timeT startOffset = 0) const; + + /** + * Get the level of emphasis for a position in a bar. 4 is lots + * of emphasis, 0 is none. + */ + int getEmphasisForTime(timeT offset); + + /** + * Return a list of divisions, subdivisions, subsubdivisions + * etc of a bar in this time, up to the given depth. For example, + * if the time signature is 6/8 and the depth is 3, return a list + * containing 2, 3, and 2 (there are 2 beats to the bar, each of + * which is best subdivided into 3 subdivisions, each of which + * divides most neatly into 2). + */ + void getDivisions(int depth, std::vector &divisions) const; + +private: + friend class Composition; + friend class TimeTempoSelection; + + TimeSignature(const Event &e) + /* throw (Event::NoData, Event::BadType, BadTimeSignature) */; + + static const std::string EventType; + static const int EventSubOrdering; + static const PropertyName NumeratorPropertyName; + static const PropertyName DenominatorPropertyName; + static const PropertyName ShowAsCommonTimePropertyName; + static const PropertyName IsHiddenPropertyName; + static const PropertyName HasHiddenBarsPropertyName; + + /// Returned event is on heap; caller takes responsibility for ownership + Event *getAsEvent(timeT absoluteTime) const; + +private: + int m_numerator; + int m_denominator; + + bool m_common; + bool m_hidden; + bool m_hiddenBars; + + mutable int m_barDuration; + mutable int m_beatDuration; + mutable int m_beatDivisionDuration; + mutable bool m_dotted; + void setInternalDurations() const; + + // a time & effort saving device + static const timeT m_crotchetTime; + static const timeT m_dottedCrotchetTime; +}; + + + +/** + * AccidentalTable represents a set of accidentals in force at a + * given time. + * + * Keep an AccidentalTable variable on-hand as you track through a + * staff; then when reading a chord, call processDisplayAccidental + * on the accidentals found in the chord to obtain the actual + * displayed accidentals and to tell the AccidentalTable to + * remember the accidentals that have been found in the chord. + * Then when the chord ends, call update() on the AccidentalTable + * so that that chord's accidentals are taken into account for the + * next one. + * + * Create a new AccidentalTable whenever a new key is encountered, + * and call newBar() or newClef() when a new bar happens or a new + * clef is encountered. + */ +class AccidentalTable +{ +public: + enum OctaveType { + OctavesIndependent, // if c' and c'' sharp, mark them both sharp + OctavesCautionary, // if c' and c'' sharp, put the second one in brackets + OctavesEquivalent // if c' and c'' sharp, only mark the first one + }; + + enum BarResetType { + BarResetNone, // c# | c -> omit natural + BarResetCautionary, // c# | c -> add natural to c in brackets + BarResetExplicit // c# | c -> add natural to c + }; + + AccidentalTable(const Key &, const Clef &, + OctaveType = OctavesCautionary, + BarResetType = BarResetCautionary); + + AccidentalTable(const AccidentalTable &); + AccidentalTable &operator=(const AccidentalTable &); + + Accidental processDisplayAccidental(const Accidental &displayAcc, + int heightOnStaff, + bool &cautionary); + + void update(); + + void newBar(); + void newClef(const Clef &); + +private: + Key m_key; + Clef m_clef; + OctaveType m_octaves; + BarResetType m_barReset; + + struct AccidentalRec { + AccidentalRec() : accidental(Accidentals::NoAccidental), previousBar(false) { } + AccidentalRec(Accidental a, bool p) : accidental(a), previousBar(p) { } + Accidental accidental; + bool previousBar; + }; + + typedef std::map AccidentalMap; + + AccidentalMap m_accidentals; + AccidentalMap m_canonicalAccidentals; + + AccidentalMap m_newAccidentals; + AccidentalMap m_newCanonicalAccidentals; +}; + + +} + + +#endif diff --git a/src/base/Profiler.cpp b/src/base/Profiler.cpp new file mode 100644 index 0000000..4f3ab42 --- /dev/null +++ b/src/base/Profiler.cpp @@ -0,0 +1,187 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 +#include "Profiler.h" + +#include +#include + +//#define NO_TIMING 1 + +#ifdef NDEBUG +#define NO_TIMING 1 +#endif + +using std::cerr; +using std::endl; + +namespace Rosegarden +{ + +Profiles* Profiles::m_instance = 0; + +Profiles* Profiles::getInstance() +{ + if (!m_instance) m_instance = new Profiles(); + + return m_instance; +} + +Profiles::Profiles() +{ +} + +Profiles::~Profiles() +{ + dump(); +} + +void Profiles::accumulate(const char* id, clock_t time, RealTime rt) +{ +#ifndef NO_TIMING + ProfilePair &pair(m_profiles[id]); + ++pair.first; + pair.second.first += time; + pair.second.second = pair.second.second + rt; + + TimePair &timePair(m_lastCalls[id]); + timePair.first = time; + timePair.second = rt; +#endif +} + +void Profiles::dump() +{ +#ifndef NO_TIMING + cerr << "Profiles::dump() :\n"; + + // I'm finding these two confusing dumped out in random order, + // so I'm going to sort them alphabetically: + + std::vector profileNames; + for (ProfileMap::iterator i = m_profiles.begin(); + i != m_profiles.end(); ++i) { + profileNames.push_back((*i).first); + } + + std::sort(profileNames.begin(), profileNames.end()); + + for (std::vector::iterator i = profileNames.begin(); + i != profileNames.end(); ++i) { + + cerr << "-> " << *i << ": CPU: " + << m_profiles[*i].first << " calls, " + << int((m_profiles[*i].second.first * 1000.0) / CLOCKS_PER_SEC) << "ms, " + << (((double)m_profiles[*i].second.first * 1000000.0 / + (double)m_profiles[*i].first) / CLOCKS_PER_SEC) << "us/call" + << endl; + + cerr << "-> " << *i << ": real: " + << m_profiles[*i].first << " calls, " + << m_profiles[*i].second.second << ", " + << (m_profiles[*i].second.second / m_profiles[*i].first) + << "/call" + << endl; + + cerr << "-> " << *i << ": last: CPU: " + << int((m_lastCalls[*i].first * 1000.0) / CLOCKS_PER_SEC) << "ms, " + << " real: " + << m_lastCalls[*i].second << endl; + } + + cerr << "Profiles::dump() finished\n"; +#endif +} + +Profiler::Profiler(const char* c, bool showOnDestruct) + : m_c(c), + m_showOnDestruct(showOnDestruct) +{ +#ifndef NO_TIMING + m_startCPU = clock(); + + struct timeval tv; + (void)gettimeofday(&tv, 0); + m_startTime = RealTime(tv.tv_sec, tv.tv_usec * 1000); +#endif +} + +void +Profiler::update() +{ +#ifndef NO_TIMING + clock_t elapsedCPU = clock() - m_startCPU; + + struct timeval tv; + (void)gettimeofday(&tv, 0); + RealTime elapsedTime = RealTime(tv.tv_sec, tv.tv_usec * 1000) - m_startTime; + + cerr << "Profiler : id = " << m_c + << " - elapsed so far = " << ((elapsedCPU * 1000) / CLOCKS_PER_SEC) + << "ms CPU, " << elapsedTime << " real" << endl; +#endif +} + +Profiler::~Profiler() +{ +#ifndef NO_TIMING + clock_t elapsedCPU = clock() - m_startCPU; + + struct timeval tv; + (void)gettimeofday(&tv, 0); + RealTime elapsedTime = RealTime(tv.tv_sec, tv.tv_usec * 1000) - m_startTime; + + Profiles::getInstance()->accumulate(m_c, elapsedCPU, elapsedTime); + + if (m_showOnDestruct) + cerr << "Profiler : id = " << m_c + << " - elapsed = " << ((elapsedCPU * 1000) / CLOCKS_PER_SEC) + << "ms CPU, " << elapsedTime << " real" << endl; +#endif +} + +} + +/* A little usage demo + +int main() +{ + { + Profiler foo("test"); + sleep(1); + } + + { + Profiler foo("test"); + sleep(1); + } + + { + Profiler foo("test2"); + sleep(1); + } + + Profiles::getInstance()->dump(); + + return 0; +} +*/ diff --git a/src/base/Profiler.h b/src/base/Profiler.h new file mode 100644 index 0000000..4ba033b --- /dev/null +++ b/src/base/Profiler.h @@ -0,0 +1,84 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _PROFILER_H_ +#define _PROFILER_H_ + +#include +#include +#include + +#include "RealTime.h" + + +namespace Rosegarden +{ + +/** + * Profiling classes + */ + +/** + * The class holding all profiling data + * + * This class is a singleton + */ +class Profiles +{ +public: + static Profiles* getInstance(); + ~Profiles(); + + void accumulate(const char* id, clock_t time, RealTime rt); + void dump(); + +protected: + Profiles(); + + typedef std::pair TimePair; + typedef std::pair ProfilePair; + typedef std::map ProfileMap; + typedef std::map LastCallMap; + ProfileMap m_profiles; + LastCallMap m_lastCalls; + + static Profiles* m_instance; +}; + +class Profiler +{ +public: + Profiler(const char*, bool showOnDestruct = false); + ~Profiler(); + + void update(); + +protected: + const char* m_c; + clock_t m_startCPU; + RealTime m_startTime; + bool m_showOnDestruct; +}; + +} + +#endif diff --git a/src/base/Property.cpp b/src/base/Property.cpp new file mode 100644 index 0000000..45e818b --- /dev/null +++ b/src/base/Property.cpp @@ -0,0 +1,169 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Property.h" +#include +#include +#include + +namespace Rosegarden +{ +using std::string; + +string +PropertyDefn::typeName() +{ + return "UInt"; +} + +PropertyDefn::basic_type +PropertyDefn::parse(string s) +{ + return atoi(s.c_str()); +} + +string +PropertyDefn::unparse(PropertyDefn::basic_type i) +{ + static char buffer[20]; sprintf(buffer, "%ld", i); + return buffer; +} + + +string +PropertyDefn::typeName() +{ + return "Int"; +} + +PropertyDefn::basic_type +PropertyDefn::parse(string s) +{ + return atoi(s.c_str()); +} + +string +PropertyDefn::unparse(PropertyDefn::basic_type i) +{ + static char buffer[20]; sprintf(buffer, "%ld", i); + return buffer; +} + +string +PropertyDefn::typeName() +{ + return "String"; +} + +PropertyDefn::basic_type +PropertyDefn::parse(string s) +{ + return s; +} + +string +PropertyDefn::unparse(PropertyDefn::basic_type i) +{ + return i; +} + +string +PropertyDefn::typeName() +{ + return "Bool"; +} + +PropertyDefn::basic_type +PropertyDefn::parse(string s) +{ + return s == "true"; +} + +string +PropertyDefn::unparse(PropertyDefn::basic_type i) +{ + return (i ? "true" : "false"); +} + +string +PropertyDefn::typeName() +{ + return "RealTimeT"; +} + +PropertyDefn::basic_type +PropertyDefn::parse(string s) +{ + string sec = s.substr(0, s.find('/')), + nsec = s.substr(s.find('/') + 1); + + return RealTime(atoi(sec.c_str()), atoi(nsec.c_str())); +} + +string +PropertyDefn::unparse(PropertyDefn::basic_type i) +{ + static char buffer[256]; sprintf(buffer, "%d/%d", i.sec, i.nsec); + return buffer; +} + +PropertyStoreBase::~PropertyStoreBase() +{ +} + +template <> +size_t +PropertyStore::getStorageSize() const +{ + return sizeof(*this); +} + +template <> +size_t +PropertyStore::getStorageSize() const +{ + return sizeof(*this); +} + +template <> +size_t +PropertyStore::getStorageSize() const +{ + return sizeof(*this) + m_data.size(); +} + +template <> +size_t +PropertyStore::getStorageSize() const +{ + return sizeof(*this); +} + +template <> +size_t +PropertyStore::getStorageSize() const +{ + return sizeof(*this); +} + +} + diff --git a/src/base/Property.h b/src/base/Property.h new file mode 100644 index 0000000..32b6226 --- /dev/null +++ b/src/base/Property.h @@ -0,0 +1,225 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _PROPERTY_H_ +#define _PROPERTY_H_ + +#include + +#include "RealTime.h" + +namespace Rosegarden +{ + +enum PropertyType { Int, String, Bool, RealTimeT, UInt }; + +template +class PropertyDefn +{ +public: + struct PropertyDefnNotDefined { + PropertyDefnNotDefined() { throw(0); } + }; + typedef PropertyDefnNotDefined basic_type; + + static std::string typeName() { return "Undefined"; } + static basic_type parse(std::string); + static std::string unparse(basic_type); +}; + +template +typename PropertyDefn

::basic_type +PropertyDefn

::parse(std::string) +{ + throw(0); + return ""; +} + +template +std::string +PropertyDefn

::unparse(PropertyDefn

::basic_type) +{ + throw(0); + return ""; +} + + +template <> +class PropertyDefn +{ +public: + typedef long basic_type; + + static std::string typeName(); + static basic_type parse(std::string s); + static std::string unparse(basic_type i); +}; + + +template <> +class PropertyDefn +{ +public: + typedef std::string basic_type; + + static std::string typeName(); + static basic_type parse(std::string s); + static std::string unparse(basic_type i); +}; + +template <> +class PropertyDefn +{ +public: + typedef bool basic_type; + + static std::string typeName(); + static basic_type parse(std::string s); + static std::string unparse(basic_type i); +}; + +template <> +class PropertyDefn +{ +public: + typedef RealTime basic_type; + + static std::string typeName(); + static basic_type parse(std::string s); + static std::string unparse(basic_type i); +}; + +template <> +class PropertyDefn +{ +public: + typedef unsigned int basic_type; + + static std::string typeName(); + static basic_type parse(std::string s); + static std::string unparse(basic_type i); +}; + + +class PropertyStoreBase { +public: + virtual ~PropertyStoreBase(); + + virtual PropertyType getType() const = 0; + virtual std::string getTypeName() const = 0; + virtual PropertyStoreBase *clone() = 0; + virtual std::string unparse() const = 0; + + virtual size_t getStorageSize() const = 0; // for debugging + +#ifndef NDEBUG + virtual void dump(std::ostream&) const = 0; +#else + virtual void dump(std::ostream&) const { } +#endif +}; + +#ifndef NDEBUG +inline std::ostream& operator<<(std::ostream &out, PropertyStoreBase &e) +{ e.dump(out); return out; } +#endif + +template +class PropertyStore : public PropertyStoreBase +{ +public: + PropertyStore(typename PropertyDefn

::basic_type d) : + m_data(d) { } + PropertyStore(const PropertyStore

&p) : + PropertyStoreBase(p), m_data(p.m_data) { } + PropertyStore &operator=(const PropertyStore

&p); + + virtual PropertyType getType() const; + virtual std::string getTypeName() const; + + virtual PropertyStoreBase* clone(); + + virtual std::string unparse() const; + + typename PropertyDefn

::basic_type getData() { return m_data; } + void setData(typename PropertyDefn

::basic_type data) { m_data = data; } + + virtual size_t getStorageSize() const; + +#ifndef NDEBUG + void dump(std::ostream&) const; +#endif + +private: + typename PropertyDefn

::basic_type m_data; +}; + +template +PropertyStore

& +PropertyStore

::operator=(const PropertyStore

&p) { + if (this != &p) { + m_data = p.m_data; + } + return *this; +} + +template +PropertyType +PropertyStore

::getType() const +{ + return P; +} + +template +std::string +PropertyStore

::getTypeName() const +{ + return PropertyDefn

::typeName(); +} + +template +PropertyStoreBase* +PropertyStore

::clone() +{ + return new PropertyStore

(*this); +} + +template +std::string +PropertyStore

::unparse() const +{ + return PropertyDefn

::unparse(m_data); +} + +#ifndef NDEBUG +template +void +PropertyStore

::dump(std::ostream &out) const +{ + out << getTypeName() << " - " << unparse(); +} +#endif + +} + + +#endif diff --git a/src/base/PropertyMap.cpp b/src/base/PropertyMap.cpp new file mode 100644 index 0000000..5958dc2 --- /dev/null +++ b/src/base/PropertyMap.cpp @@ -0,0 +1,101 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 +#include +#include +#include "PropertyMap.h" +#include "XmlExportable.h" + +namespace Rosegarden +{ +using std::string; + +#ifdef PROPERTY_MAP_IS_HASH_MAP +PropertyMap::PropertyMap() : + __HASH_NS::hash_map(50, PropertyNameHash()) +{ + // nothing +} +#endif + +PropertyMap::PropertyMap(const PropertyMap &pm) : + +#ifdef PROPERTY_MAP_IS_HASH_MAP + + __HASH_NS::hash_map(50, PropertyNameHash()) +#else + + std::map() + +#endif + +{ + for (const_iterator i = pm.begin(); i != pm.end(); ++i) { + insert(PropertyPair(i->first, i->second->clone())); + } +} + +PropertyMap::~PropertyMap() +{ + for (iterator i = begin(); i != end(); ++i) delete i->second; +} + +void +PropertyMap::clear() +{ + for (iterator i = begin(); i != end(); ++i) delete i->second; + erase(begin(), end()); +} + + +// We could derive from XmlExportable and make this a virtual method +// overriding XmlExportable's pure virtual. We don't, because this +// class has no other virtual methods and for such a core class we +// could do without the overhead (given that it wouldn't really gain +// us anything anyway). + +string +PropertyMap::toXmlString() +{ + string xml; + + for (const_iterator i = begin(); i != end(); ++i) { + + xml += + "first.getName()) + + "\" " + i->second->getTypeName() + + "=\"" + XmlExportable::encode(i->second->unparse()) + + "\"/>"; + + } + + return xml; +} + +} + diff --git a/src/base/PropertyMap.h b/src/base/PropertyMap.h new file mode 100644 index 0000000..fca603c --- /dev/null +++ b/src/base/PropertyMap.h @@ -0,0 +1,50 @@ +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _PROPERTY_MAP_H_ +#define _PROPERTY_MAP_H_ + +#include "Property.h" +#include "PropertyName.h" + +#include + +namespace Rosegarden { + +class PropertyMap : public std::map +{ +public: + PropertyMap() { } + PropertyMap(const PropertyMap &pm); + + ~PropertyMap(); + + void clear(); + + std::string toXmlString(); + +private: + PropertyMap &operator=(const PropertyMap &); // not provided +}; + +typedef PropertyMap::value_type PropertyPair; + +} + +#endif diff --git a/src/base/PropertyName.cpp b/src/base/PropertyName.cpp new file mode 100644 index 0000000..11ff019 --- /dev/null +++ b/src/base/PropertyName.cpp @@ -0,0 +1,86 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 +#include + +#include "PropertyName.h" +#include "Exception.h" + + +namespace Rosegarden +{ +using std::string; + +PropertyName::intern_map *PropertyName::m_interns = 0; +PropertyName::intern_reverse_map *PropertyName::m_internsReversed = 0; +int PropertyName::m_nextValue = 0; + +int PropertyName::intern(const string &s) +{ + if (!m_interns) { + m_interns = new intern_map; + m_internsReversed = new intern_reverse_map; + } + + intern_map::iterator i(m_interns->find(s)); + + if (i != m_interns->end()) { + return i->second; + } else { + int nv = ++m_nextValue; + m_interns->insert(intern_pair(s, nv)); + m_internsReversed->insert(intern_reverse_pair(nv, s)); + return nv; + } +} + +string PropertyName::getName() const +{ + intern_reverse_map::iterator i(m_internsReversed->find(m_value)); + if (i != m_internsReversed->end()) return i->second; + + // dump some informative data, even if we aren't in debug mode, + // because this really shouldn't be happening + std::cerr << "ERROR: PropertyName::getName: value corrupted!\n"; + std::cerr << "PropertyName's internal value is " << m_value << std::endl; + std::cerr << "Reverse interns are "; + i = m_internsReversed->begin(); + if (i == m_internsReversed->end()) std::cerr << "(none)"; + else while (i != m_internsReversed->end()) { + if (i != m_internsReversed->begin()) { + std::cerr << ", "; + } + std::cerr << i->first << "=" << i->second; + ++i; + } + std::cerr << std::endl; + + throw Exception + ("Serious problem in PropertyName::getName(): property " + "name's internal value is corrupted -- see stderr for details"); +} + +const PropertyName PropertyName::EmptyPropertyName = ""; + +} + diff --git a/src/base/PropertyName.h b/src/base/PropertyName.h new file mode 100644 index 0000000..f9e8c20 --- /dev/null +++ b/src/base/PropertyName.h @@ -0,0 +1,158 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _PROPERTY_NAME_H_ +#define _PROPERTY_NAME_H_ + +#include +#include +#include + +namespace Rosegarden +{ + +/** + + A PropertyName is something that can be constructed from a string, + compared quickly as an int, hashed as a key in a hash map, and + streamed out again as a string. It must have accompanying functors + PropertyNamesEqual and PropertyNameHash which compare and hash + PropertyName objects. + + The simplest implementation is a string: + + typedef std::string PropertyName; + + struct PropertyNamesEqual { + bool operator() (const PropertyName &s1, const PropertyName &s2) const { + return s1 == s2; + } + }; + + struct PropertyNameHash { + static std::hash _H; + size_t operator() (const PropertyName &s) const { + return _H(s.c_str()); + } + }; + + std::hash PropertyNameHash::_H; + + but our implementation is faster in practice: while it behaves + outwardly like a string, for the Event that makes use of it, + it performs much like a machine integer. It also shares + strings, reducing storage sizes if there are many names in use. + + A big caveat with this class is that it is _not_ safe to persist + the values of PropertyNames and assume that the original strings + can be recovered; they can't. The values are assigned on demand, + and there's no guarantee that a given string will always map to + the same value (on separate invocations of the program). This + is why there's no PropertyName(int) constructor and no mechanism + for storing PropertyNames in properties. (Of course, you can + store the string representation of a PropertyName in a property; + but that's slow.) + +*/ + +class PropertyName +{ +public: + PropertyName() : m_value(-1) { } + PropertyName(const char *cs) { std::string s(cs); m_value = intern(s); } + PropertyName(const std::string &s) : m_value(intern(s)) { } + PropertyName(const PropertyName &p) : m_value(p.m_value) { } + ~PropertyName() { } + + PropertyName &operator=(const char *cs) { + std::string s(cs); + m_value = intern(s); + return *this; + } + PropertyName &operator=(const std::string &s) { + m_value = intern(s); + return *this; + } + PropertyName &operator=(const PropertyName &p) { + m_value = p.m_value; + return *this; + } + + bool operator==(const PropertyName &p) const { + return m_value == p.m_value; + } + bool operator< (const PropertyName &p) const { + return m_value < p.m_value; + } + + const char *c_str() const { + return getName().c_str(); + } + + std::string getName() const /* throw (CorruptedValue) */; + + int getValue() const { return m_value; } + + static const PropertyName EmptyPropertyName; + +private: + typedef std::map intern_map; + typedef intern_map::value_type intern_pair; + + typedef std::map intern_reverse_map; + typedef intern_reverse_map::value_type intern_reverse_pair; + + static intern_map *m_interns; + static intern_reverse_map *m_internsReversed; + static int m_nextValue; + + int m_value; + + static int intern(const std::string &s); +}; + +inline std::ostream& operator<<(std::ostream &out, const PropertyName &n) { + out << n.getName(); + return out; +} + +inline std::string operator+(const std::string &s, const PropertyName &n) { + return s + n.getName(); +} + +struct PropertyNamesEqual +{ + bool operator() (const PropertyName &s1, const PropertyName &s2) const { + return s1 == s2; + } +}; + +struct PropertyNameHash +{ + size_t operator() (const PropertyName &s) const { + return static_cast(s.getValue()); + } +}; + +} + +#endif diff --git a/src/base/Quantizer.cpp b/src/base/Quantizer.cpp new file mode 100644 index 0000000..c0e4d1b --- /dev/null +++ b/src/base/Quantizer.cpp @@ -0,0 +1,496 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Quantizer.h" +#include "BaseProperties.h" +#include "NotationTypes.h" +#include "Selection.h" +#include "Composition.h" +#include "Sets.h" +#include "Profiler.h" + +#include +#include +#include // for sprintf +#include + +using std::cout; +using std::cerr; +using std::endl; + +//#define DEBUG_NOTATION_QUANTIZER 1 + +namespace Rosegarden { + +Quantizer::Quantizer(std::string source, + std::string target) : + m_source(source), m_target(target) +{ + makePropertyNames(); +} + + +Quantizer::Quantizer(std::string target) : + m_target(target) +{ + if (target == RawEventData) { + m_source = GlobalSource; + } else { + m_source = RawEventData; + } + + makePropertyNames(); +} + + +Quantizer::~Quantizer() +{ + // nothing +} + +void +Quantizer::quantize(Segment *s) const +{ + quantize(s, s->begin(), s->getEndMarker()); +} + +void +Quantizer::quantize(Segment *s, + Segment::iterator from, + Segment::iterator to) const +{ + assert(m_toInsert.size() == 0); + + quantizeRange(s, from, to); + + insertNewEvents(s); +} + +void +Quantizer::quantize(EventSelection *selection) +{ + assert(m_toInsert.size() == 0); + + Segment &segment = selection->getSegment(); + + // Attempt to handle non-contiguous selections. + + // We have to be a bit careful here, because the rest- + // normalisation that's carried out as part of a quantize + // process is liable to replace the event that follows + // the quantized range. (moved here from editcommands.cpp) + + EventSelection::RangeList ranges(selection->getRanges()); + + // So that we can retrieve a list of new events we cheat and stop + // the m_toInsert vector from being cleared automatically. Remember + // to turn it back on. + // + + EventSelection::RangeList::iterator r = ranges.end(); + while (r-- != ranges.begin()) { + +/* + cerr << "Quantizer: quantizing range "; + if (r->first == segment.end()) { + cerr << "end"; + } else { + cerr << (*r->first)->getAbsoluteTime(); + } + cerr << " to "; + if (r->second == segment.end()) { + cerr << "end"; + } else { + cerr << (*r->second)->getAbsoluteTime(); + } + cerr << endl; +*/ + + quantizeRange(&segment, r->first, r->second); + } + + // Push the new events to the selection + for (int i = 0; i < m_toInsert.size(); ++i) { + selection->addEvent(m_toInsert[i]); + } + + // and then to the segment + insertNewEvents(&segment); +} + + +void +Quantizer::fixQuantizedValues(Segment *s, + Segment::iterator from, + Segment::iterator to) const +{ + assert(m_toInsert.size() == 0); + + quantize(s, from, to); + + if (m_target == RawEventData) return; + + for (Segment::iterator nextFrom = from; from != to; from = nextFrom) { + + ++nextFrom; + + timeT t = getFromTarget(*from, AbsoluteTimeValue); + timeT d = getFromTarget(*from, DurationValue); + Event *e = new Event(**from, t, d); + s->erase(from); + m_toInsert.push_back(e); + } + + insertNewEvents(s); +} + + +timeT +Quantizer::getQuantizedDuration(const Event *e) const +{ + if (m_target == RawEventData) { + return e->getDuration(); + } else if (m_target == NotationPrefix) { + return e->getNotationDuration(); + } else { + timeT d = e->getDuration(); + e->get(m_targetProperties[DurationValue], d); + return d; + } +} + +timeT +Quantizer::getQuantizedAbsoluteTime(const Event *e) const +{ + if (m_target == RawEventData) { + return e->getAbsoluteTime(); + } else if (m_target == NotationPrefix) { + return e->getNotationAbsoluteTime(); + } else { + timeT t = e->getAbsoluteTime(); + e->get(m_targetProperties[AbsoluteTimeValue], t); + return t; + } +} + +timeT +Quantizer::getUnquantizedAbsoluteTime(Event *e) const +{ + return getFromSource(e, AbsoluteTimeValue); +} + +timeT +Quantizer::getUnquantizedDuration(Event *e) const +{ + return getFromSource(e, DurationValue); +} + +void +Quantizer::quantizeRange(Segment *s, + Segment::iterator from, + Segment::iterator to) const +{ + //!!! It is vital that ordering is maintained after quantization. + // That is, an event whose absolute time quantizes to a time t must + // appear in the original segment before all events whose times + // quantize to greater than t. This means we must quantize the + // absolute times of non-note events as well as notes. + + // We don't need to worry about quantizing rests, however; they're + // only used for notation and will be explicitly recalculated when + // the notation quantization values change. + + for (Segment::iterator nextFrom = from; from != to; from = nextFrom) { + + ++nextFrom; + quantizeSingle(s, from); + } +} + +void +Quantizer::unquantize(Segment *s, + Segment::iterator from, + Segment::iterator to) const +{ + assert(m_toInsert.size() == 0); + + for (Segment::iterator nextFrom = from; from != to; from = nextFrom) { + ++nextFrom; + + if (m_target == RawEventData || m_target == NotationPrefix) { + setToTarget(s, from, + getFromSource(*from, AbsoluteTimeValue), + getFromSource(*from, DurationValue)); + + } else { + removeTargetProperties(*from); + } + } + + insertNewEvents(s); +} + +void +Quantizer::unquantize(EventSelection *selection) const +{ + assert(m_toInsert.size() == 0); + + Segment *s = &selection->getSegment(); + + Rosegarden::EventSelection::eventcontainer::iterator it + = selection->getSegmentEvents().begin(); + + for (; it != selection->getSegmentEvents().end(); it++) { + + if (m_target == RawEventData || m_target == NotationPrefix) { + + Segment::iterator from = s->findSingle(*it); + Segment::iterator to = s->findSingle(*it); + setToTarget(s, from, + getFromSource(*from, AbsoluteTimeValue), + getFromSource(*to, DurationValue)); + + } else { + removeTargetProperties(*it); + } + } + + insertNewEvents(&selection->getSegment()); +} + + + +timeT +Quantizer::getFromSource(Event *e, ValueType v) const +{ + Profiler profiler("Quantizer::getFromSource"); + +// cerr << "Quantizer::getFromSource: source is \"" << m_source << "\"" << endl; + + if (m_source == RawEventData) { + + if (v == AbsoluteTimeValue) return e->getAbsoluteTime(); + else return e->getDuration(); + + } else if (m_source == NotationPrefix) { + + if (v == AbsoluteTimeValue) return e->getNotationAbsoluteTime(); + else return e->getNotationDuration(); + + } else { + + // We need to write the source from the target if the + // source doesn't exist (and the target does) + + bool haveSource = e->has(m_sourceProperties[v]); + bool haveTarget = ((m_target == RawEventData) || + (e->has(m_targetProperties[v]))); + timeT t = 0; + + if (!haveSource && haveTarget) { + t = getFromTarget(e, v); + e->setMaybe(m_sourceProperties[v], t); + return t; + } + + e->get(m_sourceProperties[v], t); + return t; + } +} + +timeT +Quantizer::getFromTarget(Event *e, ValueType v) const +{ + Profiler profiler("Quantizer::getFromTarget"); + + if (m_target == RawEventData) { + + if (v == AbsoluteTimeValue) return e->getAbsoluteTime(); + else return e->getDuration(); + + } else if (m_target == NotationPrefix) { + + if (v == AbsoluteTimeValue) return e->getNotationAbsoluteTime(); + else return e->getNotationDuration(); + + } else { + timeT value; + if (v == AbsoluteTimeValue) value = e->getAbsoluteTime(); + else value = e->getDuration(); + e->get(m_targetProperties[v], value); + return value; + } +} + +void +Quantizer::setToTarget(Segment *s, Segment::iterator i, + timeT absTime, timeT duration) const +{ + Profiler profiler("Quantizer::setToTarget"); + + //cerr << "Quantizer::setToTarget: target is \"" << m_target << "\", absTime is " << absTime << ", duration is " << duration << " (unit is " << m_unit << ", original values are absTime " << (*i)->getAbsoluteTime() << ", duration " << (*i)->getDuration() << ")" << endl; + + timeT st = 0, sd = 0; + bool haveSt = false, haveSd = false; + if (m_source != RawEventData && m_target == RawEventData) { + haveSt = (*i)->get(m_sourceProperties[AbsoluteTimeValue], st); + haveSd = (*i)->get(m_sourceProperties[DurationValue], sd); + } + + Event *e; + if (m_target == RawEventData) { + e = new Event(**i, absTime, duration); + } else if (m_target == NotationPrefix) { + // Setting the notation absolute time on an event without + // recreating it would be dodgy, just as setting the absolute + // time would, because it could change the ordering of events + // that are already being referred to in ViewElementLists, + // preventing us from locating them in the ViewElementLists + // because their ordering would have silently changed +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "Quantizer: setting " << absTime << " to notation absolute time and " + << duration << " to notation duration" + << endl; +#endif + e = new Event(**i, (*i)->getAbsoluteTime(), (*i)->getDuration(), + (*i)->getSubOrdering(), absTime, duration); + } else { + e = *i; + e->clearNonPersistentProperties(); + } + + if (m_target == NotationPrefix) { + timeT normalizeStart = std::min(absTime, (*i)->getAbsoluteTime()); + timeT normalizeEnd = std::max(absTime + duration, + (*i)->getAbsoluteTime() + + (*i)->getDuration()) + 1; + + if (m_normalizeRegion.first != m_normalizeRegion.second) { + normalizeStart = std::min(normalizeStart, m_normalizeRegion.first); + normalizeEnd = std::max(normalizeEnd, m_normalizeRegion.second); + } + + m_normalizeRegion = std::pair + (normalizeStart, normalizeEnd); + } + + if (haveSt) e->setMaybe(m_sourceProperties[AbsoluteTimeValue],st); + if (haveSd) e->setMaybe(m_sourceProperties[DurationValue], sd); + + if (m_target != RawEventData && m_target != NotationPrefix) { + e->setMaybe(m_targetProperties[AbsoluteTimeValue], absTime); + e->setMaybe(m_targetProperties[DurationValue], duration); + } else { + s->erase(i); + m_toInsert.push_back(e); + } + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "m_toInsert.size() is now " << m_toInsert.size() << endl; +#endif +} + +void +Quantizer::removeProperties(Event *e) const +{ + if (m_source != RawEventData) { + e->unset(m_sourceProperties[AbsoluteTimeValue]); + e->unset(m_sourceProperties[DurationValue]); + } + + if (m_target != RawEventData && m_target != NotationPrefix) { + e->unset(m_targetProperties[AbsoluteTimeValue]); + e->unset(m_targetProperties[DurationValue]); + } +} + +void +Quantizer::removeTargetProperties(Event *e) const +{ + if (m_target != RawEventData) { + e->unset(m_targetProperties[AbsoluteTimeValue]); + e->unset(m_targetProperties[DurationValue]); + } +} + +void +Quantizer::makePropertyNames() +{ + if (m_source != RawEventData && m_source != NotationPrefix) { + m_sourceProperties[AbsoluteTimeValue] = m_source + "AbsoluteTimeSource"; + m_sourceProperties[DurationValue] = m_source + "DurationSource"; + } + + if (m_target != RawEventData && m_target != NotationPrefix) { + m_targetProperties[AbsoluteTimeValue] = m_target + "AbsoluteTimeTarget"; + m_targetProperties[DurationValue] = m_target + "DurationTarget"; + } +} + +void +Quantizer::insertNewEvents(Segment *s) const +{ + unsigned int sz = m_toInsert.size(); + + timeT minTime = m_normalizeRegion.first, + maxTime = m_normalizeRegion.second; + + for (unsigned int i = 0; i < sz; ++i) { + + timeT myTime = m_toInsert[i]->getAbsoluteTime(); + timeT myDur = m_toInsert[i]->getDuration(); + if (i == 0 || myTime < minTime) minTime = myTime; + if (i == 0 || myTime + myDur > maxTime) maxTime = myTime + myDur; + + s->insert(m_toInsert[i]); + } + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "Quantizer::insertNewEvents: sz is " << sz + << ", minTime " << minTime << ", maxTime " << maxTime + << endl; +#endif + + if (m_target == NotationPrefix || m_target == RawEventData) { + + if (m_normalizeRegion.first == m_normalizeRegion.second) { + if (sz > 0) { + s->normalizeRests(minTime, maxTime); + } + } else { + s->normalizeRests(minTime, maxTime); + m_normalizeRegion = std::pair(0, 0); + } + } + +#ifdef DEBUG_NOTATION_QUANTIZER + cout << "Quantizer: calling normalizeRests(" + << minTime << ", " << maxTime << ")" << endl; +#endif + + m_toInsert.clear(); +} + + + + +} diff --git a/src/base/Quantizer.h b/src/base/Quantizer.h new file mode 100644 index 0000000..407b651 --- /dev/null +++ b/src/base/Quantizer.h @@ -0,0 +1,249 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 QUANTIZER_H +#define QUANTIZER_H + +#include "Segment.h" +#include "Event.h" +#include "NotationTypes.h" +#include "FastVector.h" +#include + +namespace Rosegarden { + +class EventSelection; + +/** + The Quantizer class rounds the starting times and durations of note + and rest events according to one of a set of possible criteria. +*/ + +class Quantizer +{ + // define the Quantizer API + +public: + virtual ~Quantizer(); + + /** + * Quantize a Segment. + */ + void quantize(Segment *) const; + + /** + * Quantize a section of a Segment. + */ + void quantize(Segment *, + Segment::iterator from, + Segment::iterator to) const; + + /** + * Quantize an EventSelection. + */ + void quantize(EventSelection *); + + /** + * Quantize a section of a Segment, and force the quantized + * results into the formal absolute time and duration of + * the events. This is a destructive operation that should + * not be carried out except on a user's explicit request. + * (If target is RawEventData, this will do nothing besides + * quantize. In this case, but no other, unquantize will + * still work afterwards.) + */ + void fixQuantizedValues(Segment *, + Segment::iterator from, + Segment::iterator to) const; + + /** + * Return the quantized duration of the event if it has been + * quantized -- otherwise just return the unquantized duration. + * Do not modify the event. + */ + virtual timeT getQuantizedDuration(const Event *e) const; + + /** + * Return the quantized absolute time of the event if it has been + * quantized -- otherwise just return the unquantized time. Do + * not modify the event. + */ + virtual timeT getQuantizedAbsoluteTime(const Event *e) const; + + /** + * Return the unquantized absolute time of the event -- + * the absolute time that would be restored by a call to + * unquantize. + */ + virtual timeT getUnquantizedAbsoluteTime(Event *e) const; + + /** + * Return the unquantized absolute time of the event -- + * the absolute time that would be restored by a call to + * unquantize. + */ + virtual timeT getUnquantizedDuration(Event *e) const; + + /** + * Unquantize all events in the given range, for this + * quantizer. Properties set by other quantizers with + * different propertyNamePrefix values will remain. + */ + void unquantize(Segment *, + Segment::iterator from, Segment::iterator to) const; + + /** + * Unquantize a selection of Events + */ + void unquantize(EventSelection *) const; + + static const std::string RawEventData; + static const std::string DefaultTarget; + static const std::string GlobalSource; + static const std::string NotationPrefix; + +protected: + /** + * \arg source, target : Description of where to find the + * times to be quantized, and where to put the quantized results. + * + * These may be strings, specifying a prefix for the names + * of properties to contain the timings, or may be the special + * value RawEventData in which case the event's absolute time + * and duration will be used instead of properties. + * + * If source specifies a property prefix for properties that are + * found not to exist, they will be pre-filled from the original + * timings in the target values before being quantized and then + * set back into the target. (This permits a quantizer to write + * directly into the Event's absolute time and duration without + * losing the original values, because they are backed up + * automatically into the source properties.) + * + * Note that because it's impossible to modify the duration or + * absolute time of an event after construction, if target is + * RawEventData the quantizer must re-construct each event in + * order to adjust its timings. This operation (deliberately) + * loses any non-persistent properties in the events. This + * does not happen if target is a property prefix. + * + * Examples: + * + * -- if source == RawEventData and target == "MyPrefix", + * values will be read from the event's absolute time and + * duration, quantized, and written into MyPrefixAbsoluteTime + * and MyPrefixDuration properties on the event. A call to + * unquantize will simply delete these properties. + * + * -- if source == "MyPrefix" and target == RawEventData, + * the MyPrefixAbsoluteTime and MyPrefixDuration will be + * populated if necessary from the event's absolute time and + * duration, and then quantized and written back into the + * event's values. A call to unquantize will write the + * MyPrefix-property timings back into the event's values, + * and delete the MyPrefix properties. + * + * -- if source == "YourPrefix" and target == "MyPrefix", + * values will be read from YourPrefixAbsoluteTime and + * YourPrefixDuration, quantized, and written into the + * MyPrefix-properties. This may be useful for piggybacking + * onto another quantizer's output. + * + * -- if source == RawEventData and target == RawEventData, + * values will be read from the event's absolute time and + * duration, quantized, and written back to these values. + */ + Quantizer(std::string source, std::string target); + + /** + * If only target is supplied, source is deduced appropriately + * as GlobalSource if target == RawEventData and RawEventData + * otherwise. + */ + Quantizer(std::string target); + + /** + * To implement a subclass of Quantizer, you should + * override either quantizeSingle (if your quantizer is simple + * enough only to have to look at a single event at a time) or + * quantizeRange. The default implementation of quantizeRange + * simply calls quantizeSingle on each non-rest event in turn. + * The default implementation of quantizeSingle, as you see, + * does nothing. + * + * Note that implementations of these methods should call + * getFromSource and setToTarget to get and set the unquantized + * and quantized data; they should not query the event properties + * or timings directly. + * + * NOTE: It is vital that ordering is maintained after + * quantization. That is, an event whose absolute time quantizes + * to a time t must appear in the original segment before all + * events whose times quantize to greater than t. This means you + * must quantize the absolute times of non-note events as well as + * notes. You don't need to worry about quantizing rests, + * however; they're only used for notation and will be + * automatically recalculated if the notation quantization values + * are seen to change. + */ + virtual void quantizeSingle(Segment *, + Segment::iterator) const { } + + /** + * See note for quantizeSingle. + */ + virtual void quantizeRange(Segment *, + Segment::iterator, + Segment::iterator) const; + + std::string m_source; + std::string m_target; + mutable std::pair m_normalizeRegion; + + enum ValueType { AbsoluteTimeValue = 0, DurationValue = 1 }; + + PropertyName m_sourceProperties[2]; + PropertyName m_targetProperties[2]; + +public: // should be protected, but gcc-2.95 doesn't like allowing NotationQuantizer::m_impl to access them + timeT getFromSource(Event *, ValueType) const; + timeT getFromTarget(Event *, ValueType) const; + void setToTarget(Segment *, Segment::iterator, timeT t, timeT d) const; + mutable FastVector m_toInsert; + +protected: + void removeProperties(Event *) const; + void removeTargetProperties(Event *) const; + void makePropertyNames(); + + void insertNewEvents(Segment *) const; + +private: // not provided + Quantizer(const Quantizer &); + Quantizer &operator=(const Quantizer &); + bool operator==(const Quantizer &) const; + bool operator!=(const Quantizer & c) const; +}; + + +} + +#endif diff --git a/src/base/RealTime.cpp b/src/base/RealTime.cpp new file mode 100644 index 0000000..8f8125f --- /dev/null +++ b/src/base/RealTime.cpp @@ -0,0 +1,236 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +#include "RealTime.h" +#include "sys/time.h" + +namespace Rosegarden { + +// A RealTime consists of two ints that must be at least 32 bits each. +// A signed 32-bit int can store values exceeding +/- 2 billion. This +// means we can safely use our lower int for nanoseconds, as there are +// 1 billion nanoseconds in a second and we need to handle double that +// because of the implementations of addition etc that we use. +// +// The maximum valid RealTime on a 32-bit system is somewhere around +// 68 years: 999999999 nanoseconds longer than the classic Unix epoch. + +#define ONE_BILLION 1000000000 + +RealTime::RealTime(int s, int n) : + sec(s), nsec(n) +{ + if (sec == 0) { + while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; } + while (nsec >= ONE_BILLION) { nsec -= ONE_BILLION; ++sec; } + } else if (sec < 0) { + while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; } + while (nsec > 0) { nsec -= ONE_BILLION; ++sec; } + } else { + while (nsec >= ONE_BILLION) { nsec -= ONE_BILLION; ++sec; } + while (nsec < 0) { nsec += ONE_BILLION; --sec; } + } +} + +RealTime +RealTime::fromSeconds(double sec) +{ + return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5)); +} + +RealTime +RealTime::fromMilliseconds(int msec) +{ + return RealTime(msec / 1000, (msec % 1000) * 1000000); +} + +RealTime +RealTime::fromTimeval(const struct timeval &tv) +{ + return RealTime(tv.tv_sec, tv.tv_usec * 1000); +} + +std::ostream &operator<<(std::ostream &out, const RealTime &rt) +{ + if (rt < RealTime::zeroTime) { + out << "-"; + } else { + out << " "; + } + + int s = (rt.sec < 0 ? -rt.sec : rt.sec); + int n = (rt.nsec < 0 ? -rt.nsec : rt.nsec); + + out << s << "."; + + int nn(n); + if (nn == 0) out << "00000000"; + else while (nn < (ONE_BILLION / 10)) { + out << "0"; + nn *= 10; + } + + out << n << "R"; + return out; +} + +std::string +RealTime::toString(bool align) const +{ + std::stringstream out; + out << *this; + +#if (__GNUC__ < 3) + out << std::ends; +#endif + + std::string s = out.str(); + + if (!align && *this >= RealTime::zeroTime) { + // remove leading " " + s = s.substr(1, s.length() - 1); + } + + // remove trailing R + return s.substr(0, s.length() - 1); +} + +std::string +RealTime::toText(bool fixedDp) const +{ + if (*this < RealTime::zeroTime) return "-" + (-*this).toText(); + + std::stringstream out; + + if (sec >= 3600) { + out << (sec / 3600) << ":"; + } + + if (sec >= 60) { + out << (sec % 3600) / 60 << ":"; + } + + if (sec >= 10) { + out << ((sec % 60) / 10); + } + + out << (sec % 10); + + int ms = msec(); + + if (ms != 0) { + out << "."; + out << (ms / 100); + ms = ms % 100; + if (ms != 0) { + out << (ms / 10); + ms = ms % 10; + } else if (fixedDp) { + out << "0"; + } + if (ms != 0) { + out << ms; + } else if (fixedDp) { + out << "0"; + } + } else if (fixedDp) { + out << ".000"; + } + +#if (__GNUC__ < 3) + out << std::ends; +#endif + + std::string s = out.str(); + + return s; +} + +RealTime +RealTime::operator*(double m) const +{ + double t = (double(nsec) / ONE_BILLION) * m; + t += sec * m; + return fromSeconds(t); +} + +RealTime +RealTime::operator/(int d) const +{ + int secdiv = sec / d; + int secrem = sec % d; + + double nsecdiv = (double(nsec) + ONE_BILLION * double(secrem)) / d; + + return RealTime(secdiv, int(nsecdiv + 0.5)); +} + +double +RealTime::operator/(const RealTime &r) const +{ + double lTotal = double(sec) * ONE_BILLION + double(nsec); + double rTotal = double(r.sec) * ONE_BILLION + double(r.nsec); + + if (rTotal == 0) return 0.0; + else return lTotal/rTotal; +} + +long +RealTime::realTime2Frame(const RealTime &time, unsigned int sampleRate) +{ + if (time < zeroTime) return -realTime2Frame(-time, sampleRate); + + // We like integers. The last term is always zero unless the + // sample rate is greater than 1MHz, but hell, you never know... + + long frame = + time.sec * sampleRate + + (time.msec() * sampleRate) / 1000 + + ((time.usec() - 1000 * time.msec()) * sampleRate) / 1000000 + + ((time.nsec - 1000 * time.usec()) * sampleRate) / 1000000000; + + return frame; +} + +RealTime +RealTime::frame2RealTime(long frame, unsigned int sampleRate) +{ + if (frame < 0) return -frame2RealTime(-frame, sampleRate); + + RealTime rt; + rt.sec = frame / sampleRate; + frame -= rt.sec * sampleRate; + rt.nsec = (int)(((float(frame) * 1000000) / sampleRate) * 1000); + return rt; +} + +const RealTime RealTime::zeroTime(0,0); + +} diff --git a/src/base/RealTime.h b/src/base/RealTime.h new file mode 100644 index 0000000..3ebef26 --- /dev/null +++ b/src/base/RealTime.h @@ -0,0 +1,124 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _REAL_TIME_H_ +#define _REAL_TIME_H_ + +#include +#include + +struct timeval; + +namespace Rosegarden +{ + +struct RealTime +{ + int sec; + int nsec; + + int usec() const { return nsec / 1000; } + int msec() const { return nsec / 1000000; } + + RealTime(): sec(0), nsec(0) {} + RealTime(int s, int n); + + RealTime(const RealTime &r) : + sec(r.sec), nsec(r.nsec) { } + + static RealTime fromSeconds(double sec); + static RealTime fromMilliseconds(int msec); + static RealTime fromTimeval(const struct timeval &); + + RealTime &operator=(const RealTime &r) { + sec = r.sec; nsec = r.nsec; return *this; + } + + RealTime operator+(const RealTime &r) const { + return RealTime(sec + r.sec, nsec + r.nsec); + } + RealTime operator-(const RealTime &r) const { + return RealTime(sec - r.sec, nsec - r.nsec); + } + RealTime operator-() const { + return RealTime(-sec, -nsec); + } + + bool operator <(const RealTime &r) const { + if (sec == r.sec) return nsec < r.nsec; + else return sec < r.sec; + } + + bool operator >(const RealTime &r) const { + if (sec == r.sec) return nsec > r.nsec; + else return sec > r.sec; + } + + bool operator==(const RealTime &r) const { + return (sec == r.sec && nsec == r.nsec); + } + + bool operator!=(const RealTime &r) const { + return !(r == *this); + } + + bool operator>=(const RealTime &r) const { + if (sec == r.sec) return nsec >= r.nsec; + else return sec >= r.sec; + } + + bool operator<=(const RealTime &r) const { + if (sec == r.sec) return nsec <= r.nsec; + else return sec <= r.sec; + } + + RealTime operator*(double m) const; + RealTime operator/(int d) const; + + // Find the fractional difference between times + // + double operator/(const RealTime &r) const; + + // Return a human-readable debug-type string to full precision + // (probably not a format to show to a user directly). If align + // is true, prepend " " to the start of positive values so that + // they line up with negative ones (which start with "-"). + // + std::string toString(bool align = false) const; + + // Return a user-readable string to the nearest millisecond + // in a form like HH:MM:SS.mmm + // + std::string toText(bool fixedDp = false) const; + + // Convenience functions for handling sample frames + // + static long realTime2Frame(const RealTime &r, unsigned int sampleRate); + static RealTime frame2RealTime(long frame, unsigned int sampleRate); + + static const RealTime zeroTime; +}; + +std::ostream &operator<<(std::ostream &out, const RealTime &rt); + +} + +#endif diff --git a/src/base/RefreshStatus.h b/src/base/RefreshStatus.h new file mode 100644 index 0000000..4c39a18 --- /dev/null +++ b/src/base/RefreshStatus.h @@ -0,0 +1,76 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _REFRESH_STATUS_H_ +#define _REFRESH_STATUS_H_ + +namespace Rosegarden +{ + +class RefreshStatus +{ +public: + RefreshStatus() : m_needsRefresh(true) {} + + bool needsRefresh() { return m_needsRefresh; } + void setNeedsRefresh(bool s) { m_needsRefresh = s; } + +protected: + bool m_needsRefresh; +}; + +template +class RefreshStatusArray +{ +public: + unsigned int getNewRefreshStatusId(); + size_t size() { return m_refreshStatuses.size(); } + RS& getRefreshStatus(unsigned int id) { return m_refreshStatuses[id]; } + void updateRefreshStatuses(); + +protected: + std::vector m_refreshStatuses; +}; + +template +unsigned int RefreshStatusArray::getNewRefreshStatusId() +{ + m_refreshStatuses.push_back(RS()); + unsigned int res = m_refreshStatuses.size() - 1; + return res; +} + +void breakpoint(); + +template +void RefreshStatusArray::updateRefreshStatuses() +{ + // breakpoint(); // for debug purposes, so one can set a breakpoint + // in this template code (set it in breakpoint() itself). + for(unsigned int i = 0; i < m_refreshStatuses.size(); ++i) + m_refreshStatuses[i].setNeedsRefresh(true); +} + + +} + +#endif diff --git a/src/base/RulerScale.cpp b/src/base/RulerScale.cpp new file mode 100644 index 0000000..510a0a5 --- /dev/null +++ b/src/base/RulerScale.cpp @@ -0,0 +1,243 @@ + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 +#include "RulerScale.h" +#include "Composition.h" + +namespace Rosegarden { + + +////////////////////////////////////////////////////////////////////// +// RulerScale +////////////////////////////////////////////////////////////////////// + +RulerScale::RulerScale(Composition *c) : + m_composition(c) +{ + // nothing +} + +RulerScale::~RulerScale() +{ + // nothing +} + +int +RulerScale::getFirstVisibleBar() const +{ + return m_composition->getBarNumber(m_composition->getStartMarker()); +} + +int +RulerScale::getLastVisibleBar() const +{ + return m_composition->getBarNumber(m_composition->getEndMarker()); +} + +double +RulerScale::getBarWidth(int n) const +{ + return getBarPosition(n + 1) - getBarPosition(n); +} + +double +RulerScale::getBeatWidth(int n) const +{ + std::pair barRange = m_composition->getBarRange(n); + timeT barDuration = barRange.second - barRange.first; + if (barDuration == 0) return 0; + + bool isNew; + TimeSignature timeSig = m_composition->getTimeSignatureInBar(n, isNew); + + // cope with partial bars + double theoreticalWidth = + (getBarWidth(n) * timeSig.getBarDuration()) / barDuration; + + return theoreticalWidth / timeSig.getBeatsPerBar(); +} + +int +RulerScale::getBarForX(double x) const +{ + // binary search + + int minBar = getFirstVisibleBar(), + maxBar = getLastVisibleBar(); + + while (maxBar > minBar) { + int middle = minBar + (maxBar - minBar) / 2; + if (x > getBarPosition(middle)) minBar = middle + 1; + else maxBar = middle; + } + + // we've just done equivalent of lower_bound -- we're one bar too + // far into the list + + if (minBar > getFirstVisibleBar()) return minBar - 1; + else return minBar; +} + +timeT +RulerScale::getTimeForX(double x) const +{ + int n = getBarForX(x); + + double barWidth = getBarWidth(n); + std::pair barRange = m_composition->getBarRange(n); + + if (barWidth < 1.0) { + + return barRange.first; + + } else { + + timeT barDuration = barRange.second - barRange.first; + x -= getBarPosition(n); + + return barRange.first + (timeT)nearbyint(((double)(x * barDuration) / barWidth)); + } +} + +double +RulerScale::getXForTime(timeT time) const +{ + int n = m_composition->getBarNumber(time); + + double barWidth = getBarWidth(n); + std::pair barRange = m_composition->getBarRange(n); + timeT barDuration = barRange.second - barRange.first; + + if (barDuration == 0) { + + return getBarPosition(n); + + } else { + + time -= barRange.first; + return getBarPosition(n) + (double)(time * barWidth) / barDuration; + } +} + +timeT +RulerScale::getDurationForWidth(double x, double width) const +{ + return getTimeForX(x + width) - getTimeForX(x); +} + +double +RulerScale::getWidthForDuration(timeT startTime, timeT duration) const +{ + return getXForTime(startTime + duration) - getXForTime(startTime); +} + +double +RulerScale::getTotalWidth() const +{ + int n = getLastVisibleBar(); + return getBarPosition(n) + getBarWidth(n); +} + + + + +////////////////////////////////////////////////////////////////////// +// SimpleRulerScale +////////////////////////////////////////////////////////////////////// + + +SimpleRulerScale::SimpleRulerScale(Composition *composition, + double origin, double ratio) : + RulerScale(composition), + m_origin(origin), + m_ratio(ratio) +{ + // nothing +} + +SimpleRulerScale::SimpleRulerScale(const SimpleRulerScale &ruler): + RulerScale(ruler.getComposition()), + m_origin(ruler.getOrigin()), + m_ratio(ruler.getUnitsPerPixel()) +{ + // nothing +} + + +SimpleRulerScale::~SimpleRulerScale() +{ + // nothing +} + +double +SimpleRulerScale::getBarPosition(int n) const +{ + timeT barStart = m_composition->getBarRange(n).first; + return getXForTime(barStart); +} + +double +SimpleRulerScale::getBarWidth(int n) const +{ + std::pair range = m_composition->getBarRange(n); + return (double)(range.second - range.first) / m_ratio; +} + +double +SimpleRulerScale::getBeatWidth(int n) const +{ + bool isNew; + TimeSignature timeSig(m_composition->getTimeSignatureInBar(n, isNew)); + return (double)(timeSig.getBeatDuration()) / m_ratio; +} + +int +SimpleRulerScale::getBarForX(double x) const +{ + return m_composition->getBarNumber(getTimeForX(x)); +} + +timeT +SimpleRulerScale::getTimeForX(double x) const +{ + timeT t = (timeT)(nearbyint((double)(x - m_origin) * m_ratio)); + + int firstBar = getFirstVisibleBar(); + if (firstBar != 0) { + t += m_composition->getBarRange(firstBar).first; + } + + return t; +} + +double +SimpleRulerScale::getXForTime(timeT time) const +{ + int firstBar = getFirstVisibleBar(); + if (firstBar != 0) { + time -= m_composition->getBarRange(firstBar).first; + } + + return m_origin + (double)time / m_ratio; +} + + +} diff --git a/src/base/RulerScale.h b/src/base/RulerScale.h new file mode 100644 index 0000000..763ca13 --- /dev/null +++ b/src/base/RulerScale.h @@ -0,0 +1,166 @@ + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _RULER_SCALE_H_ +#define _RULER_SCALE_H_ + +#include "Event.h" + +namespace Rosegarden { + +class Composition; + +/** + * RulerScale is a base for classes that may be queried in order to + * discover the correct x-coordinates for bar lines and bar + * subdivisions. + * + * RulerScale does not contain any methods that relate bar numbers + * to times, time signature or duration -- those are in Composition. + * + * The methods in RulerScale should return extrapolated (but valid) + * results even when passed a bar number outside the theoretically + * visible or existant bar range. + * + * Apart from getBarPosition, every method in this class has a + * default implementation, which should work correctly provided + * the subclass maintains spacing proportional to time within a + * bar, but which may not be an efficient implementation for any + * given subclass. + * + * (Potential to-do: At the moment all our RulerScales are used in + * contexts where spacing proportional to time within a bar is the + * only interpretation that makes sense, so this is okay. In + * theory though we should probably subclass out these "default" + * implementations into an intermediate abstract class.) + */ + +class RulerScale +{ +public: + virtual ~RulerScale(); + Composition *getComposition() const { return m_composition; } + + /** + * Return the number of the first visible bar. + */ + virtual int getFirstVisibleBar() const; + + /** + * Return the number of the last visible bar. (The last + * visible bar_line_ will be at the end of this bar.) + */ + virtual int getLastVisibleBar() const; + + /** + * Return the x-coordinate at which bar number n starts. + */ + virtual double getBarPosition(int n) const = 0; + + /** + * Return the width of bar number n. + */ + virtual double getBarWidth(int n) const; + + /** + * Return the width of each beat subdivision in bar n. + */ + virtual double getBeatWidth(int n) const; + + /** + * Return the number of the bar containing the given x-coord. + */ + virtual int getBarForX(double x) const; + + /** + * Return the nearest time value to the given x-coord. + */ + virtual timeT getTimeForX(double x) const; + + /** + * Return the x-coord corresponding to the given time value. + */ + virtual double getXForTime(timeT time) const; + + /** + * Return the duration corresponding to the given delta-x + * starting at the given x-coord. + */ + virtual timeT getDurationForWidth(double x, double width) const; + + /** + * Return the width corresponding to the given duration + * starting at the given time. + */ + virtual double getWidthForDuration(timeT startTime, timeT duration) const; + + /** + * Return the width of the entire scale. + */ + virtual double getTotalWidth() const; + +protected: + RulerScale(Composition *c); + Composition *m_composition; +}; + + +/** + * SimpleRulerScale is an implementation of RulerScale that maintains + * a strict proportional correspondence between x-coordinate and time. + */ + +class SimpleRulerScale : public RulerScale +{ +public: + /** + * Construct a SimpleRulerScale for the given Composition, with a + * given origin and x-coord/time ratio. (For example, a ratio of + * 10 means that one pixel equals 10 time units.) + */ + SimpleRulerScale(Composition *composition, + double origin, double unitsPerPixel); + virtual ~SimpleRulerScale(); + + double getOrigin() const { return m_origin; } + void setOrigin(double origin) { m_origin = origin; } + + double getUnitsPerPixel() const { return m_ratio; } + void setUnitsPerPixel(double ratio) { m_ratio = ratio; } + + virtual double getBarPosition(int n) const; + virtual double getBarWidth(int n) const; + virtual double getBeatWidth(int n) const; + virtual int getBarForX(double x) const; + virtual timeT getTimeForX(double x) const; + virtual double getXForTime(timeT time) const; + +protected: + double m_origin; + double m_ratio; + +private: + SimpleRulerScale(const SimpleRulerScale &ruler); + SimpleRulerScale &operator=(const SimpleRulerScale &ruler); +}; + +} + +#endif diff --git a/src/base/ScriptAPI.cpp b/src/base/ScriptAPI.cpp new file mode 100644 index 0000000..be01550 --- /dev/null +++ b/src/base/ScriptAPI.cpp @@ -0,0 +1,85 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2004 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "ScriptAPI.h" + +#include "Composition.h" +#include "Segment.h" +#include "Event.h" +#include "Sets.h" + +#include + +namespace Rosegarden +{ + +class ScriptRep +{ +public: + + //!!! Needs to be a SegmentObserver _and_ CompositionObserver. + // If an event is removed from a segment, we have to drop it too. + // If a segment is removed from a composition likewise + + Event *getEvent(ScriptInterface::EventId id); + + +protected: + Composition *m_composition; + CompositionTimeSliceAdapter *m_adapter; + GlobalChord *m_chord; + std::map m_events; +}; + +Event * +ScriptRep::getEvent(ScriptInterface::EventId id) +{ + return m_events[id]; +} + +class ScriptInterface::ScriptContainer : + public std::map { }; + +ScriptInterface::ScriptInterface(Rosegarden::Composition *composition) : + m_composition(composition), + m_scripts(new ScriptContainer()) +{ +} + +ScriptInterface::~ScriptInterface() +{ +} + +std::string +ScriptInterface::getEventType(ScriptId id, EventId eventId) +{ + ScriptRep *rep = (*m_scripts)[id]; + if (!rep) return ""; + + Event *event = rep->getEvent(eventId); + if (!event) return ""; + + return event->getType(); +} + + +} + diff --git a/src/base/ScriptAPI.h b/src/base/ScriptAPI.h new file mode 100644 index 0000000..8d721a4 --- /dev/null +++ b/src/base/ScriptAPI.h @@ -0,0 +1,128 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2004 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _SCRIPT_API_H_ +#define _SCRIPT_API_H_ + +#include "Segment.h" + +namespace Rosegarden +{ + +class Composition; + +class ScriptInterface +{ +public: + typedef int ScriptId; + typedef int SegmentId; + typedef int EventId; + typedef int ScriptTime; + + // Resolution defines the meaning of ScriptTime units. If set to + // the QuantizedNN values, each ScriptTime unit will correspond to + // the duration of an NN-th note. If Unquantized, ScriptTime will + // correspond to timeT, i.e. 960 to a quarter note. + // And Notation is like Quantized64 except that the times are + // obtained from the notation time and duration properties of each + // event instead of the raw ones. + + enum Resolution { + Unquantized, + Notation, + Quantized64, + Quantized32, + Quantized16 + }; + + enum Scope { + Global, + Segment + }; + + class ScriptEvent { + EventId id; + int bar; // number, 1-based + ScriptTime time; // within bar + ScriptTime duration; + int pitch; // 0-127 if note, -1 otherwise + }; + + class ScriptTimeSignature { + int numerator; + int denominator; + ScriptTime duration; + }; + + class ScriptKeySignature { + int accidentals; + bool sharps; + bool minor; + }; + + ScriptInterface(Composition *composition); + virtual ~ScriptInterface(); + + ScriptId createScript(SegmentId target, Resolution resolution, Scope scope); + void destroyScript(ScriptId id); + + // A script can only proceed forwards. The getEvent and getNote + // methods get the next event (including notes) or note within the + // current chord or timeslice; the advance method moves forwards + // to the next chord or other event. So to process through all + // events, call advance() followed by a loop of getEvent() calls + // before the next advance(), and so on. An event with id -1 + // marks the end of a slice. ( -1 is an out-of-range value for + // all types of id.) + + ScriptEvent getEvent(ScriptId id); + ScriptEvent getNote(ScriptId id); + + bool advance(ScriptId id); + + ScriptTimeSignature getTimeSignature(ScriptId id); + ScriptKeySignature getKeySignature(ScriptId id); + + EventId addNote(ScriptId id, + int bar, ScriptTime time, ScriptTime duration, int pitch); + + EventId addEvent(ScriptId id, + std::string type, int bar, ScriptTime time, ScriptTime duration); + + void deleteEvent(ScriptId id, EventId event); + + std::string getEventType(ScriptId id, EventId event); + std::string getProperty(ScriptId id, EventId event, std::string property); + void setProperty(ScriptId id, EventId event, std::string property, std::string value); + +protected: + Composition *m_composition; + + class ScriptContainer; + ScriptContainer *m_scripts; +}; + +} + + +#endif + + diff --git a/src/base/Segment.cpp b/src/base/Segment.cpp new file mode 100644 index 0000000..2f65acd --- /dev/null +++ b/src/base/Segment.cpp @@ -0,0 +1,1294 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Segment.h" +#include "NotationTypes.h" +#include "BaseProperties.h" +#include "Composition.h" +#include "BasicQuantizer.h" +#include "Profiler.h" + +#include +#include +#include +#include +#include + +namespace Rosegarden +{ +using std::cerr; +using std::endl; +using std::string; + +//#define DEBUG_NORMALIZE_RESTS 1 + +static int _runtimeSegmentId = 0; + +Segment::Segment(SegmentType segmentType, timeT startTime) : + std::multiset(), + m_composition(0), + m_startTime(startTime), + m_endMarkerTime(0), + m_endTime(startTime), + m_track(0), + m_type(segmentType), + m_colourIndex(0), + m_id(0), + m_audioFileId(0), + m_unstretchedFileId(0), + m_stretchRatio(1.0), + m_audioStartTime(0, 0), + m_audioEndTime(0, 0), + m_repeating(false), + m_quantizer(new BasicQuantizer()), + m_quantize(false), + m_transpose(0), + m_delay(0), + m_realTimeDelay(0, 0), + m_clefKeyList(0), + m_runtimeSegmentId(_runtimeSegmentId++), + m_snapGridSize(-1), + m_viewFeatures(0), + m_autoFade(false), + m_fadeInTime(Rosegarden::RealTime::zeroTime), + m_fadeOutTime(Rosegarden::RealTime::zeroTime), + m_highestPlayable(127), + m_lowestPlayable(0) +{ +} + +Segment::Segment(const Segment &segment): + std::multiset(), + m_composition(0), // Composition should decide what's in it and what's not + m_startTime(segment.getStartTime()), + m_endMarkerTime(segment.m_endMarkerTime ? + new timeT(*segment.m_endMarkerTime) : 0), + m_endTime(segment.getEndTime()), + m_track(segment.getTrack()), + m_type(segment.getType()), + m_label(segment.getLabel()), + m_colourIndex(segment.getColourIndex()), + m_id(0), + m_audioFileId(segment.getAudioFileId()), + m_unstretchedFileId(segment.getUnstretchedFileId()), + m_stretchRatio(segment.getStretchRatio()), + m_audioStartTime(segment.getAudioStartTime()), + m_audioEndTime(segment.getAudioEndTime()), + m_repeating(segment.isRepeating()), + m_quantizer(new BasicQuantizer(segment.m_quantizer->getUnit(), + segment.m_quantizer->getDoDurations())), + m_quantize(segment.hasQuantization()), + m_transpose(segment.getTranspose()), + m_delay(segment.getDelay()), + m_realTimeDelay(segment.getRealTimeDelay()), + m_clefKeyList(0), + m_runtimeSegmentId(_runtimeSegmentId++), + m_snapGridSize(-1), + m_viewFeatures(0), + m_autoFade(segment.isAutoFading()), + m_fadeInTime(segment.getFadeInTime()), + m_fadeOutTime(segment.getFadeOutTime()), + m_highestPlayable(127), + m_lowestPlayable(0) +{ + for (const_iterator it = segment.begin(); + segment.isBeforeEndMarker(it); ++it) { + insert(new Event(**it)); + } +} + +Segment::~Segment() +{ + if (!m_observers.empty()) { + cerr << "Warning: Segment::~Segment() with " << m_observers.size() + << " observers still extant" << endl; + cerr << "Observers are:"; + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + cerr << " " << (void *)(*i); + cerr << " [" << typeid(**i).name() << "]"; + } + cerr << endl; + } + + notifySourceDeletion(); + + if (m_composition) m_composition->detachSegment(this); + + if (m_clefKeyList) { + // don't delete contents of m_clefKeyList: the pointers + // are just aliases for events in the main segment + delete m_clefKeyList; + } + + // Clear EventRulers + // + EventRulerListIterator it; + for (it = m_eventRulerList.begin(); it != m_eventRulerList.end(); ++it) delete *it; + m_eventRulerList.clear(); + + // delete content + for (iterator it = begin(); it != end(); ++it) delete (*it); + + delete m_endMarkerTime; +} + + +void +Segment::setTrack(TrackId id) +{ + Composition *c = m_composition; + if (c) c->weakDetachSegment(this); // sets m_composition to 0 + TrackId oldTrack = m_track; + m_track = id; + if (c) { + c->weakAddSegment(this); + c->updateRefreshStatuses(); + c->notifySegmentTrackChanged(this, oldTrack, id); + } +} + +timeT +Segment::getStartTime() const +{ + return m_startTime; +} + +timeT +Segment::getEndMarkerTime() const +{ + timeT endTime; + + if (m_type == Audio && m_composition) { + + RealTime startRT = m_composition->getElapsedRealTime(m_startTime); + RealTime endRT = startRT - m_audioStartTime + m_audioEndTime; + endTime = m_composition->getElapsedTimeForRealTime(endRT); + + } else { + + if (m_endMarkerTime) { + endTime = *m_endMarkerTime; + } else { + endTime = getEndTime(); + } + + if (m_composition) { + endTime = std::min(endTime, m_composition->getEndMarker()); + } + } + + return endTime; +} + +timeT +Segment::getEndTime() const +{ + if (m_type == Audio && m_composition) { + RealTime startRT = m_composition->getElapsedRealTime(m_startTime); + RealTime endRT = startRT - m_audioStartTime + m_audioEndTime; + return m_composition->getElapsedTimeForRealTime(endRT); + } else { + return m_endTime; + } +} + +void +Segment::setStartTime(timeT t) +{ + int dt = t - m_startTime; + if (dt == 0) return; + + // reset the time of all events. can't just setAbsoluteTime on these, + // partly 'cos we're not allowed, partly 'cos it might screw up the + // quantizer (which is why we're not allowed) + + // still, this is rather unsatisfactory + + FastVector events; + + for (iterator i = begin(); i != end(); ++i) { + events.push_back((*i)->copyMoving(dt)); + } + + timeT previousEndTime = m_endTime; + + erase(begin(), end()); + + m_endTime = previousEndTime + dt; + if (m_endMarkerTime) *m_endMarkerTime += dt; + + if (m_composition) m_composition->setSegmentStartTime(this, t); + else m_startTime = t; + + for (int i = 0; i < events.size(); ++i) { + insert(events[i]); + } + + notifyStartChanged(m_startTime); + updateRefreshStatuses(m_startTime, m_endTime); +} + +void +Segment::setEndMarkerTime(timeT t) +{ + if (t < m_startTime) t = m_startTime; + + if (m_type == Audio) { + if (m_endMarkerTime) *m_endMarkerTime = t; + else m_endMarkerTime = new timeT(t); + RealTime oldAudioEndTime = m_audioEndTime; + if (m_composition) { + m_audioEndTime = m_audioStartTime + + m_composition->getRealTimeDifference(m_startTime, t); + if (oldAudioEndTime != m_audioEndTime) { + notifyEndMarkerChange(m_audioEndTime < oldAudioEndTime); + } + } + } else { + + timeT endTime = getEndTime(); + timeT oldEndMarker = getEndMarkerTime(); + bool shorten = (t < oldEndMarker); + + if (t > endTime) { + fillWithRests(endTime, t); + if (oldEndMarker < endTime) { + updateRefreshStatuses(oldEndMarker, t); + } + } else { + // only need to do this if we aren't inserting or + // deleting any actual events + if (oldEndMarker < t) { + updateRefreshStatuses(oldEndMarker, t); + } + updateRefreshStatuses(t, endTime); + } + + if (m_endMarkerTime) *m_endMarkerTime = t; + else m_endMarkerTime = new timeT(t); + notifyEndMarkerChange(shorten); + } +} + +void +Segment::setEndTime(timeT t) +{ + timeT endTime = getEndTime(); + if (t < m_startTime) t = m_startTime; + + if (m_type == Audio) { + setEndMarkerTime(t); + } else { + if (t < endTime) { + erase(findTime(t), end()); + endTime = getEndTime(); + if (m_endMarkerTime && endTime < *m_endMarkerTime) { + *m_endMarkerTime = endTime; + notifyEndMarkerChange(true); + } + } else if (t > endTime) { + fillWithRests(endTime, t); + } + } +} + +Segment::iterator +Segment::getEndMarker() +{ + if (m_endMarkerTime) { + return findTime(*m_endMarkerTime); + } else { + return end(); + } +} + +bool +Segment::isBeforeEndMarker(const_iterator i) const +{ + if (i == end()) return false; + + timeT absTime = (*i)->getAbsoluteTime(); + timeT endTime = getEndMarkerTime(); + + return ((absTime < endTime) || + (absTime == endTime && (*i)->getDuration() == 0)); +} + +void +Segment::clearEndMarker() +{ + delete m_endMarkerTime; + m_endMarkerTime = 0; + notifyEndMarkerChange(false); +} + +const timeT * +Segment::getRawEndMarkerTime() const +{ + return m_endMarkerTime; +} + + +void +Segment::updateRefreshStatuses(timeT startTime, timeT endTime) +{ + for(unsigned int i = 0; i < m_refreshStatusArray.size(); ++i) + m_refreshStatusArray.getRefreshStatus(i).push(startTime, endTime); +} + + +Segment::iterator +Segment::insert(Event *e) +{ + assert(e); + + timeT t0 = e->getAbsoluteTime(); + timeT t1 = t0 + e->getDuration(); + + if (t0 < m_startTime || + (begin() == end() && t0 > m_startTime)) { + + if (m_composition) m_composition->setSegmentStartTime(this, t0); + else m_startTime = t0; + notifyStartChanged(m_startTime); + } + + if (t1 > m_endTime || + begin() == end()) { + timeT oldTime = m_endTime; + m_endTime = t1; + notifyEndMarkerChange(m_endTime < oldTime); + } + + iterator i = std::multiset::insert(e); + notifyAdd(e); + updateRefreshStatuses(e->getAbsoluteTime(), + e->getAbsoluteTime() + e->getDuration()); + return i; +} + + +void +Segment::updateEndTime() +{ + m_endTime = m_startTime; + for (iterator i = begin(); i != end(); ++i) { + timeT t = (*i)->getAbsoluteTime() + (*i)->getDuration(); + if (t > m_endTime) m_endTime = t; + } +} + + +void +Segment::erase(iterator pos) +{ + Event *e = *pos; + + assert(e); + + timeT t0 = e->getAbsoluteTime(); + timeT t1 = t0 + e->getDuration(); + + std::multiset::erase(pos); + notifyRemove(e); + delete e; + updateRefreshStatuses(t0, t1); + + if (t0 == m_startTime && begin() != end()) { + timeT startTime = (*begin())->getAbsoluteTime(); + if (m_composition) m_composition->setSegmentStartTime(this, startTime); + else m_startTime = startTime; + notifyStartChanged(m_startTime); + } + if (t1 == m_endTime) { + updateEndTime(); + } +} + + +void +Segment::erase(iterator from, iterator to) +{ + timeT startTime = 0, endTime = m_endTime; + if (from != end()) startTime = (*from)->getAbsoluteTime(); + if (to != end()) endTime = (*to)->getAbsoluteTime() + (*to)->getDuration(); + + // Not very efficient, but without an observer event for + // multiple erase we can't do any better. + + for (Segment::iterator i = from; i != to; ) { + + Segment::iterator j(i); + ++j; + + Event *e = *i; + assert(e); + + std::multiset::erase(i); + notifyRemove(e); + delete e; + + i = j; + } + + if (startTime == m_startTime && begin() != end()) { + timeT startTime = (*begin())->getAbsoluteTime(); + if (m_composition) m_composition->setSegmentStartTime(this, startTime); + else m_startTime = startTime; + notifyStartChanged(m_startTime); + } + + if (endTime == m_endTime) { + updateEndTime(); + } + + updateRefreshStatuses(startTime, endTime); +} + + +bool +Segment::eraseSingle(Event* e) +{ + iterator elPos = findSingle(e); + + if (elPos != end()) { + + erase(elPos); + return true; + + } else return false; + +} + + +Segment::iterator +Segment::findSingle(Event* e) +{ + iterator res = end(); + + std::pair interval = equal_range(e); + + for (iterator i = interval.first; i != interval.second; ++i) { + if (*i == e) { + res = i; + break; + } + } + return res; +} + + +Segment::iterator +Segment::findTime(timeT t) +{ + Event dummy("dummy", t, 0, MIN_SUBORDERING); + return lower_bound(&dummy); +} + + +Segment::iterator +Segment::findNearestTime(timeT t) +{ + iterator i = findTime(t); + if (i == end() || (*i)->getAbsoluteTime() > t) { + if (i == begin()) return end(); + else --i; + } + return i; +} + + +timeT +Segment::getBarStartForTime(timeT t) const +{ + if (t < getStartTime()) t = getStartTime(); + return getComposition()->getBarStartForTime(t); +} + + +timeT +Segment::getBarEndForTime(timeT t) const +{ + if (t > getEndMarkerTime()) t = getEndMarkerTime(); + return getComposition()->getBarEndForTime(t); +} + + +int Segment::getNextId() const +{ + return m_id++; +} + + +void +Segment::fillWithRests(timeT endTime) +{ + fillWithRests(getEndTime(), endTime); +} + +void +Segment::fillWithRests(timeT startTime, timeT endTime) +{ + if (startTime < m_startTime) { + if (m_composition) m_composition->setSegmentStartTime(this, startTime); + else m_startTime = startTime; + notifyStartChanged(m_startTime); + } + + TimeSignature ts; + timeT sigTime = 0; + + if (getComposition()) { + sigTime = getComposition()->getTimeSignatureAt(startTime, ts); + } + + timeT restDuration = endTime - startTime; + if (restDuration <= 0) return; + +#ifdef DEBUG_NORMALIZE_RESTS + cerr << "fillWithRests (" << startTime << "->" << endTime << "), composition " + << (getComposition() ? "exists" : "does not exist") << ", sigTime " + << sigTime << ", timeSig duration " << ts.getBarDuration() << ", restDuration " << restDuration << endl; +#endif + + DurationList dl; + ts.getDurationListForInterval(dl, restDuration, startTime - sigTime); + + timeT acc = startTime; + + for (DurationList::iterator i = dl.begin(); i != dl.end(); ++i) { + Event *e = new Event(Note::EventRestType, acc, *i, + Note::EventRestSubOrdering); + insert(e); + acc += *i; + } +} + +void +Segment::normalizeRests(timeT startTime, timeT endTime) +{ + Profiler profiler("Segment::normalizeRests"); + +#ifdef DEBUG_NORMALIZE_RESTS + cerr << "normalizeRests (" << startTime << "->" << endTime << "), segment starts at " << m_startTime << endl; +#endif + + if (startTime < m_startTime) { +#ifdef DEBUG_NORMALIZE_RESTS + cerr << "normalizeRests: pulling start time back from " + << m_startTime << " to " << startTime << endl; +#endif + if (m_composition) m_composition->setSegmentStartTime(this, startTime); + else m_startTime = startTime; + notifyStartChanged(m_startTime); + } + + //!!! Need to remove the rests then relocate the start time + // and get the notation end time for the nearest note before that + // (?) + + //!!! We need to insert rests at fictitious unquantized times that + //are broadly correct, so as to maintain ordering of notes and + //rests in the unquantized segment. The quantized times should go + //in notation-prefix properties. + + // Preliminary: If there are any time signature changes between + // the start and end times, consider separately each of the sections + // they divide the range up into. + + Composition *composition = getComposition(); + if (composition) { + int timeSigNo = composition->getTimeSignatureNumberAt(startTime); + if (timeSigNo < composition->getTimeSignatureCount() - 1) { + timeT nextSigTime = + composition->getTimeSignatureChange(timeSigNo + 1).first; + if (nextSigTime < endTime) { +#ifdef DEBUG_NORMALIZE_RESTS + cerr << "normalizeRests: divide-and-conquer on timesig at " << nextSigTime << endl; +#endif + normalizeRests(startTime, nextSigTime); + normalizeRests(nextSigTime, endTime); + return; + } + } + } + + // First stage: erase all existing non-tupleted rests in this range. + + timeT segmentEndTime = m_endTime; + + iterator ia = findNearestTime(startTime); + if (ia == end()) ia = begin(); + if (ia == end()) { // the segment is empty +#ifdef DEBUG_NORMALIZE_RESTS + cerr << "normalizeRests: empty segment" << endl; +#endif + fillWithRests(startTime, endTime); + return; + } else { + if (startTime > (*ia)->getNotationAbsoluteTime()) { + startTime = (*ia)->getNotationAbsoluteTime(); + } + } + + iterator ib = findTime(endTime); + if (ib == end()) { + if (ib != begin()) { + --ib; + // if we're pointing at the real-end-time of the last event, + // use its notation-end-time instead + if (endTime == (*ib)->getAbsoluteTime() + (*ib)->getDuration()) { + endTime = + (*ib)->getNotationAbsoluteTime() + + (*ib)->getNotationDuration(); + } + ++ib; + } + } else { + endTime = (*ib)->getNotationAbsoluteTime(); + } + + // If there's a rest preceding the start time, with no notes + // between us and it, and if it doesn't have precisely the + // right duration, then we need to normalize it too + + //!!! needs modification for new scheme + + iterator scooter = ia; + while (scooter-- != begin()) { +// if ((*scooter)->isa(Note::EventRestType)) { //!!! experimental + if ((*scooter)->getDuration() > 0) { + if ((*scooter)->getNotationAbsoluteTime() + + (*scooter)->getNotationDuration() != + startTime) { + startTime = (*scooter)->getNotationAbsoluteTime(); +#ifdef DEBUG_NORMALIZE_RESTS + cerr << "normalizeRests: scooting back to " << startTime << endl; +#endif + ia = scooter; + } + break; +/*!!! + } else if ((*scooter)->getDuration() > 0) { + break; +*/ + } + } + + for (iterator i = ia, j = i; i != ib && i != end(); i = j) { + ++j; + if ((*i)->isa(Note::EventRestType) && + !(*i)->has(BaseProperties::BEAMED_GROUP_TUPLET_BASE)) { +#ifdef DEBUG_NORMALIZE_RESTS + cerr << "normalizeRests: erasing rest at " << (*i)->getAbsoluteTime() << endl; +#endif + erase(i); + } + } + + // It's possible we've just removed all the events between here + // and the end of the segment, if they were all rests. Check. + + if (endTime < segmentEndTime && m_endTime < segmentEndTime) { + endTime = segmentEndTime; + } + + // Second stage: find the gaps that need to be filled with + // rests. We don't mind about the case where two simultaneous + // notes end at different times -- we're only interested in + // the one ending sooner. Each time an event ends, we start + // a candidate gap. + + std::vector > gaps; + + timeT lastNoteStarts = startTime; + timeT lastNoteEnds = startTime; + + // Re-find this, as it might have been erased + ia = findNearestTime(startTime); + + if (ia == end()) { + // already have good lastNoteStarts, lastNoteEnds + ia = begin(); + } else { + lastNoteStarts = (*ia)->getNotationAbsoluteTime(); + lastNoteEnds = lastNoteStarts; + } + + if (ib != end()) { + //!!! This and related code really need to get a quantized + // absolute time of a note event that has the same unquantized + // time as ib, not necessarily of ib itself... or else the + // quantizer needs to set the quantized times of all non-note + // events that happen at the same unquantized time as a note + // event to the same as that of the note event... yeah, that's + // probably the right thing + endTime = (*ib)->getNotationAbsoluteTime(); + + // was this just a nasty hack? + ++ib; + } + + iterator i = ia; + + for (; i != ib && i != end(); ++i) { + + // Boundary events for sets of rests may be notes (obviously), + // text events (because they need to be "attached" to + // something that has the correct timing), or rests (any + // remaining rests in this area have tuplet data so should be + // treated as "hard" rests); + if (!((*i)->isa(Note::EventType) || + (*i)->isa(Text::EventType) || + (*i)->isa(Note::EventRestType))) { + continue; + } + + timeT thisNoteStarts = (*i)->getNotationAbsoluteTime(); + +#ifdef DEBUG_NORMALIZE_RESTS + cerr << "normalizeRests: scanning: thisNoteStarts " << thisNoteStarts + << ", lastNoteStarts " << lastNoteStarts + << ", lastNoteEnds " << lastNoteEnds << endl; +#endif + + /* BR #988185: "Notation: Rest can be simultaneous with note but follow it" + + This conditional tested whether a note started before the + preceding note ended, and if so inserted rests simultaneous + with the preceding note to make up the gap. Without the + ability to lay out those rests partwise, this is never any + better than plain confusing. Revert the change. + + if (thisNoteStarts < lastNoteEnds && + thisNoteStarts > lastNoteStarts) { + gaps.push_back(std::pair + (lastNoteStarts, + thisNoteStarts - lastNoteStarts)); + } + */ + + if (thisNoteStarts > lastNoteEnds) { + gaps.push_back(std::pair + (lastNoteEnds, + thisNoteStarts - lastNoteEnds)); + } + + lastNoteStarts = thisNoteStarts; + lastNoteEnds = thisNoteStarts + (*i)->getNotationDuration(); + } + + if (endTime > lastNoteEnds) { + gaps.push_back(std::pair + (lastNoteEnds, endTime - lastNoteEnds)); + } + + timeT duration; + + for (unsigned int gi = 0; gi < gaps.size(); ++gi) { + +#ifdef DEBUG_NORMALIZE_RESTS + cerr << "normalizeRests: gap " << gi << ": " << gaps[gi].first << " -> " << (gaps[gi].first + gaps[gi].second) << endl; +#endif + + startTime = gaps[gi].first; + duration = gaps[gi].second; + + if (duration >= Note(Note::Shortest).getDuration()) { + fillWithRests(startTime, startTime + duration); + } + } +} + + + +void Segment::getTimeSlice(timeT absoluteTime, iterator &start, iterator &end) +{ + Event dummy("dummy", absoluteTime, 0, MIN_SUBORDERING); + + // No, this won't work -- we need to include things that don't + // compare equal because they have different suborderings, as long + // as they have the same times + +// std::pair res = equal_range(&dummy); + +// start = res.first; +// end = res.second; + + // Got to do this instead: + + start = end = lower_bound(&dummy); + + while (end != this->end() && + (*end)->getAbsoluteTime() == (*start)->getAbsoluteTime()) + ++end; +} + +void Segment::getTimeSlice(timeT absoluteTime, const_iterator &start, const_iterator &end) + const +{ + Event dummy("dummy", absoluteTime, 0, MIN_SUBORDERING); + + start = end = lower_bound(&dummy); + + while (end != this->end() && + (*end)->getAbsoluteTime() == (*start)->getAbsoluteTime()) + ++end; +} + +void +Segment::setQuantization(bool quantize) +{ + if (m_quantize != quantize) { + m_quantize = quantize; + if (m_quantize) { + m_quantizer->quantize(this, begin(), end()); + } else { + m_quantizer->unquantize(this, begin(), end()); + } + } +} + +bool +Segment::hasQuantization() const +{ + return m_quantize; +} + +void +Segment::setQuantizeLevel(timeT unit) +{ + if (m_quantizer->getUnit() == unit) return; + + m_quantizer->setUnit(unit); + if (m_quantize) m_quantizer->quantize(this, begin(), end()); +} + +const BasicQuantizer * +Segment::getQuantizer() const +{ + return m_quantizer; +} + + +void +Segment::setRepeating(bool value) +{ + m_repeating = value; + if (m_composition) { + m_composition->updateRefreshStatuses(); + m_composition->notifySegmentRepeatChanged(this, value); + } +} + +void +Segment::setDelay(timeT delay) +{ + m_delay = delay; + if (m_composition) { + // don't updateRefreshStatuses() - affects playback only + m_composition->notifySegmentEventsTimingChanged(this, delay, RealTime::zeroTime); + } +} + +void +Segment::setRealTimeDelay(RealTime delay) +{ + m_realTimeDelay = delay; + if (m_composition) { + // don't updateRefreshStatuses() - affects playback only + m_composition->notifySegmentEventsTimingChanged(this, 0, delay); + } +} + +void +Segment::setTranspose(int transpose) +{ + m_transpose = transpose; + if (m_composition) { + // don't updateRefreshStatuses() - affects playback only + m_composition->notifySegmentTransposeChanged(this, transpose); + } +} + +void +Segment::setAudioFileId(unsigned int id) +{ + m_audioFileId = id; + updateRefreshStatuses(getStartTime(), getEndTime()); +} + +void +Segment::setUnstretchedFileId(unsigned int id) +{ + m_unstretchedFileId = id; +} + +void +Segment::setStretchRatio(float ratio) +{ + m_stretchRatio = ratio; +} + +void +Segment::setAudioStartTime(const RealTime &time) +{ + m_audioStartTime = time; + updateRefreshStatuses(getStartTime(), getEndTime()); +} + +void +Segment::setAudioEndTime(const RealTime &time) +{ + RealTime oldAudioEndTime = m_audioEndTime; + m_audioEndTime = time; + updateRefreshStatuses(getStartTime(), getEndTime()); + notifyEndMarkerChange(time < oldAudioEndTime); +} + +void +Segment::setAutoFade(bool value) +{ + m_autoFade = value; + updateRefreshStatuses(getStartTime(), getEndTime()); +} + +void +Segment::setFadeInTime(const RealTime &time) +{ + m_fadeInTime = time; + updateRefreshStatuses(getStartTime(), getEndTime()); +} + +void +Segment::setFadeOutTime(const RealTime &time) +{ + m_fadeOutTime = time; + updateRefreshStatuses(getStartTime(), getEndTime()); +} + +void +Segment::setLabel(const std::string &label) +{ + m_label = label; + if (m_composition) m_composition->updateRefreshStatuses(); + notifyAppearanceChange(); +} + +bool +Segment::ClefKeyCmp::operator()(const Event *e1, const Event *e2) const +{ + if (e1->getType() == e2->getType()) return Event::EventCmp()(e1, e2); + else return e1->getType() < e2->getType(); +} + +Clef +Segment::getClefAtTime(timeT time) const +{ + timeT ctime; + return getClefAtTime(time, ctime); +} + +Clef +Segment::getClefAtTime(timeT time, timeT &ctime) const +{ + if (!m_clefKeyList) return Clef(); + + Event ec(Clef::EventType, time); + ClefKeyList::iterator i = m_clefKeyList->lower_bound(&ec); + + while (i == m_clefKeyList->end() || + (*i)->getAbsoluteTime() > time || + (*i)->getType() != Clef::EventType) { + + if (i == m_clefKeyList->begin()) { + ctime = getStartTime(); + return Clef(); + } + --i; + } + + try { + ctime = (*i)->getAbsoluteTime(); + return Clef(**i); + } catch (const Exception &e) { + std::cerr << "Segment::getClefAtTime(" << time + << "): bogus clef in ClefKeyList: event dump follows:" + << std::endl; + (*i)->dump(std::cerr); + return Clef(); + } +} + +Key +Segment::getKeyAtTime(timeT time) const +{ + timeT ktime; + return getKeyAtTime(time, ktime); +} + +Key +Segment::getKeyAtTime(timeT time, timeT &ktime) const +{ + if (!m_clefKeyList) return Key(); + + Event ek(Key::EventType, time); + ClefKeyList::iterator i = m_clefKeyList->lower_bound(&ek); + + while (i == m_clefKeyList->end() || + (*i)->getAbsoluteTime() > time || + (*i)->getType() != Key::EventType) { + + if (i == m_clefKeyList->begin()) { + ktime = getStartTime(); + return Key(); + } + --i; + } + + try { + ktime = (*i)->getAbsoluteTime(); + return Key(**i); + } catch (const Exception &e) { + std::cerr << "Segment::getClefAtTime(" << time + << "): bogus key in ClefKeyList: event dump follows:" + << std::endl; + (*i)->dump(std::cerr); + return Key(); + } +} + +void +Segment::getFirstClefAndKey(Clef &clef, Key &key) +{ + bool keyFound = false; + bool clefFound = false; + clef = Clef(); // Default clef + key = Key(); // Default key signature + + iterator i = begin(); + while (i!=end()) { + // Keep current clef and key as soon as a note or rest event is found + if ((*i)->isa(Note::EventRestType) || (*i)->isa(Note::EventType)) return; + + // Remember the first clef event found + if ((*i)->isa(Clef::EventType)) { + clef = Clef(*(*i)); + // and return if a key has already been found + if (keyFound) return; + clefFound = true; + } + + // Remember the first key event found + if ((*i)->isa(Key::EventType)) { + key = Key(*(*i)); + // and return if a clef has already been found + if (clefFound) return; + keyFound = true; + } + + ++i; + } +} + +timeT +Segment::getRepeatEndTime() const +{ + timeT endMarker = getEndMarkerTime(); + + if (m_repeating && m_composition) { + Composition::iterator i(m_composition->findSegment(this)); + assert(i != m_composition->end()); + ++i; + if (i != m_composition->end() && (*i)->getTrack() == getTrack()) { + timeT t = (*i)->getStartTime(); + if (t < endMarker) return endMarker; + else return t; + } else { + return m_composition->getEndMarker(); + } + } + + return endMarker; +} + + +void +Segment::notifyAdd(Event *e) const +{ + if (e->isa(Clef::EventType) || e->isa(Key::EventType)) { + if (!m_clefKeyList) m_clefKeyList = new ClefKeyList; + m_clefKeyList->insert(e); + } + + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->eventAdded(this, e); + } +} + + +void +Segment::notifyRemove(Event *e) const +{ + if (m_clefKeyList && (e->isa(Clef::EventType) || e->isa(Key::EventType))) { + ClefKeyList::iterator i; + for (i = m_clefKeyList->find(e); i != m_clefKeyList->end(); ++i) { + // fix for bug#1485643 (crash erasing a duplicated key signature) + if ((*i) == e) { + m_clefKeyList->erase(i); + break; + } + } + } + + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->eventRemoved(this, e); + } +} + + +void +Segment::notifyAppearanceChange() const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->appearanceChanged(this); + } +} + +void +Segment::notifyStartChanged(timeT newTime) +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->startChanged(this, newTime); + } + if (m_composition) { + m_composition->notifySegmentStartChanged(this, newTime); + } +} + + +void +Segment::notifyEndMarkerChange(bool shorten) +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->endMarkerTimeChanged(this, shorten); + } + if (m_composition) { + m_composition->notifySegmentEndMarkerChange(this, shorten); + } +} + + +void +Segment::notifySourceDeletion() const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->segmentDeleted(this); + } +} + + +void +Segment::setColourIndex(const unsigned int input) +{ + m_colourIndex = input; + updateRefreshStatuses(getStartTime(), getEndTime()); + if (m_composition) m_composition->updateRefreshStatuses(); + notifyAppearanceChange(); +} + +void +Segment::addEventRuler(const std::string &type, int controllerValue, bool active) +{ + EventRulerListConstIterator it; + + for (it = m_eventRulerList.begin(); it != m_eventRulerList.end(); ++it) + if ((*it)->m_type == type && (*it)->m_controllerValue == controllerValue) + return; + + m_eventRulerList.push_back(new EventRuler(type, controllerValue, active)); +} + +bool +Segment::deleteEventRuler(const std::string &type, int controllerValue) +{ + EventRulerListIterator it; + + for (it = m_eventRulerList.begin(); it != m_eventRulerList.end(); ++it) + { + if ((*it)->m_type == type && (*it)->m_controllerValue == controllerValue) + { + delete *it; + m_eventRulerList.erase(it); + return true; + } + } + + return false; +} + +Segment::EventRuler* +Segment::getEventRuler(const std::string &type, int controllerValue) +{ + EventRulerListConstIterator it; + for (it = m_eventRulerList.begin(); it != m_eventRulerList.end(); ++it) + if ((*it)->m_type == type && (*it)->m_controllerValue == controllerValue) + return *it; + + return 0; +} + + + +SegmentHelper::~SegmentHelper() { } + + +void +SegmentRefreshStatus::push(timeT from, timeT to) +{ + if (!needsRefresh()) { // don't do anything subtle - just erase the old data + + m_from = from; + m_to = to; + + } else { // accumulate on what was already there + + if (from < m_from) m_from = from; + if (to > m_to) m_to = to; + + } + + if (m_to < m_from) std::swap(m_from, m_to); + + setNeedsRefresh(true); +} + + + + +} diff --git a/src/base/Segment.h b/src/base/Segment.h new file mode 100644 index 0000000..564d118 --- /dev/null +++ b/src/base/Segment.h @@ -0,0 +1,783 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _SEGMENT_H_ +#define _SEGMENT_H_ + +#include +#include +#include + +#include "Track.h" +#include "Event.h" +#include "NotationTypes.h" +#include "RefreshStatus.h" +#include "RealTime.h" +#include "MidiProgram.h" + +namespace Rosegarden +{ + +class SegmentRefreshStatus : public RefreshStatus +{ +public: + SegmentRefreshStatus() : m_from(0), m_to(0) {} + + void push(timeT from, timeT to); + + timeT from() const { return m_from; } + timeT to() const { return m_to; } + +protected: + timeT m_from; + timeT m_to; +}; + + +/** + * Segment is the container for a set of Events that are all played on + * the same track. Each event has an absolute starting time, + * which is used as the index within the segment. Multiple events may + * have the same absolute time. + * + * (For example, chords are represented simply as a sequence of notes + * that share a starting time. The Segment can contain counterpoint -- + * notes that overlap, rather than starting and ending together -- but + * in practice it's probably too hard to display so we should make + * more than one Segment if we want to represent true counterpoint.) + * + * If you want to carry out notation-related editing operations on + * a Segment, take a look at SegmentNotationHelper. If you want to play a + * Segment, try SegmentPerformanceHelper for duration calculations. + * + * The Segment owns the Events its items are pointing at. + */ + +class SegmentObserver; +class Quantizer; +class BasicQuantizer; +class Composition; + +class Segment : public std::multiset +{ +public: + /// A Segment contains either Internal representation or Audio + typedef enum { + Internal, + Audio + } SegmentType; + + /** + * Construct a Segment of a given type with a given formal starting time. + */ + Segment(SegmentType segmentType = Internal, + timeT startTime = 0); + /** + * Copy constructor + */ + Segment(const Segment&); + + virtual ~Segment(); + + + ////// + // + // BASIC SEGMENT ATTRIBUTES + + /** + * Get the Segment type (Internal or Audio) + */ + SegmentType getType() const { return m_type; } + + /** + * Note that a Segment does not have to be in a Composition; + * if it isn't, this will return zero + */ + Composition *getComposition() const { + return m_composition; + } + + /** + * Get the track number this Segment is associated with. + */ + TrackId getTrack() const { return m_track; } + + /** + * Set the track number this Segment is associated with. + */ + void setTrack(TrackId i); + + // label + // + void setLabel(const std::string &label); + std::string getLabel() const { return m_label; } + + // Colour information + void setColourIndex(const unsigned int input); + unsigned int getColourIndex() const { return m_colourIndex; } + + /** + * Returns a numeric id of some sort + * The id is guaranteed to be unique within the segment, but not to + * have any other interesting properties + */ + int getNextId() const; + + /** + * Returns a MIDI pitch representing the highest suggested playable note for + * notation contained in this segment, as a convenience reminder to composers. + * + * This property, and its corresponding lowest note counterpart, initialize by + * default such that no limitation is imposed. (lowest = 0, highest = 127) + */ + int getHighestPlayable() { return m_highestPlayable; } + + /** + * Set the highest suggested playable note for this segment + */ + void setHighestPlayable(int pitch) { m_highestPlayable = pitch; } + + /** + * Returns a MIDI pitch representing the lowest suggested playable note for + * notation contained in this segment, as a convenience reminder to composers + */ + int getLowestPlayable() { return m_lowestPlayable; } + + /** + * Set the highest suggested playable note for this segment + */ + void setLowestPlayable(int pitch) { m_lowestPlayable = pitch; } + + + + ////// + // + // TIME & DURATION VALUES + + /** + * Return the start time of the Segment. For a non-audio + * Segment, this is the start time of the first event in it. + */ + timeT getStartTime() const; + + /** + * Return the nominal end time of the Segment. This must + * be the same as or earlier than the getEndTime() value. + * The return value will not necessarily be that last set + * with setEndMarkerTime, as if there is a Composition its + * end marker will also be used for clipping. + */ + timeT getEndMarkerTime() const; + + /** + * Return the time of the end of the last event stored in the + * Segment. This time may be outside the audible/editable + * range of the Segment, depending on the location of the end + * marker. + */ + timeT getEndTime() const; + + /** + * Shift the start time of the Segment by moving the start + * times of all the events in the Segment. + */ + void setStartTime(timeT); + + /** + * DO NOT USE THIS METHOD + * Simple accessor for the m_startTime member. Used by + * Composition#setSegmentStartTime + */ + void setStartTimeDataMember(timeT t) { m_startTime = t; } + + /** + * Set the end marker (nominal end time) of this Segment. + * + * If the given time is later than the current end of the + * Segment's storage, extend the Segment by filling it with + * rests; if earlier, simply move the end marker. The end + * marker time may not precede the start time. + */ + void setEndMarkerTime(timeT); + + /** + * Set the end time of the Segment. + * + * If the given time is later than the current end of the + * Segment's storage, extend the Segment by filling it with + * rests; if earlier, shorten it by throwing away events as + * necessary (though do not truncate any events) and also move + * the end marker to the given time. The end time may not + * precede the start time. + * + * Note that simply inserting an event beyond the end of the + * Segment will also change the end time, although it does + * not fill with rests in the desirable way. + * + * Consider using setEndMarkerTime in preference to this. + */ + void setEndTime(timeT); + + /** + * Return an iterator pointing to the nominal end of the + * Segment. This may be earlier than the end() iterator. + */ + iterator getEndMarker(); + + /** + * Return true if the given iterator points earlier in the + * Segment than the nominal end marker. You can use this + * as an extent test in code such as + * + * while (segment.isBeforeEndMarker(my_iterator)) { + * // ... + * ++my_iterator; + * } + * + * It is not generally safe to write + * + * while (my_iterator != segment.getEndMarker()) { + * // ... + * ++my_iterator; + * } + * + * as the loop will not terminate if my_iterator's initial + * value is already beyond the end marker. (Also takes the + * Composition's end marker into account.) + */ + bool isBeforeEndMarker(const_iterator) const; + + /** + * Remove the end marker, thus making the Segment end + * at its storage end time (unless the Composition's + * end marker is earlier). + */ + void clearEndMarker(); + + /** + * Return the end marker in raw form, that is, a pointer to + * its value or null if none is set. Does not take the + * composition's end marker into account. + */ + const timeT *getRawEndMarkerTime() const; + + + ////// + // + // QUANTIZATION + + /** + * Switch quantization on or off. + */ + void setQuantization(bool quantize); + + /** + * Find out whether quantization is on or off. + */ + bool hasQuantization() const; + + /** + * Set the quantization level. + * (This does not switch quantization on, if it's currently off, + * it only changes the level that will be used when it's next + * switched on.) + */ + void setQuantizeLevel(timeT unit); + + /** + * Get the quantizer currently in (or not in) use. + */ + const BasicQuantizer *getQuantizer() const; + + + + ////// + // + // EVENT MANIPULATION + + /** + * Inserts a single Event + */ + iterator insert(Event *e); + + /** + * Erases a single Event + */ + void erase(iterator pos); + + /** + * Erases a set of Events + */ + void erase(iterator from, iterator to); + + /** + * Clear the segment. + */ + void clear() { erase(begin(), end()); } + + /** + * Looks up an Event and if it finds it, erases it. + * @return true if the event was found and erased, false otherwise. + */ + bool eraseSingle(Event*); + + /** + * Returns an iterator pointing to that specific element, + * end() otherwise + */ + iterator findSingle(Event*); + + const_iterator findSingle(Event *e) const { + return const_iterator(((Segment *)this)->findSingle(e)); + } + + /** + * Returns an iterator pointing to the first element starting at + * or beyond the given absolute time + */ + iterator findTime(timeT time); + + const_iterator findTime(timeT time) const { + return const_iterator(((Segment *)this)->findTime(time)); + } + + /** + * Returns an iterator pointing to the first element starting at + * or before the given absolute time (so returns end() if the + * time precedes the first event, not if it follows the last one) + */ + iterator findNearestTime(timeT time); + + const_iterator findNearestTime(timeT time) const { + return const_iterator(((Segment *)this)->findNearestTime(time)); + } + + + ////// + // + // ADVANCED, ESOTERIC, or PLAIN STUPID MANIPULATION + + /** + * Returns the range [start, end[ of events which are at absoluteTime + */ + void getTimeSlice(timeT absoluteTime, iterator &start, iterator &end); + + /** + * Returns the range [start, end[ of events which are at absoluteTime + */ + void getTimeSlice(timeT absoluteTime, const_iterator &start, const_iterator &end) const; + + /** + * Return the starting time of the bar that contains time t. This + * differs from Composition's bar methods in that it will truncate + * to the start and end times of this Segment, and is guaranteed + * to return the start time of a bar that is at least partially + * within this Segment. + * + * (See Composition for most of the generally useful bar methods.) + */ + timeT getBarStartForTime(timeT t) const; + + /** + * Return the ending time of the bar that contains time t. This + * differs from Composition's bar methods in that it will truncate + * to the start and end times of this Segment, and is guaranteed + * to return the end time of a bar that is at least partially + * within this Segment. + * + * (See Composition for most of the generally useful bar methods.) + */ + timeT getBarEndForTime(timeT t) const; + + /** + * Fill up the segment with rests, from the end of the last event + * currently on the segment to the endTime given. Actually, this + * does much the same as setEndTime does when it extends a segment. + */ + void fillWithRests(timeT endTime); + + /** + * Fill up a section within a segment with rests, from the + * startTime given to the endTime given. This may be useful if + * you have a pathological segment that contains notes already but + * not rests, but it is is likely to be dangerous unless you're + * quite careful about making sure the given range doesn't overlap + * any notes. + */ + void fillWithRests(timeT startTime, timeT endTime); + + /** + * For each series of contiguous rests found between the start and + * end time, replace the series of rests with another series of + * the same duration but composed of the theoretically "correct" + * rest durations to fill the gap, in the current time signature. + * The start and end time should be the raw absolute times of the + * events, not the notation-quantized versions, although the code + * will use the notation quantizations if it finds them. + */ + void normalizeRests(timeT startTime, timeT endTime); + + /** + * Return the clef in effect at the given time. This is a + * reasonably quick call. + */ + Clef getClefAtTime(timeT time) const; + + /** + * Return the clef in effect at the given time, and set ctime to + * the time of the clef change. This is a reasonably quick call. + */ + Clef getClefAtTime(timeT time, timeT &ctime) const; + + /** + * Return the key signature in effect at the given time. This is + * a reasonably quick call. + */ + Key getKeyAtTime(timeT time) const; + + /** + * Return the key signature in effect at the given time, and set + * ktime to the time of the key change. This is a reasonably + * quick call. + */ + Key getKeyAtTime(timeT time, timeT &ktime) const; + + /** + * Return the clef and key signature in effect at the beginning of the + * segment using the following rules : + * + * - Return the default clef if no clef change is preceding the first + * note or rest event, + * - else return the first clef event in the segment, + * - else return the default clef if the segment has no note event nor + * clef change in it. + * + * - Use the same rules with the key signature. + */ + void getFirstClefAndKey(Clef &clef, Key &key); + + + ////// + // + // REPEAT, DELAY, TRANSPOSE + + // Is this Segment repeating? + // + bool isRepeating() const { return m_repeating; } + void setRepeating(bool value); + + /** + * If this Segment is repeating, calculate and return the time at + * which the repeating stops. This is the start time of the + * following Segment on the same Track, if any, or else the end + * time of the Composition. If this Segment does not repeat, or + * the time calculated would precede the end time of the Segment, + * instead return the end time of the Segment. + */ + timeT getRepeatEndTime() const; + + timeT getDelay() const { return m_delay; } + void setDelay(timeT delay); + + RealTime getRealTimeDelay() const { return m_realTimeDelay; } + void setRealTimeDelay(RealTime delay); + + int getTranspose() const { return m_transpose; } + void setTranspose(int transpose); + + + + ////// + // + // AUDIO + + // Get and set Audio file Id (see the AudioFileManager) + // + unsigned int getAudioFileId() const { return m_audioFileId; } + void setAudioFileId(unsigned int id); + + unsigned int getUnstretchedFileId() const { return m_unstretchedFileId; } + void setUnstretchedFileId(unsigned int id); + + float getStretchRatio() const { return m_stretchRatio; } + void setStretchRatio(float ratio); + + // The audio start and end times tell us how far into + // audio file "m_audioFileId" this Segment starts and + // how far into the sample the Segment finishes. + // + RealTime getAudioStartTime() const { return m_audioStartTime; } + RealTime getAudioEndTime() const { return m_audioEndTime; } + void setAudioStartTime(const RealTime &time); + void setAudioEndTime(const RealTime &time); + + bool isAutoFading() const { return m_autoFade; } + void setAutoFade(bool value); + + RealTime getFadeInTime() const { return m_fadeInTime; } + void setFadeInTime(const RealTime &time); + + RealTime getFadeOutTime() const { return m_fadeOutTime; } + void setFadeOutTime(const RealTime &time); + + ////// + // + // MISCELLANEOUS + + /// Should only be called by Composition + void setComposition(Composition *composition) { + m_composition = composition; + } + + // The runtime id for this segment + // + int getRuntimeId() const { return m_runtimeSegmentId; } + + // Grid size for matrix view (and others probably) + // + void setSnapGridSize(int size) { m_snapGridSize = size; } + int getSnapGridSize() const { return m_snapGridSize; } + + // Other view features we might want to set on this Segment + // + void setViewFeatures(int features) { m_viewFeatures = features; } + int getViewFeatures() const { return m_viewFeatures; } + + /** + * The compare class used by Composition + */ + struct SegmentCmp + { + bool operator()(const Segment* a, const Segment* b) const + { + if (a->getTrack() == b->getTrack()) + return a->getStartTime() < b->getStartTime(); + + return a->getTrack() < b->getTrack(); + } + }; + + + /// For use by SegmentObserver objects like Composition & Staff + void addObserver(SegmentObserver *obs) { m_observers.push_back(obs); } + + /// For use by SegmentObserver objects like Composition & Staff + void removeObserver(SegmentObserver *obs) { m_observers.remove(obs); } + + // List of visible EventRulers attached to this segment + // + class EventRuler + { + public: + EventRuler(const std::string &type, int controllerValue, bool active): + m_type(type), m_controllerValue(controllerValue), m_active(active) {;} + + std::string m_type; // Event Type + int m_controllerValue; // if controller event, then which value + bool m_active; // is this Ruler active? + }; + + typedef std::vector EventRulerList; + typedef std::vector::iterator EventRulerListIterator; + typedef std::vector::const_iterator EventRulerListConstIterator; + + EventRulerList& getEventRulerList() { return m_eventRulerList; } + EventRuler* getEventRuler(const std::string &type, int controllerValue = -1); + + void addEventRuler(const std::string &type, int controllerValue = -1, bool active = 0); + bool deleteEventRuler(const std::string &type, int controllerValue = -1); + + ////// + // + // REFRESH STATUS + + // delegate part of the RefreshStatusArray API + + unsigned int getNewRefreshStatusId() { + return m_refreshStatusArray.getNewRefreshStatusId(); + } + + SegmentRefreshStatus &getRefreshStatus(unsigned int id) { + return m_refreshStatusArray.getRefreshStatus(id); + } + + void updateRefreshStatuses(timeT startTime, timeT endTime); + +private: + Composition *m_composition; // owns me, if it exists + + timeT m_startTime; + timeT *m_endMarkerTime; // points to end time, or null if none + timeT m_endTime; + + void updateEndTime(); // called after erase of item at end + + TrackId m_track; + SegmentType m_type; // identifies Segment type + std::string m_label; // segment label + + unsigned int m_colourIndex; // identifies Colour Index (default == 0) + + mutable int m_id; // not id of Segment, but a value for return by getNextId + + unsigned int m_audioFileId; // audio file ID (see AudioFileManager) + unsigned int m_unstretchedFileId; + float m_stretchRatio; + RealTime m_audioStartTime; // start time relative to start of audio file + RealTime m_audioEndTime; // end time relative to start of audio file + + bool m_repeating; // is this segment repeating? + + BasicQuantizer *const m_quantizer; + bool m_quantize; + + int m_transpose; // all Events tranpose + timeT m_delay; // all Events delay + RealTime m_realTimeDelay; // all Events delay (the delays are cumulative) + + int m_highestPlayable; // suggestion for highest playable note (notation) + int m_lowestPlayable; // suggestion for lowest playable note (notation) + + RefreshStatusArray m_refreshStatusArray; + + struct ClefKeyCmp { + bool operator()(const Event *e1, const Event *e2) const; + }; + typedef std::multiset ClefKeyList; + mutable ClefKeyList *m_clefKeyList; + + // EventRulers currently selected as visible on this segment + // + EventRulerList m_eventRulerList; + +private: // stuff to support SegmentObservers + + typedef std::list ObserverSet; + ObserverSet m_observers; + + void notifyAdd(Event *) const; + void notifyRemove(Event *) const; + void notifyAppearanceChange() const; + void notifyStartChanged(timeT); + void notifyEndMarkerChange(bool shorten); + void notifySourceDeletion() const; + +private: // assignment operator not provided + + Segment &operator=(const Segment &); + + // Used for mapping the segment to runtime things like PlayableAudioFiles at + // the sequencer. + // + int m_runtimeSegmentId; + + // Remember the last used snap grid size for this segment + // + int m_snapGridSize; + + // Switch for other view-specific features we want to remember in the segment + // + int m_viewFeatures; + + // Audio autofading + // + bool m_autoFade; + RealTime m_fadeInTime; + RealTime m_fadeOutTime; + +}; + + +class SegmentObserver +{ +public: + virtual ~SegmentObserver() {} + + /** + * Called after the event has been added to the segment + */ + virtual void eventAdded(const Segment *, Event *) { } + + /** + * Called after the event has been removed from the segment, + * and just before it is deleted + */ + virtual void eventRemoved(const Segment *, Event *) { } + + /** + * Called after a change in the segment that will change the way its displays, + * like a label change for instance + */ + virtual void appearanceChanged(const Segment *) { } + + /** + * Called after a change that affects the start time of the segment + */ + virtual void startChanged(const Segment *, timeT) { } + + /** + * Called after the segment's end marker time has been + * changed + * + * @param shorten true if the marker change shortens the segment's duration + */ + virtual void endMarkerTimeChanged(const Segment *, bool /*shorten*/) { } + + /** + * Called from the segment dtor + * MUST BE IMPLEMENTED BY ALL OBSERVERS + */ + virtual void segmentDeleted(const Segment *) = 0; +}; + + + +// an abstract base + +class SegmentHelper +{ +protected: + SegmentHelper(Segment &t) : m_segment(t) { } + virtual ~SegmentHelper(); + + typedef Segment::iterator iterator; + + Segment &segment() { return m_segment; } + + Segment::iterator begin() { return segment().begin(); } + Segment::iterator end() { return segment().end(); } + + bool isBeforeEndMarker(Segment::const_iterator i) { + return segment().isBeforeEndMarker(i); + } + + Segment::iterator insert(Event *e) { return segment().insert(e); } + void erase(Segment::iterator i) { segment().erase(i); } + +private: + Segment &m_segment; +}; + +} + + +#endif diff --git a/src/base/SegmentMatrixHelper.cpp b/src/base/SegmentMatrixHelper.cpp new file mode 100644 index 0000000..d9af52c --- /dev/null +++ b/src/base/SegmentMatrixHelper.cpp @@ -0,0 +1,56 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "SegmentMatrixHelper.h" +#include "BaseProperties.h" + +namespace Rosegarden +{ + +Segment::iterator SegmentMatrixHelper::insertNote(Event* e) +{ + Segment::iterator i = segment().insert(e); + segment().normalizeRests(e->getAbsoluteTime(), + e->getAbsoluteTime() + e->getDuration()); + return i; +} + +bool +SegmentMatrixHelper::isDrumColliding(Event* e) +{ + long pitch = 0; + if (!e->get(BaseProperties::PITCH, pitch)) + return false; + + timeT evTime = e->getAbsoluteTime(); + + Segment::iterator it; + for (it = segment().findTime(evTime); it != end(); ++it) { + if ((*it) == e) continue; + if ((*it)->getAbsoluteTime() != evTime) break; + long p = 0; + if (!(*it)->get(BaseProperties::PITCH, p)) continue; + if (p == pitch) return true; + } + return false; +} + +} diff --git a/src/base/SegmentMatrixHelper.h b/src/base/SegmentMatrixHelper.h new file mode 100644 index 0000000..1790496 --- /dev/null +++ b/src/base/SegmentMatrixHelper.h @@ -0,0 +1,53 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _SEGMENT_MATRIX_HELPER_H_ +#define _SEGMENT_MATRIX_HELPER_H_ + +#include "SegmentNotationHelper.h" + +namespace Rosegarden +{ + +class SegmentMatrixHelper : protected SegmentNotationHelper +{ +public: + SegmentMatrixHelper(Segment &t) : SegmentNotationHelper(t) { } + + iterator insertNote(Event *); + + /** + * Returns true if event is colliding another note in percussion + * matrix (ie event is a note and has the same start time and the + * same pitch as another note). + */ + bool isDrumColliding(Event *); + + using SegmentHelper::segment; + using SegmentNotationHelper::deleteEvent; + using SegmentNotationHelper::deleteNote; + +}; + + +} + +#endif diff --git a/src/base/SegmentNotationHelper.cpp b/src/base/SegmentNotationHelper.cpp new file mode 100644 index 0000000..a6c8ab8 --- /dev/null +++ b/src/base/SegmentNotationHelper.cpp @@ -0,0 +1,2129 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "SegmentNotationHelper.h" +#include "NotationTypes.h" +#include "Quantizer.h" +#include "BasicQuantizer.h" +#include "NotationQuantizer.h" +#include "BaseProperties.h" +#include "Composition.h" + +#include +#include +#include +#include + +namespace Rosegarden +{ +using std::cerr; +using std::endl; +using std::string; +using std::list; + +using namespace BaseProperties; + + +SegmentNotationHelper::~SegmentNotationHelper() { } + + +const Quantizer & +SegmentNotationHelper::basicQuantizer() { + return *(segment().getComposition()->getBasicQuantizer()); +} + +const Quantizer & +SegmentNotationHelper::notationQuantizer() { + return *(segment().getComposition()->getNotationQuantizer()); +} + + +//!!! we need to go very carefully through this file and check calls +//to getAbsoluteTime/getDuration -- the vast majority should almost +//certainly now be using getNotationAbsoluteTime/getNotationDuration + +Segment::iterator +SegmentNotationHelper::findNotationAbsoluteTime(timeT t) +{ + iterator i(segment().findTime(t)); + + // We don't know whether the notation absolute time t will appear + // before or after the real absolute time t. First scan backwards + // until we find a notation absolute time prior to (or equal to) + // t, and then scan forwards until we find the first one that + // isn't prior to t + + while (i != begin() && + ((i == end() ? t + 1 : (*i)->getNotationAbsoluteTime()) > t)) + --i; + + while (i != end() && + ((*i)->getNotationAbsoluteTime() < t)) + ++i; + + return i; +} + +Segment::iterator +SegmentNotationHelper::findNearestNotationAbsoluteTime(timeT t) +{ + iterator i(segment().findTime(t)); + + // Exactly findNotationAbsoluteTime, only with the two scan loops + // in the other order + + while (i != end() && + ((*i)->getNotationAbsoluteTime() < t)) + ++i; + + while (i != begin() && + ((i == end() ? t + 1 : (*i)->getNotationAbsoluteTime()) > t)) + --i; + + return i; +} + +void +SegmentNotationHelper::setNotationProperties(timeT startTime, timeT endTime) +{ + Segment::iterator from = begin(); + Segment::iterator to = end(); + + if (startTime != endTime) { + from = segment().findTime(startTime); + to = segment().findTime(endTime); + } +/*!!! + bool justSeenGraceNote = false; + timeT graceNoteStart = 0; +*/ + for (Segment::iterator i = from; + i != to && segment().isBeforeEndMarker(i); ++i) { + + if ((*i)->has(NOTE_TYPE) /*!!! && !(*i)->has(IS_GRACE_NOTE) */) continue; + + timeT duration = (*i)->getNotationDuration(); + + if ((*i)->has(BEAMED_GROUP_TUPLET_BASE)) { + int tcount = (*i)->get(BEAMED_GROUP_TUPLED_COUNT); + int ucount = (*i)->get(BEAMED_GROUP_UNTUPLED_COUNT); + + if (tcount == 0) { + std::cerr << "WARNING: SegmentNotationHelper::setNotationProperties: zero tuplet count:" << std::endl; + (*i)->dump(std::cerr); + } else { + // nominal duration is longer than actual (sounding) duration + duration = (duration / tcount) * ucount; + } + } + + if ((*i)->isa(Note::EventType) || (*i)->isa(Note::EventRestType)) { + + if ((*i)->isa(Note::EventType)) { +/*!!! + if ((*i)->has(IS_GRACE_NOTE) && + (*i)->get(IS_GRACE_NOTE)) { + + if (!justSeenGraceNote) { + graceNoteStart = (*i)->getNotationAbsoluteTime(); + justSeenGraceNote = true; + } + + } else if (justSeenGraceNote) { + + duration += (*i)->getNotationAbsoluteTime() - graceNoteStart; + justSeenGraceNote = false; + } +*/ + } + + Note n(Note::getNearestNote(duration)); + + (*i)->setMaybe(NOTE_TYPE, n.getNoteType()); + (*i)->setMaybe(NOTE_DOTS, n.getDots()); + } + } +} + +timeT +SegmentNotationHelper::getNotationEndTime(Event *e) +{ + return e->getNotationAbsoluteTime() + e->getNotationDuration(); +} + + +Segment::iterator +SegmentNotationHelper::getNextAdjacentNote(iterator i, + bool matchPitch, + bool allowOverlap) +{ + iterator j(i); + if (!isBeforeEndMarker(i)) return i; + if (!(*i)->isa(Note::EventType)) return end(); + + timeT iEnd = getNotationEndTime(*i); + long ip = 0, jp = 0; + if (!(*i)->get(PITCH, ip) && matchPitch) return end(); + + while (true) { + if (!isBeforeEndMarker(j) || !isBeforeEndMarker(++j)) return end(); + if (!(*j)->isa(Note::EventType)) continue; + + timeT jStart = (*j)->getNotationAbsoluteTime(); + if (jStart > iEnd) return end(); + + if (matchPitch) { + if (!(*j)->get(PITCH, jp) || (jp != ip)) continue; + } + + if (allowOverlap || (jStart == iEnd)) return j; + } +} + + +Segment::iterator +SegmentNotationHelper::getPreviousAdjacentNote(iterator i, + timeT rangeStart, + bool matchPitch, + bool allowOverlap) +{ + iterator j(i); + if (!isBeforeEndMarker(i)) return i; + if (!(*i)->isa(Note::EventType)) return end(); + + timeT iStart = (*i)->getNotationAbsoluteTime(); + timeT iEnd = getNotationEndTime(*i); + long ip = 0, jp = 0; + if (!(*i)->get(PITCH, ip) && matchPitch) return end(); + + while (true) { + if (j == begin()) return end(); else --j; + if (!(*j)->isa(Note::EventType)) continue; + if ((*j)->getAbsoluteTime() < rangeStart) return end(); + + timeT jEnd = getNotationEndTime(*j); + + // don't consider notes that end after i ends or before i begins + + if (jEnd > iEnd || jEnd < iStart) continue; + + if (matchPitch) { + if (!(*j)->get(PITCH, jp) || (jp != ip)) continue; + } + + if (allowOverlap || (jEnd == iStart)) return j; + } +} + + +Segment::iterator +SegmentNotationHelper::findContiguousNext(iterator el) +{ + std::string elType = (*el)->getType(), + reject, accept; + + if (elType == Note::EventType) { + accept = Note::EventType; + reject = Note::EventRestType; + } else if (elType == Note::EventRestType) { + accept = Note::EventRestType; + reject = Note::EventType; + } else { + accept = elType; + reject = ""; + } + + bool success = false; + + iterator i = ++el; + + for(; isBeforeEndMarker(i); ++i) { + std::string iType = (*i)->getType(); + + if (iType == reject) { + success = false; + break; + } + if (iType == accept) { + success = true; + break; + } + } + + if (success) return i; + else return end(); + +} + +Segment::iterator +SegmentNotationHelper::findContiguousPrevious(iterator el) +{ + if (el == begin()) return end(); + + std::string elType = (*el)->getType(), + reject, accept; + + if (elType == Note::EventType) { + accept = Note::EventType; + reject = Note::EventRestType; + } else if (elType == Note::EventRestType) { + accept = Note::EventRestType; + reject = Note::EventType; + } else { + accept = elType; + reject = ""; + } + + bool success = false; + + iterator i = --el; + + while (true) { + std::string iType = (*i)->getType(); + + if (iType == reject) { + success = false; + break; + } + if (iType == accept) { + success = true; + break; + } + if (i == begin()) break; + --i; + } + + if (success) return i; + else return end(); +} + + +bool +SegmentNotationHelper::noteIsInChord(Event *note) +{ + iterator i = segment().findSingle(note); + timeT t = note->getNotationAbsoluteTime(); + + for (iterator j = i; j != end(); ++j) { // not isBeforeEndMarker, unnecessary here + if (j == i) continue; + if ((*j)->isa(Note::EventType)) { + timeT tj = (*j)->getNotationAbsoluteTime(); + if (tj == t) return true; + else if (tj > t) break; + } + } + + for (iterator j = i; ; ) { + if (j == begin()) break; + --j; + if ((*j)->isa(Note::EventType)) { + timeT tj = (*j)->getNotationAbsoluteTime(); + if (tj == t) return true; + else if (tj < t) break; + } + } + + return false; + +/*!!! + iterator first, second; + segment().getTimeSlice(note->getAbsoluteTime(), first, second); + + int noteCount = 0; + for (iterator i = first; i != second; ++i) { + if ((*i)->isa(Note::EventType)) ++noteCount; + } + + return noteCount > 1; +*/ +} + + +//!!! This doesn't appear to be used any more and may well not work. +// Ties are calculated in several different places, and it's odd that +// we don't have a decent API for them +Segment::iterator +SegmentNotationHelper::getNoteTiedWith(Event *note, bool forwards) +{ + bool tied = false; + + if (!note->get(forwards ? + BaseProperties::TIED_FORWARD : + BaseProperties::TIED_BACKWARD, tied) || !tied) { + return end(); + } + + timeT myTime = note->getAbsoluteTime(); + timeT myDuration = note->getDuration(); + int myPitch = note->get(BaseProperties::PITCH); + + iterator i = segment().findSingle(note); + if (!isBeforeEndMarker(i)) return end(); + + for (;;) { + i = forwards ? findContiguousNext(i) : findContiguousPrevious(i); + + if (!isBeforeEndMarker(i)) return end(); + if ((*i)->getAbsoluteTime() == myTime) continue; + + if (forwards && ((*i)->getAbsoluteTime() != myTime + myDuration)) { + return end(); + } + if (!forwards && + (((*i)->getAbsoluteTime() + (*i)->getDuration()) != myTime)) { + return end(); + } + + if (!(*i)->get(forwards ? + BaseProperties::TIED_BACKWARD : + BaseProperties::TIED_FORWARD, tied) || !tied) { + continue; + } + + if ((*i)->get(BaseProperties::PITCH) == myPitch) return i; + } + + return end(); +} + + +bool +SegmentNotationHelper::collapseRestsIfValid(Event* e, bool& collapseForward) +{ + iterator elPos = segment().findSingle(e); + if (elPos == end()) return false; + + timeT myDuration = (*elPos)->getNotationDuration(); + + // findContiguousNext won't return an iterator beyond the end marker + iterator nextEvent = findContiguousNext(elPos), + previousEvent = findContiguousPrevious(elPos); + + // Remark: findContiguousXXX is inadequate for notes, we would + // need to check adjacency using e.g. getNextAdjacentNote if this + // method were to work for notes as well as rests. + + // collapse to right if (a) not at end... + if (nextEvent != end() && + // ...(b) rests can be merged to a single, valid unit + isCollapseValid((*nextEvent)->getNotationDuration(), myDuration) && + // ...(c) event is in same bar (no cross-bar collapsing) + (*nextEvent)->getAbsoluteTime() < + segment().getBarEndForTime(e->getAbsoluteTime())) { + + // collapse right is OK; collapse e with nextEvent + Event *e1(new Event(*e, e->getAbsoluteTime(), + e->getDuration() + (*nextEvent)->getDuration())); + + collapseForward = true; + erase(elPos); + erase(nextEvent); + insert(e1); + return true; + } + + // logic is exactly backwards from collapse to right logic above + if (previousEvent != end() && + isCollapseValid((*previousEvent)->getNotationDuration(), myDuration) && + (*previousEvent)->getAbsoluteTime() > + segment().getBarStartForTime(e->getAbsoluteTime())) { + + // collapse left is OK; collapse e with previousEvent + Event *e1(new Event(**previousEvent, + (*previousEvent)->getAbsoluteTime(), + e->getDuration() + + (*previousEvent)->getDuration())); + + collapseForward = false; + erase(elPos); + erase(previousEvent); + insert(e1); + return true; + } + + return false; +} + + +bool +SegmentNotationHelper::isCollapseValid(timeT a, timeT b) +{ + return (isViable(a + b)); +} + + +bool +SegmentNotationHelper::isSplitValid(timeT a, timeT b) +{ + return (isViable(a) && isViable(b)); +} + +Segment::iterator +SegmentNotationHelper::splitIntoTie(iterator &i, timeT baseDuration) +{ + if (i == end()) return end(); + iterator i2; + segment().getTimeSlice((*i)->getAbsoluteTime(), i, i2); + return splitIntoTie(i, i2, baseDuration); +} + +Segment::iterator +SegmentNotationHelper::splitIntoTie(iterator &from, iterator to, + timeT baseDuration) +{ + // so long as we do the quantization checks for validity before + // calling this method, we should be fine splitting precise times + // in this method. only problem is deciding not to split something + // if its duration is very close to requested duration, but that's + // probably not a task for this function + + timeT eventDuration = (*from)->getDuration(); + timeT baseTime = (*from)->getAbsoluteTime(); + + long firstGroupId = -1; + (*from)->get(BEAMED_GROUP_ID, firstGroupId); + + long nextGroupId = -1; + iterator ni(to); + + if (segment().isBeforeEndMarker(ni) && segment().isBeforeEndMarker(++ni)) { + (*ni)->get(BEAMED_GROUP_ID, nextGroupId); + } + + list toInsert; + list toErase; + + // Split all the note and rest events in range [from, to[ + // + for (iterator i = from; i != to; ++i) { + + if (!(*i)->isa(Note::EventType) && + !(*i)->isa(Note::EventRestType)) continue; + + if ((*i)->getAbsoluteTime() != baseTime) { + // no way to really cope with an error, because at this + // point we may already have splut some events. Best to + // skip this event + cerr << "WARNING: SegmentNotationHelper::splitIntoTie(): (*i)->getAbsoluteTime() != baseTime (" << (*i)->getAbsoluteTime() << " vs " << baseTime << "), ignoring this event\n"; + continue; + } + + if ((*i)->getDuration() != eventDuration) { + if ((*i)->getDuration() == 0) continue; + cerr << "WARNING: SegmentNotationHelper::splitIntoTie(): (*i)->getDuration() != eventDuration (" << (*i)->getDuration() << " vs " << eventDuration << "), changing eventDuration to match\n"; + eventDuration = (*i)->getDuration(); + } + + if (baseDuration >= eventDuration) { +// cerr << "SegmentNotationHelper::splitIntoTie() : baseDuration >= eventDuration, ignoring event\n"; + continue; + } + + std::pair split = + splitPreservingPerformanceTimes(*i, baseDuration); + + Event *eva = split.first; + Event *evb = split.second; + + if (!eva || !evb) { + cerr << "WARNING: SegmentNotationHelper::splitIntoTie(): No valid split for event of duration " << eventDuration << " at " << baseTime << " (baseDuration " << baseDuration << "), ignoring this event\n"; + continue; + } + + // we only want to tie Note events: + + if (eva->isa(Note::EventType)) { + + // if the first event was already tied forward, the + // second one will now be marked as tied forward + // (which is good). set up the relationship between + // the original (now shorter) event and the new one. + + evb->set(TIED_BACKWARD, true); + eva->set(TIED_FORWARD, true); + } + + // we may also need to change some group information: if + // the first event is in a beamed group but the event + // following the insertion is not or is in a different + // group, then the new second event should not be in a + // group. otherwise, it should inherit the grouping info + // from the first event (as it already does, because it + // was created using the copy constructor). + + // (this doesn't apply to tupled groups, which we want + // to persist wherever possible.) + + if (firstGroupId != -1 && + nextGroupId != firstGroupId && + !evb->has(BEAMED_GROUP_TUPLET_BASE)) { + evb->unset(BEAMED_GROUP_ID); + evb->unset(BEAMED_GROUP_TYPE); + } + + toInsert.push_back(eva); + toInsert.push_back(evb); + toErase.push_back(i); + } + + // erase the old events + for (list::iterator i = toErase.begin(); + i != toErase.end(); ++i) { + segment().erase(*i); + } + + from = end(); + iterator last = end(); + + // now insert the new events + for (list::iterator i = toInsert.begin(); + i != toInsert.end(); ++i) { + last = insert(*i); + if (from == end()) from = last; + } + + return last; +} + +bool +SegmentNotationHelper::isViable(timeT duration, int dots) +{ + bool viable; + +/*!!! + duration = basicQuantizer().quantizeDuration(duration); + + if (dots >= 0) { + viable = (duration == Quantizer(Quantizer::RawEventData, + Quantizer::DefaultTarget, + Quantizer::NoteQuantize, 1, dots). + quantizeDuration(duration)); + } else { + viable = (duration == notationQuantizer().quantizeDuration(duration)); + } +*/ + + //!!! what to do about this? + + timeT nearestDuration = + Note::getNearestNote(duration, dots >= 0 ? dots : 2).getDuration(); + +// std::cerr << "SegmentNotationHelper::isViable: nearestDuration is " << nearestDuration << ", duration is " << duration << std::endl; + viable = (nearestDuration == duration); + + return viable; +} + + +void +SegmentNotationHelper::makeRestViable(iterator i) +{ + timeT absTime = (*i)->getAbsoluteTime(); + timeT duration = (*i)->getDuration(); + erase(i); + segment().fillWithRests(absTime, absTime + duration); +} + + +void +SegmentNotationHelper::makeNotesViable(iterator from, iterator to, + bool splitAtBars) +{ + // We don't use quantized values here; we want a precise division. + // Even if it doesn't look precise on the score (because the score + // is quantized), we want any playback to produce exactly the same + // duration of note as was originally recorded + + std::vector toInsert; + + for (Segment::iterator i = from, j = i; + segment().isBeforeEndMarker(i) && i != to; i = j) { + + ++j; + + if (!(*i)->isa(Note::EventType) && !(*i)->isa(Note::EventRestType)) { + continue; + } + + if ((*i)->has(BEAMED_GROUP_TUPLET_BASE)) { + continue; + } + + DurationList dl; + + // Behaviour differs from TimeSignature::getDurationListForInterval + + timeT acc = 0; + timeT required = (*i)->getNotationDuration(); + + while (acc < required) { + timeT remaining = required - acc; + if (splitAtBars) { + timeT thisNoteStart = (*i)->getNotationAbsoluteTime() + acc; + timeT toNextBar = + segment().getBarEndForTime(thisNoteStart) - thisNoteStart; + if (toNextBar > 0 && remaining > toNextBar) remaining = toNextBar; + } + timeT component = Note::getNearestNote(remaining).getDuration(); + if (component > (required - acc)) dl.push_back(required - acc); + else dl.push_back(component); + acc += component; + } + + if (dl.size() < 2) { + // event is already of the correct duration + continue; + } + + acc = (*i)->getNotationAbsoluteTime(); + Event *e = new Event(*(*i)); + + bool lastTiedForward = false; + e->get(TIED_FORWARD, lastTiedForward); + + e->set(TIED_FORWARD, true); + erase(i); + + for (DurationList::iterator dli = dl.begin(); dli != dl.end(); ++dli) { + + DurationList::iterator dlj(dli); + if (++dlj == dl.end()) { + // end of duration list + if (!lastTiedForward) e->unset(TIED_FORWARD); + toInsert.push_back(e); + e = 0; + break; + } + + std::pair splits = + splitPreservingPerformanceTimes(e, *dli); + + if (!splits.first || !splits.second) { + cerr << "WARNING: SegmentNotationHelper::makeNoteViable(): No valid split for event of duration " << e->getDuration() << " at " << e->getAbsoluteTime() << " (split duration " << *dli << "), ignoring remainder\n"; + cerr << "WARNING: This is probably a bug; fix required" << std::endl; + toInsert.push_back(e); + e = 0; + break; + } + + toInsert.push_back(splits.first); + delete e; + e = splits.second; + + acc += *dli; + + e->set(TIED_BACKWARD, true); + } + + delete e; + } + + for (std::vector::iterator ei = toInsert.begin(); + ei != toInsert.end(); ++ei) { + insert(*ei); + } +} + +void +SegmentNotationHelper::makeNotesViable(timeT startTime, timeT endTime, + bool splitAtBars) +{ + Segment::iterator from = segment().findTime(startTime); + Segment::iterator to = segment().findTime(endTime); + + makeNotesViable(from, to, splitAtBars); +} + + +Segment::iterator +SegmentNotationHelper::insertNote(timeT absoluteTime, Note note, int pitch, + Accidental explicitAccidental) +{ + Event *e = new Event(Note::EventType, absoluteTime, note.getDuration()); + e->set(PITCH, pitch); + e->set(ACCIDENTAL, explicitAccidental); + iterator i = insertNote(e); + delete e; + return i; +} + +Segment::iterator +SegmentNotationHelper::insertNote(Event *modelEvent) +{ + timeT absoluteTime = modelEvent->getAbsoluteTime(); + iterator i = segment().findNearestTime(absoluteTime); + + // If our insertion time doesn't match up precisely with any + // existing event, and if we're inserting over a rest, split the + // rest at the insertion time first. + + if (i != end() && + (*i)->getAbsoluteTime() < absoluteTime && + (*i)->getAbsoluteTime() + (*i)->getDuration() > absoluteTime && + (*i)->isa(Note::EventRestType)) { + i = splitIntoTie(i, absoluteTime - (*i)->getAbsoluteTime()); + } + + timeT duration = modelEvent->getDuration(); + + if (i != end() && (*i)->has(BEAMED_GROUP_TUPLET_BASE)) { + duration = duration * (*i)->get(BEAMED_GROUP_TUPLED_COUNT) / + (*i)->get(BEAMED_GROUP_UNTUPLED_COUNT); + } + + //!!! Deal with end-of-bar issues! + + return insertSomething(i, duration, modelEvent, false); +} + + +Segment::iterator +SegmentNotationHelper::insertRest(timeT absoluteTime, Note note) +{ + iterator i, j; + segment().getTimeSlice(absoluteTime, i, j); + + //!!! Deal with end-of-bar issues! + + timeT duration(note.getDuration()); + + if (i != end() && (*i)->has(BEAMED_GROUP_TUPLET_BASE)) { + duration = duration * (*i)->get(BEAMED_GROUP_TUPLED_COUNT) / + (*i)->get(BEAMED_GROUP_UNTUPLED_COUNT); + } + + Event *modelEvent = new Event(Note::EventRestType, absoluteTime, + note.getDuration(), + Note::EventRestSubOrdering); + + i = insertSomething(i, duration, modelEvent, false); + delete modelEvent; + return i; +} + + +// return an iterator pointing to the "same" event as the original +// iterator (which will have been replaced) + +Segment::iterator +SegmentNotationHelper::collapseRestsForInsert(iterator i, + timeT desiredDuration) +{ + // collapse at most once, then recurse + + if (!segment().isBeforeEndMarker(i) || + !(*i)->isa(Note::EventRestType)) return i; + + timeT d = (*i)->getDuration(); + iterator j = findContiguousNext(i); // won't return itr after end marker + if (d >= desiredDuration || j == end()) return i; + + Event *e(new Event(**i, (*i)->getAbsoluteTime(), d + (*j)->getDuration())); + iterator ii(insert(e)); + erase(i); + erase(j); + + return collapseRestsForInsert(ii, desiredDuration); +} + + +Segment::iterator +SegmentNotationHelper::insertSomething(iterator i, int duration, + Event *modelEvent, bool tiedBack) +{ + // Rules: + // + // 1. If we hit a bar line in the course of the intended inserted + // note, we should split the note rather than make the bar the + // wrong length. (Not implemented yet) + // + // 2. If there's nothing at the insertion point but rests (and + // enough of them to cover the entire duration of the new note), + // then we should insert the new note/rest literally and remove + // rests as appropriate. Rests should never prevent us from + // inserting what the user asked for. + // + // 3. If there are notes in the way of an inserted note, however, + // we split whenever "reasonable" and truncate our user's note if + // not reasonable to split. We can't always give users the Right + // Thing here, so to hell with them. + + while (i != end() && + ((*i)->getDuration() == 0 || + !((*i)->isa(Note::EventType) || (*i)->isa(Note::EventRestType)))) + ++i; + + if (i == end()) { + return insertSingleSomething(i, duration, modelEvent, tiedBack); + } + + // If there's a rest at the insertion position, merge it with any + // following rests, if available, until we have at least the + // duration of the new note. + i = collapseRestsForInsert(i, duration); + + timeT existingDuration = (*i)->getNotationDuration(); + +// cerr << "SegmentNotationHelper::insertSomething: asked to insert duration " << duration +// << " over event of duration " << existingDuration << ":" << endl; + (*i)->dump(cerr); + + if (duration == existingDuration) { + + // 1. If the new note or rest is the same length as an + // existing note or rest at that position, chord the existing + // note or delete the existing rest and insert. + +// cerr << "Durations match; doing simple insert" << endl; + + return insertSingleSomething(i, duration, modelEvent, tiedBack); + + } else if (duration < existingDuration) { + + // 2. If the new note or rest is shorter than an existing one, + // split the existing one and chord or replace the first part. + + if ((*i)->isa(Note::EventType)) { + + if (!isSplitValid(duration, existingDuration - duration)) { + +// cerr << "Bad split, coercing new note" << endl; + + // not reasonable to split existing note, so force new one + // to same duration instead + duration = (*i)->getNotationDuration(); + + } else { +// cerr << "Good split, splitting old event" << endl; + splitIntoTie(i, duration); + } + } else if ((*i)->isa(Note::EventRestType)) { + +// cerr << "Found rest, splitting" << endl; + iterator last = splitIntoTie(i, duration); + + // Recover viability for the second half of any split rest + // (we duck out of this if we find we're in a tupleted zone) + + if (last != end() && !(*last)->has(BEAMED_GROUP_TUPLET_BASE)) { + makeRestViable(last); + } + } + + return insertSingleSomething(i, duration, modelEvent, tiedBack); + + } else { // duration > existingDuration + + // 3. If the new note is longer, split the new note so that + // the first part is the same duration as the existing note or + // rest, and recurse to step 1 with both the first and the + // second part in turn. + + bool needToSplit = true; + + // special case: existing event is a rest, and it's at the end + // of the segment + + if ((*i)->isa(Note::EventRestType)) { + iterator j; + for (j = i; j != end(); ++j) { + if ((*j)->isa(Note::EventType)) break; + } + if (j == end()) needToSplit = false; + } + + if (needToSplit) { + + //!!! This is not quite right for rests. Because they + //replace (rather than chording with) any events already + //present, they don't need to be split in the case where + //their duration spans several note-events. Worry about + //that later, I guess. We're actually getting enough + //is-note/is-rest decisions here to make it possibly worth + //splitting this method into note and rest versions again + +// cerr << "Need to split new note" << endl; + + i = insertSingleSomething + (i, existingDuration, modelEvent, tiedBack); + + if (modelEvent->isa(Note::EventType)) + (*i)->set(TIED_FORWARD, true); + + timeT insertedTime = (*i)->getAbsoluteTime(); + while (i != end() && + ((*i)->getNotationAbsoluteTime() < + (insertedTime + existingDuration))) ++i; + + return insertSomething + (i, duration - existingDuration, modelEvent, true); + + } else { +// cerr << "No need to split new note" << endl; + return insertSingleSomething(i, duration, modelEvent, tiedBack); + } + } +} + +Segment::iterator +SegmentNotationHelper::insertSingleSomething(iterator i, int duration, + Event *modelEvent, bool tiedBack) +{ + timeT time; + timeT notationTime; + bool eraseI = false; + timeT effectiveDuration(duration); + + if (i == end()) { + time = segment().getEndTime(); + notationTime = time; + } else { + time = (*i)->getAbsoluteTime(); + notationTime = (*i)->getNotationAbsoluteTime(); + if (modelEvent->isa(Note::EventRestType) || + (*i)->isa(Note::EventRestType)) eraseI = true; + } + + Event *e = new Event(*modelEvent, time, effectiveDuration, + modelEvent->getSubOrdering(), notationTime); + + // If the model event already has group info, I guess we'd better use it! + if (!e->has(BEAMED_GROUP_ID)) { + setInsertedNoteGroup(e, i); + } + + if (tiedBack && e->isa(Note::EventType)) { + e->set(TIED_BACKWARD, true); + } + + if (eraseI) { + // erase i and all subsequent events with the same type and + // absolute time + timeT time((*i)->getAbsoluteTime()); + std::string type((*i)->getType()); + iterator j(i); + while (j != end() && (*j)->getAbsoluteTime() == time) { + ++j; + if ((*i)->isa(type)) erase(i); + i = j; + } + } + + return insert(e); +} + +void +SegmentNotationHelper::setInsertedNoteGroup(Event *e, iterator i) +{ + // Formerly this was posited on the note being inserted between + // two notes in the same group, but that's quite wrong-headed: we + // want to place it in the same group as any existing note at the + // same time, and otherwise leave it alone. + + e->unset(BEAMED_GROUP_ID); + e->unset(BEAMED_GROUP_TYPE); + + while (isBeforeEndMarker(i) && + (!((*i)->isa(Note::EventRestType)) || + (*i)->has(BEAMED_GROUP_TUPLET_BASE)) && + (*i)->getNotationAbsoluteTime() == e->getAbsoluteTime()) { + + if ((*i)->has(BEAMED_GROUP_ID)) { + + string type = (*i)->get(BEAMED_GROUP_TYPE); + if (type != GROUP_TYPE_TUPLED && !(*i)->isa(Note::EventType)) { + if ((*i)->isa(Note::EventRestType)) return; + else { + ++i; + continue; + } + } + + e->set(BEAMED_GROUP_ID, (*i)->get(BEAMED_GROUP_ID)); + e->set(BEAMED_GROUP_TYPE, type); + + if ((*i)->has(BEAMED_GROUP_TUPLET_BASE)) { + + e->set(BEAMED_GROUP_TUPLET_BASE, + (*i)->get(BEAMED_GROUP_TUPLET_BASE)); + e->set(BEAMED_GROUP_TUPLED_COUNT, + (*i)->get(BEAMED_GROUP_TUPLED_COUNT)); + e->set(BEAMED_GROUP_UNTUPLED_COUNT, + (*i)->get(BEAMED_GROUP_UNTUPLED_COUNT)); + } + + return; + } + + ++i; + } +} + + +Segment::iterator +SegmentNotationHelper::insertClef(timeT absoluteTime, Clef clef) +{ + return insert(clef.getAsEvent(absoluteTime)); +} + + +Segment::iterator +SegmentNotationHelper::insertKey(timeT absoluteTime, Key key) +{ + return insert(key.getAsEvent(absoluteTime)); +} + + +Segment::iterator +SegmentNotationHelper::insertText(timeT absoluteTime, Text text) +{ + return insert(text.getAsEvent(absoluteTime)); +} + + +void +SegmentNotationHelper::deleteNote(Event *e, bool collapseRest) +{ + iterator i = segment().findSingle(e); + + if (i == end()) return; + + if ((*i)->has(TIED_BACKWARD) && (*i)->get(TIED_BACKWARD)) { + iterator j = getPreviousAdjacentNote(i, segment().getStartTime(), + true, false); + if (j != end()) { + (*j)->unset(TIED_FORWARD); // don't even check if it has it set + } + } + + if ((*i)->has(TIED_FORWARD) && (*i)->get(TIED_FORWARD)) { + iterator j = getNextAdjacentNote(i, true, false); + if (j != end()) { + (*j)->unset(TIED_BACKWARD); // don't even check if it has it set + } + } + + // If any notes start at the same time as this one but end first, + // or start after this one starts but before it ends, then we go + // for the delete-event-and-normalize-rests option. Otherwise + // (the notationally simpler case) we go for the + // replace-note-by-rest option. We still lose in the case where + // another note starts before this one, overlaps it, but then also + // ends before it does -- but I think we can live with that. + + iterator j = i; + timeT endTime = (*i)->getAbsoluteTime() + (*i)->getDuration(); + + while (j != end() && (*j)->getAbsoluteTime() < endTime) { + + bool complicatedOverlap = false; + + if ((*j)->getAbsoluteTime() != (*i)->getAbsoluteTime()) { + complicatedOverlap = true; + } else if (((*j)->getAbsoluteTime() + (*j)->getDuration()) < endTime) { + complicatedOverlap = true; + } + + if (complicatedOverlap) { + timeT startTime = (*i)->getAbsoluteTime(); + segment().erase(i); + segment().normalizeRests(startTime, endTime); + return; + } + + ++j; + } + + if (noteIsInChord(e)) { + + erase(i); + + } else { + + // replace with a rest + Event *newRest = new Event(Note::EventRestType, + e->getAbsoluteTime(), e->getDuration(), + Note::EventRestSubOrdering); + insert(newRest); + erase(i); + + // collapse the new rest + if (collapseRest) { + bool dummy; + collapseRestsIfValid(newRest, dummy); + } + + } +} + +bool +SegmentNotationHelper::deleteRest(Event *e) +{ + bool collapseForward; + return collapseRestsIfValid(e, collapseForward); +} + +bool +SegmentNotationHelper::deleteEvent(Event *e, bool collapseRest) +{ + bool res = true; + + if (e->isa(Note::EventType)) deleteNote(e, collapseRest); + else if (e->isa(Note::EventRestType)) res = deleteRest(e); + else { + // just plain delete + iterator i = segment().findSingle(e); + if (i != end()) erase(i); + } + + return res; +} + + +bool +SegmentNotationHelper::hasEffectiveDuration(iterator i) +{ + bool hasDuration = ((*i)->getDuration() > 0); + + if ((*i)->isa(Note::EventType)) { + iterator i0(i); + if (++i0 != end() && + (*i0)->isa(Note::EventType) && + (*i0)->getNotationAbsoluteTime() == + (*i)->getNotationAbsoluteTime()) { + // we're in a chord or something + hasDuration = false; + } + } + + return hasDuration; +} + + +void +SegmentNotationHelper::makeBeamedGroup(timeT from, timeT to, string type) +{ + makeBeamedGroupAux(segment().findTime(from), segment().findTime(to), + type, false); +} + +void +SegmentNotationHelper::makeBeamedGroup(iterator from, iterator to, string type) +{ + makeBeamedGroupAux + ((from == end()) ? from : segment().findTime((*from)->getAbsoluteTime()), + (to == end()) ? to : segment().findTime((*to )->getAbsoluteTime()), + type, false); +} + +void +SegmentNotationHelper::makeBeamedGroupExact(iterator from, iterator to, string type) +{ + makeBeamedGroupAux(from, to, type, true); +} + +void +SegmentNotationHelper::makeBeamedGroupAux(iterator from, iterator to, + string type, bool groupGraces) +{ +// cerr << "SegmentNotationHelper::makeBeamedGroupAux: type " << type << endl; +// if (from == to) cerr << "from == to" <getType() << " at " << (*i)->getAbsoluteTime() << std::endl; + + // don't permit ourselves to change the type of an + // already-grouped event here + if ((*i)->has(BEAMED_GROUP_TYPE) && + (*i)->get(BEAMED_GROUP_TYPE) != GROUP_TYPE_BEAMED) { + continue; + } + + if (!groupGraces) { + if ((*i)->has(IS_GRACE_NOTE) && + (*i)->get(IS_GRACE_NOTE)) { + continue; + } + } + + // don't beam anything longer than a quaver unless it's + // between beamed quavers -- in which case marking it as + // beamed will ensure that it gets re-stemmed appropriately + + if ((*i)->isa(Note::EventType) && + (*i)->getNotationDuration() >= Note(Note::Crotchet).getDuration()) { +// std::cerr << "too long" <getType() == Note::EventType && + (*j)->getNotationAbsoluteTime() > (*i)->getNotationAbsoluteTime() && + (*j)->getNotationDuration() < Note(Note::Crotchet).getDuration()) { + somethingLeft = true; + break; + } + } + if (!somethingLeft) continue; + } + +// std::cerr << "beaming it" <set(BEAMED_GROUP_ID, groupId); + (*i)->set(BEAMED_GROUP_TYPE, type); + } +} + +void +SegmentNotationHelper::makeTupletGroup(timeT t, int untupled, int tupled, + timeT unit) +{ + int groupId = segment().getNextId(); + + cerr << "SegmentNotationHelper::makeTupletGroup: time " << t << ", unit "<< unit << ", params " << untupled << "/" << tupled << ", id " << groupId << endl; + + list toInsert; + list toErase; + timeT notationTime = t; + timeT fillWithRestsTo = t; + bool haveStartNotationTime = false; + + for (iterator i = segment().findTime(t); i != end(); ++i) { + + if (!haveStartNotationTime) { + notationTime = (*i)->getNotationAbsoluteTime(); + fillWithRestsTo = notationTime + (untupled * unit); + haveStartNotationTime = true; + } + + if ((*i)->getNotationAbsoluteTime() >= + notationTime + (untupled * unit)) break; + + timeT offset = (*i)->getNotationAbsoluteTime() - notationTime; + timeT duration = (*i)->getNotationDuration(); + + if ((*i)->isa(Note::EventRestType) && + ((offset + duration) > (untupled * unit))) { + fillWithRestsTo = std::max(fillWithRestsTo, + notationTime + offset + duration); + duration = (untupled * unit) - offset; + if (duration <= 0) { + toErase.push_back(i); + continue; + } + } + + Event *e = new Event(**i, + notationTime + (offset * tupled / untupled), + duration * tupled / untupled); + + cerr << "SegmentNotationHelper::makeTupletGroup: made event at time " << e->getAbsoluteTime() << ", duration " << e->getDuration() << endl; + + e->set(BEAMED_GROUP_ID, groupId); + e->set(BEAMED_GROUP_TYPE, GROUP_TYPE_TUPLED); + + e->set(BEAMED_GROUP_TUPLET_BASE, unit); + e->set(BEAMED_GROUP_TUPLED_COUNT, tupled); + e->set(BEAMED_GROUP_UNTUPLED_COUNT, untupled); + + toInsert.push_back(e); + toErase.push_back(i); + } + + for (list::iterator i = toErase.begin(); + i != toErase.end(); ++i) { + segment().erase(*i); + } + + for (list::iterator i = toInsert.begin(); + i != toInsert.end(); ++i) { + segment().insert(*i); + } + + if (haveStartNotationTime) { + segment().fillWithRests(notationTime + (tupled * unit), + fillWithRestsTo); + } +} + + + + +void +SegmentNotationHelper::unbeam(timeT from, timeT to) +{ + unbeamAux(segment().findTime(from), segment().findTime(to)); +} + +void +SegmentNotationHelper::unbeam(iterator from, iterator to) +{ + unbeamAux + ((from == end()) ? from : segment().findTime((*from)->getAbsoluteTime()), + (to == end()) ? to : segment().findTime((*to )->getAbsoluteTime())); +} + +void +SegmentNotationHelper::unbeamAux(iterator from, iterator to) +{ + for (iterator i = from; i != to; ++i) { + (*i)->unset(BEAMED_GROUP_ID); + (*i)->unset(BEAMED_GROUP_TYPE); + (*i)->clearNonPersistentProperties(); + } +} + + + +/* + + Auto-beaming code derived from Rosegarden 2.1's ItemListAutoBeam + and ItemListAutoBeamSub in editor/src/ItemList.c. + +*/ + +void +SegmentNotationHelper::autoBeam(timeT from, timeT to, string type) +{ + /* + std::cerr << "autoBeam from " << from << " to " << to << " on segment start time " << segment().getStartTime() << ", end time " << segment().getEndTime() << ", end marker " << segment().getEndMarkerTime() << std::endl; + */ + + autoBeam(segment().findTime(from), segment().findTime(to), type); +} + +void +SegmentNotationHelper::autoBeam(iterator from, iterator to, string type) +{ + // This can only manage whole bars at a time, and it will split + // the from-to range out to encompass the whole bars in which they + // each occur + + if (!segment().getComposition()) { + cerr << "WARNING: SegmentNotationHelper::autoBeam requires Segment be in a Composition" << endl; + return; + } + + if (!segment().isBeforeEndMarker(from)) return; + + Composition *comp = segment().getComposition(); + + int fromBar = comp->getBarNumber((*from)->getAbsoluteTime()); + int toBar = comp->getBarNumber(segment().isBeforeEndMarker(to) ? + (*to)->getAbsoluteTime() : + segment().getEndMarkerTime()); + + for (int barNo = fromBar; barNo <= toBar; ++barNo) { + + std::pair barRange = comp->getBarRange(barNo); + iterator barStart = segment().findTime(barRange.first); + iterator barEnd = segment().findTime(barRange.second); + + // Make sure we're examining the notes defined to be within + // the bar in notation terms rather than raw terms + + while (barStart != segment().end() && + (*barStart)->getNotationAbsoluteTime() < barRange.first) ++barStart; + + iterator scooter = barStart; + if (barStart != segment().end()) { + while (scooter != segment().begin()) { + --scooter; + if ((*scooter)->getNotationAbsoluteTime() < barRange.first) break; + barStart = scooter; + } + } + + while (barEnd != segment().end() && + (*barEnd)->getNotationAbsoluteTime() < barRange.second) ++barEnd; + + scooter = barEnd; + if (barEnd != segment().end()) { + while (scooter != segment().begin()) { + --scooter; + if ((*scooter)->getNotationAbsoluteTime() < barRange.second) break; + barEnd = scooter; + } + } + + TimeSignature timeSig = + segment().getComposition()->getTimeSignatureAt(barRange.first); + + autoBeamBar(barStart, barEnd, timeSig, type); + } +} + + +/* + + Derived from (and no less mystifying than) Rosegarden 2.1's + ItemListAutoBeamSub in editor/src/ItemList.c. + + "Today I want to celebrate "Montreal" by Autechre, because of + its sleep-disturbing aura, because it sounds like the sort of music + which would be going around in the gunman's head as he trains a laser + sight into your bedroom through the narrow gap in your curtains and + dances the little red dot around nervously on your wall." + +*/ + +void +SegmentNotationHelper::autoBeamBar(iterator from, iterator to, + TimeSignature tsig, string type) +{ + int num = tsig.getNumerator(); + int denom = tsig.getDenominator(); + + timeT average; + timeT minimum = 0; + + // If the denominator is 2 or 4, beam in twos (3/4, 6/2 etc). + + if (denom == 2 || denom == 4) { + + if (num % 3) { + average = Note(Note::Quaver).getDuration(); + } else { + average = Note(Note::Semiquaver).getDuration(); + minimum = average; + } + + } else { + + if (num == 6 && denom == 8) { // special hack for 6/8 + average = 3 * Note(Note::Quaver).getDuration(); + + } else { + // find a divisor (at least 2) for the numerator + int n = 2; + while (num >= n && num % n != 0) ++n; + average = n * Note(Note::Semiquaver).getDuration(); + } + } + + if (minimum == 0) minimum = average / 2; + if (denom > 4) average /= 2; + + autoBeamBar(from, to, average, minimum, average * 4, type); +} + + +void +SegmentNotationHelper::autoBeamBar(iterator from, iterator to, + timeT average, timeT minimum, + timeT maximum, string type) +{ + timeT accumulator = 0; + timeT crotchet = Note(Note::Crotchet).getDuration(); + timeT semiquaver = Note(Note::Semiquaver).getDuration(); + + iterator e = end(); + + for (iterator i = from; i != to && i != e; ++i) { + + // only look at one note in each chord, and at rests + if (!hasEffectiveDuration(i)) continue; + timeT idur = (*i)->getNotationDuration(); + + if (accumulator % average == 0 && // "beamable duration" threshold + idur < crotchet) { + + // This could be the start of a beamed group. We maintain + // two sorts of state as we scan along here: data about + // the best group we've found so far (beamDuration, + // prospective, k etc), and data about the items we're + // looking at (count, beamable, longerThanDemi etc) just + // in case we find a better candidate group before the + // eight-line conditional further down makes us give up + // the search, beam our best shot, and start again. + + // I hope this is clear. + + iterator k = end(); // best-so-far last item in group; + // end() indicates that we've found nothing + + timeT tmin = minimum; + timeT count = 0; + timeT prospective = 0; + timeT beamDuration = 0; + + int beamable = 0; + int longerThanDemi = 0; + + for (iterator j = i; j != to; ++j) { + + if (!hasEffectiveDuration(j)) continue; + timeT jdur = (*j)->getNotationDuration(); + + if ((*j)->isa(Note::EventType)) { + if (jdur < crotchet) ++beamable; + if (jdur >= semiquaver) ++longerThanDemi; + } + + count += jdur; + + if (count % tmin == 0) { + + k = j; + beamDuration = count; + prospective = accumulator + count; + + // found a group; now accept only double this + // group's length for a better one + tmin *= 2; + } + + // Stop scanning and make the group if our scan has + // reached the maximum length of beamed group, we have + // more than 4 semis or quavers, we're at the end of + // our run, the next chord is longer than the current + // one, or there's a rest ahead. (We used to check + // that the rest had non-zero duration, but the new + // quantization regime should ensure that this doesn't + // happen unless we really are displaying completely + // unquantized data in which case anything goes.) + + iterator jnext(j); + + if ((count > maximum) + || (longerThanDemi > 4) + || (++jnext == to) + || ((*j )->isa(Note::EventType) && + (*jnext)->isa(Note::EventType) && + (*jnext)->getNotationDuration() > jdur) + || ((*jnext)->isa(Note::EventRestType))) { + + if (k != end() && beamable >= 2) { + + iterator knext(k); + ++knext; + + makeBeamedGroup(i, knext, type); + } + + // If this group is at least as long as the check + // threshold ("average"), its length must be a + // multiple of the threshold and hence we can + // continue scanning from the end of the group + // without losing the modulo properties of the + // accumulator. + + if (k != end() && beamDuration >= average) { + + i = k; + accumulator = prospective; + + } else { + + // Otherwise, we continue from where we were. + // (This must be safe because we can't get + // another group starting half-way through, as + // we know the last group is shorter than the + // check threshold.) + + accumulator += idur; + } + + break; + } + } + } else { + + accumulator += idur; + } + } +} + + +// based on Rosegarden 2.1's GuessItemListClef in editor/src/MidiIn.c + +Clef +SegmentNotationHelper::guessClef(iterator from, iterator to) +{ + long totalHeight = 0; + int noteCount = 0; + + // just the defaults: + Clef clef; + Key key; + + for (iterator i = from; i != to; ++i) { + if ((*i)->isa(Note::EventType)) { +//!!! NotationDisplayPitch p((*i)->get(PITCH), clef, key); + try { + Pitch p(**i); + totalHeight += p.getHeightOnStaff(clef, key); + ++noteCount; + } catch (Exception e) { + // no pitch in note + } + } + } + + if (noteCount == 0) return Clef(Clef::Treble); + + int average = totalHeight / noteCount; + + if (average < -6) return Clef(Clef::Bass); + else if (average < -3) return Clef(Clef::Tenor); + else if (average < 1) return Clef(Clef::Alto); + else return Clef(Clef::Treble); +} + + +bool +SegmentNotationHelper::removeRests(timeT time, timeT &duration, bool testOnly) +{ + Event dummy("dummy", time, 0, MIN_SUBORDERING); + + std::cerr << "SegmentNotationHelper::removeRests(" << time + << ", " << duration << ")" << std::endl; + + iterator from = segment().lower_bound(&dummy); + + // ignore any number of zero-duration events at the start + while (from != segment().end() && + (*from)->getAbsoluteTime() == time && + (*from)->getDuration() == 0) ++from; + if (from == segment().end()) return false; + + iterator to = from; + + timeT eventTime = time; + timeT finalTime = time + duration; + + //!!! We should probably not use an accumulator, but instead + // calculate based on each event's absolute time + duration -- + // in case we've somehow ended up with overlapping rests + + // Iterate on events, checking if all are rests + // + while ((eventTime < finalTime) && (to != end())) { + + if (!(*to)->isa(Note::EventRestType)) { + // a non-rest was found + duration = (*to)->getAbsoluteTime() - time; + return false; + } + + timeT nextEventDuration = (*to)->getDuration(); + + if ((eventTime + nextEventDuration) <= finalTime) { + eventTime += nextEventDuration; + duration = eventTime - time; + } else break; + + ++to; + } + + bool checkLastRest = false; + iterator lastEvent = to; + + if (eventTime < finalTime) { + // shorten last event's duration, if possible + + + if (lastEvent == end()) { + duration = segment().getEndTime() - time; + return false; + } + + if (!testOnly) { + // can't safely change the absolute time of an event in a segment + Event *newEvent = new Event(**lastEvent, finalTime, + (*lastEvent)->getDuration() - + (finalTime - eventTime)); + duration = finalTime + (*lastEvent)->getDuration() - time; + bool same = (from == to); + segment().erase(lastEvent); + to = lastEvent = segment().insert(newEvent); + if (same) from = to; + checkLastRest = true; + } + } + + if (testOnly) return true; + + segment().erase(from, to); + + // we must defer calling makeRestViable() until after erase, + // because it will invalidate 'to' + // + if (checkLastRest) makeRestViable(lastEvent); + + return true; +} + + +void +SegmentNotationHelper::collapseRestsAggressively(timeT startTime, + timeT endTime) +{ + reorganizeRests(startTime, endTime, + &SegmentNotationHelper::mergeContiguousRests); +} + + +void +SegmentNotationHelper::reorganizeRests(timeT startTime, timeT endTime, + Reorganizer reorganizer) +{ + iterator ia = segment().findTime(startTime); + iterator ib = segment().findTime(endTime); + + if (ia == end()) return; + + std::vector erasable; + std::vector insertable; + +// cerr << "SegmentNotationHelper::reorganizeRests (" << startTime << "," +// << endTime << ")" << endl; + + for (iterator i = ia; i != ib; ++i) { + + if ((*i)->isa(Note::EventRestType)) { + + timeT startTime = (*i)->getAbsoluteTime(); + timeT duration = 0; + iterator j = i; + + for ( ; j != ib; ++j) { + + if ((*j)->isa(Note::EventRestType)) { + duration += (*j)->getDuration(); + erasable.push_back(j); + } else break; + } + + (this->*reorganizer)(startTime, duration, insertable); + if (j == ib) break; + i = j; + } + } + + for (unsigned int ei = 0; ei < erasable.size(); ++ei) + segment().erase(erasable[ei]); + + for (unsigned int ii = 0; ii < insertable.size(); ++ii) + segment().insert(insertable[ii]); +} + + +void +SegmentNotationHelper::normalizeContiguousRests(timeT startTime, + timeT duration, + std::vector &toInsert) +{ + TimeSignature ts; + timeT sigTime = + segment().getComposition()->getTimeSignatureAt(startTime, ts); + +// cerr << "SegmentNotationHelper::normalizeContiguousRests:" +// << " startTime = " << startTime << ", duration = " +// << duration << endl; + + DurationList dl; + ts.getDurationListForInterval(dl, duration, startTime - sigTime); + + timeT acc = startTime; + + for (DurationList::iterator i = dl.begin(); i != dl.end(); ++i) { + Event *e = new Event(Note::EventRestType, acc, *i, + Note::EventRestSubOrdering); + toInsert.push_back(e); + acc += *i; + } +} + + +void +SegmentNotationHelper::mergeContiguousRests(timeT startTime, + timeT duration, + std::vector &toInsert) +{ + while (duration > 0) { + + timeT d = Note::getNearestNote(duration).getDuration(); + + Event *e = new Event(Note::EventRestType, startTime, d, + Note::EventRestSubOrdering); + toInsert.push_back(e); + + startTime += d; + duration -= d; + } +} + + +Segment::iterator +SegmentNotationHelper::collapseNoteAggressively(Event *note, + timeT rangeEnd) +{ + iterator i = segment().findSingle(note); + if (i == end()) return end(); + + iterator j = getNextAdjacentNote(i, true, true); + if (j == end() || (*j)->getAbsoluteTime() >= rangeEnd) return end(); + + timeT iEnd = (*i)->getAbsoluteTime() + (*i)->getDuration(); + timeT jEnd = (*j)->getAbsoluteTime() + (*j)->getDuration(); + + Event *newEvent = new Event + (**i, (*i)->getAbsoluteTime(), + (std::max(iEnd, jEnd) - (*i)->getAbsoluteTime())); + + newEvent->unset(TIED_BACKWARD); + newEvent->unset(TIED_FORWARD); + + segment().erase(i); + segment().erase(j); + return segment().insert(newEvent); +} + +std::pair +SegmentNotationHelper::splitPreservingPerformanceTimes(Event *e, timeT q1) +{ + timeT ut = e->getAbsoluteTime(); + timeT ud = e->getDuration(); + timeT qt = e->getNotationAbsoluteTime(); + timeT qd = e->getNotationDuration(); + + timeT u1 = (qt + q1) - ut; + timeT u2 = (ut + ud) - (qt + q1); + +// std::cerr << "splitPreservingPerformanceTimes: (ut,ud) (" << ut << "," << ud << "), (qt,qd) (" << qt << "," << qd << ") q1 " << q1 << ", u1 " << u1 << ", u2 " << u2 << std::endl; + + if (u1 <= 0 || u2 <= 0) { // can't do a meaningful split + return std::pair(0, 0); + } + + Event *e1 = new Event(*e, ut, u1, e->getSubOrdering(), qt, q1); + Event *e2 = new Event(*e, ut + u1, u2, e->getSubOrdering(), qt + q1, qd - q1); + + e1->set(TIED_FORWARD, true); + e2->set(TIED_BACKWARD, true); + + return std::pair(e1, e2); +} + +void +SegmentNotationHelper::deCounterpoint(timeT startTime, timeT endTime) +{ + // How this should work: scan through the range and, for each + // note "n" found, if the next following note "m" not at the same + // absolute time as n starts before n ends, then split n at m-n. + + // also, if m starts at the same time as n but has a different + // duration, we should split the longer of n and m at the shorter + // one's duration. + + for (Segment::iterator i = segment().findTime(startTime); + segment().isBeforeEndMarker(i); ) { + + timeT t = (*i)->getAbsoluteTime(); + if (t >= endTime) break; + +#ifdef DEBUG_DECOUNTERPOINT + std::cerr << "SegmentNotationHelper::deCounterpoint: event at " << (*i)->getAbsoluteTime() << " notation " << (*i)->getNotationAbsoluteTime() << ", duration " << (*i)->getNotationDuration() << ", type " << (*i)->getType() << std::endl; +#endif + + if (!(*i)->isa(Note::EventType)) { ++i; continue; } + + timeT ti = (*i)->getNotationAbsoluteTime(); + timeT di = (*i)->getNotationDuration(); + +#ifdef DEBUG_DECOUNTERPOINT + std::cerr<<"looking for k"<isa(Note::EventType)) { +#ifdef DEBUG_DECOUNTERPOINT + std::cerr<<"abstime "<<(*k)->getAbsoluteTime()<< std::endl; +#endif + if ((*k)->getNotationAbsoluteTime() > ti || + (*k)->getNotationDuration() != di) break; + } + ++k; + } + + if (!segment().isBeforeEndMarker(k)) break; // no split, no more notes + +#ifdef DEBUG_DECOUNTERPOINT + std::cerr << "k is at " << (k == segment().end() ? -1 : (*k)->getAbsoluteTime()) << ", notation " << (*k)->getNotationAbsoluteTime() << ", duration " << (*k)->getNotationDuration() << std::endl; +#endif + + timeT tk = (*k)->getNotationAbsoluteTime(); + timeT dk = (*k)->getNotationDuration(); + + Event *e1 = 0, *e2 = 0; + std::pair splits; + Segment::iterator toGo = segment().end(); + + if (tk == ti && dk != di) { + // do the same-time-different-durations case + if (di > dk) { // split *i +#ifdef DEBUG_DECOUNTERPOINT + std::cerr << "splitting i into " << dk << " and "<< (di-dk) << std::endl; +#endif + splits = splitPreservingPerformanceTimes(*i, dk); + + toGo = i; + } else { // split *k +#ifdef DEBUG_DECOUNTERPOINT + std::cerr << "splitting k into " << di << " and "<< (dk-di) << std::endl; +#endif + splits = splitPreservingPerformanceTimes(*k, di); + + toGo = k; + } + } else if (tk - ti > 0 && tk - ti < di) { // split *i +#ifdef DEBUG_DECOUNTERPOINT + std::cerr << "splitting i[*] into " << (tk-ti) << " and "<< (di-(tk-ti)) << std::endl; +#endif + splits = splitPreservingPerformanceTimes(*i, tk - ti); + + toGo = i; + } + + e1 = splits.first; + e2 = splits.second; + + if (e1 && e2) { // e2 is the new note + + e1->set(TIED_FORWARD, true); + e2->set(TIED_BACKWARD, true); + +#ifdef DEBUG_DECOUNTERPOINT + std::cerr<<"Erasing:"<dump(std::cerr); +#endif + + segment().erase(toGo); + +#ifdef DEBUG_DECOUNTERPOINT + std::cerr<<"Inserting:"<dump(std::cerr); +#endif + + segment().insert(e1); + +#ifdef DEBUG_DECOUNTERPOINT + std::cerr<<"Inserting:"<dump(std::cerr); +#endif + + segment().insert(e2); + + i = segment().findTime(t); + +#ifdef DEBUG_DECOUNTERPOINT + std::cerr<<"resync at " << t << ":" << std::endl; + if (i != segment().end()) (*i)->dump(std::cerr); + else std::cerr << "(end)" << std::endl; +#endif + + } else { + + // no split here + +#ifdef DEBUG_DECOUNTERPOINT + std::cerr<<"no split"<getNotationAbsoluteTime(); + + long newGroupId = -1; + if ((*i)->get(BEAMED_GROUP_ID, newGroupId)) { + if (groupId == newGroupId) { // group continuing + if (t > prevTime) { + ++count; + prevLegato = thisLegato; + thisLegato = Marks::hasMark(**i, Marks::Tenuto); + } + prevTime = t; + continue; + } + } else { + if (groupId == -1) continue; // no group + } + + // a group has ended (and a new one might have begun) + + if (groupId >= 0 && count > 1 && (!legatoOnly || prevLegato)) { + Indication ind(Indication::Slur, t - potentialStart); + segment().insert(ind.getAsEvent(potentialStart)); + if (legatoOnly) { + for (iterator j = segment().findTime(potentialStart); j != i; ++j) { + Marks::removeMark(**j, Marks::Tenuto); + } + } + } + + potentialStart = t; + groupId = newGroupId; + prevTime = t; + count = 0; + thisLegato = false; + prevLegato = false; + } + + if (groupId >= 0 && count > 1 && (!legatoOnly || prevLegato)) { + Indication ind(Indication::Slur, endTime - potentialStart); + segment().insert(ind.getAsEvent(potentialStart)); + if (legatoOnly) { + for (iterator j = segment().findTime(potentialStart); + segment().isBeforeEndMarker(j) && j != to; ++j) { + Marks::removeMark(**j, Marks::Tenuto); + } + } + } +} + + +} // end of namespace + diff --git a/src/base/SegmentNotationHelper.h b/src/base/SegmentNotationHelper.h new file mode 100644 index 0000000..5094929 --- /dev/null +++ b/src/base/SegmentNotationHelper.h @@ -0,0 +1,591 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _SEGMENT_NOTATION_HELPER_H_ +#define _SEGMENT_NOTATION_HELPER_H_ + +#include "Segment.h" + +namespace Rosegarden +{ + +class SegmentNotationHelper : protected SegmentHelper +{ +public: + SegmentNotationHelper(Segment &t) : SegmentHelper(t) { } + virtual ~SegmentNotationHelper(); + + SegmentHelper::segment; + + /** + * Set the NOTE_TYPE and NOTE_DOTS properties on the events + * in the segment. If startTime and endTime are equal, operates + * on the whole segment. + */ + void setNotationProperties(timeT startTime = 0, timeT endTime = 0); + + /** + * Return the notation absolute time plus the notation duration. + */ + timeT getNotationEndTime(Event *e); + + /** + * Return an iterator pointing at the first event in the segment + * to have an absolute time of t or later. (Most of the time, the + * non-notation absolute times should be used as reference + * timings; this and the next function are provided for + * completeness, but in most cases if you're about to call them + * you should ask yourself why.) + */ + iterator findNotationAbsoluteTime(timeT t); + + /** + * Return an iterator pointing at the last event in the segment + * to have an absolute time of t or earlier. (Most of the time, + * the non-notation absolute times should be used as reference + * timings; this and the previous function are provided for + * completeness, but in most cases if you're about to call them + * you should ask yourself why.) + */ + iterator findNearestNotationAbsoluteTime(timeT t); + + + /** + * Looks for another note immediately following the one pointed to + * by the given iterator, and (if matchPitch is true) of the same + * pitch, and returns an iterator pointing to that note. Returns + * end() if there is no such note. + * + * The notes are considered "adjacent" if the quantized start + * time of one matches the quantized end time of the other, unless + * allowOverlap is true in which case overlapping notes are also + * considered adjacent so long as one does not completely enclose + * the other. + */ + iterator getNextAdjacentNote(iterator i, + bool matchPitch = true, + bool allowOverlap = true); + + + /** + * Looks for another note immediately preceding the one pointed to + * by the given iterator, and (if matchPitch is true) of the same + * pitch, and returns an iterator pointing to that note. Returns + * end() if there is no such note. + * + * rangeStart gives a bound to the distance that will be scanned + * to find events -- no event with starting time earlier than that + * will be considered. (This method has no other way to know when + * to stop scanning; potentially the very first note in the segment + * could turn out to be adjacent to the very last one.) + * + * The notes are considered "adjacent" if the quantized start + * time of one matches the quantized end time of the other, unless + * allowOverlap is true in which case overlapping notes are also + * considered adjacent so long as one does not completely enclose + * the other. + */ + iterator getPreviousAdjacentNote(iterator i, + timeT rangeStart = 0, + bool matchPitch = true, + bool allowOverlap = true); + + + /** + * Returns an iterator pointing to the next contiguous element of + * the same type (note or rest) as the one passed as argument, if + * any. Returns end() otherwise. + * + * (for instance if the argument points to a note and the next + * element is a rest, end() will be returned) + * + * Note that if the iterator points to a note, the "contiguous" + * iterator returned may point to a note that follows the first + * one, overlaps with it, shares a starting time (i.e. they're + * both in the same chord) or anything else. "Contiguous" refers + * only to their locations in the segment's event container, + * which normally means what you expect for rests but not notes. + * + * See also SegmentNotationHelper::getNextAdjacentNote. + */ + iterator findContiguousNext(iterator); + + /** + * Returns an iterator pointing to the previous contiguous element + * of the same type (note or rest) as the one passed as argument, + * if any. Returns end() otherwise. + * + * (for instance if the argument points to a note and the previous + * element is a rest, end() will be returned) + * + * Note that if the iterator points to a note, the "contiguous" + * iterator returned may point to a note that precedes the first + * one, overlaps with it, shares a starting time (i.e. they're + * both in the same chord) or anything else. "Contiguous" refers + * only to their locations in the segment's event container, + * which normally means what you expect for rests but not notes. + * + * See also SegmentNotationHelper::getPreviousAdjacentNote. + */ + iterator findContiguousPrevious(iterator); + + /** + * Returns true if the iterator points at a note in a chord + * e.g. if there are more notes at the same absolute time + */ + bool noteIsInChord(Event *note); + + /** + * Returns an iterator pointing to the note that this one is tied + * with, in the forward direction if goForwards or back otherwise. + * Returns end() if none. + * + * Untested and probably marked-for-expiry -- prefer + * SegmentPerformanceHelper::getTiedNotes + */ + iterator getNoteTiedWith(Event *note, bool goForwards); + + + /** + * Checks whether it's reasonable to split a single event + * of duration a+b into two events of durations a and b, for some + * working definition of "reasonable". + * + * You should pass note-quantized durations into this method + */ + bool isSplitValid(timeT a, timeT b); + + + /** + * Splits events in the [from, to[ interval into + * tied events of duration baseDuration + events of duration R, + * with R being equal to the events' initial duration minus baseDuration + * + * The events in [from, to[ must all be at the same absolute time + * + * Does not check "reasonableness" of expansion first + * + * Events may be notes or rests (rests will obviously not be tied) + * + * @return iterator pointing at the last inserted event. Also + * modifies from to point at the first split event (the original + * iterator would have been invalidated). + */ + iterator splitIntoTie(iterator &from, iterator to, timeT baseDuration); + + + /** + * Splits (splits) events in the same timeslice as that pointed + * to by i into tied events of duration baseDuration + events of + * duration R, with R being equal to the events' initial duration + * minus baseDuration + * + * Does not check "reasonableness" of expansion first + * + * Events may be notes or rests (rests will obviously not be tied) + * + * @return iterator pointing at the last inserted event. Also + * modifies i to point at the first split event (the original + * iterator would have been invalidated). + */ + iterator splitIntoTie(iterator &i, timeT baseDuration); + + + /** + * Returns true if Events of durations a and b can reasonably be + * collapsed into a single one of duration a+b, for some + * definition of "reasonably". For use by collapseRestsIfValid + * + * You should pass note-quantized durations into this method + */ + bool isCollapseValid(timeT a, timeT b); + + /** + * If possible, collapses the rest event with the following or + * previous one. + * + * @return true if collapse was done, false if it wasn't reasonable + * + * collapseForward is set to true if the collapse was with the + * following element, false if it was with the previous one + */ + bool collapseRestsIfValid(Event*, bool& collapseForward); + + /** + * Inserts a note, doing all the clever split/merge stuff as + * appropriate. Requires segment to be in a composition. Returns + * iterator pointing to last event inserted (there may be more + * than one, as note may have had to be split) + * + * This method will only work correctly if there is a note or + * rest event already starting at absoluteTime. + */ + iterator insertNote(timeT absoluteTime, Note note, int pitch, + Accidental explicitAccidental); + + /** + * Inserts a note, doing all the clever split/merge stuff as + * appropriate. Requires segment to be in a composition. Returns + * iterator pointing to last event inserted (there may be more + * than one, as note may have had to be split) + * + * This method will only work correctly if there is a note or + * rest event already starting at the model event's absoluteTime. + * + * Passing a model event has the advantage over the previous + * method of allowing additional properties to be supplied. The + * model event will be copied but not itself used; the caller + * continues to own it and should release it after return. + */ + iterator insertNote(Event *modelEvent); + + /** + * Inserts a rest, doing all the clever split/merge stuff as + * appropriate. Requires segment to be in a composition. + * Returns iterator pointing to last event inserted (there + * may be more than one, as rest may have had to be split) + * + * This method will only work correctly if there is a note or + * rest event already starting at absoluteTime. + */ + iterator insertRest(timeT absoluteTime, Note note); + + /** + * Insert a clef. + * Returns iterator pointing to clef. + */ + iterator insertClef(timeT absoluteTime, Clef clef); + + /** + * Insert a key. + * Returns iterator pointing to key. + */ + iterator insertKey(timeT absoluteTime, Key key); + + /** + * Insert a text event. + * Returns iterator pointing to text event. + */ + iterator insertText(timeT absoluteTime, Text text); + + /** + * Deletes a note, doing all the clever split/merge stuff as + * appropriate. Requires segment to be in a composition. + */ + void deleteNote(Event *e, bool collapseRest = false); + + /** + * Deletes a rest, doing all the clever split/merge stuff as + * appropriate. Requires segment to be in a composition. + * + * @return whether the rest could be deleted -- a rest can only + * be deleted if there's a suitable rest next to it to merge it + * with. + */ + bool deleteRest(Event *e); + + /** + * Deletes an event. If the event is a note or a rest, calls + * deleteNote or deleteRest. + * + * @return whether the event was deleted (always true, unless the + * event is a rest). + * + * @see deleteRest, deleteNote + */ + bool deleteEvent(Event *e, bool collapseRest = false); + + /** + * Check whether a note or rest event has a duration that can be + * represented by a single note-type. (If not, the code that's + * doing the check might wish to split the event.) + * + * If dots is specified, a true value will only be returned if the + * best-fit note has no more than that number of dots. e.g. if + * dots = 0, only notes that are viable without the use of dots + * will be acceptable. The default is whatever the segment's + * quantizer considers acceptable (probably either 1 or 2 dots). + */ + bool isViable(Event *e, int dots = -1) { + return isViable(e->getDuration(), dots); + } + + /** + * Check whether a duration can be represented by a single + * note-type. (If not, the code that's doing the check might wish + * to split the duration.) + * + * If dots is specified, a true value will only be returned if the + * best-fit note has no more than that number of dots. e.g. if + * dots = 0, only notes that are viable without the use of dots + * will be acceptable. The default is whatever the segment's + * quantizer considers acceptable (probably either 1 or 2 dots). + */ + bool isViable(timeT duration, int dots = -1); + + + /** + * Given an iterator pointing to a rest, split that rest up + * according to the durations returned by TimeSignature's + * getDurationListForInterval + */ + void makeRestViable(iterator i); + + + /** + * Split notes and rests up into tied notes or shorter rests of + * viable lengths (longest possible viable duration first, then + * longest possible viable component of remainder &c). Also + * optionally splits notes and rests at barlines -- this is + * actually the most common user-visible use of this function. + */ + void makeNotesViable(iterator i, iterator j, bool splitAtBars = true); + + + /** + * As above but given a range in time rather than iterators. + */ + void makeNotesViable(timeT startTime, timeT endTime, + bool splitAtBars = true); + + + /** + * Give all events between the start of the timeslice containing + * from and the start of the timeslice containing to the same new + * group id and the given type. + * + * Do not use this for making tuplet groups, unless the events + * in the group already have the other tuplet properties or you + * intend to add those yourself. Use makeTupletGroup instead. + */ + void makeBeamedGroup(timeT from, timeT to, std::string type); + + /** + * Give all events between the start of the timeslice containing + * from and the start of the timeslice containing to the same new + * group id and the given type. + * + * Do not use this for making tuplet groups, unless the events + * in the group already have the other tuplet properties or you + * intend to add those yourself. Use makeTupletGroup instead. + */ + void makeBeamedGroup(iterator from, iterator to, std::string type); + + /** + * Give all events between from and to the same new group id and + * the given type. + * + * Use makeBeamedGroup for normal notes. This function is usually + * used for groups of grace notes, which are equal in time and + * distinguished by subordering. + * + * Do not use this for making tuplet groups, unless the events + * in the group already have the other tuplet properties or you + * intend to add those yourself. + */ + void makeBeamedGroupExact(iterator from, iterator to, std::string type); + + + /** + * Make a beamed group of tuplet type, whose tuplet properties are + * specified as "(untupled-count) notes of duration (unit) played + * in the time of (tupled-count)". For example, a quaver triplet + * group could be specified with untupled = 3, tupled = 2, unit = + * (the duration of a quaver). + * + * The group will start at the beginning of the timeslice containing + * the time t, and will be constructed by compressing the appropriate + * number of following notes into the tuplet time, and filling the + * space that this compression left behind (after the group) with + * rests. The results may be unexpected if overlapping events are + * present. + */ + void makeTupletGroup(timeT t, int untupled, int tupled, timeT unit); + + + /** + * Divide the notes between the start of the bar containing + * from and the end of the bar containing to up into sensible + * beamed groups and give each group the right group properties + * using makeBeamedGroup. Requires segment to be in a composition. + */ + void autoBeam(timeT from, timeT to, std::string type); + + /** + * Divide the notes between the start of the bar containing + * from and the end of the bar containing to up into sensible + * beamed groups and give each group the right group properties + * using makeBeamedGroup. Requires segment to be in a composition. + */ + void autoBeam(iterator from, iterator to, std::string type); + + + /** + * Clear the group id and group type from all events between the + * start of the timeslice containing from and the start of the + * timeslice containing to + */ + void unbeam(timeT from, timeT to); + + /** + * Clear the group id and group type from all events between the + * start of the timeslice containing from and the start of the + * timeslice containing to + */ + void unbeam(iterator from, iterator to); + + /** + * Guess which clef a section of music is supposed to be in, + * ignoring any clef events actually found in the section. + */ + Clef guessClef(iterator from, iterator to); + + + /** + * Removes all rests starting at \a time for \a duration, + * splitting the last rest if needed. + * + * Modifies duration to the actual duration of the series + * of rests that has been changed by this action (i.e. if + * the last rest was split, duration will be extended to + * include the second half of this rest). This is intended + * to be of use when calculating the extents of a command + * for undo/refresh purposes. + * + * If there's an event which is not a rest in this interval, + * returns false and sets duration to the maximum duration + * that would have succeeded. + * + * If testOnly is true, does not actually remove any rests; + * just checks whether the rests can be removed and sets + * duration and the return value appropriately. + * + * (Used for Event pasting.) + */ + bool removeRests(timeT time, timeT &duration, bool testOnly = false); + + + /** + * For each series of contiguous rests found between the start and + * end time, replace the series of rests with another series of + * the same duration but composed of the longest possible valid + * rest plus the remainder + */ + void collapseRestsAggressively(timeT startTime, timeT endTime); + + + /** + * Locate the given event and, if it's a note, collapse it with + * any following adjacent note of the same pitch, so long as its + * start time is before the the given limit. Does not care + * whether the resulting note is viable. + * + * Returns an iterator pointing to the event that replaced the + * original one if a collapse happened, segment.end() if no + * collapse or event not found + */ + iterator collapseNoteAggressively(Event *, timeT rangeEnd); + + + + std::pair splitPreservingPerformanceTimes(Event *e, + timeT q1); + + /** + * Look for examples of overlapping notes within the given range, + * and split each into chords with some tied notes. + */ + void deCounterpoint(timeT startTime, timeT endTime); + + /** + * A rather specialised function: Add a slur to every beamed group. + * If legatoOnly is true, add a slur only to those beamed groups + * in which every note except the last has a tenuto mark already + * (and remove that mark). + * This is basically intended as a post-notation-quantization-auto- + * beam step. + */ + void autoSlur(timeT startTime, timeT endTime, bool legatoOnly); + + +protected: + const Quantizer &basicQuantizer(); + const Quantizer ¬ationQuantizer(); + + /** + * Collapse multiple consecutive rests into one, in preparation + * for insertion of a note (whose duration may exceed that of the + * first rest) at the given position. The resulting rest event + * may have a duration that is not expressible as a single note + * type, and may therefore require splitting again after the + * insertion. + * + * Returns position at which the collapse ended (i.e. the first + * uncollapsed event) + */ + iterator collapseRestsForInsert(iterator firstRest, timeT desiredDuration); + + + /// for use by insertNote and insertRest + iterator insertSomething(iterator position, int duration, + Event *modelEvent, bool tiedBack); + + /// for use by insertSomething + iterator insertSingleSomething(iterator position, int duration, + Event *modelEvent, bool tiedBack); + + /// for use by insertSingleSomething + void setInsertedNoteGroup(Event *e, iterator i); + + /// for use by makeBeamedGroup + void makeBeamedGroupAux(iterator from, iterator to, std::string type, + bool groupGraces); + + /// for use by unbeam + void unbeamAux(iterator from, iterator to); + + /// for use by autoBeam + + void autoBeamBar(iterator from, iterator to, TimeSignature timesig, + std::string type); + + void autoBeamBar(iterator from, iterator to, timeT average, + timeT minimum, timeT maximum, std::string type); + + /// used by autoBeamAux (duplicate of private method in Segment) + bool hasEffectiveDuration(iterator i); + + typedef void (SegmentNotationHelper::*Reorganizer)(timeT, timeT, + std::vector&); + + void reorganizeRests(timeT, timeT, Reorganizer); + + /// for use by normalizeRests + void normalizeContiguousRests(timeT, timeT, std::vector&); + + /// for use by collapseRestsAggressively + void mergeContiguousRests(timeT, timeT, std::vector&); +}; + +} + +#endif diff --git a/src/base/SegmentPerformanceHelper.cpp b/src/base/SegmentPerformanceHelper.cpp new file mode 100644 index 0000000..930a794 --- /dev/null +++ b/src/base/SegmentPerformanceHelper.cpp @@ -0,0 +1,472 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "SegmentPerformanceHelper.h" +#include "BaseProperties.h" +#include + +namespace Rosegarden +{ +using std::endl; +using std::string; + +using namespace BaseProperties; + +SegmentPerformanceHelper::~SegmentPerformanceHelper() { } + + +SegmentPerformanceHelper::iteratorcontainer +SegmentPerformanceHelper::getTiedNotes(iterator i) +{ + iteratorcontainer c; + c.push_back(i); + + Event *e = *i; + if (!e->isa(Note::EventType)) return c; + Segment::iterator j(i); + + bool tiedBack = false, tiedForward = false; + e->get(TIED_BACKWARD, tiedBack); + e->get(TIED_FORWARD, tiedForward); + + timeT d = e->getNotationDuration(); + timeT t = e->getNotationAbsoluteTime(); + + if (!e->has(PITCH)) return c; + int pitch = e->get(PITCH); + + bool valid = false; + + if (tiedBack) { + // #1171463: If we can find no preceding TIED_FORWARD event, + // then we remove this property + + while (j != begin()) { + + --j; + if (!(*j)->isa(Note::EventType)) continue; + e = *j; // can reuse e because this branch always returns + + timeT t2 = e->getNotationAbsoluteTime() + e->getNotationDuration(); + if (t2 < t) break; + + if (t2 > t || !e->has(PITCH) || + e->get(PITCH) != pitch) continue; + + bool prevTiedForward = false; + if (!e->get(TIED_FORWARD, prevTiedForward) || + !prevTiedForward) break; + + valid = true; + break; + } + + if (valid) { + return iteratorcontainer(); + } else { + (*i)->unset(TIED_BACKWARD); + return c; + } + } + else if (!tiedForward) return c; + + for (;;) { + while (++j != end() && !(*j)->isa(Note::EventType)); + if (j == end()) return c; + + e = *j; + + timeT t2 = e->getNotationAbsoluteTime(); + + if (t2 > t + d) break; + else if (t2 < t + d || !e->has(PITCH) || + e->get(PITCH) != pitch) continue; + + if (!e->get(TIED_BACKWARD, tiedBack) || + !tiedBack) break; + + d += e->getNotationDuration(); + c.push_back(j); + valid = true; + + if (!e->get(TIED_FORWARD, tiedForward) || + !tiedForward) return c; + } + + if (!valid) { + // Related to #1171463: If we can find no following + // TIED_BACKWARD event, then we remove this property + (*i)->unset(TIED_FORWARD); + } + + return c; +} + + +bool +SegmentPerformanceHelper::getGraceAndHostNotes(iterator i, + iteratorcontainer &graceNotes, + iteratorcontainer &hostNotes, + bool &isHostNote) +{ + if (i == end() || !(*i)->isa(Note::EventType)) return false; + + Segment::iterator j = i; + Segment::iterator firstGraceNote = i; + Segment::iterator firstHostNote = i; + + if ((*i)->has(IS_GRACE_NOTE) && (*i)->get(IS_GRACE_NOTE)) { + + // i is a grace note. Find the first host note following it + + j = i; + while (++j != end()) { + if ((*j)->getNotationAbsoluteTime() > + (*i)->getNotationAbsoluteTime()) break; + if ((*j)->getSubOrdering() < 0) continue; + if ((*j)->isa(Note::EventType)) { + firstHostNote = j; + break; + } + } + + if (firstHostNote == i) { + std::cerr << "SegmentPerformanceHelper::getGraceAndHostNotes: REMARK: Grace note at " << (*i)->getAbsoluteTime() << " has no host note" << std::endl; + return false; + } + } else { + + // i is a host note, but we need to ensure we have the first + // one, not just any one + + j = i; + + while (j != begin()) { + --j; + if ((*j)->getNotationAbsoluteTime() < + (*i)->getNotationAbsoluteTime()) break; + if ((*j)->getSubOrdering() < + (*i)->getSubOrdering()) break; + if ((*j)->isa(Note::EventType)) { + firstHostNote = j; + break; + } + } + } + + // firstHostNote now points to the first host note, which is + // either the first non-grace note after i (if i was a grace note) + // or the first note with the same time and subordering as i (if i + // was not a grace note). + + if ((*firstHostNote)->getSubOrdering() < 0) { + std::cerr << "SegmentPerformanceHelper::getGraceAndHostNotes: WARNING: Note at " << (*firstHostNote)->getAbsoluteTime() << " has subordering " << (*i)->getSubOrdering() << " but is not a grace note" << std::endl; + return false; + } + + j = firstHostNote; + + while (j != begin()) { + --j; + if ((*j)->getNotationAbsoluteTime() < + (*firstHostNote)->getNotationAbsoluteTime()) break; + if ((*j)->getSubOrdering() >= 0) continue; + if (!(*j)->isa(Note::EventType)) continue; + if (!(*j)->has(IS_GRACE_NOTE) || !(*j)->get(IS_GRACE_NOTE)) { + std::cerr << "SegmentPerformanceHelper::getGraceAndHostNotes: WARNING: Note at " << (*j)->getAbsoluteTime() << " (in trackback) has subordering " << (*j)->getSubOrdering() << " but is not a grace note" << std::endl; + break; + } + firstGraceNote = j; + } + + if (firstGraceNote == firstHostNote) { + std::cerr << "SegmentPerformanceHelper::getGraceAndHostNotes: REMARK: Note at " << (*firstHostNote)->getAbsoluteTime() << " has no grace notes" << std::endl; + return false; + } + + j = firstGraceNote; + + // push all of the grace notes, and notes with the same time as + // the first host note, onto the container + + isHostNote = false; + + while (j != end()) { + if ((*j)->isa(Note::EventType)) { + if ((*j)->getSubOrdering() < 0) { + if ((*j)->has(IS_GRACE_NOTE) && (*j)->get(IS_GRACE_NOTE)) { + graceNotes.push_back(j); + } + } else { + hostNotes.push_back(j); + if (j == i) isHostNote = true; + } + } + if ((*j)->getNotationAbsoluteTime() > + (*firstHostNote)->getNotationAbsoluteTime()) break; + ++j; + } + + return true; +} + + +timeT +SegmentPerformanceHelper::getSoundingAbsoluteTime(iterator i) +{ + timeT t = 0; + + timeT discard; + +// std::cerr << "SegmentPerformanceHelper::getSoundingAbsoluteTime at " << (*i)->getAbsoluteTime() << std::endl; + + if ((*i)->has(IS_GRACE_NOTE)) { +// std::cerr << "it's a grace note" << std::endl; + if (getGraceNoteTimeAndDuration(false, i, t, discard)) return t; + } + if ((*i)->has(MAY_HAVE_GRACE_NOTES)) { +// std::cerr << "it's a candidate host note" << std::endl; + if (getGraceNoteTimeAndDuration(true, i, t, discard)) return t; + } + + return (*i)->getAbsoluteTime(); +} + +timeT +SegmentPerformanceHelper::getSoundingDuration(iterator i) +{ + timeT d = 0; + + timeT discard; + +// std::cerr << "SegmentPerformanceHelper::getSoundingDuration at " << (*i)->getAbsoluteTime() << std::endl; + + if ((*i)->has(IS_GRACE_NOTE)) { +// std::cerr << "it's a grace note" << std::endl; + if (getGraceNoteTimeAndDuration(false, i, discard, d)) return d; + } + if ((*i)->has(MAY_HAVE_GRACE_NOTES)) { +// std::cerr << "it's a candidate host note" << std::endl; + if (getGraceNoteTimeAndDuration(true, i, discard, d)) return d; + } + + if ((*i)->has(TIED_BACKWARD)) { + + // Formerly we just returned d in this case, but now we check + // with getTiedNotes so as to remove any bogus backward ties + // that have no corresponding forward tie. Unfortunately this + // is quite a bit slower. + + //!!! optimize. at least we should add a marker property to + //anything we've already processed from this helper this time + //around. + + iteratorcontainer c(getTiedNotes(i)); + + if (c.empty()) { // the tie back is valid + return 0; + } + } + + if (!(*i)->has(TIED_FORWARD) || !(*i)->isa(Note::EventType)) { + + d = (*i)->getDuration(); + + } else { + + // tied forward but not back + + iteratorcontainer c(getTiedNotes(i)); + + for (iteratorcontainer::iterator ci = c.begin(); + ci != c.end(); ++ci) { + d += (**ci)->getDuration(); + } + } + + return d; +} + + +// In theory we can do better with tuplets, because real time has +// finer precision than timeT time. With a timeT resolution of 960ppq +// however the difference is probably not audible + +RealTime +SegmentPerformanceHelper::getRealAbsoluteTime(iterator i) +{ + return segment().getComposition()->getElapsedRealTime + (getSoundingAbsoluteTime(i)); +} + + +// In theory we can do better with tuplets, because real time has +// finer precision than timeT time. With a timeT resolution of 960ppq +// however the difference is probably not audible +// +// (If we did want to do this, it'd help to have abstime->realtime +// conversion methods that accept double args in Composition) + +RealTime +SegmentPerformanceHelper::getRealSoundingDuration(iterator i) +{ + timeT t0 = getSoundingAbsoluteTime(i); + timeT t1 = t0 + getSoundingDuration(i); + + if (t1 > segment().getEndMarkerTime()) { + t1 = segment().getEndMarkerTime(); + } + + return segment().getComposition()->getRealTimeDifference(t0, t1); +} + + +bool +SegmentPerformanceHelper::getGraceNoteTimeAndDuration(bool host, iterator i, + timeT &t, timeT &d) +{ + // [This code currently assumes appoggiatura. Acciaccatura later.] + + // For our present purposes, we will assume that grace notes start + // at the same time as their host note was intended to, and + // "steal" a proportion of the duration of their host note. This + // causes the host note to start later, and be shorter, by that + // same proportion. + + // If a host note has more than one (consecutive) grace note, they + // should take a single cut from the grace note and divide it + // amongst themselves. + + // To begin with we will set the proportion to 1/4, but we will + // probably want it to be (a) something different [because I don't + // really know what I'm doing], (b) adaptive [e.g. shorter host + // note or more grace notes = longer proportion], (c) + // configurable, or (d) all of the above. + + // Of course we also ought to be taking into account the notated + // duration of the grace notes -- though in my working examples it + // generally doesn't seem to be the case that we can always just + // follow those. I wonder if we can always use the grace notes' + // notated duration if the ratio of grace note duration to host + // note duration is less than some value? Whatever we do, we + // should be dividing the grace note duration up in proportion to + // the durations of the grace notes, in situations where we have + // more than one grace note consecutively of different durations; + // that isn't handled at all here. + + if (i == end()) return false; + + iteratorcontainer graceNotes, hostNotes; + bool isHostNote; + + if (!getGraceAndHostNotes(i, graceNotes, hostNotes, isHostNote)) { + std::cerr << "SegmentPerformanceHelper::getGraceNoteTimeAndDuration: REMARK: Note at " << (*i)->getAbsoluteTime() << " is not a grace note, or has no grace notes" << std::endl; + return false; + } + + if (!isHostNote) { + + if (!(*i)->has(IS_GRACE_NOTE) || !(*i)->get(IS_GRACE_NOTE)) { + std::cerr << "SegmentPerformanceHelper::getGraceNoteTimeAndDuration: WARNING: Note at " << (*i)->getAbsoluteTime() << " is neither grace nor host note, but was reported as suitable by getGraceAndHostNotes" << std::endl; + return false; + } + } + + if (hostNotes.empty()) { + std::cerr << "SegmentPerformanceHelper::getGraceNoteTimeAndDuration: REMARK: Grace note at " << (*i)->getAbsoluteTime() << " has no host note" << std::endl; + return false; + } + + if (graceNotes.empty()) { + std::cerr << "SegmentPerformanceHelper::getGraceNoteTimeAndDuration: REMARK: Note at " << (*i)->getAbsoluteTime() << " has no grace notes" << std::endl; + return false; + } + + timeT hostNoteEarliestTime = 0; + timeT hostNoteShortestDuration = 0; + timeT hostNoteNotationDuration = 0; + + for (iteratorcontainer::iterator j = hostNotes.begin(); + j != hostNotes.end(); ++j) { + + if (j == hostNotes.begin() || + (**j)->getAbsoluteTime() < hostNoteEarliestTime) { + hostNoteEarliestTime = (**j)->getAbsoluteTime(); + } + if (j == hostNotes.begin() || + (**j)->getDuration() < hostNoteShortestDuration) { + hostNoteShortestDuration = (**j)->getDuration(); + } + if (j == hostNotes.begin() || + (**j)->getNotationDuration() > hostNoteNotationDuration) { + hostNoteNotationDuration = (**j)->getNotationDuration(); + } + (**j)->set(MAY_HAVE_GRACE_NOTES, true); + } + + timeT graceNoteTime = hostNoteEarliestTime; + timeT graceNoteDuration = hostNoteNotationDuration / 4; + if (graceNoteDuration > hostNoteShortestDuration / 2) { + graceNoteDuration = hostNoteShortestDuration / 2; + } + + if (isHostNote) { + t = (*i)->getAbsoluteTime() + graceNoteDuration; + d = (*i)->getDuration() - graceNoteDuration; + } else { + + int count = 0, index = 0; + bool found = false; + int prevSubOrdering = 0; + + for (iteratorcontainer::iterator j = graceNotes.begin(); + j != graceNotes.end(); ++j) { + + bool newChord = false; + + if ((**j)->getSubOrdering() != prevSubOrdering) { + newChord = true; + prevSubOrdering = (**j)->getSubOrdering(); + } + + if (newChord) ++count; + + if (*j == i) found = true; + + if (!found) { + if (newChord) ++index; + } + } + + if (index == count) index = 0; + if (count == 0) count = 1; // should not happen + + d = graceNoteDuration / count; + t = hostNoteEarliestTime + d * index; + } + + return true; + + +} + + +} diff --git a/src/base/SegmentPerformanceHelper.h b/src/base/SegmentPerformanceHelper.h new file mode 100644 index 0000000..e0ce745 --- /dev/null +++ b/src/base/SegmentPerformanceHelper.h @@ -0,0 +1,126 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _SEGMENT_PERFORMANCE_HELPER_H_ +#define _SEGMENT_PERFORMANCE_HELPER_H_ + +#include "Segment.h" +#include "Composition.h" // for RealTime + +namespace Rosegarden +{ + +class SegmentPerformanceHelper : protected SegmentHelper +{ +public: + SegmentPerformanceHelper(Segment &t) : SegmentHelper(t) { } + virtual ~SegmentPerformanceHelper(); + + typedef std::vector iteratorcontainer; + + /** + * Returns a sequence of iterators pointing to the note events + * that are tied with the given event. If the given event is not + * a note event or is not tied, its iterator will be the only one + * in the sequence. If the given event is tied but is not the + * first in the tied chain, the returned sequence will be empty. + */ + iteratorcontainer getTiedNotes(iterator i); + + /** + * Returns two sequences of iterators pointing to the note events + * that are grace notes, or host notes for grace notes, associated + * with the given event, which is itself either a grace note or a + * host note for a grace note. The grace note iterators are + * returned in the graceNotes sequence, and the host note + * iterators in hostNotes. isHostNote is set to true if the + * given event is a host note, false otherwise. + * + * If the given event is not a grace note, is a grace note with no + * host note, or is a potential host note without any grace notes, + * the sequences will both be empty and the function will return + * false. + */ + bool getGraceAndHostNotes(iterator i, + iteratorcontainer &graceNotes, + iteratorcontainer &hostNotes, + bool &isHostNote); + + /** + * Returns the absolute time of the note event pointed to by i. + */ + timeT getSoundingAbsoluteTime(iterator i); + + /** + * Returns the duration of the note event pointed to by i, taking + * into account any ties the note may have etc. + * + * If the note is the first of two or more tied notes, this will + * return the accumulated duration of the whole series of notes + * it's tied to. + * + * If the note is in a tied series but is not the first, this will + * return zero, because the note's duration is presumed to have + * been accounted for by a previous call to this method when + * examining the first note in the tied series. + * + * If the note is not tied, or if i does not point to a note + * event, this will just return the duration of the event at i. + * + * This method may return an incorrect duration for any note + * event that is tied but lacks a pitch property. This is + * expected behaviour; don't create tied notes without pitches. + */ + timeT getSoundingDuration(iterator i); + + /** + * Returns the absolute time of the event pointed to by i, + * in microseconds elapsed since the start of the Composition. + * This method exploits the Composition's getElapsedRealTime + * method to take into account any tempo changes that appear + * in the section of the composition preceding i. + */ + RealTime getRealAbsoluteTime(iterator i); + + /** + * Returns the duration of the note event pointed to by i, + * in microseconds. This takes into account the tempo in + * force at i's position within the composition, as well as + * any tempo changes occurring during the event at i. + */ + RealTime getRealSoundingDuration(iterator i); + + /** + * Return a sounding duration (estimated) and start time for the + * note event pointed to by i. If host is true, i is expected to + * be the "host" note for one or more grace notes; if host is + * false, i is expected to point to a grace note. If the relevant + * expectation is not met, this function returns false. Otherwise + * the sounding time and duration are returned through t and d and + * the function returns true. + */ + bool getGraceNoteTimeAndDuration(bool host, iterator i, timeT &t, timeT &d); +}; + +} + +#endif diff --git a/src/base/Selection.cpp b/src/base/Selection.cpp new file mode 100644 index 0000000..6e5ca2f --- /dev/null +++ b/src/base/Selection.cpp @@ -0,0 +1,318 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Selection.h" +#include "Segment.h" +#include "SegmentNotationHelper.h" +#include "BaseProperties.h" + +namespace Rosegarden { + +EventSelection::EventSelection(Segment& t) : + m_originalSegment(t), + m_beginTime(0), + m_endTime(0), + m_haveRealStartTime(false) +{ + t.addObserver(this); +} + +EventSelection::EventSelection(Segment& t, timeT beginTime, timeT endTime, bool overlap) : + m_originalSegment(t), + m_beginTime(0), + m_endTime(0), + m_haveRealStartTime(false) +{ + t.addObserver(this); + + Segment::iterator i = t.findTime(beginTime); + Segment::iterator j = t.findTime(endTime); + + if (i != t.end()) { + m_beginTime = (*i)->getAbsoluteTime(); + while (i != j) { + m_endTime = (*i)->getAbsoluteTime() + (*i)->getDuration(); + m_segmentEvents.insert(*i); + ++i; + } + m_haveRealStartTime = true; + } + + // Find events overlapping the beginning + // + if (overlap) { + i = t.findTime(beginTime); + + while (i != t.begin() && i != t.end() && i != j) { + + if ((*i)->getAbsoluteTime() + (*i)->getDuration() > beginTime) + { + m_segmentEvents.insert(*i); // duplicates are filtered automatically + m_beginTime = (*i)->getAbsoluteTime(); + } + else + break; + + --i; + } + + } + +} + +EventSelection::EventSelection(const EventSelection &sel) : + SegmentObserver(), + m_originalSegment(sel.m_originalSegment), + m_segmentEvents(sel.m_segmentEvents), + m_beginTime(sel.m_beginTime), + m_endTime(sel.m_endTime), + m_haveRealStartTime(sel.m_haveRealStartTime) +{ + m_originalSegment.addObserver(this); +} + +EventSelection::~EventSelection() +{ + m_originalSegment.removeObserver(this); +} + +void EventSelection::addEvent(Event *e) +{ + timeT eventDuration = e->getDuration(); + if (eventDuration == 0) eventDuration = 1; + + if (contains(e)) return; + + if (e->getAbsoluteTime() < m_beginTime || !m_haveRealStartTime) { + m_beginTime = e->getAbsoluteTime(); + m_haveRealStartTime = true; + } + if (e->getAbsoluteTime() + eventDuration > m_endTime) { + m_endTime = e->getAbsoluteTime() + eventDuration; + } + m_segmentEvents.insert(e); +} + +void EventSelection::addFromSelection(EventSelection *sel) +{ + for (eventcontainer::iterator i = sel->getSegmentEvents().begin(); + i != sel->getSegmentEvents().end(); ++i) { + if (!contains(*i)) addEvent(*i); + } +} + +void EventSelection::removeEvent(Event *e) +{ + std::pair + interval = m_segmentEvents.equal_range(e); + + for (eventcontainer::iterator it = interval.first; + it != interval.second; it++) + { + if (*it == e) { + m_segmentEvents.erase(it); + return; + } + } +} + +bool EventSelection::contains(Event *e) const +{ + std::pair + interval = m_segmentEvents.equal_range(e); + + for (eventcontainer::const_iterator it = interval.first; + it != interval.second; ++it) + { + if (*it == e) return true; + } + + return false; +} + +bool EventSelection::contains(const std::string &type) const +{ + for (eventcontainer::const_iterator i = m_segmentEvents.begin(); + i != m_segmentEvents.end(); ++i) { + if ((*i)->isa(type)) return true; + } + return false; +} + +timeT EventSelection::getTotalDuration() const +{ + return getEndTime() - getStartTime(); +} + +EventSelection::RangeList +EventSelection::getRanges() const +{ + RangeList ranges; + + Segment::iterator i = m_originalSegment.findTime(getStartTime()); + Segment::iterator j = i; + Segment::iterator k = m_originalSegment.findTime(getEndTime()); + + while (j != k) { + + for (j = i; j != k && contains(*j); ++j); + + if (j != i) { + ranges.push_back(RangeList::value_type(i, j)); + } + + for (i = j; i != k && !contains(*i); ++i); + j = i; + } + + return ranges; +} + +EventSelection::RangeTimeList +EventSelection::getRangeTimes() const +{ + RangeList ranges(getRanges()); + RangeTimeList rangeTimes; + + for (RangeList::iterator i = ranges.begin(); i != ranges.end(); ++i) { + timeT startTime = m_originalSegment.getEndTime(); + timeT endTime = m_originalSegment.getEndTime(); + if (i->first != m_originalSegment.end()) { + startTime = (*i->first)->getAbsoluteTime(); + } + if (i->second != m_originalSegment.end()) { + endTime = (*i->second)->getAbsoluteTime(); + } + rangeTimes.push_back(RangeTimeList::value_type(startTime, endTime)); + } + + return rangeTimes; +} + +void +EventSelection::eventRemoved(const Segment *s, Event *e) +{ + if (s == &m_originalSegment /*&& contains(e)*/) { + removeEvent(e); + } +} + +void +EventSelection::segmentDeleted(const Segment *) +{ + /* + std::cerr << "WARNING: EventSelection notified of segment deletion: this is probably a bug " + << "(selection should have been deleted before segment)" << std::endl; + */ +} + +bool SegmentSelection::hasNonAudioSegment() const +{ + for (const_iterator i = begin(); i != end(); ++i) { + if ((*i)->getType() == Segment::Internal) + return true; + } + return false; +} + + +TimeSignatureSelection::TimeSignatureSelection() { } + +TimeSignatureSelection::TimeSignatureSelection(Composition &composition, + timeT beginTime, + timeT endTime, + bool includeOpeningTimeSig) +{ + int n = composition.getTimeSignatureNumberAt(endTime); + + for (int i = composition.getTimeSignatureNumberAt(beginTime); + i <= n; + ++i) { + + if (i < 0) continue; + + std::pair sig = + composition.getTimeSignatureChange(i); + + if (sig.first < endTime) { + if (sig.first < beginTime) { + if (includeOpeningTimeSig) { + sig.first = beginTime; + } else { + continue; + } + } + addTimeSignature(sig.first, sig.second); + } + } +} + +TimeSignatureSelection::~TimeSignatureSelection() { } + +void +TimeSignatureSelection::addTimeSignature(timeT t, TimeSignature timeSig) +{ + m_timeSignatures.insert(timesigcontainer::value_type(t, timeSig)); +} + +TempoSelection::TempoSelection() { } + +TempoSelection::TempoSelection(Composition &composition, + timeT beginTime, + timeT endTime, + bool includeOpeningTempo) +{ + int n = composition.getTempoChangeNumberAt(endTime); + + for (int i = composition.getTempoChangeNumberAt(beginTime); + i <= n; + ++i) { + + if (i < 0) continue; + + std::pair change = composition.getTempoChange(i); + + if (change.first < endTime) { + if (change.first < beginTime) { + if (includeOpeningTempo) { + change.first = beginTime; + } else { + continue; + } + } + std::pair ramping = + composition.getTempoRamping(i, false); + addTempo(change.first, change.second, + ramping.first ? ramping.second : -1); + } + } +} + +TempoSelection::~TempoSelection() { } + +void +TempoSelection::addTempo(timeT t, tempoT tempo, tempoT targetTempo) +{ + m_tempos.insert(tempocontainer::value_type + (t, tempochange(tempo, targetTempo))); +} + +} diff --git a/src/base/Selection.h b/src/base/Selection.h new file mode 100644 index 0000000..93ce4b4 --- /dev/null +++ b/src/base/Selection.h @@ -0,0 +1,263 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 SELECTION_H +#define SELECTION_H + +#include +#include "PropertyName.h" +#include "Event.h" +#include "Segment.h" +#include "NotationTypes.h" +#include "Composition.h" + +namespace Rosegarden { + +/** + * EventSelection records a (possibly non-contiguous) selection + * of Events in a single Segment, used for cut'n paste operations. + * It does not take a copy of those Events, it just remembers + * which ones they are. + */ + +class EventSelection : public SegmentObserver +{ +public: + typedef std::multiset eventcontainer; + + /** + * Construct an empty EventSelection based on the given Segment. + */ + EventSelection(Segment &); + + /** + * Construct an EventSelection selecting all the events in the + * given range of the given Segment. Set overlap if you want + * to include Events overlapping the selection edges. + */ + EventSelection(Segment &, timeT beginTime, timeT endTime, bool overlap = false); + + EventSelection(const EventSelection&); + + virtual ~EventSelection(); + + /** + * Add an Event to the selection. The Event should come from + * the Segment that was passed to the constructor. Will + * silently drop any event that is already in the selection. + */ + void addEvent(Event* e); + + /** + * Add all the Events in the given Selection to this one. + * Will silently drop any events that are already in the + * selection. + */ + void addFromSelection(EventSelection *sel); + + /** + * If the given Event is in the selection, take it out. + */ + void removeEvent(Event *e); + + /** + * Test whether a given Event (in the Segment) is part of + * this selection. + */ + bool contains(Event *e) const; + + /** + * Return true if there are any events of the given type in + * this selection. Slow. + */ + bool contains(const std::string &eventType) const; + + /** + * Return the time at which the first Event in the selection + * begins. + */ + timeT getStartTime() const { return m_beginTime; } + + /** + * Return the time at which the last Event in the selection ends. + */ + timeT getEndTime() const { return m_endTime; } + + /** + * Return the total duration spanned by the selection. + */ + timeT getTotalDuration() const; + + typedef std::vector > RangeList; + /** + * Return a set of ranges spanned by the selection, such that + * each range covers only events within the selection. + */ + RangeList getRanges() const; + + typedef std::vector > RangeTimeList; + /** + * Return a set of times spanned by the selection, such that + * each time range covers only events within the selection. + */ + RangeTimeList getRangeTimes() const; + + /** + * Return the number of events added to this selection. + */ + unsigned int getAddedEvents() const { return m_segmentEvents.size(); } + + const eventcontainer &getSegmentEvents() const { return m_segmentEvents; } + eventcontainer &getSegmentEvents() { return m_segmentEvents; } + + const Segment &getSegment() const { return m_originalSegment; } + Segment &getSegment() { return m_originalSegment; } + + // SegmentObserver methods + virtual void eventAdded(const Segment *, Event *) { } + virtual void eventRemoved(const Segment *, Event *); + virtual void endMarkerTimeChanged(const Segment *, bool) { } + virtual void segmentDeleted(const Segment *); + +private: + EventSelection &operator=(const EventSelection &); + +protected: + //--------------- Data members --------------------------------- + + Segment& m_originalSegment; + + /// pointers to Events in the original Segment + eventcontainer m_segmentEvents; + + timeT m_beginTime; + timeT m_endTime; + bool m_haveRealStartTime; +}; + + +/** + * SegmentSelection is much simpler than EventSelection, we don't + * need to do much with this really + */ + +class SegmentSelection : public std::set +{ +public: + bool hasNonAudioSegment() const; +}; + + +/** + * A selection that includes (only) time signatures. Unlike + * EventSelection, this does copy its contents, not just refer to + * them. + */ +class TimeSignatureSelection +{ +public: + /** + * Construct an empty TimeSignatureSelection. + */ + TimeSignatureSelection(); + + /** + * Construct a TimeSignatureSelection containing all the time + * signatures in the given range of the given Composition. + * + * If includeOpeningTimeSig is true, the selection will start with + * a duplicate of the time signature (if any) that is already in + * force at beginTime. Otherwise the selection will only start + * with a time signature at beginTime if there is an explicit + * signature there in the source composition. + */ + TimeSignatureSelection(Composition &, timeT beginTime, timeT endTime, + bool includeOpeningTimeSig); + + virtual ~TimeSignatureSelection(); + + /** + * Add a time signature to the selection. + */ + void addTimeSignature(timeT t, TimeSignature timeSig); + + typedef std::multimap timesigcontainer; + + const timesigcontainer &getTimeSignatures() const { return m_timeSignatures; } + timesigcontainer::const_iterator begin() const { return m_timeSignatures.begin(); } + timesigcontainer::const_iterator end() const { return m_timeSignatures.end(); } + bool empty() const { return begin() == end(); } + +protected: + timesigcontainer m_timeSignatures; +}; + + +/** + * A selection that includes (only) tempo changes. + */ + +class TempoSelection +{ +public: + /** + * Construct an empty TempoSelection. + */ + TempoSelection(); + + /** + * Construct a TempoSelection containing all the time + * signatures in the given range of the given Composition. + * + * If includeOpeningTempo is true, the selection will start with a + * duplicate of the tempo (if any) that is already in force at + * beginTime. Otherwise the selection will only start with a + * tempo at beginTime if there is an explicit tempo change there + * in the source composition. + */ + TempoSelection(Composition &, timeT beginTime, timeT endTime, + bool includeOpeningTempo); + + virtual ~TempoSelection(); + + /** + * Add a time signature to the selection. + */ + void addTempo(timeT t, tempoT tempo, tempoT targetTempo = -1); + + typedef std::pair tempochange; + typedef std::multimap tempocontainer; + + const tempocontainer &getTempos() const { return m_tempos; } + tempocontainer::const_iterator begin() const { return m_tempos.begin(); } + tempocontainer::const_iterator end() const { return m_tempos.end(); } + bool empty() const { return begin() == end(); } + +protected: + tempocontainer m_tempos; +}; + + + +} + +#endif diff --git a/src/base/Sets.cpp b/src/base/Sets.cpp new file mode 100644 index 0000000..5111f37 --- /dev/null +++ b/src/base/Sets.cpp @@ -0,0 +1,108 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Sets.h" + +#include "Event.h" +#include "BaseProperties.h" +#include "Quantizer.h" + +namespace Rosegarden { + +template <> +Event * +AbstractSet::getAsEvent(const Segment::iterator &i) +{ + return *i; +} + +template <> +Event * +AbstractSet::getAsEvent(const CompositionTimeSliceAdapter::iterator &i) +{ + return *i; +} + +/* + * This ridiculous shit appears to be necessary to please gcc. + * Compiler bug? My own misunderstanding of some huge crock of crap + * in the C++ standard? No idea. If you know, tell me. Anyway, as + * it stands I can't get any calls to get<> or set<> from the Set or + * Chord methods to compile -- the compiler appears to parse the + * opening < of the template arguments as an operator<. Hence this. + */ + +extern long +get__Int(Event *e, const PropertyName &name) +{ + return e->get(name); +} + +extern bool +get__Bool(Event *e, const PropertyName &name) +{ + return e->get(name); +} + +extern std::string +get__String(Event *e, const PropertyName &name) +{ + return e->get(name); +} + +extern bool +get__Int(Event *e, const PropertyName &name, long &ref) +{ + return e->get(name, ref); +} + +extern bool +get__Bool(Event *e, const PropertyName &name, bool &ref) +{ + return e->get(name, ref); +} + +extern bool +get__String(Event *e, const PropertyName &name, std::string &ref) +{ + return e->get(name, ref); +} + +extern bool +isPersistent__Bool(Event *e, const PropertyName &name) +{ + return e->isPersistent(name); +} + +extern void +setMaybe__Int(Event *e, const PropertyName &name, long value) +{ + e->setMaybe(name, value); +} + +extern void +setMaybe__String(Event *e, const PropertyName &name, const std::string &value) +{ + e->setMaybe(name, value); +} + +} + diff --git a/src/base/Sets.h b/src/base/Sets.h new file mode 100644 index 0000000..4fe14d1 --- /dev/null +++ b/src/base/Sets.h @@ -0,0 +1,698 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _SETS_H_ +#define _SETS_H_ + +#include +#include + +#include "Event.h" +#include "Segment.h" +#include "CompositionTimeSliceAdapter.h" +#include "BaseProperties.h" +#include "NotationTypes.h" +#include "MidiTypes.h" +#include "Quantizer.h" + +namespace Rosegarden +{ + +class Quantizer; + +/** + * A "set" in Rosegarden terminology is a collection of elements found + * in a container (indeed, a subset of that container) all of which + * share a particular property and are located near to one another: + * generally either contiguous or within the same bar. The elements + * are most usually Events and the container most usually a Segment, + * and although this does not have to be the case (for other examples + * see gui/notationsets.h), the elements do have to be convertible to + * Events somehow. + * + * To construct a set requires (at least) a container reference plus + * an iterator into that container. The constructor (or more + * precisely the initialise() method called by the constructor) then + * scans the surrounding area of the list for the maximal set of + * contiguous or within-the-same-bar elements before and after the + * passed-in iterator such that all elements are in the same set + * (i.e. Chord, BeamedGroup etc) as the one that the passed-in + * iterator pointed to. + * + * The extents of the set within the list can then be discovered via + * getInitialElement() and getFinalElement(). If the iterator passed + * in to the constructor was at end() or did not point to an element + * that could be a member of this kind of set, getInitialElement() + * will return end(); if the passed-in iterator pointed to the only + * member of this set, getInitialElement() and getFinalElement() will + * be equal. + * + * These classes are not intended to be stored anywhere; all they + * contain is iterators into the main container, and those might not + * persist. Instead you should create these on-the-fly when you want, + * for example, to consider a note as part of a chord; and then you + * should let them expire when you've finished with them. + */ + +template +class AbstractSet // abstract base +{ +public: + typedef typename Container::iterator Iterator; + + virtual ~AbstractSet() { } + + /** + * getInitialElement() returns end() if there are no elements in + * the set. getInitialElement() == getFinalElement() if there is + * only one element in the set + */ + Iterator getInitialElement() const { return m_initial; } + Iterator getFinalElement() const { return m_final; } + + /// only return note elements; will return end() if there are none + Iterator getInitialNote() const { return m_initialNote; } + Iterator getFinalNote() const { return m_finalNote; } + + /** + * only elements with duration > 0 are candidates for shortest and + * longest; these will return end() if there are no such elements + */ + Iterator getLongestElement() const { return m_longest; } + Iterator getShortestElement() const { return m_shortest; } + + /// these will return end() if there are no note elements in the set + Iterator getHighestNote() const { return m_highest; } + Iterator getLowestNote() const { return m_lowest; } + + virtual bool contains(const Iterator &) const = 0; + + /// Return the pointed-to element, in Event form (public to work around gcc-2.95 bug) + static Event *getAsEvent(const Iterator &i); + +protected: + AbstractSet(Container &c, Iterator elementInSet, const Quantizer *); + void initialise(); + + /// Return true if this element is not definitely beyond bounds of set + virtual bool test(const Iterator &i) = 0; + + /// Return true if this element, known to test() true, is a set member + virtual bool sample(const Iterator &i, bool goingForwards); + + Container &getContainer() const { return m_container; } + const Quantizer &getQuantizer() const { return *m_quantizer; } + + // Data members: + + Container &m_container; + Iterator m_initial, m_final, m_initialNote, m_finalNote; + Iterator m_shortest, m_longest, m_highest, m_lowest; + Iterator m_baseIterator; + const Quantizer *m_quantizer; +}; + + +/** + * Chord is subclassed from a vector of iterators; this vector + * contains iterators pointing at all the notes in the chord, in + * ascending order of pitch. You can also track through all the + * events in the chord by iterating from getInitialElement() to + * getFinalElement(), but this will only get them in the order in + * which they appear in the original container. + * + * However, the notes in a chord might not be contiguous events in the + * container, as there could be other zero-duration events such as + * controllers (or even conceivably some short rests) between notes in + * the same chord, depending on the quantization settings. The Chord + * itself only contains iterators pointing at the notes, so if you + * want to iterate through all events spanned by the Chord, iterate + * from getInitialElement() to getFinalElement() instead. + * + * This class can tell you various things about the chord it + * describes, but not everything. It can't tell you whether the + * chord has a stem, for example, because that depends on the + * notation style and system in use. See gui/notationsets.h + * for a NotationChord class (subclassed from this) that can. + */ + +template +class GenericChord : public AbstractSet, + public std::vector +{ +public: + typedef typename Container::iterator Iterator; + + /* You only need to provide the clef and key if the notes + making up your chord lack HEIGHT_ON_STAFF properties, in + which case this constructor will write those properties + in to the chord for you */ + GenericChord(Container &c, + Iterator elementInChord, + const Quantizer *quantizer, + PropertyName stemUpProperty = PropertyName::EmptyPropertyName); + + virtual ~GenericChord(); + + virtual int getMarkCountForChord() const; + virtual std::vector getMarksForChord() const; + virtual std::vector getPitches() const; + virtual bool contains(const Iterator &) const; + + /** + * Return an iterator pointing to the previous note before this + * chord, or container's end() if there is no previous note. + */ + virtual Iterator getPreviousNote(); + + /** + * Return an iterator pointing to the next note after this chord, + * or container's end() if there is no next note. Remember this + * class can't know about Segment end marker times, so if your + * container is a Segment, check the returned note is actually + * before the end marker. + */ + virtual Iterator getNextNote(); + + /** + * It's possible for a chord to surround (in the segment) elements + * that are not members of the chord. This function returns an + * iterator pointing to the first of those after the iterator that + * was passed to the chord's constructor. If there are none, it + * returns the container's end(). + */ + virtual Iterator getFirstElementNotInChord(); + + virtual int getSubOrdering() { return m_subordering; } + +protected: + virtual bool test(const Iterator&); + virtual bool sample(const Iterator&, bool goingForwards); + + class PitchGreater { + public: + bool operator()(const Iterator &a, const Iterator &b); + }; + + void copyGroupProperties(Event *e0, Event *e1) const; + + //--------------- Data members --------------------------------- + + PropertyName m_stemUpProperty; + timeT m_time; + int m_subordering; + Iterator m_firstReject; +}; + + + +/// +/// Implementation only from here on. +/// + +// forward declare hack functions -- see Sets.C for an explanation + +extern long +get__Int(Event *e, const PropertyName &name); + +extern bool +get__Bool(Event *e, const PropertyName &name); + +extern std::string +get__String(Event *e, const PropertyName &name); + +extern bool +get__Int(Event *e, const PropertyName &name, long &ref); + +extern bool +get__Bool(Event *e, const PropertyName &name, bool &ref); + +extern bool +get__String(Event *e, const PropertyName &name, std::string &ref); + +extern bool +isPersistent__Bool(Event *e, const PropertyName &name); + +extern void +setMaybe__Int(Event *e, const PropertyName &name, long value); + +extern void +setMaybe__String(Event *e, const PropertyName &name, const std::string &value); + + + +template +AbstractSet::AbstractSet(Container &c, + Iterator i, const Quantizer *q): + m_container(c), + m_initial(c.end()), + m_final(c.end()), + m_initialNote(c.end()), + m_finalNote(c.end()), + m_shortest(c.end()), + m_longest(c.end()), + m_highest(c.end()), + m_lowest(c.end()), + m_baseIterator(i), + m_quantizer(q) +{ + // ... +} + +template +void +AbstractSet::initialise() +{ + if (m_baseIterator == getContainer().end() || !test(m_baseIterator)) return; + + m_initial = m_baseIterator; + m_final = m_baseIterator; + sample(m_baseIterator, true); + + if (getAsEvent(m_baseIterator)->isa(Note::EventType)) { + m_initialNote = m_baseIterator; + m_finalNote = m_baseIterator; + } + + Iterator i, j; + + // first scan back to find an element not in the desired set, + // sampling everything as far back as the one after it + + for (i = j = m_baseIterator; i != getContainer().begin() && test(--j); i = j){ + if (sample(j, false)) { + m_initial = j; + if (getAsEvent(j)->isa(Note::EventType)) { + m_initialNote = j; + if (m_finalNote == getContainer().end()) { + m_finalNote = j; + } + } + } + } + + j = m_baseIterator; + + // then scan forwards to find an element not in the desired set, + // sampling everything as far forward as the one before it + + for (i = j = m_baseIterator; ++j != getContainer().end() && test(j); i = j) { + if (sample(j, true)) { + m_final = j; + if (getAsEvent(j)->isa(Note::EventType)) { + m_finalNote = j; + if (m_initialNote == getContainer().end()) { + m_initialNote = j; + } + } + } + } +} + +template +bool +AbstractSet::sample(const Iterator &i, bool) +{ + const Quantizer &q(getQuantizer()); + Event *e = getAsEvent(i); + timeT d(q.getQuantizedDuration(e)); + + if (e->isa(Note::EventType) || d > 0) { + if (m_longest == getContainer().end() || + d > q.getQuantizedDuration(getAsEvent(m_longest))) { +// std::cerr << "New longest in set at duration " << d << " and time " << e->getAbsoluteTime() << std::endl; + m_longest = i; + } + if (m_shortest == getContainer().end() || + d < q.getQuantizedDuration(getAsEvent(m_shortest))) { +// std::cerr << "New shortest in set at duration " << d << " and time " << e->getAbsoluteTime() << std::endl; + m_shortest = i; + } + } + + if (e->isa(Note::EventType)) { + long p = get__Int(e, BaseProperties::PITCH); + + if (m_highest == getContainer().end() || + p > get__Int(getAsEvent(m_highest), BaseProperties::PITCH)) { +// std::cerr << "New highest in set at pitch " << p << " and time " << e->getAbsoluteTime() << std::endl; + m_highest = i; + } + if (m_lowest == getContainer().end() || + p < get__Int(getAsEvent(m_lowest), BaseProperties::PITCH)) { +// std::cerr << "New lowest in set at pitch " << p << " and time " << e->getAbsoluteTime() << std::endl; + m_lowest = i; + } + } + + return true; +} + + +////////////////////////////////////////////////////////////////////// + +template +GenericChord::GenericChord(Container &c, + Iterator i, + const Quantizer *q, + PropertyName stemUpProperty) : + AbstractSet(c, i, q), + m_stemUpProperty(stemUpProperty), + m_time(q->getQuantizedAbsoluteTime(getAsEvent(i))), + m_subordering(getAsEvent(i)->getSubOrdering()), + m_firstReject(c.end()) +{ + AbstractSet::initialise(); + + if (std::vector::size() > 1) { + std::stable_sort(std::vector::begin(), + std::vector::end(), + PitchGreater()); + } + +/*!!! this should all be removed ultimately +// std::cerr << "GenericChord::GenericChord: pitches are:" << std::endl; + int prevPitch = -999; + for (unsigned int i = 0; i < size(); ++i) { + try { + int pitch = getAsEvent((*this)[i])->get(BaseProperties::PITCH); +// std::cerr << i << ": " << pitch << std::endl; + if (pitch < prevPitch) { + cerr << "ERROR: Pitch less than previous pitch (" << pitch + << " < " << prevPitch << ")" << std::endl; + throw(1); + } + } catch (Event::NoData) { + std::cerr << i << ": no pitch property" << std::endl; + } + } +*/ +} + +template +GenericChord::~GenericChord() +{ +} + +template +bool +GenericChord::test(const Iterator &i) +{ + Event *e = getAsEvent(i); + if (AbstractSet:: + getQuantizer().getQuantizedAbsoluteTime(e) != m_time) { + return false; + } + if (e->getSubOrdering() != m_subordering) { + return false; + } + + // We permit note or rest events etc here, because if a chord is a + // little staggered (for performance reasons) then it's not at all + // unlikely we could get other events (even rests) in the middle + // of it. So long as sample() only permits notes, we should be + // okay with this. + // + // (We're really only refusing things like clef and key events + // here, though it's slightly quicker [since most things are + // notes] and perhaps a bit safer to do it by testing for + // inclusion rather than exclusion.) + + std::string type(e->getType()); + return (type == Note::EventType || + type == Note::EventRestType || + type == Text::EventType || + type == Indication::EventType || + type == PitchBend::EventType || + type == Controller::EventType || + type == KeyPressure::EventType || + type == ChannelPressure::EventType); +} + +template +bool +GenericChord::sample(const Iterator &i, + bool goingForwards) +{ + Event *e1 = getAsEvent(i); + if (!e1->isa(Note::EventType)) { + if (goingForwards && m_firstReject == AbstractSet::getContainer().end()) m_firstReject = i; + return false; + } + + if (singleStaff) { + + // Two notes that would otherwise be in a chord but are + // explicitly in different groups, or have stems pointing in + // different directions by design, or have substantially + // different x displacements, count as separate chords. + + // Per #930473 ("Inserting notes into beamed chords is + // broken"), if one note is in a group and the other isn't, + // that's no problem. In fact we should actually modify the + // one that isn't so as to suggest that it is. + + if (AbstractSet::m_baseIterator != AbstractSet::getContainer().end()) { + + Event *e0 = getAsEvent(AbstractSet::m_baseIterator); + + if (!(m_stemUpProperty == PropertyName::EmptyPropertyName)) { + + if (e0->has(m_stemUpProperty) && + e1->has(m_stemUpProperty) && + isPersistent__Bool(e0, m_stemUpProperty) && + isPersistent__Bool(e1, m_stemUpProperty) && + get__Bool(e0, m_stemUpProperty) != + get__Bool(e1, m_stemUpProperty)) { + + if (goingForwards && m_firstReject == AbstractSet::getContainer().end()) + m_firstReject = i; + return false; + } + } + + long dx0 = 0, dx1 = 0; + get__Int(e0, BaseProperties::DISPLACED_X, dx0); + get__Int(e1, BaseProperties::DISPLACED_X, dx1); + if (abs(dx0 - dx1) >= 700) { + if (goingForwards && m_firstReject == AbstractSet::getContainer().end()) + m_firstReject = i; + return false; + } + + if (e0->has(BaseProperties::BEAMED_GROUP_ID)) { + if (e1->has(BaseProperties::BEAMED_GROUP_ID)) { + if (get__Int(e1, BaseProperties::BEAMED_GROUP_ID) != + get__Int(e0, BaseProperties::BEAMED_GROUP_ID)) { + if (goingForwards && m_firstReject == AbstractSet::getContainer().end()) + m_firstReject = i; + return false; + } + } else { + copyGroupProperties(e0, e1); // #930473 + } + } else { + if (e1->has(BaseProperties::BEAMED_GROUP_ID)) { + copyGroupProperties(e1, e0); // #930473 + } + } + } + } + + AbstractSet::sample(i, goingForwards); + push_back(i); + return true; +} + +template +void +GenericChord::copyGroupProperties(Event *e0, + Event *e1) const +{ + if (e0->has(BaseProperties::BEAMED_GROUP_TYPE)) { + setMaybe__String(e1, BaseProperties::BEAMED_GROUP_TYPE, + get__String(e0, BaseProperties::BEAMED_GROUP_TYPE)); + } + if (e0->has(BaseProperties::BEAMED_GROUP_ID)) { + setMaybe__Int(e1, BaseProperties::BEAMED_GROUP_ID, + get__Int(e0, BaseProperties::BEAMED_GROUP_ID)); + } + if (e0->has(BaseProperties::BEAMED_GROUP_TUPLET_BASE)) { + setMaybe__Int(e1, BaseProperties::BEAMED_GROUP_TUPLET_BASE, + get__Int(e0, BaseProperties::BEAMED_GROUP_TUPLET_BASE)); + } + if (e0->has(BaseProperties::BEAMED_GROUP_TUPLED_COUNT)) { + setMaybe__Int(e1, BaseProperties::BEAMED_GROUP_TUPLED_COUNT, + get__Int(e0, BaseProperties::BEAMED_GROUP_TUPLED_COUNT)); + } + if (e0->has(BaseProperties::BEAMED_GROUP_UNTUPLED_COUNT)) { + setMaybe__Int(e1, BaseProperties::BEAMED_GROUP_UNTUPLED_COUNT, + get__Int(e0, BaseProperties::BEAMED_GROUP_UNTUPLED_COUNT)); + } +} + + +template +int +GenericChord::getMarkCountForChord() const +{ + // need to weed out duplicates + + std::set cmarks; + + for (unsigned int i = 0; i < std::vector::size(); ++i) { + + Event *e = getAsEvent((*this)[i]); + std::vector marks(Marks::getMarks(*e)); + + for (std::vector::iterator j = marks.begin(); j != marks.end(); ++j) { + cmarks.insert(*j); + } + } + + return cmarks.size(); +} + + +template +std::vector +GenericChord::getMarksForChord() const +{ + std::vector cmarks; + + for (unsigned int i = 0; i < std::vector::size(); ++i) { + + Event *e = getAsEvent((*this)[i]); + std::vector marks(Marks::getMarks(*e)); + + + for (std::vector::iterator j = marks.begin(); j != marks.end(); ++j) { + + // We permit multiple identical fingering marks per chord, + // but not any other sort + if (Marks::isFingeringMark(*j) || + std::find(cmarks.begin(), cmarks.end(), *j) == cmarks.end()) { + cmarks.push_back(*j); + } + } + } + + return cmarks; +} + + +template +std::vector +GenericChord::getPitches() const +{ + std::vector pitches; + + for (typename std::vector::const_iterator + i = std::vector::begin(); i != std::vector::end(); ++i) { + if (getAsEvent(*i)->has(BaseProperties::PITCH)) { + int pitch = get__Int + (getAsEvent(*i), BaseProperties::PITCH); + if (pitches.size() > 0 && pitches[pitches.size()-1] == pitch) + continue; + pitches.push_back(pitch); + } + } + + return pitches; +} + + +template +bool +GenericChord::contains(const Iterator &itr) const +{ + for (typename std::vector::const_iterator + i = std::vector::begin(); + i != std::vector::end(); ++i) { + if (*i == itr) return true; + } + return false; +} + + +template +typename GenericChord::Iterator +GenericChord::getPreviousNote() +{ + Iterator i(AbstractSet::getInitialElement()); + while (1) { + if (i == AbstractSet::getContainer().begin()) return AbstractSet::getContainer().end(); + --i; + if (getAsEvent(i)->isa(Note::EventType)) { + return i; + } + } +} + + +template +typename GenericChord::Iterator +GenericChord::getNextNote() +{ + Iterator i(AbstractSet::getFinalElement()); + while ( i != AbstractSet::getContainer().end() && + ++i != AbstractSet::getContainer().end()) { + if (getAsEvent(i)->isa(Note::EventType)) { + return i; + } + } + return AbstractSet::getContainer().end(); +} + + +template +typename GenericChord::Iterator +GenericChord::getFirstElementNotInChord() +{ + return m_firstReject; +} + + +template +bool +GenericChord::PitchGreater::operator()(const Iterator &a, + const Iterator &b) +{ + try { + long ap = get__Int(getAsEvent(a), BaseProperties::PITCH); + long bp = get__Int(getAsEvent(b), BaseProperties::PITCH); + return (ap < bp); + } catch (Event::NoData) { + std::cerr << "Bad karma: PitchGreater failed to find one or both pitches" << std::endl; + return false; + } +} + + +typedef GenericChord Chord; +typedef GenericChord GlobalChord; + + +} + + +#endif + diff --git a/src/base/SnapGrid.cpp b/src/base/SnapGrid.cpp new file mode 100644 index 0000000..6d0061e --- /dev/null +++ b/src/base/SnapGrid.cpp @@ -0,0 +1,192 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "SnapGrid.h" +#include "Composition.h" + +namespace Rosegarden { + + +////////////////////////////////////////////////////////////////////// +// SnapGrid +////////////////////////////////////////////////////////////////////// + + +const timeT SnapGrid::NoSnap = -1; +const timeT SnapGrid::SnapToBar = -2; +const timeT SnapGrid::SnapToBeat = -3; +const timeT SnapGrid::SnapToUnit = -4; + +SnapGrid::SnapGrid(RulerScale *rulerScale, int ysnap) : + m_rulerScale(rulerScale), + m_snapTime(SnapToBeat), + m_ysnap(ysnap) +{ + // nothing else +} + +void +SnapGrid::setSnapTime(timeT snap) +{ + assert(snap > 0 || + snap == NoSnap || + snap == SnapToBar || + snap == SnapToBeat || + snap == SnapToUnit); + m_snapTime = snap; +} + +timeT +SnapGrid::getSnapSetting() const +{ + return m_snapTime; +} + +timeT +SnapGrid::getSnapTime(double x) const +{ + timeT time = m_rulerScale->getTimeForX(x); + return getSnapTime(time); +} + +timeT +SnapGrid::getSnapTime(timeT time) const +{ + if (m_snapTime == NoSnap) return 0; + + Rosegarden::Composition *composition = m_rulerScale->getComposition(); + int barNo = composition->getBarNumber(time); + std::pair barRange = composition->getBarRange(barNo); + + timeT snapTime = barRange.second - barRange.first; + + if (m_snapTime == SnapToBeat) { + snapTime = composition->getTimeSignatureAt(time).getBeatDuration(); + } else if (m_snapTime == SnapToUnit) { + snapTime = composition->getTimeSignatureAt(time).getUnitDuration(); + } else if (m_snapTime != SnapToBar && m_snapTime < snapTime) { + snapTime = m_snapTime; + } + + return snapTime; +} + +timeT +SnapGrid::snapX(double x, SnapDirection direction) const +{ + return snapTime(m_rulerScale->getTimeForX(x), direction); +} + +timeT +SnapGrid::snapTime(timeT time, SnapDirection direction) const +{ + if (m_snapTime == NoSnap) return time; + + Rosegarden::Composition *composition = m_rulerScale->getComposition(); + int barNo = composition->getBarNumber(time); + std::pair barRange = composition->getBarRange(barNo); + + timeT snapTime = barRange.second - barRange.first; + + if (m_snapTime == SnapToBeat) { + snapTime = composition->getTimeSignatureAt(time).getBeatDuration(); + } else if (m_snapTime == SnapToUnit) { + snapTime = composition->getTimeSignatureAt(time).getUnitDuration(); + } else if (m_snapTime != SnapToBar && m_snapTime < snapTime) { + snapTime = m_snapTime; + } + + timeT offset = (time - barRange.first); + timeT rounded = (offset / snapTime) * snapTime; + + timeT left = rounded + barRange.first; + timeT right = left + snapTime; + + if (direction == SnapLeft) return left; + else if (direction == SnapRight) return right; + else if ((offset - rounded) > (rounded + snapTime - offset)) return right; + else return left; +} + +int +SnapGrid::getYBin(int y) const +{ + if (m_ysnap == 0) return y; + + int cy = 0; + + std::map::const_iterator i = m_ymultiple.begin(); + + int nextbin = -1; + if (i != m_ymultiple.end()) nextbin = i->first; + + for (int b = 0; ; ++b) { + + if (nextbin == b) { + + cy += i->second * m_ysnap; + ++i; + if (i == m_ymultiple.end()) nextbin = -1; + else nextbin = i->first; + + } else { + + cy += m_ysnap; + } + + if (cy > y) { + return b; + } + } +} + +int +SnapGrid::getYBinCoordinate(int bin) const +{ + if (m_ysnap == 0) return bin; + + int y = 0; + + std::map::const_iterator i = m_ymultiple.begin(); + + int nextbin = -1; + if (i != m_ymultiple.end()) nextbin = i->first; + + for (int b = 0; b < bin; ++b) { + + if (nextbin == b) { + + y += i->second * m_ysnap; + ++i; + if (i == m_ymultiple.end()) nextbin = -1; + else nextbin = i->first; + + } else { + + y += m_ysnap; + } + } + + return y; +} + + +} diff --git a/src/base/SnapGrid.h b/src/base/SnapGrid.h new file mode 100644 index 0000000..e0c9ec5 --- /dev/null +++ b/src/base/SnapGrid.h @@ -0,0 +1,183 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _SNAP_GRID_H_ +#define _SNAP_GRID_H_ + +#include "RulerScale.h" + +#include + +namespace Rosegarden { + +/** + * SnapGrid is a class that maps x-coordinate onto time, using a + * RulerScale to get the mapping but constraining the results to a + * discrete set of suitable times. + * + * (It also snaps y-coordinates, but that bit isn't very interesting.) + */ + +class SnapGrid +{ +public: + /** + * Construct a SnapGrid that uses the given RulerScale for + * x-coordinate mappings and the given ysnap for y-coords. + * If ysnap is zero, y-coords are not snapped at all. + */ + SnapGrid(RulerScale *rulerScale, int ysnap = 0); + + static const timeT NoSnap; + static const timeT SnapToBar; + static const timeT SnapToBeat; + static const timeT SnapToUnit; + + enum SnapDirection { SnapEither, SnapLeft, SnapRight }; + + /** + * Set the snap size of the grid to the given time. + * The snap time must be positive, or else one of the + * special constants NoSnap, SnapToBar, SnapToBeat or + * SnapToUnit. + * The default is SnapToBeat. + */ + void setSnapTime(timeT snap); + + /** + * Return the snap size of the grid, at the given x-coordinate. + * (The x-coordinate is required in case the built-in snap size is + * SnapToBar, SnapToBeat or SnapToUnit, in which case we need to + * know the current time signature.) Returns zero for NoSnap. + */ + timeT getSnapTime(double x) const; + + /** + * Return the snap setting -- the argument that was passed to + * setSnapTime. This differs from getSnapTime, which interprets + * the NoSnap, SnapToBar, SnapToBeat and SnapToUnit settings to + * return actual timeT values; instead this function returns those + * actual constants if set. + */ + timeT getSnapSetting() const; + + /** + * Return the snap size of the grid, at the given time. (The time + * is required in case the built-in snap size is SnapToBar, + * SnapToBeat or SnapToUnit, in which case we need to know the + * current time signature.) Returns zero for NoSnap. + */ + timeT getSnapTime(timeT t) const; + + /** + * Snap a given x-coordinate to the nearest time on the grid. Of + * course this also does x-to-time conversion, so it's useful even + * in NoSnap mode. If the snap time is greater than the bar + * duration at this point, the bar duration will be used instead. + * + * If d is SnapLeft or SnapRight, a time to the left or right + * respectively of the given coordinate will be returned; + * otherwise the nearest time on either side will be returned. + */ + timeT snapX(double x, SnapDirection d = SnapEither) const; + + /** + * Snap a given time to the nearest time on the grid. Unlike + * snapX, this is not useful in NoSnap mode. If the snap time is + * greater than the bar duration at this point, the bar duration + * will be used instead. + * + * If d is SnapLeft or SnapRight, a time to the left or right + * respectively of the given coordinate will be returned; + * otherwise the nearest time on either side will be returned. + */ + timeT snapTime(timeT t, SnapDirection d = SnapEither) const; + + /** + * Snap a given y-coordinate to the nearest lower bin coordinate. + */ + int snapY(int y) const { + if (m_ysnap == 0) return y; + return getYBinCoordinate(getYBin(y)); + } + + /** + * Return the bin number for the given y-coordinate. + */ + int getYBin(int y) const; + + /** + * Return the y-coordinate of the grid line at the start of the + * given bin. + */ + int getYBinCoordinate(int bin) const; + + /** + * Set the default vertical step. This is used as the height for + * bins that have no specific height multiple set, and the base + * height for bins that have a multiple. Setting the Y snap here + * is equivalent to specifying it in the constructor. + */ + void setYSnap(int ysnap) { + m_ysnap = ysnap; + } + + /** + * Retrieve the default vertical step. + */ + int getYSnap() const { + return m_ysnap; + } + + /** + * Set the height multiple for a specific bin. The bin will be + * multiple * ysnap high. The default is 1 for all bins. + */ + void setBinHeightMultiple(int bin, int multiple) { + m_ymultiple[bin] = multiple; + } + + /** + * Retrieve the height multiple for a bin. + */ + int getBinHeightMultiple(int bin) { + if (m_ymultiple.find(bin) == m_ymultiple.end()) return 1; + return m_ymultiple[bin]; + } + + RulerScale *getRulerScale() { + return m_rulerScale; + } + + const RulerScale *getRulerScale() const { + return m_rulerScale; + } + +protected: + RulerScale *m_rulerScale; // I don't own this + timeT m_snapTime; + int m_ysnap; + std::map m_ymultiple; +}; + +} + +#endif diff --git a/src/base/SoftSynthDevice.cpp b/src/base/SoftSynthDevice.cpp new file mode 100644 index 0000000..9a736b7 --- /dev/null +++ b/src/base/SoftSynthDevice.cpp @@ -0,0 +1,174 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "SoftSynthDevice.h" +#include "Instrument.h" +#include "MidiTypes.h" + +#include +#include + + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + + +namespace Rosegarden +{ + +ControlList +SoftSynthDevice::m_controlList; + +SoftSynthDevice::SoftSynthDevice() : + Device(0, "Default Soft Synth Device", Device::SoftSynth) +{ + checkControlList(); +} + +SoftSynthDevice::SoftSynthDevice(DeviceId id, const std::string &name) : + Device(id, name, Device::SoftSynth) +{ + checkControlList(); +} + + +SoftSynthDevice::SoftSynthDevice(const SoftSynthDevice &dev) : + Device(dev.getId(), dev.getName(), dev.getType()), + Controllable() +{ + // Copy the instruments + // + InstrumentList insList = dev.getAllInstruments(); + InstrumentList::iterator iIt = insList.begin(); + for (; iIt != insList.end(); iIt++) + m_instruments.push_back(new Instrument(**iIt)); +} + +SoftSynthDevice::~SoftSynthDevice() +{ +} + +void +SoftSynthDevice::checkControlList() +{ + // Much as MidiDevice::generateDefaultControllers + + static std::string controls[][9] = { + { "Pan", Rosegarden::Controller::EventType, "", "0", "127", "64", "10", "2", "0" }, + { "Chorus", Rosegarden::Controller::EventType, "", "0", "127", "0", "93", "3", "1" }, + { "Volume", Rosegarden::Controller::EventType, "", "0", "127", "0", "7", "1", "2" }, + { "Reverb", Rosegarden::Controller::EventType, "", "0", "127", "0", "91", "3", "3" }, + { "Sustain", Rosegarden::Controller::EventType, "", "0", "127", "0", "64", "4", "-1" }, + { "Expression", Rosegarden::Controller::EventType, "", "0", "127", "100", "11", "2", "-1" }, + { "Modulation", Rosegarden::Controller::EventType, "", "0", "127", "0", "1", "4", "-1" }, + { "PitchBend", Rosegarden::PitchBend::EventType, "", "0", "16383", "8192", "1", "4", "-1" } + }; + + if (m_controlList.empty()) { + + for (unsigned int i = 0; i < sizeof(controls) / sizeof(controls[0]); ++i) { + + Rosegarden::ControlParameter con(controls[i][0], + controls[i][1], + controls[i][2], + atoi(controls[i][3].c_str()), + atoi(controls[i][4].c_str()), + atoi(controls[i][5].c_str()), + Rosegarden::MidiByte(atoi(controls[i][6].c_str())), + atoi(controls[i][7].c_str()), + atoi(controls[i][8].c_str())); + m_controlList.push_back(con); + } + } +} + +const ControlParameter * +SoftSynthDevice::getControlParameter(int index) const +{ + if (index >= 0 && ((unsigned int)index) < m_controlList.size()) + return &m_controlList[index]; + return 0; +} + +const ControlParameter * +SoftSynthDevice::getControlParameter(const std::string &type, + Rosegarden::MidiByte controllerValue) const +{ + ControlList::iterator it = m_controlList.begin(); + + for (; it != m_controlList.end(); ++it) + { + if (it->getType() == type) + { + // Return matched on type for most events + // + if (type != Rosegarden::Controller::EventType) + return &*it; + + // Also match controller value for Controller events + // + if (it->getControllerValue() == controllerValue) + return &*it; + } + } + + return 0; +} + +std::string +SoftSynthDevice::toXmlString() +{ + std::stringstream ssiDevice; + InstrumentList::iterator iit; + + ssiDevice << " " << std::endl; + + for (iit = m_instruments.begin(); iit != m_instruments.end(); ++iit) + ssiDevice << (*iit)->toXmlString(); + + ssiDevice << " " +#if (__GNUC__ < 3) + << std::endl << std::ends; +#else + << std::endl; +#endif + + return ssiDevice.str(); +} + + +// Add to instrument list +// +void +SoftSynthDevice::addInstrument(Instrument *instrument) +{ + m_instruments.push_back(instrument); +} + +} + + diff --git a/src/base/SoftSynthDevice.h b/src/base/SoftSynthDevice.h new file mode 100644 index 0000000..381a58b --- /dev/null +++ b/src/base/SoftSynthDevice.h @@ -0,0 +1,70 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _SOFT_SYNTH_DEVICE_H_ +#define _SOFT_SYNTH_DEVICE_H_ + +#include + +#include "Device.h" +#include "Instrument.h" +#include "Controllable.h" + +namespace Rosegarden +{ + +class SoftSynthDevice : public Device, public Controllable +{ + +public: + SoftSynthDevice(); + SoftSynthDevice(DeviceId id, const std::string &name); + virtual ~SoftSynthDevice(); + + // Copy constructor + // + SoftSynthDevice(const SoftSynthDevice &); + + virtual void addInstrument(Instrument*); + + // Turn into XML string + // + virtual std::string toXmlString(); + + virtual InstrumentList getAllInstruments() const { return m_instruments; } + virtual InstrumentList getPresentationInstruments() const + { return m_instruments; } + + // implemented from Controllable interface + // + virtual const ControlList &getControlParameters() const { return m_controlList; } + virtual const ControlParameter *getControlParameter(int index) const; + virtual const ControlParameter *getControlParameter(const std::string &type, + MidiByte controllerNumber) const; + +private: + static ControlList m_controlList; + static void checkControlList(); +}; + +} + +#endif diff --git a/src/base/Staff.cpp b/src/base/Staff.cpp new file mode 100644 index 0000000..e34aaca --- /dev/null +++ b/src/base/Staff.cpp @@ -0,0 +1,213 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Staff.h" + +namespace Rosegarden +{ + +Staff::Staff(Segment &t) : + m_segment(t), + m_viewElementList(0) +{ + // empty +} + +Staff::~Staff() +{ + if (m_viewElementList) m_segment.removeObserver(this); + notifySourceDeletion(); + delete m_viewElementList; +} + +ViewElementList * +Staff::getViewElementList() +{ + return getViewElementList(m_segment.begin(), m_segment.end()); +} + +ViewElementList * +Staff::getViewElementList(Segment::iterator from, + Segment::iterator to) +{ + if (!m_viewElementList) { + + m_viewElementList = new ViewElementList; + + for (Segment::iterator i = from; i != to; ++i) { + + if (!wrapEvent(*i)) continue; + + ViewElement *el = makeViewElement(*i); + m_viewElementList->insert(el); + } + + m_segment.addObserver(this); + + } + + return m_viewElementList; +} + +bool +Staff::wrapEvent(Event *e) +{ + timeT emt = m_segment.getEndMarkerTime(); + return + (e->getAbsoluteTime() < emt) || + (e->getAbsoluteTime() == emt && e->getDuration() == 0); +} + +ViewElementList::iterator +Staff::findEvent(Event *e) +{ + // Note that we have to create this using the virtual + // makeViewElement, because the result of equal_range depends on + // the value of the view absolute time for the element, which + // depends on the particular subclass of ViewElement in use. + + //!!! (This is also why this method has to be here and not in + // ViewElementList -- ViewElementList has no equivalent of + // makeViewElement. Possibly things like NotationElementList + // should be subclasses of ViewElementList that implement + // makeViewElement instead of having makeViewElement in Staff, but + // that's for another day.) + + ViewElement *dummy = makeViewElement(e); + + std::pair + r = m_viewElementList->equal_range(dummy); + + delete dummy; + + for (ViewElementList::iterator i = r.first; i != r.second; ++i) { + if ((*i)->event() == e) { + return i; + } + } + + return m_viewElementList->end(); +} + +void +Staff::eventAdded(const Segment *t, Event *e) +{ + assert(t == &m_segment); + (void)t; // avoid warnings + + if (wrapEvent(e)) { + ViewElement *el = makeViewElement(e); + m_viewElementList->insert(el); + notifyAdd(el); + } +} + +void +Staff::eventRemoved(const Segment *t, Event *e) +{ + assert(t == &m_segment); + (void)t; // avoid warnings + + // If we have it, lose it + + ViewElementList::iterator i = findEvent(e); + if (i != m_viewElementList->end()) { + notifyRemove(*i); + m_viewElementList->erase(i); + return; + } + +// std::cerr << "Event at " << e->getAbsoluteTime() << ", notation time " << e->getNotationAbsoluteTime() << ", type " << e->getType() +// << " not found in Staff" << std::endl; +} + +void +Staff::endMarkerTimeChanged(const Segment *segment, bool shorten) +{ + Segment *s = const_cast(segment); + + assert(s == &m_segment); + + if (shorten) { + + m_viewElementList->erase + (m_viewElementList->findTime(s->getEndMarkerTime()), + m_viewElementList->end()); + + } else { + + timeT myLastEltTime = s->getStartTime(); + if (m_viewElementList->end() != m_viewElementList->begin()) { + ViewElementList::iterator i = m_viewElementList->end(); + myLastEltTime = (*--i)->event()->getAbsoluteTime(); + } + + for (Segment::iterator j = s->findTime(myLastEltTime); + s->isBeforeEndMarker(j); ++j) { + + ViewElementList::iterator newi = findEvent(*j); + if (newi == m_viewElementList->end()) { + m_viewElementList->insert(makeViewElement(*j)); + } + } + } +} +void +Staff::segmentDeleted(const Segment *s) +{ + assert(s == &m_segment); + (void)s; // avoid warnings + /* + std::cerr << "WARNING: Staff notified of segment deletion: this is probably a bug " + << "(staff should have been deleted before segment)" << std::endl; + */ +} + +void +Staff::notifyAdd(ViewElement *e) const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->elementAdded(this, e); + } +} + +void +Staff::notifyRemove(ViewElement *e) const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->elementRemoved(this, e); + } +} + +void +Staff::notifySourceDeletion() const +{ + for (ObserverSet::const_iterator i = m_observers.begin(); + i != m_observers.end(); ++i) { + (*i)->staffDeleted(this); + } +} + + +} diff --git a/src/base/Staff.h b/src/base/Staff.h new file mode 100644 index 0000000..ad06930 --- /dev/null +++ b/src/base/Staff.h @@ -0,0 +1,149 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _STAFF_H_ +#define _STAFF_H_ + +#include "ViewElement.h" +#include "Segment.h" + +#include +#include + +namespace Rosegarden +{ + +class StaffObserver; + +/** + * Staff is the base class for classes which represent a Segment as an + * on-screen graphic. It manages the relationship between Segment/Event + * and specific implementations of ViewElement. + * + * The template argument T must be a subclass of ViewElement. + * + * Staff was formerly known as ViewElementsManager. + */ +class Staff : public SegmentObserver +{ +public: + virtual ~Staff(); + + /** + * Create a new ViewElementList wrapping all Events in the + * segment, or return the previously created one + */ + ViewElementList *getViewElementList(); + + /** + * Create a new ViewElementList wrapping Events in the + * [from, to[ interval, or return the previously created one + * (even if passed new arguments) + */ + ViewElementList *getViewElementList(Segment::iterator from, + Segment::iterator to); + + /** + * Return the Segment wrapped by this object + */ + Segment &getSegment() { return m_segment; } + + /** + * Return the Segment wrapped by this object + */ + const Segment &getSegment() const { return m_segment; } + + /** + * Return the location of the given event in this Staff + */ + ViewElementList::iterator findEvent(Event *); + + /** + * SegmentObserver method - called after the event has been added to + * the segment + */ + virtual void eventAdded(const Segment *, Event *); + + /** + * SegmentObserver method - called after the event has been removed + * from the segment, and just before it is deleted + */ + virtual void eventRemoved(const Segment *, Event *); + + /** + * SegmentObserver method - called after the segment's end marker + * time has been changed + */ + virtual void endMarkerTimeChanged(const Segment *, bool shorten); + + /** + * SegmentObserver method - called from Segment dtor + */ + virtual void segmentDeleted(const Segment *); + + void addObserver (StaffObserver *obs) { m_observers.push_back(obs); } + void removeObserver(StaffObserver *obs) { m_observers.remove(obs); } + +protected: + Staff(Segment &); + virtual ViewElement* makeViewElement(Event*) = 0; + + /** + * Return true if the event should be wrapped + * Useful for piano roll where we only want to wrap notes + * (always true by default) + */ + virtual bool wrapEvent(Event *); + + void notifyAdd(ViewElement *) const; + void notifyRemove(ViewElement *) const; + void notifySourceDeletion() const; + + //--------------- Data members --------------------------------- + + Segment &m_segment; + ViewElementList *m_viewElementList; + + typedef std::list ObserverSet; + ObserverSet m_observers; + +private: // not provided + Staff(const Staff &); + Staff &operator=(const Staff &); +}; + +class StaffObserver +{ +public: + virtual ~StaffObserver() {} + virtual void elementAdded(const Staff *, ViewElement *) = 0; + virtual void elementRemoved(const Staff *, ViewElement *) = 0; + + /// called when the observed object is being deleted + virtual void staffDeleted(const Staff *) = 0; +}; + + + +} + +#endif + diff --git a/src/base/StaffExportTypes.h b/src/base/StaffExportTypes.h new file mode 100644 index 0000000..0aeeb78 --- /dev/null +++ b/src/base/StaffExportTypes.h @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file Coypright 2008 D. Michael McIntyre + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 _STAFF_EXPORT_H_ +#define _STAFF_EXPORT_H_ + +namespace Rosegarden +{ + +/** + * StaffTypes are currently only used for LilyPond export, and amount to named + * constant indices for the Track Parameters Box. They are used to control the + * size of notation exported on a given staff, and boil down to a complicated + * way to insert a \tiny or \small in the data stream ahead of the first clef, + * etc. + */ + +typedef int StaffType; + +namespace StaffTypes +{ + const StaffType Normal = 0; + const StaffType Small = 1; + const StaffType Tiny = 2; +} + +/** + * Brackets are currently only used for LilyPond export, and amount to named + * constant indices for the Track Parameters Box. They are used to control how + * staffs are bracketed together, and it is unfortunately necessary to have a + * staggering number of them in order to handle all the possible combinations of + * opening and closing brackets while keeping the interface as simple as + * possible. + */ + +typedef int Bracket; + +namespace Brackets +{ + const Bracket None = 0; // ---- + const Bracket SquareOn = 1; // [ + const Bracket SquareOff = 2; // ] + const Bracket SquareOnOff = 3; // [ ] + const Bracket CurlyOn = 4; // { + const Bracket CurlyOff = 5; // } + const Bracket CurlySquareOn = 6; // {[ + const Bracket CurlySquareOff = 7; // ]} +} + +} + +#endif diff --git a/src/base/Studio.cpp b/src/base/Studio.cpp new file mode 100644 index 0000000..89e7cc1 --- /dev/null +++ b/src/base/Studio.cpp @@ -0,0 +1,674 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 + +#include "Studio.h" +#include "MidiDevice.h" +#include "AudioDevice.h" +#include "Instrument.h" + +#include "Segment.h" +#include "Track.h" +#include "Composition.h" + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +using std::cerr; +using std::endl; + + +namespace Rosegarden +{ + +Studio::Studio() : + m_midiThruFilter(0), + m_midiRecordFilter(0), + m_mixerDisplayOptions(0), + m_metronomeDevice(0) +{ + // We _always_ have a buss with id zero, for the master out + m_busses.push_back(new Buss(0)); + + // And we always create one audio record in + m_recordIns.push_back(new RecordIn()); +} + +Studio::~Studio() +{ + DeviceListIterator dIt = m_devices.begin(); + + for (; dIt != m_devices.end(); ++dIt) + delete(*dIt); + + m_devices.clear(); + + for (size_t i = 0; i < m_busses.size(); ++i) { + delete m_busses[i]; + } + + for (size_t i = 0; i < m_recordIns.size(); ++i) { + delete m_recordIns[i]; + } +} + +void +Studio::addDevice(const std::string &name, + DeviceId id, + Device::DeviceType type) +{ + switch(type) + { + case Device::Midi: + m_devices.push_back(new MidiDevice(id, name, MidiDevice::Play)); + break; + + case Device::Audio: + m_devices.push_back(new AudioDevice(id, name)); + break; + + default: + std::cerr << "Studio::addDevice() - unrecognised device" + << endl; + break; + } +} + +void +Studio::addDevice(Device *device) +{ + m_devices.push_back(device); +} + +void +Studio::removeDevice(DeviceId id) +{ + DeviceListIterator it; + for (it = m_devices.begin(); it != m_devices.end(); it++) + { + if ((*it)->getId() == id) { + delete *it; + m_devices.erase(it); + return; + } + } +} + +InstrumentList +Studio::getAllInstruments() +{ + InstrumentList list, subList; + + DeviceListIterator it; + + // Append lists + // + for (it = m_devices.begin(); it != m_devices.end(); it++) + { + // get sub list + subList = (*it)->getAllInstruments(); + + // concetenate + list.insert(list.end(), subList.begin(), subList.end()); + } + + return list; + +} + +InstrumentList +Studio::getPresentationInstruments() +{ + InstrumentList list, subList; + + std::vector::iterator it; + MidiDevice *midiDevice; + + // Append lists + // + for (it = m_devices.begin(); it != m_devices.end(); it++) + { + midiDevice = dynamic_cast(*it); + + if (midiDevice) + { + // skip read-only devices + if (midiDevice->getDirection() == MidiDevice::Record) + continue; + } + + // get sub list + subList = (*it)->getPresentationInstruments(); + + // concatenate + list.insert(list.end(), subList.begin(), subList.end()); + } + + return list; + +} + +Instrument* +Studio::getInstrumentById(InstrumentId id) +{ + std::vector::iterator it; + InstrumentList list; + InstrumentList::iterator iit; + + for (it = m_devices.begin(); it != m_devices.end(); it++) + { + list = (*it)->getAllInstruments(); + + for (iit = list.begin(); iit != list.end(); iit++) + if ((*iit)->getId() == id) + return (*iit); + } + + return 0; + +} + +// From a user selection (from a "Presentation" list) return +// the matching Instrument +// +Instrument* +Studio::getInstrumentFromList(int index) +{ + std::vector::iterator it; + InstrumentList list; + InstrumentList::iterator iit; + int count = 0; + + for (it = m_devices.begin(); it != m_devices.end(); it++) + { + MidiDevice *midiDevice = dynamic_cast(*it); + + if (midiDevice) + { + // skip read-only devices + if (midiDevice->getDirection() == MidiDevice::Record) + continue; + } + + list = (*it)->getPresentationInstruments(); + + for (iit = list.begin(); iit != list.end(); iit++) + { + if (count == index) + return (*iit); + + count++; + } + } + + return 0; + +} + +Instrument * +Studio::getInstrumentFor(Segment *segment) +{ + if (!segment) return 0; + if (!segment->getComposition()) return 0; + TrackId tid = segment->getTrack(); + Track *track = segment->getComposition()->getTrackById(tid); + if (!track) return 0; + return getInstrumentFor(track); +} + +Instrument * +Studio::getInstrumentFor(Track *track) +{ + if (!track) return 0; + InstrumentId iid = track->getInstrument(); + return getInstrumentById(iid); +} + +BussList +Studio::getBusses() +{ + return m_busses; +} + +Buss * +Studio::getBussById(BussId id) +{ + for (BussList::iterator i = m_busses.begin(); i != m_busses.end(); ++i) { + if ((*i)->getId() == id) return *i; + } + return 0; +} + +void +Studio::addBuss(Buss *buss) +{ + m_busses.push_back(buss); +} + +PluginContainer * +Studio::getContainerById(InstrumentId id) +{ + PluginContainer *pc = getInstrumentById(id); + if (pc) return pc; + else return getBussById(id); +} + +RecordIn * +Studio::getRecordIn(int number) +{ + if (number >= 0 && number < int(m_recordIns.size())) return m_recordIns[number]; + else return 0; +} + +// Clear down the devices - the devices will clear down their +// own Instruments. +// +void +Studio::clear() +{ + InstrumentList list; + std::vector::iterator it; + + for (it = m_devices.begin(); it != m_devices.end(); it++) + delete *it; + + m_devices.erase(m_devices.begin(), m_devices.end()); +} + +std::string +Studio::toXmlString() +{ + return toXmlString(std::vector()); +} + +std::string +Studio::toXmlString(const std::vector &devices) +{ + std::stringstream studio; + + studio << "" << endl << endl; + + studio << endl; + + InstrumentList list; + + // Get XML version of devices + // + if (devices.empty()) { // export all devices and busses + + for (DeviceListIterator it = m_devices.begin(); + it != m_devices.end(); it++) { + studio << (*it)->toXmlString() << endl << endl; + } + + for (BussList::iterator it = m_busses.begin(); + it != m_busses.end(); ++it) { + studio << (*it)->toXmlString() << endl << endl; + } + + } else { + for (std::vector::const_iterator di(devices.begin()); + di != devices.end(); ++di) { + Device *d = getDevice(*di); + if (!d) { + std::cerr << "WARNING: Unknown device id " << (*di) + << " in Studio::toXmlString" << std::endl; + } else { + studio << d->toXmlString() << endl << endl; + } + } + } + + studio << endl << endl; + +#if (__GNUC__ < 3) + studio << "" << endl << std::ends; +#else + studio << "" << endl; +#endif + + return studio.str(); +} + +// Run through the Devices checking for MidiDevices and +// returning the first Metronome we come across +// +const MidiMetronome* +Studio::getMetronomeFromDevice(DeviceId id) +{ + std::vector::iterator it; + MidiDevice *midiDevice; + + for (it = m_devices.begin(); it != m_devices.end(); it++) + { + midiDevice = dynamic_cast(*it); + + if (midiDevice && + midiDevice->getId() == id && + midiDevice->getMetronome()) + { + return midiDevice->getMetronome(); + } + } + + return 0; +} + +// Scan all MIDI devices for available channels and map +// them to a current program + +Instrument* +Studio::assignMidiProgramToInstrument(MidiByte program, + int msb, int lsb, + bool percussion) +{ + MidiDevice *midiDevice; + std::vector::iterator it; + Rosegarden::InstrumentList::iterator iit; + Rosegarden::InstrumentList instList; + + // Instruments that we may return + // + Rosegarden::Instrument *newInstrument = 0; + Rosegarden::Instrument *firstInstrument = 0; + + bool needBank = (msb >= 0 || lsb >= 0); + if (needBank) { + if (msb < 0) msb = 0; + if (lsb < 0) lsb = 0; + } + + // Pass one - search through all MIDI instruments looking for + // a match that we can re-use. i.e. if we have a matching + // Program Change then we can use this Instrument. + // + for (it = m_devices.begin(); it != m_devices.end(); it++) + { + midiDevice = dynamic_cast(*it); + + if (midiDevice && midiDevice->getDirection() == MidiDevice::Play) + { + instList = (*it)->getPresentationInstruments(); + + for (iit = instList.begin(); iit != instList.end(); iit++) + { + if (firstInstrument == 0) + firstInstrument = *iit; + + // If we find an Instrument sending the right program already. + // + if ((*iit)->sendsProgramChange() && + (*iit)->getProgramChange() == program && + (!needBank || ((*iit)->sendsBankSelect() && + (*iit)->getMSB() == msb && + (*iit)->getLSB() == lsb && + (*iit)->isPercussion() == percussion))) + { + return (*iit); + } + else + { + // Ignore the program change and use the percussion + // flag. + // + if ((*iit)->isPercussion() && percussion) + { + return (*iit); + } + + // Otherwise store the first Instrument for + // possible use later. + // + if (newInstrument == 0 && + (*iit)->sendsProgramChange() == false && + (*iit)->sendsBankSelect() == false && + (*iit)->isPercussion() == percussion) + newInstrument = *iit; + } + } + } + } + + + // Okay, if we've got this far and we have a new Instrument to use + // then use it. + // + if (newInstrument != 0) + { + newInstrument->setSendProgramChange(true); + newInstrument->setProgramChange(program); + + if (needBank) { + newInstrument->setSendBankSelect(true); + newInstrument->setPercussion(percussion); + newInstrument->setMSB(msb); + newInstrument->setLSB(lsb); + } + } + else // Otherwise we just reuse the first Instrument we found + newInstrument = firstInstrument; + + + return newInstrument; +} + +// Just make all of these Instruments available for automatic +// assignment in the assignMidiProgramToInstrument() method +// by invalidating the ProgramChange flag. +// +// This method sounds much more dramatic than it actually is - +// it could probably do with a rename. +// +// +void +Studio::unassignAllInstruments() +{ + MidiDevice *midiDevice; + AudioDevice *audioDevice; + std::vector::iterator it; + Rosegarden::InstrumentList::iterator iit; + Rosegarden::InstrumentList instList; + int channel = 0; + + for (it = m_devices.begin(); it != m_devices.end(); it++) + { + midiDevice = dynamic_cast(*it); + + if (midiDevice) + { + instList = (*it)->getPresentationInstruments(); + + for (iit = instList.begin(); iit != instList.end(); iit++) + { + // Only for true MIDI Instruments - not System ones + // + if ((*iit)->getId() >= MidiInstrumentBase) + { + (*iit)->setSendBankSelect(false); + (*iit)->setSendProgramChange(false); + (*iit)->setMidiChannel(channel); + channel = ( channel + 1 ) % 16; + + (*iit)->setSendPan(false); + (*iit)->setSendVolume(false); + (*iit)->setPan(MidiMidValue); + (*iit)->setVolume(100); + + } + } + } + else + { + audioDevice = dynamic_cast(*it); + + if (audioDevice) + { + instList = (*it)->getPresentationInstruments(); + + for (iit = instList.begin(); iit != instList.end(); iit++) + (*iit)->emptyPlugins(); + } + } + } +} + +void +Studio::clearMidiBanksAndPrograms() +{ + MidiDevice *midiDevice; + std::vector::iterator it; + + for (it = m_devices.begin(); it != m_devices.end(); it++) + { + midiDevice = dynamic_cast(*it); + + if (midiDevice) + { + midiDevice->clearProgramList(); + midiDevice->clearBankList(); + } + } +} + +void +Studio::clearBusses() +{ + for (size_t i = 0; i < m_busses.size(); ++i) { + delete m_busses[i]; + } + m_busses.clear(); + m_busses.push_back(new Buss(0)); +} + +void +Studio::clearRecordIns() +{ + for (size_t i = 0; i < m_recordIns.size(); ++i) { + delete m_recordIns[i]; + } + m_recordIns.clear(); + m_recordIns.push_back(new RecordIn()); +} + +Device* +Studio::getDevice(DeviceId id) +{ + //cerr << "Studio[" << this << "]::getDevice(" << id << ")... "; + + std::vector::iterator it; + + for (it = m_devices.begin(); it != m_devices.end(); it++) { + +// if (it != m_devices.begin()) cerr << ", "; + +// cerr << (*it)->getId(); + if ((*it)->getId() == id) { + //cerr << ". Found" << endl; + return (*it); + } + } + + //cerr << ". Not found" << endl; + + return 0; +} + +std::string +Studio::getSegmentName(InstrumentId id) +{ + MidiDevice *midiDevice; + std::vector::iterator it; + Rosegarden::InstrumentList::iterator iit; + Rosegarden::InstrumentList instList; + + for (it = m_devices.begin(); it != m_devices.end(); it++) + { + midiDevice = dynamic_cast(*it); + + if (midiDevice) + { + instList = (*it)->getAllInstruments(); + + for (iit = instList.begin(); iit != instList.end(); iit++) + { + if ((*iit)->getId() == id) + { + if ((*iit)->sendsProgramChange()) + { + return (*iit)->getProgramName(); + } + else + { + return midiDevice->getName() + " " + (*iit)->getName(); + } + } + } + } + } + + return std::string(""); +} + +InstrumentId +Studio::getAudioPreviewInstrument() +{ + AudioDevice *audioDevice; + std::vector::iterator it; + + for (it = m_devices.begin(); it != m_devices.end(); it++) + { + audioDevice = dynamic_cast(*it); + + // Just the first one will do - we can make this more + // subtle if we need to later. + // + if (audioDevice) + return audioDevice->getPreviewInstrument(); + } + + // system instrument - won't accept audio + return 0; +} + +bool +Studio::haveMidiDevices() const +{ + Rosegarden::DeviceListConstIterator it = m_devices.begin(); + for (; it != m_devices.end(); it++) + { + if ((*it)->getType() == Device::Midi) return true; + } + return false; +} + + +} + diff --git a/src/base/Studio.h b/src/base/Studio.h new file mode 100644 index 0000000..7223524 --- /dev/null +++ b/src/base/Studio.h @@ -0,0 +1,208 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 +#include + +#include "XmlExportable.h" +#include "Instrument.h" +#include "Device.h" +#include "MidiProgram.h" +#include "ControlParameter.h" + +// The Studio is where Midi and Audio devices live. We can query +// them for a list of Instruments, connect them together or to +// effects units (eventually) and generally do real studio-type +// stuff to them. +// +// + + +#ifndef _STUDIO_H_ +#define _STUDIO_H_ + +namespace Rosegarden +{ + +typedef std::vector InstrumentList; +typedef std::vector DeviceList; +typedef std::vector BussList; +typedef std::vector RecordInList; +typedef std::vector::iterator DeviceListIterator; +typedef std::vector::const_iterator DeviceListConstIterator; + +class MidiDevice; + +class Segment; +class Track; + + +class Studio : public XmlExportable +{ + +public: + Studio(); + ~Studio(); + +private: + Studio(const Studio &); + Studio& operator=(const Studio &); +public: + + void addDevice(const std::string &name, + DeviceId id, + Device::DeviceType type); + void addDevice(Device *device); + + void removeDevice(DeviceId id); + + // Return the combined instrument list from all devices + // for all and presentation Instrument (Presentation is + // just a subset of All). + // + InstrumentList getAllInstruments(); + InstrumentList getPresentationInstruments(); + + // Return an Instrument + Instrument* getInstrumentById(InstrumentId id); + Instrument* getInstrumentFromList(int index); + + // Convenience functions + Instrument *getInstrumentFor(Segment *); + Instrument *getInstrumentFor(Track *); + + // Return a Buss + BussList getBusses(); + Buss *getBussById(BussId id); + void addBuss(Buss *buss); + + // Return an Instrument or a Buss + PluginContainer *getContainerById(InstrumentId id); + + RecordInList getRecordIns() { return m_recordIns; } + RecordIn *getRecordIn(int number); + void addRecordIn(RecordIn *ri) { m_recordIns.push_back(ri); } + + // A clever method to best guess MIDI file program mappings + // to available MIDI channels across all MidiDevices. + // + // Set the percussion flag if it's a percussion channel (mapped + // channel) we're after. + // + Instrument* assignMidiProgramToInstrument(MidiByte program, + bool percussion) { + return assignMidiProgramToInstrument(program, -1, -1, percussion); + } + + // Same again, but with bank select + // + Instrument* assignMidiProgramToInstrument(MidiByte program, + int msb, int lsb, + bool percussion); + + // Get a suitable name for a Segment belonging to this instrument. + // Takes into account ProgramChanges. + // + std::string getSegmentName(InstrumentId id); + + // Clear down all the ProgramChange flags in all MIDI Instruments + // + void unassignAllInstruments(); + + // Clear down all MIDI banks and programs on all MidiDevices + // prior to reloading. The Instruments and Devices are generated + // at the Sequencer - the Banks and Programs are loaded from the + // RG4 file. + // + void clearMidiBanksAndPrograms(); + + void clearBusses(); + void clearRecordIns(); + + // Clear down + void clear(); + + // Get a MIDI metronome from a given device + // + const MidiMetronome* getMetronomeFromDevice(DeviceId id); + + // Return the device list + // + DeviceList* getDevices() { return &m_devices; } + + // Const iterators + // + DeviceListConstIterator begin() const { return m_devices.begin(); } + DeviceListConstIterator end() const { return m_devices.end(); } + + // Get a device by ID + // + Device* getDevice(DeviceId id); + + bool haveMidiDevices() const; + + // Export as XML string + // + virtual std::string toXmlString(); + + // Export a subset of devices as XML string. If devices is empty, + // exports all devices just as the above method does. + // + virtual std::string toXmlString(const std::vector &devices); + + // Get an audio preview Instrument + // + InstrumentId getAudioPreviewInstrument(); + + // MIDI filtering into and thru Rosegarden + // + void setMIDIThruFilter(MidiFilter filter) { m_midiThruFilter = filter; } + MidiFilter getMIDIThruFilter() const { return m_midiThruFilter; } + + void setMIDIRecordFilter(MidiFilter filter) { m_midiRecordFilter = filter; } + MidiFilter getMIDIRecordFilter() const { return m_midiRecordFilter; } + + void setMixerDisplayOptions(unsigned int options) { m_mixerDisplayOptions = options; } + unsigned int getMixerDisplayOptions() const { return m_mixerDisplayOptions; } + + DeviceId getMetronomeDevice() const { return m_metronomeDevice; } + void setMetronomeDevice(DeviceId device) { m_metronomeDevice = device; } + +private: + + DeviceList m_devices; + + BussList m_busses; + RecordInList m_recordIns; + + int m_audioInputs; // stereo pairs + + MidiFilter m_midiThruFilter; + MidiFilter m_midiRecordFilter; + + unsigned int m_mixerDisplayOptions; + + DeviceId m_metronomeDevice; +}; + +} + +#endif // _STUDIO_H_ diff --git a/src/base/Track.cpp b/src/base/Track.cpp new file mode 100644 index 0000000..903e500 --- /dev/null +++ b/src/base/Track.cpp @@ -0,0 +1,201 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "Track.h" +#include +#include + +#if (__GNUC__ < 3) +#include +#define stringstream strstream +#else +#include +#endif + +#include "Composition.h" +#include "StaffExportTypes.h" + +namespace Rosegarden +{ + +Track::Track(): + m_id(0), + m_muted(false), + m_position(-1), + m_instrument(0), + m_owningComposition(0), + m_input_device(Device::ALL_DEVICES), + m_input_channel(-1), + m_armed(false), + m_clef(0), + m_transpose(0), + m_color(0), + m_highestPlayable(127), + m_lowestPlayable(0), + m_staffSize(StaffTypes::Normal), + m_staffBracket(Brackets::None) +{ +} + +Track::Track(TrackId id, + InstrumentId instrument, + int position, + const std::string &label, + bool muted): + m_id(id), + m_muted(muted), + m_label(label), + m_position(position), + m_instrument(instrument), + m_owningComposition(0), + m_input_device(Device::ALL_DEVICES), + m_input_channel(-1), + m_armed(false), + m_clef(0), + m_transpose(0), + m_color(0), + m_highestPlayable(127), + m_lowestPlayable(0), + m_staffSize(StaffTypes::Normal), + m_staffBracket(Brackets::None) +{ +} + +Track::~Track() +{ +} + +void Track::setMuted(bool muted) +{ + if (m_muted == muted) return; + + m_muted = muted; + + if (m_owningComposition) + m_owningComposition->notifyTrackChanged(this); +} + +void Track::setLabel(const std::string &label) +{ + if (m_label == label) return; + + m_label = label; + + if (m_owningComposition) + m_owningComposition->notifyTrackChanged(this); +} + +void Track::setPresetLabel(const std::string &label) +{ + if (m_presetLabel == label) return; + + m_presetLabel = label; + + if (m_owningComposition) + m_owningComposition->notifyTrackChanged(this); +} + +void Track::setInstrument(InstrumentId instrument) +{ + if (m_instrument == instrument) return; + + m_instrument = instrument; + + if (m_owningComposition) + m_owningComposition->notifyTrackChanged(this); +} + + +void Track::setArmed(bool armed) +{ + if (m_armed == armed) return; + + m_armed = armed; + + if (m_owningComposition) + m_owningComposition->notifyTrackChanged(this); +} + +void Track::setMidiInputDevice(DeviceId id) +{ + if (m_input_device == id) return; + + m_input_device = id; + + if (m_owningComposition) + m_owningComposition->notifyTrackChanged(this); +} + +void Track::setMidiInputChannel(char ic) +{ + if (m_input_channel == ic) return; + + m_input_channel = ic; + + if (m_owningComposition) + m_owningComposition->notifyTrackChanged(this); +} + + +// Our virtual method for exporting Xml. +// +// +std::string Track::toXmlString() +{ + + std::stringstream track; + + track << ""<< std::ends; +#else + track << "/>"; +#endif + + return track.str(); + +} + +} + + diff --git a/src/base/Track.h b/src/base/Track.h new file mode 100644 index 0000000..bcded51 --- /dev/null +++ b/src/base/Track.h @@ -0,0 +1,162 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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. +*/ + +// Representation of a Track +// +// + + +#ifndef _TRACK_H_ +#define _TRACK_H_ + +#include + +#include "XmlExportable.h" +#include "Instrument.h" +#include "Device.h" + +namespace Rosegarden +{ +class Composition; + +typedef unsigned int TrackId; + +/** + * A Track represents a line on the SegmentCanvas on the + * Rosegarden GUI. A Track is owned by a Composition and + * has reference to an Instrument from which the playback + * characteristics of the Track can be derived. A Track + * has no type itself - the type comes only from the + * Instrument relationship. + * + */ +class Track : public XmlExportable +{ + friend class Composition; + +public: + Track(); + Track(TrackId id, + InstrumentId instrument = 0, + int position =0 , + const std::string &label = "", + bool muted = false); + + void setId(TrackId id) { m_id = id; } + +private: + Track(const Track &); + Track operator=(const Track &); + +public: + + ~Track(); + + TrackId getId() const { return m_id; } + + void setMuted(bool muted); + bool isMuted() const { return m_muted; } + + void setPosition(int position) { m_position = position; } + int getPosition() const { return m_position; } + + void setLabel(const std::string &label); + std::string getLabel() const { return m_label; } + + void setPresetLabel(const std::string &label); + std::string getPresetLabel() const { return m_presetLabel; } + + void setInstrument(InstrumentId instrument); + InstrumentId getInstrument() const { return m_instrument; } + + // Implementation of virtual + // + virtual std::string toXmlString(); + + Composition* getOwningComposition() { return m_owningComposition; } + + void setMidiInputDevice(DeviceId id); + DeviceId getMidiInputDevice() const { return m_input_device; } + + void setMidiInputChannel(char ic); + char getMidiInputChannel() const { return m_input_channel; } + + int getClef() { return m_clef; } + void setClef(int clef) { m_clef = clef; } + + int getTranspose() { return m_transpose; } + void setTranspose(int transpose) { m_transpose = transpose; } + + int getColor() { return m_color; } + void setColor(int color) { m_color = color; } + + int getHighestPlayable() { return m_highestPlayable; } + void setHighestPlayable(int pitch) { m_highestPlayable = pitch; } + + int getLowestPlayable() { return m_lowestPlayable; } + void setLowestPlayable(int pitch) { m_lowestPlayable = pitch; } + + // Controls size of exported staff in LilyPond + int getStaffSize() { return m_staffSize; } + void setStaffSize(int index) { m_staffSize = index; } + + // Staff bracketing in LilyPond + int getStaffBracket() { return m_staffBracket; } + void setStaffBracket(int index) { m_staffBracket = index; } + + bool isArmed() const { return m_armed; } + void setArmed(bool armed); + +protected: // For Composition use only + void setOwningComposition(Composition* comp) { m_owningComposition = comp; } + +private: + //--------------- Data members --------------------------------- + + TrackId m_id; + bool m_muted; + std::string m_label; + std::string m_presetLabel; + int m_position; + InstrumentId m_instrument; + + Composition* m_owningComposition; + + DeviceId m_input_device; + char m_input_channel; + bool m_armed; + + // default parameters for new segments created belonging to this track + int m_clef; + int m_transpose; + int m_color; + int m_highestPlayable; + int m_lowestPlayable; + + // staff parameters for LilyPond export + int m_staffSize; + int m_staffBracket; +}; + +} + +#endif // _TRACK_H_ + diff --git a/src/base/TriggerSegment.cpp b/src/base/TriggerSegment.cpp new file mode 100644 index 0000000..c4c29de --- /dev/null +++ b/src/base/TriggerSegment.cpp @@ -0,0 +1,130 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "TriggerSegment.h" + +#include "Segment.h" +#include "Composition.h" +#include "BaseProperties.h" + +namespace Rosegarden +{ + +TriggerSegmentRec::~TriggerSegmentRec() +{ + // nothing -- we don't delete the segment here +} + +TriggerSegmentRec::TriggerSegmentRec(TriggerSegmentId id, + Segment *segment, + int basePitch, + int baseVelocity, + std::string timeAdjust, + bool retune) : + m_id(id), + m_segment(segment), + m_basePitch(basePitch), + m_baseVelocity(baseVelocity), + m_defaultTimeAdjust(timeAdjust), + m_defaultRetune(retune) +{ + if (m_defaultTimeAdjust == "") { + m_defaultTimeAdjust = BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH; + } + + calculateBases(); + updateReferences(); +} + +TriggerSegmentRec::TriggerSegmentRec(const TriggerSegmentRec &rec) : + m_id(rec.m_id), + m_segment(rec.m_segment), + m_basePitch(rec.m_basePitch), + m_baseVelocity(rec.m_baseVelocity), + m_defaultTimeAdjust(rec.m_defaultTimeAdjust), + m_defaultRetune(rec.m_defaultRetune), + m_references(rec.m_references) +{ + // nothing else +} + +TriggerSegmentRec & +TriggerSegmentRec::operator=(const TriggerSegmentRec &rec) +{ + if (&rec == this) return *this; + m_id = rec.m_id; + m_segment = rec.m_segment; + m_basePitch = rec.m_basePitch; + m_baseVelocity = rec.m_baseVelocity; + m_references = rec.m_references; + return *this; +} + +void +TriggerSegmentRec::updateReferences() +{ + if (!m_segment) return; + + Composition *c = m_segment->getComposition(); + if (!c) return; + + m_references.clear(); + + for (Composition::iterator i = c->begin(); i != c->end(); ++i) { + for (Segment::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { + if ((*j)->has(BaseProperties::TRIGGER_SEGMENT_ID) && + (*j)->get(BaseProperties::TRIGGER_SEGMENT_ID) == long(m_id)) { + m_references.insert((*i)->getRuntimeId()); + break; // from inner loop only: go on to next segment + } + } + } +} + +void +TriggerSegmentRec::calculateBases() +{ + if (!m_segment) return; + + for (Segment::iterator i = m_segment->begin(); + m_segment->isBeforeEndMarker(i); ++i) { + + if (m_basePitch >= 0 && m_baseVelocity >= 0) return; + + if (m_basePitch < 0) { + if ((*i)->has(BaseProperties::PITCH)) { + m_basePitch = (*i)->get(BaseProperties::PITCH); + } + } + + if (m_baseVelocity < 0) { + if ((*i)->has(BaseProperties::VELOCITY)) { + m_baseVelocity = (*i)->get(BaseProperties::VELOCITY); + } + } + } + + if (m_basePitch < 0) m_basePitch = 60; + if (m_baseVelocity < 0) m_baseVelocity = 100; +} + +} + diff --git a/src/base/TriggerSegment.h b/src/base/TriggerSegment.h new file mode 100644 index 0000000..7095e25 --- /dev/null +++ b/src/base/TriggerSegment.h @@ -0,0 +1,100 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _TRIGGER_SEGMENT_H_ +#define _TRIGGER_SEGMENT_H_ + +#include +#include + +namespace Rosegarden +{ + +typedef unsigned int TriggerSegmentId; + +class Segment; + +class TriggerSegmentRec +{ +public: + typedef std::set SegmentRuntimeIdSet; + ~TriggerSegmentRec(); + TriggerSegmentRec(const TriggerSegmentRec &); + TriggerSegmentRec &operator=(const TriggerSegmentRec &); + bool operator==(const TriggerSegmentRec &rec) { return rec.m_id == m_id; } + + TriggerSegmentId getId() const { return m_id; } + + Segment *getSegment() { return m_segment; } + const Segment *getSegment() const { return m_segment; } + + int getBasePitch() const { return m_basePitch; } + void setBasePitch(int basePitch) { m_basePitch = basePitch; } + + int getBaseVelocity() const { return m_baseVelocity; } + void setBaseVelocity(int baseVelocity) { m_baseVelocity = baseVelocity; } + + std::string getDefaultTimeAdjust() const { return m_defaultTimeAdjust; } + void setDefaultTimeAdjust(std::string a) { m_defaultTimeAdjust = a; } + + bool getDefaultRetune() const { return m_defaultRetune; } + void setDefaultRetune(bool r) { m_defaultRetune = r; } + + SegmentRuntimeIdSet &getReferences() { return m_references; } + const SegmentRuntimeIdSet &getReferences() const { return m_references; } + + void updateReferences(); + +protected: + friend class Composition; + TriggerSegmentRec(TriggerSegmentId id, Segment *segment, + int basePitch = -1, int baseVelocity = -1, + std::string defaultTimeAdjust = "", bool defaultRetune = true); + + void setReferences(const SegmentRuntimeIdSet &s) { m_references = s; } + + void calculateBases(); + + // data members: + + TriggerSegmentId m_id; + Segment *m_segment; + int m_basePitch; + int m_baseVelocity; + std::string m_defaultTimeAdjust; + bool m_defaultRetune; + SegmentRuntimeIdSet m_references; +}; + +struct TriggerSegmentCmp +{ + bool operator()(const TriggerSegmentRec &r1, const TriggerSegmentRec &r2) const { + return r1.getId() < r2.getId(); + } + bool operator()(const TriggerSegmentRec *r1, const TriggerSegmentRec *r2) const { + return r1->getId() < r2->getId(); + } +}; + + +} + +#endif diff --git a/src/base/ViewElement.cpp b/src/base/ViewElement.cpp new file mode 100644 index 0000000..425bdc1 --- /dev/null +++ b/src/base/ViewElement.cpp @@ -0,0 +1,172 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "ViewElement.h" +#include +#include + +namespace Rosegarden +{ + +extern const int MIN_SUBORDERING; + +ViewElement::ViewElement(Event *e) : + m_layoutX(0.0), + m_layoutY(0.0), + m_event(e) +{ + // nothing +} + +ViewElement::~ViewElement() +{ + // nothing +} + +////////////////////////////////////////////////////////////////////// + +bool +operator<(const ViewElement &a, const ViewElement &b) +{ + timeT at = a.getViewAbsoluteTime(), bt = b.getViewAbsoluteTime(); +/* + if (at < bt) { + if (!(*(a.event()) < *(b.event()))) { + std::cerr << " types: a: " << a.event()->getType() << " b: " << b.event()->getType() << std::endl; + std::cerr << "performed: a: " << a.event()->getAbsoluteTime() << " b: " << b.event()->getAbsoluteTime() << std::endl; + std::cerr << " notated: a: " << a.getViewAbsoluteTime() << " b: " << b.getViewAbsoluteTime() << std::endl; +// assert(*(a.event()) < *(b.event())); + } + } + else if (at > bt) { + if (*(a.event()) < *(b.event())) { + std::cerr << " types: a: " << a.event()->getType() << " b: " << b.event()->getType() << std::endl; + std::cerr << "performed: a: " << a.event()->getAbsoluteTime() << " b: " << b.event()->getAbsoluteTime() << std::endl; + std::cerr << " notated: a: " << a.getViewAbsoluteTime() << " b: " << b.getViewAbsoluteTime() << std::endl; +// assert(!(*(a.event()) < *(b.event()))); + } + } +*/ + if (at == bt) return *(a.event()) < *(b.event()); + else return (at < bt); +} + +////////////////////////////////////////////////////////////////////// + + +ViewElementList::~ViewElementList() +{ + for (iterator i = begin(); i != end(); ++i) { + delete (*i); + } +} + +void +ViewElementList::insert(ViewElement* el) +{ + set_type::insert(el); +} + +void +ViewElementList::erase(iterator pos) +{ + delete *pos; + set_type::erase(pos); +} + +void +ViewElementList::erase(iterator from, iterator to) +{ + for (iterator i = from; i != to; ++i) { + delete *i; + } + + set_type::erase(from, to); +} + +void +ViewElementList::eraseSingle(ViewElement *el) +{ + iterator elPos = findSingle(el); + if (elPos != end()) erase(elPos); +} + +ViewElementList::iterator +ViewElementList::findPrevious(const std::string &type, iterator i) + +{ + // what to return on failure? I think probably + // end(), as begin() could be a success case + if (i == begin()) return end(); + --i; + for (;;) { + if ((*i)->event()->isa(type)) return i; + if (i == begin()) return end(); + --i; + } +} + +ViewElementList::iterator +ViewElementList::findNext(const std::string &type, iterator i) +{ + if (i == end()) return i; + for (++i; i != end() && !(*i)->event()->isa(type); ++i); + return i; +} + +ViewElementList::iterator +ViewElementList::findSingle(ViewElement *el) +{ + iterator res = end(); + + std::pair interval = equal_range(el); + + for (iterator i = interval.first; i != interval.second; ++i) { + if (*i == el) { + res = i; + break; + } + } + + return res; +} + +ViewElementList::iterator +ViewElementList::findTime(timeT time) +{ + Event dummy("dummy", time, 0, MIN_SUBORDERING); + ViewElement dummyT(&dummy); + return lower_bound(&dummyT); +} + +ViewElementList::iterator +ViewElementList::findNearestTime(timeT t) +{ + iterator i = findTime(t); + if (i == end() || (*i)->getViewAbsoluteTime() > t) { + if (i == begin()) return end(); + else --i; + } + return i; +} + +} + diff --git a/src/base/ViewElement.h b/src/base/ViewElement.h new file mode 100644 index 0000000..8cc3d09 --- /dev/null +++ b/src/base/ViewElement.h @@ -0,0 +1,164 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _VIEWELEMENT_H_ +#define _VIEWELEMENT_H_ + + +#include "Event.h" + +#include +#include + +namespace Rosegarden +{ + +/** + * The abstract base for classes which represent an Event as an + * on-screen graphic item (a note, a rectangle on a piano roll). + */ + +class ViewElement +{ + friend class ViewElementList; + friend class Staff; + +public: + virtual ~ViewElement(); + + const Event* event() const { return m_event; } + Event* event() { return m_event; } + + virtual timeT getViewAbsoluteTime() const { return event()->getAbsoluteTime(); } + virtual timeT getViewDuration() const { return event()->getDuration(); } + + /** + * Returns the X coordinate of the element, as computed by the + * layout. This is not the coordinate of the associated canvas + * item. + * + * @see getCanvasX() + */ + virtual double getLayoutX() const { return m_layoutX; } + + /** + * Returns the Y coordinate of the element, as computed by the + * layout. This is not the coordinate of the associated canvas + * item. + * + * @see getCanvasY() + */ + virtual double getLayoutY() const { return m_layoutY; } + + /** + * Sets the X coordinate which was computed by the layout engine + * @see getLayoutX() + */ + virtual void setLayoutX(double x) { m_layoutX = x; } + + /** + * Sets the Y coordinate which was computed by the layout engine + * @see getLayoutY() + */ + virtual void setLayoutY(double y) { m_layoutY = y; } + + void dump(std::ostream&) const; + + friend bool operator<(const ViewElement&, const ViewElement&); + +protected: + ViewElement(Event *); + + double m_layoutX; + double m_layoutY; + + Event *m_event; +}; + + + +class ViewElementComparator +{ +public: + bool operator()(const ViewElement *e1, const ViewElement *e2) const { + return *e1 < *e2; + } +}; + +/** + * This class owns the objects its items are pointing at. + * + * The template argument T must be a subclass of ViewElement. + */ +class ViewElementList : public std::multiset +{ + typedef std::multiset set_type; +public: + typedef set_type::iterator iterator; + + ViewElementList() : set_type() { } + virtual ~ViewElementList(); + + void insert(ViewElement *); + void erase(iterator i); + void erase(iterator from, iterator to); + void eraseSingle(ViewElement *); + + iterator findPrevious(const std::string &type, iterator i); + iterator findNext(const std::string &type, iterator i); + + /** + * Returns an iterator pointing to that specific element, + * end() otherwise + */ + iterator findSingle(ViewElement *); + + const_iterator findSingle(ViewElement *e) const { + return const_iterator(((const ViewElementList *)this)->findSingle(e)); + } + + /** + * Returns first iterator pointing at or after the given time, + * end() if time is beyond the end of the list + */ + iterator findTime(timeT time); + + const_iterator findTime(timeT time) const { + return const_iterator(((const ViewElementList *)this)->findTime(time)); + } + + /** + * Returns iterator pointing to the first element starting at + * or before the given absolute time + */ + iterator findNearestTime(timeT time); + + const_iterator findNearestTime(timeT time) const { + return const_iterator(((const ViewElementList *)this)->findNearestTime(time)); + } +}; + +} + + +#endif + diff --git a/src/base/XmlExportable.cpp b/src/base/XmlExportable.cpp new file mode 100644 index 0000000..b874340 --- /dev/null +++ b/src/base/XmlExportable.cpp @@ -0,0 +1,197 @@ +// -*- c-basic-offset: 4 -*- +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "XmlExportable.h" +#include +#include +#include + +namespace Rosegarden +{ + +static std::string s1; +static std::string multibyte; + +std::string XmlExportable::encode(const std::string &s0) +{ + static char *buffer = 0; + static size_t bufsiz = 0; + size_t buflen = 0; + + static char multibyte[20]; + size_t mblen = 0; + + size_t len = s0.length(); + + if (bufsiz < len * 2 + 10) { + bufsiz = len * 2 + 10; + buffer = (char *)malloc(bufsiz); + } + + // Escape any xml special characters, and also make sure we have + // valid utf8 -- otherwise we won't be able to re-read the xml. + // Amazing how complicated this gets. + + bool warned = false; // no point in warning forever for long bogus strings + + for (size_t i = 0; i < len; ++i) { + + unsigned char c = s0[i]; + + if (((c & 0xc0) == 0xc0) || !(c & 0x80)) { + + // 11xxxxxx or 0xxxxxxx: first byte of a character sequence + + if (mblen > 0) { + + // does multibyte contain a valid sequence? + unsigned int length = + (!(multibyte[0] & 0x20)) ? 2 : + (!(multibyte[0] & 0x10)) ? 3 : + (!(multibyte[0] & 0x08)) ? 4 : + (!(multibyte[0] & 0x04)) ? 5 : 0; + + if (length == 0 || mblen == length) { + if (bufsiz < buflen + mblen + 1) { + bufsiz = 2 * buflen + mblen + 1; + buffer = (char *)realloc(buffer, bufsiz); + } + strncpy(buffer + buflen, multibyte, mblen); + buflen += mblen; + } else { + if (!warned) { + std::cerr + << "WARNING: Invalid utf8 char width in string \"" + << s0 << "\" at index " << i << " (" + << mblen << " octet" + << (mblen != 1 ? "s" : "") + << ", expected " << length << ")" << std::endl; + warned = true; + } + // and drop the character + } + } + + mblen = 0; + + if (!(c & 0x80)) { // ascii + + if (bufsiz < buflen + 10) { + bufsiz = 2 * buflen + 10; + buffer = (char *)realloc(buffer, bufsiz); + } + + switch (c) { + case '&' : strncpy(buffer + buflen, "&", 5); buflen += 5; break; + case '<' : strncpy(buffer + buflen, "<", 4); buflen += 4; break; + case '>' : strncpy(buffer + buflen, ">", 4); buflen += 4; break; + case '"' : strncpy(buffer + buflen, """, 6); buflen += 6; break; + case '\'' : strncpy(buffer + buflen, "'", 6); buflen += 6; break; + case 0x9: + case 0xa: + case 0xd: + // convert these special cases to plain whitespace: + buffer[buflen++] = ' '; + break; + default: + if (c >= 32) buffer[buflen++] = c; + else { + if (!warned) { + std::cerr + << "WARNING: Invalid utf8 octet in string \"" + << s0 << "\" at index " << i << " (" + << (int)c << " < 32)" << std::endl; + } + warned = true; + } + } + + } else { + + // store in multibyte rather than straight to s1, so + // that we know we're in the middle of something + // (below). At this point we know mblen == 0. + multibyte[mblen++] = c; + } + + } else { + + // second or subsequent byte + + if (mblen == 0) { // ... without a first byte! + if (!warned) { + std::cerr + << "WARNING: Invalid utf8 octet sequence in string \"" + << s0 << "\" at index " << i << std::endl; + warned = true; + } + } else { + + if (mblen >= sizeof(multibyte)-1) { + if (!warned) { + std::cerr + << "WARNING: Character too wide in string \"" + << s0 << "\" at index " << i << " (reached width of " + << mblen << ")" << std::endl; + } + warned = true; + mblen = 0; + } else { + multibyte[mblen++] = c; + } + } + } + } + + if (mblen > 0) { + // does multibyte contain a valid sequence? + unsigned int length = + (!(multibyte[0] & 0x20)) ? 2 : + (!(multibyte[0] & 0x10)) ? 3 : + (!(multibyte[0] & 0x08)) ? 4 : + (!(multibyte[0] & 0x04)) ? 5 : 0; + + if (length == 0 || mblen == length) { + if (bufsiz < buflen + mblen + 1) { + bufsiz = 2 * buflen + mblen + 1; + buffer = (char *)realloc(buffer, bufsiz); + } + strncpy(buffer + buflen, multibyte, mblen); + buflen += mblen; + } else { + if (!warned) { + std::cerr + << "WARNING: Invalid utf8 char width in string \"" + << s0 << "\" at index " << len << " (" + << mblen << " octet" + << (mblen != 1 ? "s" : "") + << ", expected " << length << ")" << std::endl; + warned = true; + } + // and drop the character + } + } + buffer[buflen] = '\0'; + + return buffer; +} + +} + diff --git a/src/base/XmlExportable.h b/src/base/XmlExportable.h new file mode 100644 index 0000000..e619221 --- /dev/null +++ b/src/base/XmlExportable.h @@ -0,0 +1,55 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _XMLEXPORTABLE_H_ +#define _XMLEXPORTABLE_H_ + +#include + +// [rwb] +// +// Abstract base class that forces all derived classes +// to implement the virtual toXmlString object. +// +// Yes, this is similar to the XmlStoreableEvent class +// in gui/ but with hopes to be more general so that any +// classes in base/ can go ahead and implement it. +// +// + +namespace Rosegarden +{ + +class XmlExportable +{ +public: + XmlExportable() {;} + virtual ~XmlExportable() {;} + + virtual std::string toXmlString() = 0; + + static std::string encode(const std::string &); +}; + +} + +#endif // _XMLEXPORTABLE_H_ + diff --git a/src/base/test/Makefile b/src/base/test/Makefile new file mode 100644 index 0000000..b517955 --- /dev/null +++ b/src/base/test/Makefile @@ -0,0 +1,57 @@ + +# debug flags need to be consistent with base build +#CPPFLAGS = -O2 +CPPFLAGS = -g + +LIBBASE = ../../../RGbuild/libRosegardenCommon.a + +INCPATH = -I.. + +SRCS := test.C pitch.C + +default: test utf8 colour transpose accidentals + +clean: + rm -f test test.o pitch pitch.o utf8 utf8.o colour colour.o transpose.o transpose accidentals.o accidentals + +%.o: %.cpp + $(CXX) $(CPPFLAGS) -c $< $(INCPATH) -o $@ + +test: test.o + $(CXX) $< $(LIBBASE) -o $@ + +pitch: pitch.o + $(CXX) $< $(LIBBASE) -o $@ + +utf8: utf8.o + $(CXX) $< $(LIBBASE) -o $@ + +colour: colour.o + $(CXX) $< $(LIBBASE) -o $@ + +transpose: transpose.o + $(CXX) $< $(LIBBASE) -o $@ + +accidentals: accidentals.o + $(CXX) $< $(LIBBASE) -o $@ + + +depend: + makedepend $(INCPATH) -- $(CPPFLAGS) -- $(SRCS) + +# DO NOT DELETE + +test.o: ../Event.h ../PropertyMap.h ../Property.h ../RealTime.h +test.o: ../PropertyName.h ../Exception.h ../Segment.h ../Track.h +test.o: ../XmlExportable.h ../Instrument.h ../NotationTypes.h #../StringHash.h +test.o: ../XmlExportable.h ../Instrument.h ../NotationTypes.h +test.o: ../RefreshStatus.h ../Composition.h ../FastVector.h +test.o: ../Configuration.h ../ColourMap.h ../Colour.h +test.o: ../SegmentNotationHelper.h ../SegmentPerformanceHelper.h +test.o: ../MidiTypes.h +pitch.o: ../NotationTypes.h ../Event.h ../PropertyMap.h ../Property.h +pitch.o: ../RealTime.h ../PropertyName.h ../Exception.h ../Instrument.h +pitch.o: ../XmlExportable.h #../StringHash.h + +transpose.o: ../NotationTypes.h +accidentals.o: ../NotationTypes.h diff --git a/src/base/test/accidentals.cpp b/src/base/test/accidentals.cpp new file mode 100644 index 0000000..53dbfc8 --- /dev/null +++ b/src/base/test/accidentals.cpp @@ -0,0 +1,60 @@ +// -*- c-basic-offset: 4 -*- + +#include "NotationTypes.h" + +using namespace Rosegarden; +using std::cout; + +// Unit test-ish tests for resolving accidentals +// +// Returns -1 (or crashes :)) on error, 0 on success +void assertHasAccidental(Pitch &pitch, + const Accidental& accidental, const Key& key) +{ + Accidental calculatedAccidental = + pitch.getAccidental(key); + + std::cout << "Got " << calculatedAccidental << " for pitch " << pitch.getPerformancePitch() << " in key " << key.getName() << std::endl; + + if (calculatedAccidental != accidental) + { + std::cout << "Expected " << accidental << std::endl; + exit(-1); + } +} + +void testBInEMinor() +{ + // a B, also in E minor, has no accidental + Pitch testPitch(59 % 12); + assertHasAccidental(testPitch, + Accidentals::NoAccidental, Key("E minor")); +} + +/** + * + */ +void testFInBMinor() +{ + Pitch testPitch(77); + assertHasAccidental(testPitch, + Accidentals::NoAccidental, Key("B minor")); +} + +void testInvalidSuggestion() +{ + // If we specify an invalid suggestion, + // getAccidental() should be robust against that. + Pitch testPitch = Pitch(59, Accidentals::Sharp); + assertHasAccidental(testPitch, + Accidentals::NoAccidental, Key("E minor")); +} + +int main(int argc, char **argv) +{ + testBInEMinor(); + testFInBMinor(); + testInvalidSuggestion(); + std::cout << "Success" << std::endl; + exit(0); +} diff --git a/src/base/test/colour.cpp b/src/base/test/colour.cpp new file mode 100644 index 0000000..3aa7ba2 --- /dev/null +++ b/src/base/test/colour.cpp @@ -0,0 +1,222 @@ +// -*- c-basic-offset: 4 -*- + + +/* + Rosegarden-4 + A sequencer and musical notation editor. + + This program is Copyright 2000-2003 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2003 + Mark Hymers + + 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. +*/ + +/* + If you compile this to a test program + g++ -o colour -I../ ../Colour.C ../ColourMap.C colour.C + + you can then run it like this: + ./colour > temp.output + + and do a diff to check that it worked: + diff -u temp.output colour.output + + If there are any differences, there's a problem + (or colour.output hasn't been updated when colour.C has been changed) +*/ + +#include "Colour.h" +#include "ColourMap.h" +#include + + +using namespace Rosegarden; +using std::cout; +using std::string; + +// Some printing routines + +void printRC(Colour const *temp) +{ + cout << "red: " << temp->getRed() << " green: " << temp->getGreen() << " blue: " << temp->getBlue() << "\n"; +} + +void printSRC(const string *s, const Colour *c) +{ + cout << "name: " << *s << " "; + printRC(c); +} + +void printSIRC(const unsigned int *i, const string *s, const Colour *c) +{ + cout << "index: " << *i << " "; + printSRC(s, c); +} + +void printIteratorContents (ColourMap *input) +{ + RCMap::const_iterator iter = input->begin(); + for ( ; !(iter == input->end()) ; ++iter) + printSIRC(&(iter->first), &(iter->second.second), &(iter->second.first)); +} + +// The main test program +int main() +{ + cout << "TEST: Colour.C\n\n"; + cout << "Can we create an Colour with the right default values?\n"; + Colour *red = new Colour; + printRC(red); + + cout << "Can we set values; green here is invalid - it should be set to 0 instead\n"; + red->setRed(210); + red->setGreen(276); + red->setBlue(100); + + cout << "Testing the copy constructor\n"; + Colour *blue = new Colour(*red); + printRC(blue); + + cout << "Check operator= works\n"; + Colour green; + green = *red; + printRC(&green); + + cout << "Check the setColour routine\n"; + green.setColour(1,2,3); + printRC(&green); + + cout << "Check the getColour routine\n"; + unsigned int r, g, b; + green.getColour(r, g, b); + printRC(&green); + + cout << "\nTEST: ColourMap.C\n\n"; + cout << "Can we create a ColourMap with the right default Colour + String\n"; + ColourMap *map = new ColourMap(); + + cout << "Can we get the default colour back out of it?\n"; + string s1 = map->getNameByIndex(0); + green = map->getColourByIndex(0); + printSRC(&s1, &green); + + cout << "Can we create a ColourMap with a specified default Colour?\n"; + ColourMap *map2 = new ColourMap(*red); + + cout << "Can we get the information back out of it?\n"; + s1 = map2->getNameByIndex(0); + green = map2->getColourByIndex(0); + printSRC(&s1, &green); + + cout << "Can we add a Colour\n"; + s1 = "TEST1"; + green.setColour(100, 101, 102); + map2->addItem(green, s1); + + cout << "Can we get the info back out?\n"; + s1 = ""; + s1 = map2->getNameByIndex(1); + green = map2->getColourByIndex(1); + printSRC(&s1, &green); + + cout << "Add a couple more colours\n"; + s1 = "TEST2"; + green.setColour(101, 102, 103); + map2->addItem(green, s1); + s1 = "TEST3"; + green.setColour(102, 103, 104); + map2->addItem(green, s1); + s1 = "TEST4"; + green.setColour(103, 104, 105); + map2->addItem(green, s1); + + // From an iterator: + // iterator->first ==> Index + // iterator->second.first ==> Colour + // iterator->second.second ==> string + // This rather unwieldy notation is because we store a pair in the map which is made up of a pair + // to start with + printIteratorContents(map2); + + cout << "Now try deleting the third item\n"; + map2->deleteItemByIndex(3); + + // Print the map again + printIteratorContents(map2); + + cout << "Make sure we get false when we try and modify item number 3\n"; + s1 = "NO"; + green.setColour(199,199,199); + bool check = map2->modifyColourByIndex(3, green); + if (check) cout << "WARNING: Managed to modify colour which doesn't exist\n"; + check = map2->modifyNameByIndex(3, s1); + if (check) cout << "WARNING: Managed to modify name which doesn't exist\n"; + + cout << "Check we can modify a colour which *is* there\n"; + s1 = "YES"; + green.setColour(233,233,233); + + check = map2->modifyColourByIndex(4, green); + if (!check) cout << "WARNING: Couldn't modify colour which does exist\n"; + + check = map2->modifyNameByIndex(4, s1); + if (!check) cout << "WARNING: Couldn't modify name which does exist\n"; + + // Print the map again + printIteratorContents(map2); + + cout << "Now try adding another item - it should take the place of the one we removed.\n"; + s1 = "NEW"; + green.setColour(211, 212, 213); + map2->addItem(green, s1); + + // Print the map again + printIteratorContents(map2); + + cout << "Try swapping two items:\n"; + check = map2->swapItems(3, 4); + if (!check) cout << "WARNING: Couldn't swap two items which both exist\n"; + + // Print the map again + printIteratorContents(map2); + + cout << "\nTEST: Generic Colour routines\n\n"; + + cout << "Try getting a combination colour:\n"; + Colour blah = map2->getColourByIndex(0); + Colour blah2 = map2->getColourByIndex(1); + cout << "Original colours:\n"; + printRC(&blah); + printRC(&blah2); + cout << "Combination colour:\n"; + blah = getCombinationColour(blah, blah2); + printRC(&blah); + + // Test the XML output + cout << "\nTEST: XML Output\n\n"; + cout << "For a single colour:\n"; + cout << blah.toXmlString(); + + cout << "For a colourmap:\n"; + cout << map2->toXmlString(std::string("segmentmap")); + + + delete map; + delete map2; + delete red; + delete blue; + + return 0; +} diff --git a/src/base/test/colour.output b/src/base/test/colour.output new file mode 100644 index 0000000..d6dc301 --- /dev/null +++ b/src/base/test/colour.output @@ -0,0 +1,76 @@ +TEST: Colour.C + +Can we create an Colour with the right default values? +red: 0 green: 0 blue: 0 +Can we set values; green here is invalid - it should be set to 0 instead +Testing the copy constructor +red: 210 green: 0 blue: 100 +Check operator= works +red: 210 green: 0 blue: 100 +Check the setColour routine +red: 1 green: 2 blue: 3 +Check the getColour routine +red: 1 green: 2 blue: 3 + +TEST: ColourMap.C + +Can we create a ColourMap with the right default Colour + String +Can we get the default colour back out of it? +name: red: 197 green: 211 blue: 125 +Can we create a ColourMap with a specified default Colour? +Can we get the information back out of it? +name: red: 210 green: 0 blue: 100 +Can we add a Colour +Can we get the info back out? +name: TEST1 red: 100 green: 101 blue: 102 +Add a couple more colours +index: 0 name: red: 210 green: 0 blue: 100 +index: 1 name: TEST1 red: 100 green: 101 blue: 102 +index: 2 name: TEST2 red: 101 green: 102 blue: 103 +index: 3 name: TEST3 red: 102 green: 103 blue: 104 +index: 4 name: TEST4 red: 103 green: 104 blue: 105 +Now try deleting the third item +index: 0 name: red: 210 green: 0 blue: 100 +index: 1 name: TEST1 red: 100 green: 101 blue: 102 +index: 2 name: TEST2 red: 101 green: 102 blue: 103 +index: 4 name: TEST4 red: 103 green: 104 blue: 105 +Make sure we get false when we try and modify item number 3 +Check we can modify a colour which *is* there +index: 0 name: red: 210 green: 0 blue: 100 +index: 1 name: TEST1 red: 100 green: 101 blue: 102 +index: 2 name: TEST2 red: 101 green: 102 blue: 103 +index: 4 name: YES red: 233 green: 233 blue: 233 +Now try adding another item - it should take the place of the one we removed. +index: 0 name: red: 210 green: 0 blue: 100 +index: 1 name: TEST1 red: 100 green: 101 blue: 102 +index: 2 name: TEST2 red: 101 green: 102 blue: 103 +index: 3 name: NEW red: 211 green: 212 blue: 213 +index: 4 name: YES red: 233 green: 233 blue: 233 +Try swapping two items: +index: 0 name: red: 210 green: 0 blue: 100 +index: 1 name: TEST1 red: 100 green: 101 blue: 102 +index: 2 name: TEST2 red: 101 green: 102 blue: 103 +index: 3 name: YES red: 233 green: 233 blue: 233 +index: 4 name: NEW red: 211 green: 212 blue: 213 + +TEST: Generic Colour routines + +Try getting a combination colour: +Original colours: +red: 210 green: 0 blue: 100 +red: 100 green: 101 blue: 102 +Combination colour: +red: 155 green: 50 blue: 101 + +TEST: XML Output + +For a single colour: + +For a colourmap: + + + + + + + diff --git a/src/base/test/pitch.cpp b/src/base/test/pitch.cpp new file mode 100644 index 0000000..5d46f9e --- /dev/null +++ b/src/base/test/pitch.cpp @@ -0,0 +1,474 @@ +// -*- c-basic-offset: 4 -*- + +#include "NotationRules.h" +#include "NotationTypes.h" + +using namespace Rosegarden; +using std::cout; +using std::endl; +using std::string; + +static const int verbose = 0; + +// This is the old NotationDisplayPitch -- this file was written for +// regression testing when implementing the new Pitch class. It won't +// compile any more as NotationDisplayPitch needs to be a friend of +// Pitch for this implementation to work. Add "friend class +// NotationDisplayPitch;" to end of Pitch in ../NotationTypes.h to +// build it + +/** + * NotationDisplayPitch stores a note's pitch in terms of the position + * of the note on the staff and its associated accidental, and + * converts these values to and from performance (MIDI) pitches. + * + * Rationale: When we insert a note, we need to query the height of the + * staff line next to which it's being inserted, then translate this + * back to raw pitch according to the clef in force at the x-coordinate + * at which the note is inserted. For display, we translate from raw + * pitch using both the clef and the key in force. + * + * Whether an accidental should be displayed or not depends on the + * current key, on whether we've already shown the same accidental for + * that pitch in the same bar, on whether the note event explicitly + * requests an accidental... All we calculate here is whether the + * pitch "should" have an accidental, not whether it really will + * (e.g. if the accidental has already appeared). + * + * (See also docs/discussion/units.txt for explanation of pitch units.) + */ + +class NotationDisplayPitch +{ +public: + /** + * Construct a NotationDisplayPitch containing the given staff + * height and accidental + */ + NotationDisplayPitch(int heightOnStaff, + const Accidental &accidental); + + /** + * Construct a NotationDisplayPitch containing the height and + * accidental to which the given performance pitch corresponds + * in the given clef and key + */ + NotationDisplayPitch(int pitch, const Clef &clef, const Key &key, + const Accidental &explicitAccidental = + Accidentals::NoAccidental); + + int getHeightOnStaff() const { return m_heightOnStaff; } + Accidental getAccidental() const { return m_accidental; } + + /** + * Calculate and return the performance (MIDI) pitch + * corresponding to the stored height and accidental, in the + * given clef and key + */ + int getPerformancePitch(const Clef &clef, const Key &key) const; + + /** + * Calculate and return the performance (MIDI) pitch + * corresponding to the stored height and accidental, + * interpreting them as Rosegarden-2.1-style values (for + * backward compatibility use), in the given clef and key + */ + int getPerformancePitchFromRG21Pitch(const Clef &clef, + const Key &key) const; + + /** + * Return the stored pitch as a string (C4, Bb2, etc...) + * according to http://www.harmony-central.com/MIDI/Doc/table2.html + * + * If inclOctave is false, this will return C, Bb, etc. + */ + std::string getAsString(const Clef &clef, const Key &key, + bool inclOctave = true, + int octaveBase = -2) const; + + /** + * Return the stored pitch as a description of a note in a + * scale. Return values are: + * + * -- placeInScale: a number from 0-6 where 0 is C and 6 is B + * + * -- accidentals: a number from -2 to 2 where -2 is double flat, + * -1 is flat, 0 is nothing, 1 is sharp, 2 is double sharp + * + * -- octave: MIDI octave in range -2 to 8, where pitch 0 is in + * octave -2 and thus middle-C is in octave 3 + * + * This function is guaranteed never to return values out of + * the above ranges. + */ + void getInScale(const Clef &clef, const Key &key, + int &placeInScale, int &accidentals, int &octave) const; + +private: + int m_heightOnStaff; + Accidental m_accidental; + + static void rawPitchToDisplayPitch(int, const Clef &, const Key &, + int &, Accidental &); + static void displayPitchToRawPitch(int, Accidental, const Clef &, const Key &, + int &, bool ignoreOffset = false); +}; +////////////////////////////////////////////////////////////////////// +// NotationDisplayPitch +////////////////////////////////////////////////////////////////////// + +NotationDisplayPitch::NotationDisplayPitch(int heightOnStaff, + const Accidental &accidental) + : m_heightOnStaff(heightOnStaff), + m_accidental(accidental) +{ +} + +NotationDisplayPitch::NotationDisplayPitch(int pitch, const Clef &clef, + const Key &key, + const Accidental &explicitAccidental) : + m_accidental(explicitAccidental) +{ + rawPitchToDisplayPitch(pitch, clef, key, m_heightOnStaff, m_accidental); +} + +int +NotationDisplayPitch::getPerformancePitch(const Clef &clef, const Key &key) const +{ + int p = 0; + displayPitchToRawPitch(m_heightOnStaff, m_accidental, clef, key, p); + return p; +} + +int +NotationDisplayPitch::getPerformancePitchFromRG21Pitch(const Clef &clef, + const Key &) const +{ + // Rosegarden 2.1 pitches are a bit weird; see + // docs/data_struct/units.txt + + // We pass the accidental and clef, a faked key of C major, and a + // flag telling displayPitchToRawPitch to ignore the clef offset + // and take only its octave into account + + int p = 0; + displayPitchToRawPitch(m_heightOnStaff, m_accidental, clef, Key(), p, true); + return p; +} + + +void +NotationDisplayPitch::rawPitchToDisplayPitch(int rawpitch, + const Clef &clef, + const Key &key, + int &height, + Accidental &accidental) +{ + Pitch::rawPitchToDisplayPitch(rawpitch, clef, key, height, accidental); +} + +void +NotationDisplayPitch::displayPitchToRawPitch(int height, + Accidental accidental, + const Clef &clef, + const Key &key, + int &pitch, + bool ignoreOffset) +{ + Pitch::displayPitchToRawPitch(height, accidental, clef, key, pitch, + ignoreOffset); +} +string +NotationDisplayPitch::getAsString(const Clef &clef, const Key &key, + bool inclOctave, int octaveBase) const +{ + static const string noteNamesSharps[] = { + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" + }; + static const string noteNamesFlats[] = { + "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" + }; + + int performancePitch = getPerformancePitch(clef, key); + + // highly unlikely, but fatal if it happened: + if (performancePitch < 0) performancePitch = 0; + + int pitch = performancePitch % 12; + int octave = performancePitch / 12; + + if (!inclOctave) + return key.isSharp() ? noteNamesSharps[pitch] : noteNamesFlats[pitch]; + + char tmp[1024]; + + if (key.isSharp()) + sprintf(tmp, "%s%d", noteNamesSharps[pitch].c_str(), + octave + octaveBase); + else + sprintf(tmp, "%s%d", noteNamesFlats[pitch].c_str(), + octave + octaveBase); + + return string(tmp); +} + +void +NotationDisplayPitch::getInScale(const Clef &clef, const Key &key, + int &placeInScale, int &accidentals, int &octave) const +{ + //!!! Maybe we should bring the logic from rawPitchToDisplayPitch down + // into this method, and make rawPitchToDisplayPitch wrap this + + static int pitches[2][12] = { + { 0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6 }, + { 0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6 }, + }; + static int accidentalsForPitches[2][12] = { + { 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 }, + { 0, -1, 0, -1, 0, 0, -1, 0, -1, 0, -1, 0 }, + }; + + int performancePitch = getPerformancePitch(clef, key); + + // highly unlikely, but fatal if it happened: + if (performancePitch < 0) performancePitch = 0; + if (performancePitch > 127) performancePitch = 127; + + int pitch = performancePitch % 12; + octave = performancePitch / 12 - 2; + + if (key.isSharp()) { //!!! need to [optionally?] handle minor keys (similarly in getAsString?) + placeInScale = pitches[0][pitch]; + accidentals = accidentalsForPitches[0][pitch]; + } else { + placeInScale = pitches[1][pitch]; + accidentals = accidentalsForPitches[1][pitch]; + } +} + + + +int testNote(Accidental &acc, Key &key, int octave, int note) +{ + int rv = 0; + + Pitch pitch(note, octave, key, acc); + + static int prevPerformancePitch = -1; + static Accidental prevAcc = Accidentals::NoAccidental; + static int prevOctave = -2; + + int p = pitch.getPerformancePitch(); + if (p < prevPerformancePitch && (prevAcc == acc && prevOctave == octave)) { + cout << "testNote: " << note << " " << acc << ", " << key.getName() << ", octave " << octave << ": " + << "pitch is " << p << ", should be >= " << prevPerformancePitch << endl; + rv = 1; + } + + int nis = pitch.getNoteInScale(key); + if (nis != note) { + cout << "testNote: " << note << " " << acc << ", " << key.getName() << ", octave " << octave << ": " + << "note in scale is " << nis << " (not " << note << ")" << endl; + rv = 1; + } + + // can do special checks on C-major etc 'cos it's easy, and stuff like that + + if (key == Key("C major")) { + if (acc == Accidentals::NoAccidental) { + Pitch comparative(scale_Cmajor[nis], octave); + if (comparative.getPerformancePitch() != p) { + cout << "testNote: " << note << " " << acc << ", " << key.getName() << ", octave " << octave << ": " + << "comparative pitch is " << comparative.getPerformancePitch() << ", should be " << p << endl; + rv = 1; + } + } + } + + prevPerformancePitch = p; + prevOctave = octave; + prevAcc = acc; + + if (!rv && verbose) { + cout << "testNote: " << note << " " << acc << ", " << key.getName() << ", octave " << octave << ": " + << "pitch " << p << endl; + } + return rv; +} + +int testNoteName(Accidental &acc, Key &key, int octave, char noteName) +{ + int rv = 0; + + Pitch pitch(noteName, octave, key, acc); + + static int prevPerformancePitch = -1; + static Accidental prevAcc = Accidentals::NoAccidental; + static int prevOctave = -2; + + int p = pitch.getPerformancePitch(); + if (p < prevPerformancePitch && (prevAcc == acc && prevOctave == octave)) { + cout << "testNoteName: " << noteName << " " << acc << ", " << key.getName() << ", octave " << octave << ": " + << "pitch is " << p << ", should be >= " << prevPerformancePitch << endl; + rv = 1; + } + + char nn = pitch.getNoteName(key); + if (nn != noteName) { + cout << "testNoteName: " << noteName << " " << acc << ", " << key.getName() << ", octave " << octave << ": " + << "note is " << nn << " (not " << noteName << ") (pitch was " << p << ")" << endl; + rv = 1; + } + + prevPerformancePitch = p; + prevOctave = octave; + prevAcc = acc; + + if (!rv && verbose) { + cout << "testNoteName: " << noteName << " " << acc << ", " << key.getName() << ", octave " << octave << ": " + << "pitch " << p << endl; + } + return rv; +} + +int testPitchInOctave(Accidental &acc, Key &key, int octave, int pio) +{ + int rv = 0; + + Pitch pitch(pio, octave, acc); + + int p = pitch.getPerformancePitch(); + if (p != (octave + 2) * 12 + pio) { + cout << "testPitchInOctave: " << pio << " " << acc << ", " << key.getName() << ", octave " << octave << ": " + << "pitch is " << p << ", should be " << ((octave + 2) * 12 + pio) << endl; + rv = 1; + } + + if (!rv && verbose) { + cout << "testNote: " << pio << " " << acc << ", " << key.getName() << ", octave " << octave << ": " + << "pitch " << p << endl; + } + return rv; +} + +int testPitch(Accidental &acc, Key &key, Clef &clef, int pp) +{ + int rv = 0; + + Pitch pitch(pp, acc); + NotationDisplayPitch ndp(pp, clef, key, acc); + + int h = pitch.getHeightOnStaff(clef, key); + int nh = ndp.getHeightOnStaff(); + if (h != nh) { + cout << "testPitch: " << pp << ", " << acc << ", " << key.getName() << ", " << clef.getClefType() << ": " + << "height is " << h << " (ndp returns " << nh << ")" << endl; + rv = 1; + } + + Accidental pa = pitch.getDisplayAccidental(key); + Accidental na = ndp.getAccidental(); + if (pa != na) { + cout << "testPitch: " << pp << ", " << acc << ", " << key.getName() << ", " << clef.getClefType() << ": " + << "display acc is " << pa << " (ndp returns " << na << ")" << endl; + rv = 1; + } + + return rv; +} + +int testHeight(Accidental &acc, Key &key, Clef &clef, int height) +{ + int rv = 0; + + Pitch pitch(height, clef, key, acc); + NotationDisplayPitch ndp(height, acc); + NotationDisplayPitch ndp2(pitch.getPerformancePitch(), clef, key, acc); + + int ppp = pitch.getPerformancePitch(); + int npp = ndp.getPerformancePitch(clef, key); + + if (ppp != npp) { + cout << "testHeight: " << height << " " << acc << ", " << key.getName() << ", " << clef.getClefType() << ": " + << "pitch " << ppp << " (ndp returns " << npp << ")" << endl; + rv = 1; + } + + int h = pitch.getHeightOnStaff(clef, key); + if (h != ndp.getHeightOnStaff() || h != height) { + cout << "testHeight: " << height << " " << acc << ", " << key.getName() << ", " << clef.getClefType() << ": " + << "height " << h << " (ndp returns " << ndp.getHeightOnStaff() << ")" << endl; + rv = 1; + } + + // for NoAccidental, the Pitch object will acquire the accidental + // from the current key whereas NotationDisplayPitch will not -- + // hence we skip this test for NoAccidental + if (acc != Accidentals::NoAccidental) { + Accidental nacc = ndp2.getAccidental(); + Accidental pacc = pitch.getDisplayAccidental(key); + if (nacc != pacc) { + cout << "testHeight: " << height << " " << acc << ", " << key.getName() << ", " << clef.getClefType() << ": " + "acc " << pacc << " (ndp returns " << nacc << ")" << endl; + rv = 1; + } + } + + if (!rv && verbose) { + cout << "testHeight: " << height << " " << acc << ", " << key.getName() << ", " << clef.getClefType() << ": " + << "pitch " << ppp << endl; + } + return rv; + +} + + +int main(int argc, char **argv) +{ + Accidentals::AccidentalList accidentals(Accidentals::getStandardAccidentals()); + Clef::ClefList clefs(Clef::getClefs()); + + Key::KeyList keys; + Key::KeyList majorKeys(Key::getKeys(false)); + Key::KeyList minorKeys(Key::getKeys(true)); + keys.insert(keys.end(), majorKeys.begin(), majorKeys.end()); + keys.insert(keys.end(), minorKeys.begin(), minorKeys.end()); + + for (int a = 0; a < accidentals.size(); ++a) { + + for (int k = 0; k < keys.size(); ++k) { + + for (int o = -2; o < 9; ++o) { + for (int n = 0; n < 7; ++n) { + testNote(accidentals[a], keys[k], o, n); + } + } + + for (int o = -2; o < 9; ++o) { + for (int p = 0; p < 12; ++p) { + testPitchInOctave(accidentals[a], keys[k], o, p); + } + } + + for (int o = -2; o < 9; ++o) { + for (int p = 0; p < 7; ++p) { + testNoteName(accidentals[a], keys[k], o, Pitch::getNoteForIndex(p)); + } + } + + for (int c = 0; c < clefs.size(); ++c) { + + for (int p = 0; p < 128; ++p) { + testPitch(accidentals[a], keys[k], clefs[c], p); + } + + for (int h = -20; h < 30; ++h) { + testHeight(accidentals[a], keys[k], clefs[c], h); + } + } + } + } + + return 0; +} + diff --git a/src/base/test/seq/Makefile b/src/base/test/seq/Makefile new file mode 100644 index 0000000..c32946e --- /dev/null +++ b/src/base/test/seq/Makefile @@ -0,0 +1,6 @@ + +all: complainer generator queue-timer queue-timer-jack + +%: %.c + cc $< -o $@ -ljack -lasound + diff --git a/src/base/test/seq/complainer.c b/src/base/test/seq/complainer.c new file mode 100644 index 0000000..afe0a7f --- /dev/null +++ b/src/base/test/seq/complainer.c @@ -0,0 +1,74 @@ + +#include +#include +#include +#include + +void +callback(snd_seq_t *handle) +{ + snd_seq_event_t *ev = 0; + + do { + if (snd_seq_event_input(handle, &ev) > 0) { + + if (ev->type == SND_SEQ_EVENT_NOTEON) { + + struct timeval tv; + static long last_usec = 0; + int pitch = ev->data.note.note; + + snd_seq_timestamp_t evt = ev->time; + + gettimeofday(&tv, 0); + printf("pitch %d at %ld sec %ld usec, off by %ld usec\n", + pitch, tv.tv_sec, tv.tv_usec, tv.tv_usec - ((last_usec + 500000) % 1000000)); + + last_usec = tv.tv_usec; + } + } + + } while (snd_seq_event_input_pending(handle, 0) > 0); +} + +int +main(int argc, char **argv) +{ + snd_seq_t *handle; + int portid; + int npfd; + struct pollfd *pfd; + struct sched_param param; + + if (snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) { + fprintf(stderr, "failed to open ALSA sequencer interface\n"); + return 1; + } + + snd_seq_set_client_name(handle, "complainer"); + + if ((portid = snd_seq_create_simple_port + (handle, "complainer", + SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, 0)) < 0) { + fprintf(stderr, "failed to create ALSA sequencer port\n"); + return 1; + } + + npfd = snd_seq_poll_descriptors_count(handle, POLLIN); + pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd)); + snd_seq_poll_descriptors(handle, pfd, npfd, POLLIN); + + param.sched_priority = 99; + if (sched_setscheduler(0, SCHED_FIFO, ¶m)) { + perror("failed to set high-priority scheduler"); + } + + printf("ready\n", npfd); + + while (1) { + if (poll(pfd, npfd, 100000) > 0) { + callback(handle); + } + } +} + diff --git a/src/base/test/seq/generator.c b/src/base/test/seq/generator.c new file mode 100644 index 0000000..9f64d61 --- /dev/null +++ b/src/base/test/seq/generator.c @@ -0,0 +1,96 @@ + +#include +#include +#include + +int +main(int argc, char **argv) +{ + snd_seq_t *handle; + int portid; + int npfd; + struct pollfd *pfd; + int queue; + int i; + int rval; + int target; + snd_seq_queue_timer_t *timer; + snd_timer_id_t *timerid; + + if (argc != 2) { + fprintf(stderr, "usage: generator \n"); + exit(2); + } + target = atoi(argv[1]); + + if (snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) { + fprintf(stderr, "failed to open ALSA sequencer interface\n"); + return 1; + } + + snd_seq_set_client_name(handle, "generator"); + + if ((portid = snd_seq_create_simple_port + (handle, "generator", + SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, 0)) < 0) { + fprintf(stderr, "failed to create ALSA sequencer port\n"); + return 1; + } + + if ((queue = snd_seq_alloc_queue(handle)) < 0) { + fprintf(stderr, "failed to create ALSA sequencer queue\n"); + return 1; + } +/* + snd_seq_queue_timer_alloca(&timer); + snd_seq_get_queue_timer(handle, queue, timer); + snd_timer_id_alloca(&timerid); + snd_timer_id_set_class(timerid, SND_TIMER_CLASS_PCM); + snd_timer_id_set_sclass(timerid, SND_TIMER_SCLASS_NONE); + snd_timer_id_set_card(timerid, 0); + snd_timer_id_set_device(timerid, 0); + snd_timer_id_set_subdevice(timerid, 0); + snd_seq_queue_timer_set_id(timer, timerid); + snd_seq_set_queue_timer(handle, queue, timer); +*/ + snd_seq_start_queue(handle, queue, 0); + + // stuff two minutes worth of events on the queue + for (i = 0; i < 240; ++i) { + snd_seq_real_time_t rtime; + rtime.tv_sec = i / 2; + rtime.tv_nsec = (i % 2) * 500000000; + snd_seq_event_t ev; + snd_seq_ev_clear(&ev); + snd_seq_ev_set_source(&ev, portid); + snd_seq_ev_set_dest(&ev, target, 0); + snd_seq_ev_schedule_real(&ev, queue, 0, &rtime); + snd_seq_ev_set_noteon(&ev, 0, 64, 127); + if ((rval = snd_seq_event_output(handle, &ev)) < 0) { + fprintf(stderr, "failed to write event: %s", snd_strerror(rval)); + } + } + + snd_seq_drain_output(handle); + + for (i = 0; i < 120; ++i) { + snd_seq_queue_status_t *status; + const snd_seq_real_time_t *rtime; + struct timeval tv; + + snd_seq_queue_status_alloca(&status); + + snd_seq_get_queue_status(handle, queue, status); + rtime = snd_seq_queue_status_get_real_time(status); + + gettimeofday(&tv, 0); + + fprintf(stderr, " real time: %ld sec, %ld usec\nqueue time: %ld sec, %ld usec (diff to real time %ld sec %ld usec)\n", + tv.tv_sec, tv.tv_usec, + rtime->tv_sec, rtime->tv_nsec / 1000, + tv.tv_sec - rtime->tv_sec, tv.tv_usec - (rtime->tv_nsec / 1000)); + + sleep(1); + } +} + diff --git a/src/base/test/seq/queue-timer-jack.c b/src/base/test/seq/queue-timer-jack.c new file mode 100644 index 0000000..2648e94 --- /dev/null +++ b/src/base/test/seq/queue-timer-jack.c @@ -0,0 +1,166 @@ + +#include +#include +#include +#include + +static jack_nframes_t sample_frames = 0; + +void normalize(struct timeval *tv) +{ + if (tv->tv_sec == 0) { + while (tv->tv_usec <= -1000000) { tv->tv_usec += 1000000; --tv->tv_sec; } + while (tv->tv_usec >= 1000000) { tv->tv_usec -= 1000000; ++tv->tv_sec; } + } else if (tv->tv_sec < 0) { + while (tv->tv_usec <= -1000000) { tv->tv_usec += 1000000; --tv->tv_sec; } + while (tv->tv_usec > 0) { tv->tv_usec -= 1000000; ++tv->tv_sec; } + } else { + while (tv->tv_usec >= 1000000) { tv->tv_usec -= 1000000; ++tv->tv_sec; } + while (tv->tv_usec < 0) { tv->tv_usec += 1000000; --tv->tv_sec; } + } +} + +int +jack_process(jack_nframes_t nframes, void *arg) +{ + sample_frames += nframes; +} + +jack_nframes_t +rt_to_frame(struct timeval tv, jack_nframes_t sample_rate) +{ + if (tv.tv_sec < 0) tv.tv_sec = -tv.tv_sec; + if (tv.tv_usec < 0) tv.tv_usec = -tv.tv_usec; + return + tv.tv_sec * sample_rate + + ((tv.tv_usec / 1000) * sample_rate) / 1000 + + ((tv.tv_usec - 1000 * (tv.tv_usec / 1000)) * sample_rate) / 1000000; +} + +int +main(int argc, char **argv) +{ + snd_seq_t *handle; + int portid; + int npfd; + struct pollfd *pfd; + int queue; + int i; + int rval; + struct timeval starttv; + int countdown = -1; + snd_seq_queue_timer_t *timer; + snd_timer_id_t *timerid; + jack_client_t *jclient; + jack_nframes_t sample_rate; + + if ((jclient = jack_client_new("queue-timer-jack")) == 0) { + fprintf(stderr, "failed to connect to JACK server\n"); + return 1; + } + + jack_set_process_callback(jclient, jack_process, 0); + + sample_rate = jack_get_sample_rate(jclient); + + if (snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) { + fprintf(stderr, "failed to open ALSA sequencer interface\n"); + return 1; + } + + snd_seq_set_client_name(handle, "generator"); + + if ((portid = snd_seq_create_simple_port + (handle, "generator", + SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, 0)) < 0) { + fprintf(stderr, "failed to create ALSA sequencer port\n"); + return 1; + } + + if ((queue = snd_seq_alloc_queue(handle)) < 0) { + fprintf(stderr, "failed to create ALSA sequencer queue\n"); + return 1; + } + + snd_seq_queue_timer_alloca(&timer); + snd_seq_get_queue_timer(handle, queue, timer); + snd_timer_id_alloca(&timerid); + + /* To test a PCM timer: */ +/* + snd_timer_id_set_class(timerid, SND_TIMER_CLASS_PCM); + snd_timer_id_set_sclass(timerid, SND_TIMER_SCLASS_NONE); + snd_timer_id_set_card(timerid, 0); + snd_timer_id_set_device(timerid, 0); + snd_timer_id_set_subdevice(timerid, 0); +*/ + + /* To test the system timer: */ + snd_timer_id_set_class(timerid, SND_TIMER_CLASS_GLOBAL); + snd_timer_id_set_sclass(timerid, SND_TIMER_SCLASS_NONE); + snd_timer_id_set_device(timerid, SND_TIMER_GLOBAL_SYSTEM); + + snd_seq_queue_timer_set_id(timer, timerid); + snd_seq_set_queue_timer(handle, queue, timer); + + if (jack_activate(jclient)) { + fprintf (stderr, "cannot activate jack client"); + exit(1); + } + + snd_seq_start_queue(handle, queue, 0); + snd_seq_drain_output(handle); + + gettimeofday(&starttv, 0); + + while (countdown != 0) { + + snd_seq_queue_status_t *status; + const snd_seq_real_time_t *rtime; + struct timeval tv, qtv, jtv, diff, jdiff; + jack_nframes_t frames_now; + + snd_seq_queue_status_alloca(&status); + + snd_seq_get_queue_status(handle, queue, status); + rtime = snd_seq_queue_status_get_real_time(status); + + gettimeofday(&tv, 0); + + frames_now = sample_frames; + fprintf(stderr, " frames: %ld\n", frames_now); + + qtv.tv_sec = rtime->tv_sec; + qtv.tv_usec = rtime->tv_nsec / 1000; + + tv.tv_sec -= starttv.tv_sec; + tv.tv_usec -= starttv.tv_usec; + normalize(&tv); + + jtv.tv_sec = frames_now / sample_rate; + frames_now -= jtv.tv_sec * sample_rate; + jtv.tv_usec = (int)(((float)frames_now * 1000000) / sample_rate); + + diff.tv_sec = tv.tv_sec - qtv.tv_sec; + diff.tv_usec = tv.tv_usec - qtv.tv_usec; + normalize(&diff); + + jdiff.tv_sec = jtv.tv_sec - qtv.tv_sec; + jdiff.tv_usec = jtv.tv_usec - qtv.tv_usec; + normalize(&jdiff); + + fprintf(stderr, " real time: %12ld sec %8ld usec /%12ld frames\nqueue time: %12ld sec %8ld usec /%12ld frames\n jack time: %12ld sec %8ld usec /%12ld frames\n rq diff: %12ld sec %8ld usec /%12ld frames\n jq diff: %12ld sec %8ld usec /%12ld frames\n", + tv.tv_sec, tv.tv_usec, rt_to_frame(tv, sample_rate), + qtv.tv_sec, qtv.tv_usec, rt_to_frame(qtv, sample_rate), + jtv.tv_sec, jtv.tv_usec, rt_to_frame(jtv, sample_rate), + diff.tv_sec, diff.tv_usec, rt_to_frame(diff, sample_rate), + jdiff.tv_sec, jdiff.tv_usec, rt_to_frame(jdiff, sample_rate)); + + fprintf(stderr, "\n"); + struct timespec ts; + ts.tv_sec = 1; + ts.tv_nsec = 0; + nanosleep(&ts, 0); + } +} + diff --git a/src/base/test/seq/queue-timer.c b/src/base/test/seq/queue-timer.c new file mode 100644 index 0000000..2b7bac4 --- /dev/null +++ b/src/base/test/seq/queue-timer.c @@ -0,0 +1,123 @@ + +#include +#include +#include + +void normalize(struct timeval *tv) +{ + if (tv->tv_sec == 0) { + while (tv->tv_usec <= -1000000) { tv->tv_usec += 1000000; --tv->tv_sec; } + while (tv->tv_usec >= 1000000) { tv->tv_usec -= 1000000; ++tv->tv_sec; } + } else if (tv->tv_sec < 0) { + while (tv->tv_usec <= -1000000) { tv->tv_usec += 1000000; --tv->tv_sec; } + while (tv->tv_usec > 0) { tv->tv_usec -= 1000000; ++tv->tv_sec; } + } else { + while (tv->tv_usec >= 1000000) { tv->tv_usec -= 1000000; ++tv->tv_sec; } + while (tv->tv_usec < 0) { tv->tv_usec += 1000000; --tv->tv_sec; } + } +} + +int +main(int argc, char **argv) +{ + snd_seq_t *handle; + int portid; + int npfd; + struct pollfd *pfd; + int queue; + int i; + int rval; + struct timeval starttv, prevdiff; + int countdown = -1; + snd_seq_queue_timer_t *timer; + snd_timer_id_t *timerid; + + if (snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) { + fprintf(stderr, "failed to open ALSA sequencer interface\n"); + return 1; + } + + snd_seq_set_client_name(handle, "generator"); + + if ((portid = snd_seq_create_simple_port + (handle, "generator", + SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, 0)) < 0) { + fprintf(stderr, "failed to create ALSA sequencer port\n"); + return 1; + } + + if ((queue = snd_seq_alloc_queue(handle)) < 0) { + fprintf(stderr, "failed to create ALSA sequencer queue\n"); + return 1; + } +/* + snd_seq_queue_timer_alloca(&timer); + snd_seq_get_queue_timer(handle, queue, timer); + snd_timer_id_alloca(&timerid); + snd_timer_id_set_class(timerid, SND_TIMER_CLASS_PCM); + snd_timer_id_set_sclass(timerid, SND_TIMER_SCLASS_NONE); + snd_timer_id_set_card(timerid, 0); + snd_timer_id_set_device(timerid, 0); + snd_timer_id_set_subdevice(timerid, 0); + snd_seq_queue_timer_set_id(timer, timerid); + snd_seq_set_queue_timer(handle, queue, timer); +*/ + snd_seq_start_queue(handle, queue, 0); + snd_seq_drain_output(handle); + + gettimeofday(&starttv, 0); + prevdiff.tv_sec = 0; + prevdiff.tv_usec = 0; + + while (countdown != 0) { + + snd_seq_queue_status_t *status; + const snd_seq_real_time_t *rtime; + struct timeval tv, diff, diffdiff; + + snd_seq_queue_status_alloca(&status); + + snd_seq_get_queue_status(handle, queue, status); + rtime = snd_seq_queue_status_get_real_time(status); + + gettimeofday(&tv, 0); + + tv.tv_sec -= starttv.tv_sec; + tv.tv_usec -= starttv.tv_usec; + normalize(&tv); + + diff.tv_sec = tv.tv_sec - rtime->tv_sec; + diff.tv_usec = tv.tv_usec - rtime->tv_nsec / 1000; + normalize(&diff); + + diffdiff.tv_sec = diff.tv_sec - prevdiff.tv_sec; + diffdiff.tv_usec = diff.tv_usec - prevdiff.tv_usec; + normalize(&diffdiff); + prevdiff = diff; + + fprintf(stderr, " real time: %12ld sec %8ld usec\nqueue time: %12ld sec %8ld usec\n diff: %12ld sec %8ld usec\n diffdiff: %12ld sec %8ld usec\n", + tv.tv_sec, tv.tv_usec, + rtime->tv_sec, rtime->tv_nsec / 1000, + diff.tv_sec, diff.tv_usec, + diffdiff.tv_sec, diffdiff.tv_usec); + + if (diffdiff.tv_usec > 5000 || + diffdiff.tv_usec < -5000) { + fprintf(stderr, "oops! queue slipped\n"); + if (tv.tv_sec < 5) { + fprintf(stderr, "(ignoring in first few seconds)\n"); + } else { + countdown = 2; + } + } else { + if (countdown > 0) --countdown; + } + + fprintf(stderr, "\n"); + struct timespec ts; + ts.tv_sec = 1; + ts.tv_nsec = 0; + nanosleep(&ts, 0); + } +} + diff --git a/src/base/test/test.cpp b/src/base/test/test.cpp new file mode 100644 index 0000000..9a9b496 --- /dev/null +++ b/src/base/test/test.cpp @@ -0,0 +1,535 @@ +// -*- c-basic-offset: 4 -*- +// -*- c-file-style: "bsd" -*- + +#define NDEBUG + +// This does some rather shoddy tests on a small selection of core classes. + +#include "Event.h" +#include "Segment.h" +#include "Composition.h" +//#include "Sets.h" + +#define TEST_NOTATION_TYPES 1 +#define TEST_SPEED 1 + +#ifdef TEST_NOTATION_TYPES +#include "NotationTypes.h" +#include "SegmentNotationHelper.h" +#include "SegmentPerformanceHelper.h" +#endif + +#include "MidiTypes.h" + +#include + +#include +#include + +using namespace std; +using namespace Rosegarden; + +static const PropertyName DURATION_PROPERTY = "duration"; +static const PropertyName SOME_INT_PROPERTY = "someIntProp"; +static const PropertyName SOME_BOOL_PROPERTY = "someBoolProp"; +static const PropertyName SOME_STRING_PROPERTY = "someStringProp"; +static const PropertyName NONEXISTENT_PROPERTY = "nonexistentprop"; +static const PropertyName ANNOTATION_PROPERTY = "annotation"; + +#if 0 +// Some attempts at reproducing the func-template-within-template problem +// +enum FooType {A, B, C}; + +class Foo +{ +public: + template void func(); +}; + +template +void Foo::func() +{ + // dummy code + T j = 0; + for(T i = 0; i < 100; ++i) j += i; +} + +//template void Foo::func(); + +template +class FooR +{ +public: + void rfunc(); +}; + +template +void FooR::rfunc() +{ + // this won't compile + Foo* foo; + foo->func(); +} + +void templateTest() +{ + Foo foo; + foo.func(); + +// FooR foor; +// foor.rfunc(); +} + + +template +class GenericSet // abstract base +{ +public: + typedef typename Container::iterator Iterator; + + /// Return true if this element, known to test() true, is a set member + virtual bool sample(const Iterator &i); +}; + + +template +bool +GenericSet::sample(const Iterator &i) +{ + Event *e; + long p = e->get("blah"); +} + +#endif + +int main(int argc, char **argv) +{ + typedef std::vector intvect; + +// intvect foo; + +// GenericSet genset; +// genset.sample(foo.begin()); + + clock_t st, et; + struct tms spare; + +#ifdef TEST_WIDE_STRING + basic_string widestring(L"This is a test"); + widestring += L" of wide character strings"; + for (unsigned int i = 0; i < widestring.length(); ++i) { + if (widestring[i] == L'w' || + widestring[i] == L'c') { + widestring[i] = toupper(widestring[i]); + } + } + cout << "Testing wide string: string value is \"" << widestring << "\"" + << endl; + cout << "String's length is " << widestring.length() << endl; + cout << "and storage space is " + << (widestring.length() * sizeof(widestring[0])) + << endl; + cout << "Characters are: "; + for (unsigned int i = 0; i < widestring.length(); ++i) { + cout << widestring[i]; + if (i < widestring.length()-1) cout << " "; + else cout << endl; + } +#endif + + cout << "\nTesting Event..." << endl + << "sizeof Event : " << sizeof(Event) << endl; + + Event e("note", 0); + e.set(DURATION_PROPERTY, 20); + cout << "duration is " << e.get(DURATION_PROPERTY) << endl; + + e.set(SOME_BOOL_PROPERTY, true); + e.set(SOME_STRING_PROPERTY, "foobar"); + + cout << "sizeof event after some properties set : " + << sizeof e << endl; + + try { + cout << "duration is " << e.get(DURATION_PROPERTY) << endl; + } catch (Event::BadType bt) { + cout << "Correctly caught BadType when trying to get of duration" << endl; + } + + string s; + + if (!e.get(DURATION_PROPERTY, s)) { + cout << "Correctly got error when trying to get of duration" << endl; + } else { + cerr << "ERROR AT " << __LINE__ << endl; + } + + try { + cout << "dummy prop is " << e.get(NONEXISTENT_PROPERTY) << endl; + } catch (Event::NoData bt) { + cout << "Correctly caught NoData when trying to get non existent property" << endl; + } + + if (!e.get(NONEXISTENT_PROPERTY, s)) { + cout << "Correctly got error when trying to get of non existent property" << endl; + } else { + cerr << "ERROR AT " << __LINE__ << endl; + } + + + e.setFromString(DURATION_PROPERTY, "30"); + cout << "duration is " << e.get(DURATION_PROPERTY) << endl; + + e.setFromString(ANNOTATION_PROPERTY, "This is my house"); + cout << "annotation is " << e.get(ANNOTATION_PROPERTY) << endl; + + long durationVal; + if (e.get(DURATION_PROPERTY, durationVal)) + cout << "duration is " << durationVal << endl; + else + cerr << "ERROR AT " << __LINE__ << endl; + + if (e.get(ANNOTATION_PROPERTY, s)) + cout << "annotation is " << s << endl; + else + cerr << "ERROR AT " << __LINE__ << endl; + + cout << "\nTesting persistence & setMaybe..." << endl; + + e.setMaybe(SOME_INT_PROPERTY, 1); + if (e.get(SOME_INT_PROPERTY) == 1) { + cout << "a. Correct: 1" << endl; + } else { + cout << "a. ERROR: " << e.get(SOME_INT_PROPERTY) << endl; + } + + e.set(SOME_INT_PROPERTY, 2, false); + e.setMaybe(SOME_INT_PROPERTY, 3); + if (e.get(SOME_INT_PROPERTY) == 3) { + cout << "b. Correct: 3" << endl; + } else { + cout << "b. ERROR: " << e.get(SOME_INT_PROPERTY) << endl; + } + + e.set(SOME_INT_PROPERTY, 4); + e.setMaybe(SOME_INT_PROPERTY, 5); + if (e.get(SOME_INT_PROPERTY) == 4) { + cout << "c. Correct: 4" << endl; + } else { + cout << "c. ERROR: " << e.get(SOME_INT_PROPERTY) << endl; + } + + cout << "\nTesting debug dump : " << endl; + e.dump(cout); + cout << endl << "dump finished" << endl; + +#if TEST_SPEED + cout << "Testing speed of Event..." << endl; + int i; + long j; + + char b[20]; + strcpy(b, "test"); + +#define NAME_COUNT 500 + + PropertyName names[NAME_COUNT]; + for (i = 0; i < NAME_COUNT; ++i) { + sprintf(b+4, "%d", i); + names[i] = b; + } + + Event e1("note", 0); + int gsCount = 200000; + + st = times(&spare); + for (i = 0; i < gsCount; ++i) { + e1.set(names[i % NAME_COUNT], i); + } + et = times(&spare); + cout << "Event: " << gsCount << " setInts: " << (et-st)*10 << "ms\n"; + + st = times(&spare); + j = 0; + for (i = 0; i < gsCount; ++i) { + if (i%4==0) sprintf(b+4, "%d", i); + j += e1.get(names[i % NAME_COUNT]); + } + et = times(&spare); + cout << "Event: " << gsCount << " getInts: " << (et-st)*10 << "ms (result: " << j << ")\n"; + + st = times(&spare); + for (i = 0; i < 1000; ++i) { + Event e11(e1); + (void)e11.get(names[i % NAME_COUNT]); + } + et = times(&spare); + cout << "Event: 1000 copy ctors of " << e1.getStorageSize() << "-byte element: " + << (et-st)*10 << "ms\n"; + +// gsCount = 100000; + + for (i = 0; i < NAME_COUNT; ++i) { + sprintf(b+4, "%ds", i); + names[i] = b; + } + + st = times(&spare); + for (i = 0; i < gsCount; ++i) { + e1.set(names[i % NAME_COUNT], b); + } + et = times(&spare); + cout << "Event: " << gsCount << " setStrings: " << (et-st)*10 << "ms\n"; + + st = times(&spare); + j = 0; + for (i = 0; i < gsCount; ++i) { + if (i%4==0) sprintf(b+4, "%ds", i); + j += e1.get(names[i % NAME_COUNT]).size(); + } + et = times(&spare); + cout << "Event: " << gsCount << " getStrings: " << (et-st)*10 << "ms (result: " << j << ")\n"; + + st = times(&spare); + for (i = 0; i < 1000; ++i) { + Event e11(e1); + (void)e11.get(names[i % NAME_COUNT]); + } + et = times(&spare); + cout << "Event: 1000 copy ctors of " << e1.getStorageSize() << "-byte element: " + << (et-st)*10 << "ms\n"; + + st = times(&spare); + for (i = 0; i < 1000; ++i) { + Event e11(e1); + (void)e11.get(names[i % NAME_COUNT]); + (void)e11.set(names[i % NAME_COUNT], "blah"); + } + et = times(&spare); + cout << "Event: 1000 copy ctors plus set of " << e1.getStorageSize() << "-byte element: " + << (et-st)*10 << "ms\n"; + +// gsCount = 1000000; + + st = times(&spare); + for (i = 0; i < gsCount; ++i) { + Event e21("dummy", i, 0, MIN_SUBORDERING); + } + et = times(&spare); + cout << "Event: " << gsCount << " event ctors alone: " + << (et-st)*10 << "ms\n"; + + st = times(&spare); + for (i = 0; i < gsCount; ++i) { + std::string s0("dummy"); + std::string s1 = s0; + } + et = times(&spare); + cout << "Event: " << gsCount << " string ctors+assignents: " + << (et-st)*10 << "ms\n"; + + st = times(&spare); + for (i = 0; i < gsCount; ++i) { + Event e21("dummy", i, 0, MIN_SUBORDERING); + (void)e21.getAbsoluteTime(); + (void)e21.getDuration(); + (void)e21.getSubOrdering(); + } + et = times(&spare); + cout << "Event: " << gsCount << " event ctors plus getAbsTime/Duration/SubOrdering: " + << (et-st)*10 << "ms\n"; + + st = times(&spare); + for (i = 0; i < gsCount; ++i) { + Event e21("dummy", i, 0, MIN_SUBORDERING); + (void)e21.getAbsoluteTime(); + (void)e21.getDuration(); + (void)e21.getSubOrdering(); + e21.set(names[0], 40); + (void)e21.get(names[0]); + } + et = times(&spare); + cout << "Event: " << gsCount << " event ctors plus one get/set and getAbsTime/Duration/SubOrdering: " + << (et-st)*10 << "ms\n"; + + +#else + cout << "Skipping test speed of Event\n"; +#endif // TEST_SPEED + +#ifdef NOT_DEFINED + cout << "Testing segment shrinking\n"; + + Segment segment(5, 0); + unsigned int nbBars = segment.getNbBars(); + + cout << "Segment nbBars : " << nbBars << endl; + if (nbBars != 5) { + cerr << "%%%ERROR : segment nbBars should be 5\n"; + } + + Segment::iterator iter = segment.end(); + --iter; + cout << "Last segment el. time : " << (*iter)->getAbsoluteTime() << endl; + + cout << "Shrinking segment to 3 bars : \n"; + segment.setNbBars(3); + nbBars = segment.getNbBars(); + + cout << "Segment new nbBars : " << nbBars << endl; + if (nbBars != 3) { + cerr << "%%%ERROR : segment new nbBars should be 3\n"; + } +#endif // NOT_DEFINED + +#ifdef TEST_NOTATION_TYPES + cout << "Testing duration-list stuff\n"; + + cout << "2/4..." << endl; + TimeSignature ts(2,4); + DurationList dlist; + ts.getDurationListForInterval + (dlist, 1209, + ts.getBarDuration() - Note(Note::Semiquaver, true).getDuration()); + int acc = 0; + for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) { + cout << "duration: " << *i << endl; + acc += *i; + } + cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl; + + + + cout << "4/4 96/96..." << endl; + ts = TimeSignature(4,4); + dlist = DurationList(); + ts.getDurationListForInterval(dlist, 96, 96); + acc = 0; + for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) { + cout << "duration: " << *i << endl; + acc += *i; + } + cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl; + + + + cout << "6/8..." << endl; + ts = TimeSignature(6,8); + dlist = DurationList(); + ts.getDurationListForInterval + (dlist, 1209, + ts.getBarDuration() - Note(Note::Semiquaver, true).getDuration()); + acc = 0; + for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) { + cout << "duration: " << *i << endl; + acc += *i; + } + cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl; + + cout << "3/4..." << endl; + ts = TimeSignature(3,4); + dlist = DurationList(); + ts.getDurationListForInterval + (dlist, 1209, + ts.getBarDuration() - Note(Note::Semiquaver, true).getDuration()); + acc = 0; + for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) { + cout << "duration: " << *i << endl; + acc += *i; + } + cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl; + + cout << "4/4..." << endl; + ts = TimeSignature(4,4); + dlist = DurationList(); + ts.getDurationListForInterval + (dlist, 1209, + ts.getBarDuration() - Note(Note::Semiquaver, true).getDuration()); + acc = 0; + for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) { + cout << "duration: " << *i << endl; + acc += *i; + } + cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl; + + cout << "3/8..." << endl; + ts = TimeSignature(3,8); + dlist = DurationList(); + ts.getDurationListForInterval + (dlist, 1209, + ts.getBarDuration() - Note(Note::Semiquaver, true).getDuration()); + acc = 0; + for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) { + cout << "duration: " << *i << endl; + acc += *i; + } + cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl; + + cout << "4/4 wacky placement..." << endl; + ts = TimeSignature(4,4); + dlist = DurationList(); + ts.getDurationListForInterval(dlist, 160, 1280); + acc = 0; + for (DurationList::iterator i = dlist.begin(); i != dlist.end(); ++i) { + cout << "duration: " << *i << endl; + acc += *i; + } + cout << "total: " << acc << " (on bar duration of " << ts.getBarDuration() << ")" << endl; + + cout << "Testing Segment::splitIntoTie() - splitting 384 -> 2*192\n"; + + Composition c; + Segment *ht = new Segment(); + c.addSegment(ht); + Segment &t(*ht); + SegmentNotationHelper nh(t); + SegmentPerformanceHelper ph(t); + + Event *ev = new Event("note", 0, 384); + ev->set("pitch", 60); + t.insert(ev); + + Segment::iterator sb(t.begin()); + nh.splitIntoTie(sb, 384/2); + + for(Segment::iterator i = t.begin(); i != t.end(); ++i) { + cout << "Event at " << (*i)->getAbsoluteTime() + << " - duration : " << (*i)->getDuration() + << endl; + } + + Segment::iterator half2 = t.begin(); ++half2; + + cout << "Splitting 192 -> (48 + 144) : \n"; + + sb = t.begin(); + nh.splitIntoTie(sb, 48); + + for(Segment::iterator i = t.begin(); i != t.end(); ++i) { + cout << "Event at " << (*i)->getAbsoluteTime() + << " - duration : " << (*i)->getDuration() + << endl; + } + + cout << "Splitting 192 -> (144 + 48) : \n"; + + nh.splitIntoTie(half2, 144); + + + for(Segment::iterator i = t.begin(); i != t.end(); ++i) { + cout << "Event at " << (*i)->getAbsoluteTime() + << " - duration : " << (*i)->getDuration() + << " - performance duration : " << + ph.getSoundingDuration(i) << endl; + + cout << endl; + (*i)->dump(cout); + cout << endl; + } + + nh.autoBeam(t.begin(), t.end(), "beamed"); + +#endif // TEST_NOTATION_TYPES +}; + diff --git a/src/base/test/thread.cpp b/src/base/test/thread.cpp new file mode 100644 index 0000000..ab327ff --- /dev/null +++ b/src/base/test/thread.cpp @@ -0,0 +1,126 @@ +// -*- c-basic-offset: 4 -*- +// -*- c-file-style: "bsd" -*- + +// This does some rather shoddy tests on a small selection of core classes. + +#include "Lock.h" +#include "Composition.h" +#include "Segment.h" +#include "Event.h" + +#include +#include +#include + +#include +#include + +using namespace std; +using namespace Rosegarden; + +static void* +writer_thread1(void *arg) +{ + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + cout << "write_thread1 - init" << endl; + + Rosegarden::Composition *comp = + static_cast(arg); + + Rosegarden::Composition::segmentcontainer segs = comp->getSegments(); + Rosegarden::Composition::segmentcontainer::iterator it = segs.begin(); + Rosegarden::Segment *segment = *it; + + Rosegarden::timeT insertTime = 50000; + while (true) + { + usleep(90000); + cout << "LENGTH = " << comp->getNbBars() << endl; + segment->insert(new Event(Note::EventType, insertTime)); + insertTime += 96; + } +} + +static void* +write_thread2(void *arg) +{ + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + cout << "write_thread2 - init" << endl; + + Rosegarden::Composition *comp = + static_cast(arg); + + Rosegarden::Composition::segmentcontainer segs = comp->getSegments(); + Rosegarden::Composition::segmentcontainer::iterator it = segs.begin(); + Rosegarden::Segment *segment = *it; + + Rosegarden::timeT insertTime = 0; + while (true) + { + usleep(50); + cout << "LENGTH = " << comp->getNbBars() << endl; + segment->insert(new Event(Note::EventType, insertTime)); + insertTime += 96; + } +} + + +int +main(int argc, char **argv) +{ + clock_t st, et; + struct tms spare; + + cout << "Threading test" << endl; + + pthread_t thread1; + pthread_t thread2; + Rosegarden::Composition comp; + Rosegarden::Segment segment; + comp.addSegment(&segment); + + if (pthread_create(&thread1, 0, writer_thread1, &comp)) + { + cerr << "Couldn't start thread 1" << endl; + exit(1); + } + pthread_detach(thread1); + + if (pthread_create(&thread2, 0, write_thread2, &comp)) + { + cerr << "Couldn't start thread 2" << endl; + exit(1); + } + pthread_detach(thread2); + + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + static Lock lock; + + if (lock.getWriteLock(1)) + { + cout << "got write lock" << endl; + } + + if (lock.getWriteLock(0)) + { + cout << "got second write lock" << endl; + } + else + { + cout << "couldn't get second write lock" << endl; + } + + Rosegarden::timeT insertTime = 0; + while(true) + { + usleep(50000); + + cout << "Inserting Event at time " << insertTime << endl; + segment.insert(new Event(Note::EventType, insertTime)); + insertTime += 96; + } + +}; + + diff --git a/src/base/test/transpose.cpp b/src/base/test/transpose.cpp new file mode 100644 index 0000000..b1254f5 --- /dev/null +++ b/src/base/test/transpose.cpp @@ -0,0 +1,83 @@ +// -*- c-basic-offset: 4 -*- + +#include "NotationTypes.h" + +using namespace Rosegarden; +using std::cout; + +// Unit test-ish tests for transposition. +// +// Returns -1 (or crashes :)) on error, 0 on success + +/** + * should be in Pitch eventually + */ +void testAisDisplayAccidentalInCmaj() +{ + Pitch ais(70, Accidentals::Sharp); + Key cmaj ("C major"); + Accidental accidental = ais.getDisplayAccidental(cmaj); + if (accidental != Accidentals::Sharp) + { + std::cout << "Accidental for A# in Cmaj was " << accidental << " instead of expected Sharp" << std::endl; + exit(-1); + } +} + +/** + * transpose an A# up by a major second, should + * yield a B# (as C would be a minor triad) + */ +void testAisToBis() +{ + std::cout << "Testing transposing A# to B#... "; + Pitch ais(70, Accidentals::Sharp); + Key cmaj ("C major"); + + Pitch result = ais.transpose(cmaj, 2, 1); + + Accidental resultAccidental = result.getAccidental(cmaj); + int resultPitch = result.getPerformancePitch(); + if (resultAccidental != Accidentals::Sharp || resultPitch != 72) + { + std::cout << "Transposing A# up by a major second didn't yield B#, but " << result.getNoteName(cmaj) << resultAccidental << std::endl; + exit(-1); + } + std::cout << "Success" << std::endl; +} + +/** + * Transpose G to D in the key of D major. + */ +void testGToD() +{ + std::cout << "Testing transposing G to D... "; + Pitch g(67, Accidentals::Natural); + Key* dmaj = new Key("D major"); + + Pitch result = g.transpose(*dmaj, 7, 4); + + Accidental resultAccidental = result.getAccidental(*dmaj); + int resultPitch = result.getPerformancePitch(); + if (resultAccidental != Accidentals::NoAccidental || resultPitch != 74) + { + std::cout << "Transposing G up by a fifth didn't yield D, but " << result.getNoteName(*dmaj) << resultAccidental << std::endl; + exit(-1); + } + std::cout << "Success" << std::endl; +} + +void testKeyTransposition() +{ + +} + +int main(int argc, char **argv) +{ + testAisDisplayAccidentalInCmaj(); + testAisToBis(); + testGToD(); + testKeyTransposition(); + + return 0; +} diff --git a/src/base/test/utf8.cpp b/src/base/test/utf8.cpp new file mode 100644 index 0000000..7104cc0 --- /dev/null +++ b/src/base/test/utf8.cpp @@ -0,0 +1,96 @@ +// -*- c-basic-offset: 4 -*- + +#include "XmlExportable.h" +#include +#include + +using namespace Rosegarden; +using std::cout; +using std::cerr; +using std::endl; +using std::string; + + +string binary(unsigned char c) +{ + string s; + for (int i = 0; i < 8; ++i) { + s = ((c & 0x1) ? '1' : '0') + s; + c >>= 1; + } + return s; +} + + +int main(int argc, char **argv) +{ + string valid[] = { + "ニュース", + "주요 뉴스", + "Nyheter", + "天气", + "Notícias", + }; + + string escapable[] = { + "ニュ&ース", + "주요 <뉴스>", + "\"Nyheter\"", + "\'Notícias\'", + }; + + string invalid[] = { + "ƒ‹ƒ¥ãƒ¼ã‚¹", + "ì£¼ìš ” 뉴스", + "Nyhe\004ter", + "å天气", + "NotÃcias", + }; + + cout << "Testing valid strings -- should be no errors here" << endl; + + for (int i = 0; i < sizeof(valid)/sizeof(valid[0]); ++i) { + string encoded = XmlExportable::encode(valid[i]); + if (encoded != valid[i]) { + cerr << "Encoding failed:" << endl; + for (int j = 0; j < valid[i].length(); ++j) { + cerr << (char)valid[i][j] << " (" + << binary(valid[i][j]) << ")" << endl; + } + exit(1); + } + } + + cout << "Testing escapable strings -- should be no errors here" << endl; + + for (int i = 0; i < sizeof(escapable)/sizeof(escapable[0]); ++i) { + string encoded = XmlExportable::encode(escapable[i]); + if (encoded == escapable[i]) { + cerr << "Escaping failed:" << endl; + for (int j = 0; j < escapable[i].length(); ++j) { + cerr << (char)escapable[i][j] << " (" + << binary(escapable[i][j]) << ")" << endl; + } + exit(1); + } + } + + cout << "Testing invalid strings -- should be " + << (sizeof(invalid)/sizeof(invalid[0])) + << " errors here (but no fatal ones)" << endl; + + for (int i = 0; i < sizeof(invalid)/sizeof(invalid[0]); ++i) { + string encoded = XmlExportable::encode(invalid[i]); + if (encoded == invalid[i]) { + cerr << "Encoding succeeded but should have failed:" << endl; + for (int j = 0; j < invalid[i].length(); ++j) { + cerr << (char)invalid[i][j] << " (" + << binary(invalid[i][j]) << ")" << endl; + } + exit(1); + } + } + + exit(0); +} + diff --git a/src/commands/edit/AddDotCommand.cpp b/src/commands/edit/AddDotCommand.cpp new file mode 100644 index 0000000..b69a25c --- /dev/null +++ b/src/commands/edit/AddDotCommand.cpp @@ -0,0 +1,98 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddDotCommand.h" + +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +void +AddDotCommand::modifySegment() +{ + std::vector toErase; + std::vector toInsert; + + EventSelection::eventcontainer::iterator i; + timeT endTime = getEndTime(); + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + + Note note = Note::getNearestNote + ((*i)->getNotationDuration()); + int dots = note.getDots(); + if (++dots > 2) + dots = 0; + + toErase.push_back(*i); + + Event *e; + + if (m_notationOnly) { + e = new Event(**i, + (*i)->getAbsoluteTime(), + (*i)->getDuration(), + (*i)->getSubOrdering(), + (*i)->getNotationAbsoluteTime(), + Note(note.getNoteType(), + dots).getDuration()); + + } else { + e = new Event(**i, + (*i)->getNotationAbsoluteTime(), + Note(note.getNoteType(), + dots).getDuration()); + } + + if (e->getNotationAbsoluteTime() + e->getNotationDuration() > endTime) { + endTime = e->getNotationAbsoluteTime() + e->getNotationDuration(); + } + + toInsert.push_back(e); + } + } + + for (std::vector::iterator i = toErase.begin(); i != toErase.end(); ++i) { + m_selection->getSegment().eraseSingle(*i); + } + + for (std::vector::iterator i = toInsert.begin(); i != toInsert.end(); ++i) { + m_selection->getSegment().insert(*i); + m_selection->addEvent(*i); + } + + m_selection->getSegment().normalizeRests(getStartTime(), endTime); +} + +} diff --git a/src/commands/edit/AddDotCommand.h b/src/commands/edit/AddDotCommand.h new file mode 100644 index 0000000..a88da26 --- /dev/null +++ b/src/commands/edit/AddDotCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADDDOTCOMMAND_H_ +#define _RG_ADDDOTCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class AddDotCommand : public BasicSelectionCommand +{ +public: + AddDotCommand(EventSelection &selection, bool notationOnly) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection), + m_notationOnly(notationOnly) + { } + + static QString getGlobalName() { + return i18n("&Add Dot"); + } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + bool m_notationOnly; +}; + + + + +} + +#endif diff --git a/src/commands/edit/AddMarkerCommand.cpp b/src/commands/edit/AddMarkerCommand.cpp new file mode 100644 index 0000000..b7c665a --- /dev/null +++ b/src/commands/edit/AddMarkerCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddMarkerCommand.h" + +#include "base/Composition.h" +#include "base/Marker.h" +#include + + +namespace Rosegarden +{ + +AddMarkerCommand::AddMarkerCommand(Composition *comp, + timeT time, + const std::string &name, + const std::string &description): + KNamedCommand(getGlobalName()), + m_composition(comp), + m_detached(true) +{ + m_marker = new Marker(time, name, description); +} + +AddMarkerCommand::~AddMarkerCommand() +{ + if (m_detached) + delete m_marker; +} + +void +AddMarkerCommand::execute() +{ + m_composition->addMarker(m_marker); + m_detached = false; +} + +void +AddMarkerCommand::unexecute() +{ + m_composition->detachMarker(m_marker); + m_detached = true; +} + +} diff --git a/src/commands/edit/AddMarkerCommand.h b/src/commands/edit/AddMarkerCommand.h new file mode 100644 index 0000000..80f15c1 --- /dev/null +++ b/src/commands/edit/AddMarkerCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADDMARKERCOMMAND_H_ +#define _RG_ADDMARKERCOMMAND_H_ + +#include +#include +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Marker; +class Composition; + + +class AddMarkerCommand : public KNamedCommand +{ +public: + AddMarkerCommand(Composition *comp, + timeT time, + const std::string &name, + const std::string &description); + ~AddMarkerCommand(); + + static QString getGlobalName() { return i18n("&Add Marker"); } + + virtual void execute(); + virtual void unexecute(); + +protected: + + Composition *m_composition; + Marker *m_marker; + bool m_detached; + +}; + + + +} + +#endif diff --git a/src/commands/edit/ChangeVelocityCommand.cpp b/src/commands/edit/ChangeVelocityCommand.cpp new file mode 100644 index 0000000..fc1c1ea --- /dev/null +++ b/src/commands/edit/ChangeVelocityCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ChangeVelocityCommand.h" + +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include +#include "base/BaseProperties.h" + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +ChangeVelocityCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + + long velocity = 100; + (*i)->get + (VELOCITY, velocity); + + // round velocity up to the next multiple of delta + velocity /= m_delta; + velocity *= m_delta; + velocity += m_delta; + + if (velocity < 0) + velocity = 0; + if (velocity > 127) + velocity = 127; + (*i)->set(VELOCITY, velocity); + } + } +} + +} diff --git a/src/commands/edit/ChangeVelocityCommand.h b/src/commands/edit/ChangeVelocityCommand.h new file mode 100644 index 0000000..a0a51b1 --- /dev/null +++ b/src/commands/edit/ChangeVelocityCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CHANGEVELOCITYCOMMAND_H_ +#define _RG_CHANGEVELOCITYCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +/** Add or subtract a constant from all event velocities. + Use SelectionPropertyCommand if you want to do something more + creative. */ +class ChangeVelocityCommand : public BasicSelectionCommand +{ +public: + ChangeVelocityCommand(int delta, EventSelection &selection) : + BasicSelectionCommand(getGlobalName(delta), selection, true), + m_selection(&selection), m_delta(delta) { } + + static QString getGlobalName(int delta = 0) { + if (delta > 0) return i18n("&Increase Velocity"); + else return i18n("&Reduce Velocity"); + } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + int m_delta; +}; + + +} + +#endif diff --git a/src/commands/edit/ClearTriggersCommand.cpp b/src/commands/edit/ClearTriggersCommand.cpp new file mode 100644 index 0000000..3b58405 --- /dev/null +++ b/src/commands/edit/ClearTriggersCommand.cpp @@ -0,0 +1,53 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ClearTriggersCommand.h" + +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include +#include "base/BaseProperties.h" + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +ClearTriggersCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + (*i)->unset(TRIGGER_SEGMENT_ID); + (*i)->unset(TRIGGER_SEGMENT_RETUNE); + (*i)->unset(TRIGGER_SEGMENT_ADJUST_TIMES); + } +} + +} diff --git a/src/commands/edit/ClearTriggersCommand.h b/src/commands/edit/ClearTriggersCommand.h new file mode 100644 index 0000000..077e270 --- /dev/null +++ b/src/commands/edit/ClearTriggersCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CLEARTRIGGERSCOMMAND_H_ +#define _RG_CLEARTRIGGERSCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class ClearTriggersCommand : public BasicSelectionCommand +{ +public: + ClearTriggersCommand(EventSelection &selection, + QString name = 0) : + BasicSelectionCommand(name ? name : getGlobalName(), selection, true), + m_selection(&selection) + { } + + static QString getGlobalName() { + return i18n("&Clear Triggers"); + } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + + +} + +#endif diff --git a/src/commands/edit/CollapseNotesCommand.cpp b/src/commands/edit/CollapseNotesCommand.cpp new file mode 100644 index 0000000..225d34c --- /dev/null +++ b/src/commands/edit/CollapseNotesCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CollapseNotesCommand.h" + +#include "base/Event.h" +#include "base/SegmentNotationHelper.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +void +CollapseNotesCommand::modifySegment() +{ + SegmentNotationHelper helper(getSegment()); + timeT endTime = getEndTime(); + + // This is really nasty stuff. We can't go in forward direction + // using the j-iterator trick because collapseNoteAggressively may + // erase the following iterator as well as the preceding one. We + // can't go backward naively, because collapseNoteAggressively + // erases i from the EventSelection now that it's a + // SegmentObserver. We need the fancy hybrid j-iterator-backward + // technique applied to selections instead of segments. + + EventSelection::eventcontainer::iterator i = + m_selection->getSegmentEvents().end(); + EventSelection::eventcontainer::iterator j = i; + EventSelection::eventcontainer::iterator beg = + m_selection->getSegmentEvents().begin(); + bool thisOne = false; + + while (i != beg && (!thisOne || (*i != *beg))) { + + --j; + + if (thisOne) { + helper.collapseNoteAggressively(*i, endTime); + } + + // rather than "true" one could perform a test to see + // whether j pointed to a candidate for collapsing: + thisOne = true; + + i = j; + } + + if (thisOne) { + helper.collapseNoteAggressively(*i, endTime); + } +} + +} diff --git a/src/commands/edit/CollapseNotesCommand.h b/src/commands/edit/CollapseNotesCommand.h new file mode 100644 index 0000000..9d3a0fc --- /dev/null +++ b/src/commands/edit/CollapseNotesCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUCOLLAPSENOTESCOMMAND_H_ +#define _RG_ADJUSTMENUCOLLAPSENOTESCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + +class Collapse; + + +namespace Rosegarden +{ + +class EventSelection; + + +class CollapseNotesCommand : public BasicSelectionCommand +{ +public: + CollapseNotesCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("Collapse &Equal-Pitch Notes"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + +// Set the (numerical) property of a selection according given pattern. +// + +} + +#endif diff --git a/src/commands/edit/CopyCommand.cpp b/src/commands/edit/CopyCommand.cpp new file mode 100644 index 0000000..38aa628 --- /dev/null +++ b/src/commands/edit/CopyCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CopyCommand.h" + +#include "misc/Strings.h" +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "base/Selection.h" +#include + + +namespace Rosegarden +{ + +CopyCommand::CopyCommand(EventSelection &selection, + Clipboard *clipboard) : + KNamedCommand(getGlobalName()), + m_targetClipboard(clipboard) +{ + m_sourceClipboard = new Clipboard; + m_savedClipboard = 0; + m_sourceClipboard->newSegment(&selection)->setLabel + (selection.getSegment().getLabel() + " " + qstrtostr(i18n("(excerpt)"))); +} + +CopyCommand::CopyCommand(SegmentSelection &selection, + Clipboard *clipboard) : + KNamedCommand(getGlobalName()), + m_targetClipboard(clipboard) +{ + m_sourceClipboard = new Clipboard; + m_savedClipboard = 0; + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + QString newLabel = strtoqstr((*i)->getLabel()); + if (newLabel.contains(i18n("(copied)"))) { + m_sourceClipboard->newSegment(*i); + } else { + m_sourceClipboard->newSegment(*i)-> + setLabel(qstrtostr(i18n("%1 (copied)").arg(newLabel))); + } + } +} + +CopyCommand::CopyCommand(Composition *composition, + timeT beginTime, + timeT endTime, + Clipboard *clipboard) : + KNamedCommand(i18n("Copy Range")), + m_targetClipboard(clipboard) +{ + m_sourceClipboard = new Clipboard; + m_savedClipboard = 0; + + for (Composition::iterator i = composition->begin(); + i != composition->end(); ++i) { + if ((*i)->getStartTime() < endTime && + (*i)->getRepeatEndTime() > beginTime) { + m_sourceClipboard->newSegment(*i, beginTime, endTime, true); + } + } + + TimeSignatureSelection tsigsel + (*composition, beginTime, endTime, true); + m_sourceClipboard->setTimeSignatureSelection(tsigsel); + + TempoSelection temposel + (*composition, beginTime, endTime, true); + m_sourceClipboard->setTempoSelection(temposel); + + m_sourceClipboard->setNominalRange(beginTime, endTime); +} + +CopyCommand::~CopyCommand() +{ + delete m_sourceClipboard; + delete m_savedClipboard; +} + +void +CopyCommand::execute() +{ + if (!m_savedClipboard) { + m_savedClipboard = new Clipboard(*m_targetClipboard); + } + + m_targetClipboard->copyFrom(m_sourceClipboard); +} + +void +CopyCommand::unexecute() +{ + m_targetClipboard->copyFrom(m_savedClipboard); +} + +} diff --git a/src/commands/edit/CopyCommand.h b/src/commands/edit/CopyCommand.h new file mode 100644 index 0000000..29d6dc7 --- /dev/null +++ b/src/commands/edit/CopyCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_COPYCOMMAND_H_ +#define _RG_COPYCOMMAND_H_ + +#include +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class SegmentSelection; +class EventSelection; +class Composition; +class Clipboard; + + +/// Copy a selection + +class CopyCommand : public KNamedCommand +{ +public: + /// Make a CopyCommand that copies events from within a Segment + CopyCommand(EventSelection &selection, + Clipboard *clipboard); + + /// Make a CopyCommand that copies whole Segments + CopyCommand(SegmentSelection &selection, + Clipboard *clipboard); + + /// Make a CopyCommand that copies a range of a Composition + CopyCommand(Composition *composition, + timeT beginTime, + timeT endTime, + Clipboard *clipboard); + + virtual ~CopyCommand(); + + static QString getGlobalName() { return i18n("&Copy"); } + + virtual void execute(); + virtual void unexecute(); + +protected: + Clipboard *m_sourceClipboard; + Clipboard *m_targetClipboard; + Clipboard *m_savedClipboard; +}; + + + +} + +#endif diff --git a/src/commands/edit/CutAndCloseCommand.cpp b/src/commands/edit/CutAndCloseCommand.cpp new file mode 100644 index 0000000..a99b4ef --- /dev/null +++ b/src/commands/edit/CutAndCloseCommand.cpp @@ -0,0 +1,163 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CutAndCloseCommand.h" + +#include "base/Clipboard.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "CutCommand.h" +#include +#include "misc/Debug.h" + + +namespace Rosegarden +{ + +CutAndCloseCommand::CutAndCloseCommand(EventSelection &selection, + Clipboard *clipboard) : + KMacroCommand(getGlobalName()) +{ + addCommand(new CutCommand(selection, clipboard)); + addCommand(new CloseCommand(&selection.getSegment(), + selection.getEndTime(), + selection.getStartTime())); +} + +void +CutAndCloseCommand::CloseCommand::execute() +{ + // We shift all the events from m_gapEnd to the end of the + // segment back so that they start at m_gapStart instead of m_gapEnd. + + assert(m_gapEnd >= m_gapStart); + if (m_gapEnd == m_gapStart) + return ; + + // We also need to record how many events there are already at + // m_gapStart so that we can leave those unchanged when we undo. + // (This command is executed on the understanding that the area + // between m_gapStart and m_gapEnd is empty of all but rests, but + // in practice there may be other things such as a clef at the + // same time as m_gapStart. This will only work for events that + // have smaller subordering than notes etc.) + + m_staticEvents = 0; + for (Segment::iterator i = m_segment->findTime(m_gapStart); + m_segment->isBeforeEndMarker(i); ++i) { + if ((*i)->getAbsoluteTime() > m_gapStart) + break; + if ((*i)->isa(Note::EventRestType)) + continue; + ++m_staticEvents; + } + + std::vector events; + timeT timeDifference = m_gapEnd - m_gapStart; + + for (Segment::iterator i = m_segment->findTime(m_gapEnd); + m_segment->isBeforeEndMarker(i); ++i) { + events.push_back((*i)->copyMoving( -timeDifference)); + } + + timeT oldEndTime = m_segment->getEndTime(); + + // remove rests from target area, and everything thereafter + for (Segment::iterator i = m_segment->findTime(m_gapStart); + m_segment->isBeforeEndMarker(i); ) { + if ((*i)->getAbsoluteTime() >= m_gapEnd || + (*i)->isa(Note::EventRestType)) { + Segment::iterator j(i); + ++j; + m_segment->erase(i); + i = j; + } else { + ++i; + } + } + + for (unsigned int i = 0; i < events.size(); ++i) { + m_segment->insert(events[i]); + } + + m_segment->normalizeRests(m_segment->getEndTime(), oldEndTime); +} + +void +CutAndCloseCommand::CloseCommand::unexecute() +{ + // We want to shift events from m_gapStart to the end of the + // segment forward so as to start at m_gapEnd instead of + // m_gapStart. + + assert(m_gapEnd >= m_gapStart); + if (m_gapEnd == m_gapStart) + return ; + + // May need to ignore some static events at m_gapStart. + // These are assumed to have smaller subordering than whatever + // we're not ignoring. Actually this still isn't quite right: + // it'll do the wrong thing where we have, say, a clef then + // some notes then another clef and we cut-and-close all the + // notes and then undo. But it's better than we were doing + // before. + + Segment::iterator starti = m_segment->findTime(m_gapStart); + + while (m_segment->isBeforeEndMarker(starti)) { + if (m_staticEvents == 0) + break; + if ((*starti)->getAbsoluteTime() > m_gapStart) + break; + if (!(*starti)->isa(Note::EventRestType)) + --m_staticEvents; + ++starti; + } + + std::vector events; + timeT timeDifference = m_gapEnd - m_gapStart; + + for (Segment::iterator i = starti; m_segment->isBeforeEndMarker(i); ) { + Segment::iterator j(i); + ++j; + events.push_back((*i)->copyMoving(timeDifference)); + m_segment->erase(i); + i = j; + } + + for (unsigned int i = 0; i < events.size(); ++i) { + m_segment->insert(events[i]); + } + + timeT endTime = m_segment->getEndTime(); + NOTATION_DEBUG << "setting end time to " << (endTime - timeDifference) << endl; + //!!! this following is not working for bugaccidentals.rg: + m_segment->setEndTime(endTime - timeDifference); + + m_segment->normalizeRests(m_gapStart, m_gapEnd); +} + +} diff --git a/src/commands/edit/CutAndCloseCommand.h b/src/commands/edit/CutAndCloseCommand.h new file mode 100644 index 0000000..4be5809 --- /dev/null +++ b/src/commands/edit/CutAndCloseCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CUTANDCLOSECOMMAND_H_ +#define _RG_CUTANDCLOSECOMMAND_H_ + +#include +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Segment; +class EventSelection; +class Clipboard; + + +/// Cut a selection and close the gap + +class CutAndCloseCommand : public KMacroCommand +{ +public: + CutAndCloseCommand(EventSelection &selection, + Clipboard *clipboard); + + static QString getGlobalName() { return i18n("C&ut and Close"); } + +protected: + class CloseCommand : public KNamedCommand + { + public: + CloseCommand(Segment *segment, + timeT fromTime, + timeT toTime) : + KNamedCommand("Close"), + m_segment(segment), + m_gapEnd(fromTime), + m_gapStart(toTime) { } + + virtual void execute(); + virtual void unexecute(); + + private: + Segment *m_segment; + timeT m_gapEnd; + timeT m_gapStart; + int m_staticEvents; + }; +}; + + + +} + +#endif diff --git a/src/commands/edit/CutCommand.cpp b/src/commands/edit/CutCommand.cpp new file mode 100644 index 0000000..9d54089 --- /dev/null +++ b/src/commands/edit/CutCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CutCommand.h" + +#include "base/Clipboard.h" +#include "base/Selection.h" +#include "commands/segment/SegmentEraseCommand.h" +#include "CopyCommand.h" +#include "EraseCommand.h" +#include + + +namespace Rosegarden +{ + +CutCommand::CutCommand(EventSelection &selection, + Clipboard *clipboard) : + KMacroCommand(getGlobalName()) +{ + addCommand(new CopyCommand(selection, clipboard)); + addCommand(new EraseCommand(selection)); +} + +CutCommand::CutCommand(SegmentSelection &selection, + Clipboard *clipboard) : + KMacroCommand(getGlobalName()) +{ + addCommand(new CopyCommand(selection, clipboard)); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + addCommand(new SegmentEraseCommand(*i)); + } +} + +} diff --git a/src/commands/edit/CutCommand.h b/src/commands/edit/CutCommand.h new file mode 100644 index 0000000..186736c --- /dev/null +++ b/src/commands/edit/CutCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CUTCOMMAND_H_ +#define _RG_CUTCOMMAND_H_ + +#include +#include +#include + + +namespace Rosegarden +{ + +class SegmentSelection; +class EventSelection; +class Clipboard; + + +/// Cut a selection + +class CutCommand : public KMacroCommand +{ +public: + /// Make a CutCommand that cuts events from within a Segment + CutCommand(EventSelection &selection, + Clipboard *clipboard); + + /// Make a CutCommand that cuts whole Segments + CutCommand(SegmentSelection &selection, + Clipboard *clipboard); + + static QString getGlobalName() { return i18n("Cu&t"); } +}; + + + +} + +#endif diff --git a/src/commands/edit/EraseCommand.cpp b/src/commands/edit/EraseCommand.cpp new file mode 100644 index 0000000..8649885 --- /dev/null +++ b/src/commands/edit/EraseCommand.cpp @@ -0,0 +1,86 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EraseCommand.h" + +#include "misc/Debug.h" +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +EraseCommand::EraseCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection), + m_relayoutEndTime(getEndTime()) +{ + // nothing else +} + +void +EraseCommand::modifySegment() +{ + RG_DEBUG << "EraseCommand::modifySegment" << endl; + + std::vector toErase; + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Clef::EventType) || + (*i)->isa(Key ::EventType)) { + m_relayoutEndTime = getSegment().getEndTime(); + } + + // We used to do this by calling SegmentNotationHelper::deleteEvent + // on each event in the selection, but it's probably easier to + // cope with general selections by deleting everything in the + // selection and then normalizing the rests. The deleteEvent + // mechanism is still the more sensitive way to do it for single + // events, and it's what's used by EraseEventCommand and thus + // the notation eraser tool. + + toErase.push_back(*i); + } + + for (unsigned int j = 0; j < toErase.size(); ++j) { + getSegment().eraseSingle(toErase[j]); + } + + getSegment().normalizeRests(getStartTime(), getEndTime()); +} + +timeT +EraseCommand::getRelayoutEndTime() +{ + return m_relayoutEndTime; +} + +} diff --git a/src/commands/edit/EraseCommand.h b/src/commands/edit/EraseCommand.h new file mode 100644 index 0000000..4e583ad --- /dev/null +++ b/src/commands/edit/EraseCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ERASECOMMAND_H_ +#define _RG_ERASECOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +/// Erase a selection from within a segment + +class EraseCommand : public BasicSelectionCommand +{ +public: + EraseCommand(EventSelection &selection); + + static QString getGlobalName() { return i18n("&Erase"); } + + virtual timeT getRelayoutEndTime(); + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + timeT m_relayoutEndTime; +}; + + + +} + +#endif diff --git a/src/commands/edit/EventEditCommand.cpp b/src/commands/edit/EventEditCommand.cpp new file mode 100644 index 0000000..ef31be2 --- /dev/null +++ b/src/commands/edit/EventEditCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EventEditCommand.h" + +#include "base/Event.h" +#include "base/Segment.h" +#include "document/BasicCommand.h" +#include + + +namespace Rosegarden +{ + +EventEditCommand::EventEditCommand(Segment &segment, + Event *eventToModify, + const Event &newEvent) : + BasicCommand(getGlobalName(), + segment, + std::min(eventToModify->getAbsoluteTime(), + newEvent.getAbsoluteTime()), + std::max(eventToModify->getAbsoluteTime() + + eventToModify->getDuration(), + newEvent.getAbsoluteTime() + + newEvent.getDuration()), + true), // bruteForceRedo + m_oldEvent(eventToModify), + m_newEvent(newEvent) +{ + // nothing else to see here +} + +void +EventEditCommand::modifySegment() +{ + Segment &segment(getSegment()); + segment.eraseSingle(m_oldEvent); + segment.insert(new Event(m_newEvent)); + segment.normalizeRests(getStartTime(), getEndTime()); +} + +} diff --git a/src/commands/edit/EventEditCommand.h b/src/commands/edit/EventEditCommand.h new file mode 100644 index 0000000..5f22a1e --- /dev/null +++ b/src/commands/edit/EventEditCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EVENTEDITCOMMAND_H_ +#define _RG_EVENTEDITCOMMAND_H_ + +#include "base/Event.h" +#include "document/BasicCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class Segment; + + +/** + * Replace an event with another one (likely to be used in + * conjunction with EventEditDialog) + */ + +class EventEditCommand : public BasicCommand +{ +public: + EventEditCommand(Segment &segment, + Event *eventToModify, + const Event &newEvent); + + static QString getGlobalName() { return i18n("Edit E&vent"); } + +protected: + virtual void modifySegment(); + +private: + Event *m_oldEvent; // only used on 1st execute + Event m_newEvent; // only used on 1st execute +}; + + + +} + +#endif diff --git a/src/commands/edit/EventInsertionCommand.cpp b/src/commands/edit/EventInsertionCommand.cpp new file mode 100644 index 0000000..a684883 --- /dev/null +++ b/src/commands/edit/EventInsertionCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EventInsertionCommand.h" + +#include +#include "base/Event.h" +#include "base/Segment.h" +#include "document/BasicCommand.h" + + +namespace Rosegarden +{ + +EventInsertionCommand::EventInsertionCommand(Segment &segment, + Event *event) : + BasicCommand(i18n("Insert Event"), segment, event->getAbsoluteTime(), + event->getAbsoluteTime() + event->getDuration()), + m_event(new Event(*event)) +{ + // nothing +} + +EventInsertionCommand::~EventInsertionCommand() +{ + delete m_event; + // don't want to delete m_lastInsertedEvent, it's just an alias +} + +void EventInsertionCommand::modifySegment() +{ + m_lastInsertedEvent = new Event(*m_event); + getSegment().insert(m_lastInsertedEvent); +} + +} diff --git a/src/commands/edit/EventInsertionCommand.h b/src/commands/edit/EventInsertionCommand.h new file mode 100644 index 0000000..aee9c8b --- /dev/null +++ b/src/commands/edit/EventInsertionCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EVENTINSERTIONCOMMAND_H_ +#define _RG_EVENTINSERTIONCOMMAND_H_ + +#include "document/BasicCommand.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class EventInsertionCommand : public BasicCommand +{ +public: + EventInsertionCommand(Segment &segment, + Event *event); + + virtual ~EventInsertionCommand(); + + Event *getLastInsertedEvent() { return m_lastInsertedEvent; } + +protected: + virtual void modifySegment(); + + Event *m_event; + Event *m_lastInsertedEvent; // an alias for another event +}; + + + +} + +#endif diff --git a/src/commands/edit/EventQuantizeCommand.cpp b/src/commands/edit/EventQuantizeCommand.cpp new file mode 100644 index 0000000..775a32f --- /dev/null +++ b/src/commands/edit/EventQuantizeCommand.cpp @@ -0,0 +1,273 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EventQuantizeCommand.h" + +#include +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "base/Quantizer.h" +#include "base/BasicQuantizer.h" +#include "base/LegatoQuantizer.h" +#include "base/NotationQuantizer.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Selection.h" +#include "document/BasicCommand.h" +#include +#include +#include "base/BaseProperties.h" +#include "gui/application/RosegardenApplication.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +EventQuantizeCommand::EventQuantizeCommand(Segment &segment, + timeT startTime, + timeT endTime, + Quantizer *quantizer): + BasicCommand(getGlobalName(quantizer), segment, startTime, endTime, + true), // bruteForceRedo + m_quantizer(quantizer), + m_selection(0) +{ + // nothing else +} + +EventQuantizeCommand::EventQuantizeCommand(EventSelection &selection, + Quantizer *quantizer): + BasicCommand(getGlobalName(quantizer), + selection.getSegment(), + selection.getStartTime(), + selection.getEndTime(), + true), // bruteForceRedo + m_quantizer(quantizer), + m_selection(&selection) +{ + // nothing else +} + +EventQuantizeCommand::EventQuantizeCommand(Segment &segment, + timeT startTime, + timeT endTime, + QString configGroup, + bool notation): + BasicCommand(getGlobalName(makeQuantizer(configGroup, notation)), + segment, startTime, endTime, + true), // bruteForceRedo + m_selection(0), + m_configGroup(configGroup) +{ + // nothing else -- m_quantizer set by makeQuantizer +} + +EventQuantizeCommand::EventQuantizeCommand(EventSelection &selection, + QString configGroup, + bool notation): + BasicCommand(getGlobalName(makeQuantizer(configGroup, notation)), + selection.getSegment(), + selection.getStartTime(), + selection.getEndTime(), + true), // bruteForceRedo + m_selection(&selection), + m_configGroup(configGroup) +{ + // nothing else -- m_quantizer set by makeQuantizer +} + +EventQuantizeCommand::~EventQuantizeCommand() +{ + delete m_quantizer; +} + +QString +EventQuantizeCommand::getGlobalName(Quantizer *quantizer) +{ + if (quantizer) { + if (dynamic_cast(quantizer)) { + return i18n("Heuristic Notation &Quantize"); + } else { + return i18n("Grid &Quantize"); + } + } + + return i18n("&Quantize..."); +} + +void +EventQuantizeCommand::modifySegment() +{ + Profiler profiler("EventQuantizeCommand::modifySegment", true); + + Segment &segment = getSegment(); + SegmentNotationHelper helper(segment); + + bool rebeam = false; + bool makeviable = false; + bool decounterpoint = false; + + if (m_configGroup) { + //!!! need way to decide whether to do these even if no config group (i.e. through args to the command) + KConfig *config = kapp->config(); + config->setGroup(m_configGroup); + + rebeam = config->readBoolEntry("quantizerebeam", true); + makeviable = config->readBoolEntry("quantizemakeviable", false); + decounterpoint = config->readBoolEntry("quantizedecounterpoint", false); + } + + if (m_selection) { + m_quantizer->quantize(m_selection); + + } else { + m_quantizer->quantize(&segment, + segment.findTime(getStartTime()), + segment.findTime(getEndTime())); + } + + if (m_progressTotal > 0) { + if (rebeam || makeviable || decounterpoint) { + emit incrementProgress(m_progressTotal / 2); + rgapp->refreshGUI(50); + } else { + emit incrementProgress(m_progressTotal); + rgapp->refreshGUI(50); + } + } + + if (m_selection) { + EventSelection::RangeTimeList ranges(m_selection->getRangeTimes()); + for (EventSelection::RangeTimeList::iterator i = ranges.begin(); + i != ranges.end(); ++i) { + if (makeviable) { + helper.makeNotesViable(i->first, i->second, true); + } + if (decounterpoint) { + helper.deCounterpoint(i->first, i->second); + } + if (rebeam) { + helper.autoBeam(i->first, i->second, GROUP_TYPE_BEAMED); + helper.autoSlur(i->first, i->second, true); + } + } + } else { + if (makeviable) { + helper.makeNotesViable(getStartTime(), getEndTime(), true); + } + if (decounterpoint) { + helper.deCounterpoint(getStartTime(), getEndTime()); + } + if (rebeam) { + helper.autoBeam(getStartTime(), getEndTime(), GROUP_TYPE_BEAMED); + helper.autoSlur(getStartTime(), getEndTime(), true); + } + } + + if (m_progressTotal > 0) { + if (rebeam || makeviable || decounterpoint) { + emit incrementProgress(m_progressTotal / 2); + rgapp->refreshGUI(50); + } + } +} + +Quantizer * +EventQuantizeCommand::makeQuantizer(QString configGroup, + bool notationDefault) +{ + //!!! Excessive duplication with + // QuantizeParameters::getQuantizer in widgets.cpp + + KConfig *config = kapp->config(); + config->setGroup(configGroup); + + timeT defaultUnit = + Note(Note::Demisemiquaver).getDuration(); + + int type = config->readNumEntry("quantizetype", notationDefault ? 2 : 0); + timeT unit = config->readNumEntry("quantizeunit", defaultUnit); + bool notateOnly = config->readBoolEntry("quantizenotationonly", notationDefault); + bool durations = config->readBoolEntry("quantizedurations", false); + int simplicity = config->readNumEntry("quantizesimplicity", 13); + int maxTuplet = config->readNumEntry("quantizemaxtuplet", 3); + bool counterpoint = config->readNumEntry("quantizecounterpoint", false); + bool articulate = config->readBoolEntry("quantizearticulate", true); + int swing = config->readNumEntry("quantizeswing", 0); + int iterate = config->readNumEntry("quantizeiterate", 100); + + m_quantizer = 0; + + if (type == 0) { + if (notateOnly) { + m_quantizer = new BasicQuantizer + (Quantizer::RawEventData, + Quantizer::NotationPrefix, + unit, durations, swing, iterate); + } else { + m_quantizer = new BasicQuantizer + (Quantizer::RawEventData, + Quantizer::RawEventData, + unit, durations, swing, iterate); + } + } else if (type == 1) { + if (notateOnly) { + m_quantizer = new LegatoQuantizer + (Quantizer::RawEventData, + Quantizer::NotationPrefix, unit); + } else { + m_quantizer = new LegatoQuantizer + (Quantizer::RawEventData, + Quantizer::RawEventData, unit); + } + } else { + + NotationQuantizer *nq; + + if (notateOnly) { + nq = new NotationQuantizer(); + } else { + nq = new NotationQuantizer + (Quantizer::RawEventData, + Quantizer::RawEventData); + } + + nq->setUnit(unit); + nq->setSimplicityFactor(simplicity); + nq->setMaxTuplet(maxTuplet); + nq->setContrapuntal(counterpoint); + nq->setArticulate(articulate); + + m_quantizer = nq; + } + + return m_quantizer; +} + +} +#include "EventQuantizeCommand.moc" diff --git a/src/commands/edit/EventQuantizeCommand.h b/src/commands/edit/EventQuantizeCommand.h new file mode 100644 index 0000000..6ae1303 --- /dev/null +++ b/src/commands/edit/EventQuantizeCommand.h @@ -0,0 +1,98 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EVENTQUANTIZECOMMAND_H_ +#define _RG_EVENTQUANTIZECOMMAND_H_ + +#include "document/BasicCommand.h" +#include +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Quantizer; +class EventSelection; + + +class EventQuantizeCommand : public QObject, public BasicCommand +{ + Q_OBJECT + +public: + /// Quantizer must be on heap (EventQuantizeCommand dtor will delete) + EventQuantizeCommand(Segment &segment, + timeT startTime, + timeT endTime, + Quantizer *); + + /// Quantizer must be on heap (EventQuantizeCommand dtor will delete) + EventQuantizeCommand(EventSelection &selection, + Quantizer *); + + /// Constructs own quantizer based on KConfig data in given group + EventQuantizeCommand(Segment &segment, + timeT startTime, + timeT endTime, + QString configGroup, + bool notationDefault); + + /// Constructs own quantizer based on KConfig data in given group + EventQuantizeCommand(EventSelection &selection, + QString configGroup, + bool notationDefault); + + ~EventQuantizeCommand(); + + static QString getGlobalName(Quantizer *quantizer = 0); + void setProgressTotal(int total) { m_progressTotal = total; } + +signals: + void incrementProgress(int); + +protected: + virtual void modifySegment(); + +private: + Quantizer *m_quantizer; // I own this + EventSelection *m_selection; + QString m_configGroup; + int m_progressTotal; + + /// Sets to m_quantizer as well as returning value + Quantizer *makeQuantizer(QString, bool); +}; + +// Collapse equal-pitch notes into one event +// + +} + +#endif diff --git a/src/commands/edit/EventUnquantizeCommand.cpp b/src/commands/edit/EventUnquantizeCommand.cpp new file mode 100644 index 0000000..5a8a07e --- /dev/null +++ b/src/commands/edit/EventUnquantizeCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EventUnquantizeCommand.h" + +#include +#include "base/Quantizer.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "document/BasicCommand.h" +#include + + +namespace Rosegarden +{ + +EventUnquantizeCommand::EventUnquantizeCommand(Segment &segment, + timeT startTime, + timeT endTime, + Quantizer *quantizer) : + BasicCommand(i18n("Unquantize Events"), segment, startTime, endTime, + true), // bruteForceRedo + m_quantizer(quantizer), + m_selection(0) +{ + // nothing else +} + +EventUnquantizeCommand::EventUnquantizeCommand( + EventSelection &selection, + Quantizer *quantizer) : + BasicCommand(i18n("Unquantize Events"), + selection.getSegment(), + selection.getStartTime(), + selection.getEndTime(), + true), // bruteForceRedo + m_quantizer(quantizer), + m_selection(&selection) +{ + // nothing else +} + +EventUnquantizeCommand::~EventUnquantizeCommand() +{ + delete m_quantizer; +} + +QString +EventUnquantizeCommand::getGlobalName(Quantizer *) +{ + /*!!! + if (quantizer) { + switch (quantizer->getType()) { + case Quantizer::PositionQuantize: + return i18n("Position &Quantize"); + case Quantizer::UnitQuantize: + return i18n("Unit &Quantize"); + case Quantizer::NoteQuantize: + return i18n("Note &Quantize"); + case Quantizer::LegatoQuantize: + return i18n("Smoothing &Quantize"); + } + } + */ + return i18n("&Quantize..."); +} + +void +EventUnquantizeCommand::modifySegment() +{ + Segment &segment = getSegment(); + + if (m_selection) { + + m_quantizer->unquantize(m_selection); + + } else { + m_quantizer->unquantize(&segment, + segment.findTime(getStartTime()), + segment.findTime(getEndTime())); + } +} + +} diff --git a/src/commands/edit/EventUnquantizeCommand.h b/src/commands/edit/EventUnquantizeCommand.h new file mode 100644 index 0000000..fca3a3c --- /dev/null +++ b/src/commands/edit/EventUnquantizeCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EVENTUNQUANTIZECOMMAND_H_ +#define _RG_EVENTUNQUANTIZECOMMAND_H_ + +#include "document/BasicCommand.h" +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Quantizer; +class EventSelection; + + +class EventUnquantizeCommand : public BasicCommand +{ +public: + /// Quantizer must be on heap (EventUnquantizeCommand dtor will delete) + EventUnquantizeCommand(Segment &segment, + timeT startTime, + timeT endTime, + Quantizer *); + + /// Quantizer must be on heap (EventUnquantizeCommand dtor will delete) + EventUnquantizeCommand(EventSelection &selection, + Quantizer *); + + ~EventUnquantizeCommand(); + + static QString getGlobalName(Quantizer *quantizer = 0); + +protected: + virtual void modifySegment(); + +private: + Quantizer *m_quantizer; + EventSelection *m_selection; +}; + + + +} + +#endif diff --git a/src/commands/edit/InsertTriggerNoteCommand.cpp b/src/commands/edit/InsertTriggerNoteCommand.cpp new file mode 100644 index 0000000..65696f3 --- /dev/null +++ b/src/commands/edit/InsertTriggerNoteCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "InsertTriggerNoteCommand.h" + +#include +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentMatrixHelper.h" +#include "base/Composition.h" +#include "base/TriggerSegment.h" +#include "document/BasicCommand.h" +#include "gui/editors/notation/NotationProperties.h" +#include "gui/editors/notation/NoteStyleFactory.h" +#include "base/BaseProperties.h" + +namespace Rosegarden +{ + +using namespace BaseProperties; + +InsertTriggerNoteCommand::InsertTriggerNoteCommand(Segment &segment, + timeT time, + Note note, + int pitch, + int velocity, + NoteStyleName noteStyle, + TriggerSegmentId id, + bool retune, + std::string timeAdjust, + Mark mark) : + BasicCommand(i18n("Insert Trigger Note"), segment, + time, time + note.getDuration()), + m_time(time), + m_note(note), + m_pitch(pitch), + m_velocity(velocity), + m_noteStyle(noteStyle), + m_id(id), + m_retune(retune), + m_timeAdjust(timeAdjust), + m_mark(mark) +{ + // nothing +} + +InsertTriggerNoteCommand::~InsertTriggerNoteCommand() +{ + // nothing +} + +void +InsertTriggerNoteCommand::modifySegment() +{ + // Insert via a model event, so as to apply the note style. + // This is a subset of the work done by NoteInsertionCommand + + Event *e = new Event(Note::EventType, m_time, m_note.getDuration()); + + e->set + (PITCH, m_pitch); + e->set + (VELOCITY, m_velocity); + + if (m_noteStyle != NoteStyleFactory::DefaultStyle) { + e->set + (NotationProperties::NOTE_STYLE, m_noteStyle); + } + + e->set + (TRIGGER_SEGMENT_ID, m_id); + e->set + (TRIGGER_SEGMENT_RETUNE, m_retune); + e->set + (TRIGGER_SEGMENT_ADJUST_TIMES, m_timeAdjust); + + if (m_mark != Marks::NoMark) { + Marks::addMark(*e, m_mark, true); + } + + Segment &s(getSegment()); + Segment::iterator i = SegmentMatrixHelper(s).insertNote(e); + + Segment::iterator j = i; + while (++j != s.end()) { + if ((*j)->getAbsoluteTime() > + (*i)->getAbsoluteTime() + (*i)->getDuration()) + break; + if ((*j)->isa(Note::EventType)) { + if ((*j)->getAbsoluteTime() == + (*i)->getAbsoluteTime() + (*i)->getDuration()) { + if ((*j)->has(TIED_BACKWARD) && (*j)->get + (TIED_BACKWARD) && + (*j)->has(PITCH) && ((*j)->get(PITCH) == m_pitch)) { + (*i)->set + (TIED_FORWARD, true); + } + } + } + } + + TriggerSegmentRec *rec = + getSegment().getComposition()->getTriggerSegmentRec(m_id); + + if (rec) + rec->updateReferences(); +} + +} diff --git a/src/commands/edit/InsertTriggerNoteCommand.h b/src/commands/edit/InsertTriggerNoteCommand.h new file mode 100644 index 0000000..4dc1b19 --- /dev/null +++ b/src/commands/edit/InsertTriggerNoteCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_INSERTTRIGGERNOTECOMMAND_H_ +#define _RG_INSERTTRIGGERNOTECOMMAND_H_ + +#include "base/NotationTypes.h" +#include "base/TriggerSegment.h" +#include "document/BasicCommand.h" +#include "gui/editors/notation/NoteStyle.h" +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; + + +class InsertTriggerNoteCommand : public BasicCommand +{ +public: + InsertTriggerNoteCommand(Segment &, + timeT time, + Note note, + int pitch, + int velocity, + NoteStyleName noteStyle, + TriggerSegmentId id, + bool retune, + std::string timeAdjust, + Mark mark); + virtual ~InsertTriggerNoteCommand(); + +protected: + virtual void modifySegment(); + + timeT m_time; + Note m_note; + int m_pitch; + int m_velocity; + NoteStyleName m_noteStyle; + TriggerSegmentId m_id; + bool m_retune; + std::string m_timeAdjust; + Mark m_mark; +}; + + + +} + +#endif diff --git a/src/commands/edit/InvertCommand.cpp b/src/commands/edit/InvertCommand.cpp new file mode 100644 index 0000000..053bcf9 --- /dev/null +++ b/src/commands/edit/InvertCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "InvertCommand.h" + +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include +#include "base/BaseProperties.h" + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +InvertCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + long highestPitch, lowestPitch; + + bool firstNote = true; + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + try { + long pitch = (*i)->get + (PITCH); + if (firstNote) { + highestPitch = pitch; + lowestPitch = pitch; + firstNote = false; + } else { + if (pitch > highestPitch) + highestPitch = pitch; + else if (pitch < lowestPitch) + lowestPitch = pitch; + } + } catch (...) { } + } + } + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + try { + long pitch = (*i)->get + (PITCH); + pitch = lowestPitch + (highestPitch - pitch); + pitch += m_semitones; + (*i)->set + (PITCH, pitch); + (*i)->unset(ACCIDENTAL); + } catch (...) { } + } + } +} + +} diff --git a/src/commands/edit/InvertCommand.h b/src/commands/edit/InvertCommand.h new file mode 100644 index 0000000..5bea38d --- /dev/null +++ b/src/commands/edit/InvertCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_INVERTCOMMAND_H_ +#define _RG_INVERTCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class InvertCommand : public BasicSelectionCommand +{ +public: + InvertCommand(int semitones, EventSelection &selection) : + BasicSelectionCommand(getGlobalName(semitones), selection, true), + m_selection(&selection), m_semitones(semitones) { } + + static QString getGlobalName(int semitones = 0) { + switch (semitones) { + default: return i18n("&Invert"); + } + } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + int m_semitones; +}; + + + +} + +#endif diff --git a/src/commands/edit/ModifyMarkerCommand.cpp b/src/commands/edit/ModifyMarkerCommand.cpp new file mode 100644 index 0000000..367f545 --- /dev/null +++ b/src/commands/edit/ModifyMarkerCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ModifyMarkerCommand.h" + +#include "base/Composition.h" +#include + + +namespace Rosegarden +{ + +ModifyMarkerCommand::ModifyMarkerCommand(Composition *comp, + int id, + timeT time, + timeT newTime, + const std::string &name, + const std::string &des): + KNamedCommand(getGlobalName()), + m_composition(comp), + m_id(id), + m_time(time), + m_newTime(newTime), + m_name(name), + m_description(des), + m_oldName(""), + m_oldDescription("") +{} + +ModifyMarkerCommand::~ModifyMarkerCommand() +{} + +void +ModifyMarkerCommand::execute() +{ + Composition::markercontainer markers = + m_composition->getMarkers(); + + Composition::markerconstiterator it = markers.begin(); + + for (; it != markers.end(); ++it) { + if ((*it)->getID() == m_id) { + if (m_oldName.empty()) + m_oldName = (*it)->getName(); + if (m_oldDescription.empty()) + m_oldDescription = (*it)->getDescription(); + + (*it)->setName(m_name); + (*it)->setDescription(m_description); + (*it)->setTime(m_newTime); + return ; + } + } +} + +void +ModifyMarkerCommand::unexecute() +{ + Composition::markercontainer markers = + m_composition->getMarkers(); + + Composition::markerconstiterator it = markers.begin(); + + for (; it != markers.end(); ++it) { + if ((*it)->getID() == m_id) { + (*it)->setName(m_oldName); + (*it)->setDescription(m_oldDescription); + (*it)->setTime(m_time); + } + } +} + +} diff --git a/src/commands/edit/ModifyMarkerCommand.h b/src/commands/edit/ModifyMarkerCommand.h new file mode 100644 index 0000000..6a7e99f --- /dev/null +++ b/src/commands/edit/ModifyMarkerCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MODIFYMARKERCOMMAND_H_ +#define _RG_MODIFYMARKERCOMMAND_H_ + +#include +#include +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Composition; + + +class ModifyMarkerCommand : public KNamedCommand +{ +public: + ModifyMarkerCommand(Composition *comp, + int id, + timeT time, + timeT newTime, + const std::string &name, + const std::string &des); + ~ModifyMarkerCommand(); + + static QString getGlobalName() { return i18n("&Modify Marker"); } + + virtual void execute(); + virtual void unexecute(); + +protected: + + Composition *m_composition; + timeT m_time; + timeT m_newTime; + + int m_id; + std::string m_name; + std::string m_description; + std::string m_oldName; + std::string m_oldDescription; + +}; + + + +} + +#endif diff --git a/src/commands/edit/MoveAcrossSegmentsCommand.cpp b/src/commands/edit/MoveAcrossSegmentsCommand.cpp new file mode 100644 index 0000000..3363d65 --- /dev/null +++ b/src/commands/edit/MoveAcrossSegmentsCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MoveAcrossSegmentsCommand.h" + +#include +#include "base/Clipboard.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "CutCommand.h" +#include "PasteEventsCommand.h" +#include + + +namespace Rosegarden +{ + +MoveAcrossSegmentsCommand::MoveAcrossSegmentsCommand(Segment &, + Segment &secondSegment, + timeT newStartTime, + bool notation, + EventSelection &selection) : + KMacroCommand(getGlobalName()), + m_clipboard(new Clipboard()) +{ + addCommand(new CutCommand(selection, m_clipboard)); + + timeT newEndTime = newStartTime + selection.getEndTime() - selection.getStartTime(); + Segment::iterator i = secondSegment.findTime(newEndTime); + if (i == secondSegment.end()) + newEndTime = secondSegment.getEndTime(); + else + newEndTime = (*i)->getAbsoluteTime(); + + addCommand(new PasteEventsCommand(secondSegment, m_clipboard, + newStartTime, + newEndTime, + notation ? + PasteEventsCommand::NoteOverlay : + PasteEventsCommand::MatrixOverlay)); +} + +MoveAcrossSegmentsCommand::~MoveAcrossSegmentsCommand() +{ + delete m_clipboard; +} + +QString +MoveAcrossSegmentsCommand::getGlobalName() +{ + return i18n("&Move Events to Other Segment"); +} + +} diff --git a/src/commands/edit/MoveAcrossSegmentsCommand.h b/src/commands/edit/MoveAcrossSegmentsCommand.h new file mode 100644 index 0000000..ac3ee39 --- /dev/null +++ b/src/commands/edit/MoveAcrossSegmentsCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MOVEACROSSSEGMENTSCOMMAND_H_ +#define _RG_MOVEACROSSSEGMENTSCOMMAND_H_ + +#include +#include "base/Event.h" +#include + + + +namespace Rosegarden +{ + +class Segment; +class EventSelection; +class Clipboard; + + +class MoveAcrossSegmentsCommand : public KMacroCommand +{ +public: + MoveAcrossSegmentsCommand(Segment &firstSegment, + Segment &secondSegment, + timeT newStartTime, + bool notation, + EventSelection &selection); + virtual ~MoveAcrossSegmentsCommand(); + + static QString getGlobalName(); + +private: + Clipboard *m_clipboard; +}; + + + +} + +#endif diff --git a/src/commands/edit/MoveCommand.cpp b/src/commands/edit/MoveCommand.cpp new file mode 100644 index 0000000..5df08a7 --- /dev/null +++ b/src/commands/edit/MoveCommand.cpp @@ -0,0 +1,159 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MoveCommand.h" + +#include "misc/Debug.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Selection.h" +#include "document/BasicCommand.h" +#include + + +namespace Rosegarden +{ + +MoveCommand::MoveCommand(Segment &s, timeT delta, bool useNotationTimings, + EventSelection &sel) : + BasicCommand(getGlobalName(), s, + delta < 0 ? sel.getStartTime() + delta : sel.getStartTime(), + delta < 0 ? sel.getEndTime() + 1 : sel.getEndTime() + 1 + delta, + true), + m_selection(&sel), + m_delta(delta), + m_useNotationTimings(useNotationTimings), + m_lastInsertedEvent(0) +{ + // nothing else +} + +QString +MoveCommand::getGlobalName(timeT delta) +{ + if (delta == 0) { + return "&Move Events"; + } else if (delta < 0) { + return "&Move Events Back"; + } else { + return "&Move Events Forward"; + } +} + +void +MoveCommand::modifySegment() +{ + RG_DEBUG << "MoveCommand::modifySegment: delta is " << m_delta + << ", useNotationTimings " << m_useNotationTimings + << ", start time " << m_selection->getStartTime() + << ", end time " << m_selection->getEndTime() << endl; + + std::vector toErase; + std::vector toInsert; + + timeT a0 = m_selection->getStartTime(); + timeT a1 = m_selection->getEndTime(); + timeT b0 = a0 + m_delta; + timeT b1 = b0 + (a1 - a0); + + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + RG_DEBUG << "MoveCommand::modifySegment: event at " << (*i)->getAbsoluteTime() << " type " << (*i)->getType() << endl; + + if ((*i)->isa(Note::EventRestType)) + continue; + + toErase.push_back(*i); + timeT newTime = + (m_useNotationTimings ? + (*i)->getNotationAbsoluteTime() : (*i)->getAbsoluteTime()) + m_delta; + + Event *e; + if (m_useNotationTimings) { + e = new Event(**i, newTime, (*i)->getDuration(), (*i)->getSubOrdering(), + newTime, (*i)->getNotationDuration()); + } else { + e = new Event(**i, newTime); + } + + toInsert.push_back(e); + } + + Segment &segment(m_selection->getSegment()); + + for (unsigned int j = 0; j < toErase.size(); ++j) { + Segment::iterator jtr(segment.findSingle(toErase[j])); + if (jtr != segment.end()) { + RG_DEBUG << "found event " << j << endl; + segment.erase(jtr); + } else { + RG_DEBUG << "failed to find event " << j << endl; + } + } + + for (unsigned int j = 0; j < toInsert.size(); ++j) { + + Segment::iterator jtr = segment.end(); + + // somewhat like the NoteOverlay part of PasteEventsCommand::modifySegment + /* nah -- let's do a de-counterpoint afterwards perhaps + if (m_useNotationTimings && toInsert[j]->isa(Note::EventType)) { + long pitch = 0; + Accidental explicitAccidental = NoAccidental; + toInsert[j]->get(ACCIDENTAL, explicitAccidental); + if (toInsert[j]->get(PITCH, pitch)) { + jtr = SegmentNotationHelper(segment).insertNote + (toInsert[j]->getAbsoluteTime(), + Note::getNearestNote(toInsert[j]->getDuration()), + pitch, explicitAccidental); + delete toInsert[j]; + toInsert[j] = *jtr; + } + } else { + */ + jtr = segment.insert(toInsert[j]); + // } + + // insert new event back into selection + m_selection->addEvent(toInsert[j]); + + if (jtr != segment.end()) + m_lastInsertedEvent = toInsert[j]; + } + + if (m_useNotationTimings) { + SegmentNotationHelper(segment).deCounterpoint(b0, b1); + } + + segment.normalizeRests(a0, a1); + segment.normalizeRests(b0, b1); +} + +} diff --git a/src/commands/edit/MoveCommand.h b/src/commands/edit/MoveCommand.h new file mode 100644 index 0000000..79c0081 --- /dev/null +++ b/src/commands/edit/MoveCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MOVECOMMAND_H_ +#define _RG_MOVECOMMAND_H_ + +#include "document/BasicCommand.h" +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class EventSelection; +class Event; + + +class MoveCommand : public BasicCommand +{ +public: + MoveCommand(Segment &segment, + timeT delta, + bool useNotationTimings, + EventSelection &selection); + + static QString getGlobalName(timeT delta = 0); + + Event *getLastInsertedEvent() { return m_lastInsertedEvent; } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + timeT m_delta; + bool m_useNotationTimings; + Event *m_lastInsertedEvent; +}; + + +} + +#endif diff --git a/src/commands/edit/PasteEventsCommand.cpp b/src/commands/edit/PasteEventsCommand.cpp new file mode 100644 index 0000000..f6fd323 --- /dev/null +++ b/src/commands/edit/PasteEventsCommand.cpp @@ -0,0 +1,321 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "PasteEventsCommand.h" + +#include "misc/Debug.h" +#include "base/Clipboard.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "document/BasicCommand.h" +#include +#include "base/BaseProperties.h" + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +PasteEventsCommand::PasteEventsCommand(Segment &segment, + Clipboard *clipboard, + timeT pasteTime, + PasteType pasteType) : + BasicCommand(getGlobalName(), segment, pasteTime, + getEffectiveEndTime(segment, clipboard, pasteTime)), + m_relayoutEndTime(getEndTime()), + m_clipboard(new Clipboard(*clipboard)), + m_pasteType(pasteType), + m_pastedEvents(segment) +{ + if (pasteType != OpenAndPaste) { + + // paste clef or key -> relayout to end + + if (clipboard->isSingleSegment()) { + + Segment *s(clipboard->getSingleSegment()); + for (Segment::iterator i = s->begin(); i != s->end(); ++i) { + if ((*i)->isa(Clef::EventType) || + (*i)->isa(Key::EventType)) { + m_relayoutEndTime = s->getEndTime(); + break; + } + } + } + } +} + +PasteEventsCommand::PasteEventsCommand(Segment &segment, + Clipboard *clipboard, + timeT pasteTime, + timeT pasteEndTime, + PasteType pasteType) : + BasicCommand(getGlobalName(), segment, pasteTime, pasteEndTime), + m_relayoutEndTime(getEndTime()), + m_clipboard(new Clipboard(*clipboard)), + m_pasteType(pasteType), + m_pastedEvents(segment) +{} + +PasteEventsCommand::~PasteEventsCommand() +{ + delete m_clipboard; +} + +PasteEventsCommand::PasteTypeMap + +PasteEventsCommand::getPasteTypes() +{ + static PasteTypeMap types; + static bool haveTypes = false; + if (!haveTypes) { + types[Restricted] = + i18n("Paste into an existing gap [\"restricted\"]"); + types[Simple] = + i18n("Erase existing events to make room [\"simple\"]"); + types[OpenAndPaste] = + i18n("Move existing events out of the way [\"open-n-paste\"]"); + types[NoteOverlay] = + i18n("Overlay notes, tying against present notes [\"note-overlay\"]"); + types[MatrixOverlay] = + i18n("Overlay notes, ignoring present notes [\"matrix-overlay\"]"); + } + return types; +} + +timeT +PasteEventsCommand::getEffectiveEndTime(Segment &segment, + Clipboard *clipboard, + timeT pasteTime) +{ + if (!clipboard->isSingleSegment()) { + RG_DEBUG << "PasteEventsCommand::getEffectiveEndTime: not single segment" << endl; + return pasteTime; + } + + RG_DEBUG << "PasteEventsCommand::getEffectiveEndTime: clipboard " + << clipboard->getSingleSegment()->getStartTime() + << " -> " + << clipboard->getSingleSegment()->getEndTime() << endl; + + timeT d = clipboard->getSingleSegment()->getEndTime() - + clipboard->getSingleSegment()->getStartTime(); + + if (m_pasteType == OpenAndPaste) { + return segment.getEndTime() + d; + } else { + Segment::iterator i = segment.findTime(pasteTime + d); + if (i == segment.end()) + return segment.getEndTime(); + else + return (*i)->getAbsoluteTime(); + } +} + +timeT +PasteEventsCommand::getRelayoutEndTime() +{ + return m_relayoutEndTime; +} + +bool +PasteEventsCommand::isPossible() +{ + if (m_clipboard->isEmpty() || !m_clipboard->isSingleSegment()) { + return false; + } + + if (m_pasteType != Restricted) { + return true; + } + + Segment *source = m_clipboard->getSingleSegment(); + + timeT pasteTime = getStartTime(); + timeT origin = source->getStartTime(); + timeT duration = source->getEndTime() - origin; + + RG_DEBUG << "PasteEventsCommand::isPossible: paste time is " << pasteTime << ", origin is " << origin << ", duration is " << duration << endl; + + SegmentNotationHelper helper(getSegment()); + return helper.removeRests(pasteTime, duration, true); +} + +void +PasteEventsCommand::modifySegment() +{ + RG_DEBUG << "PasteEventsCommand::modifySegment" << endl; + + if (!m_clipboard->isSingleSegment()) + return ; + + Segment *source = m_clipboard->getSingleSegment(); + + timeT pasteTime = getStartTime(); + timeT origin = source->getStartTime(); + timeT duration = source->getEndTime() - origin; + + Segment *destination(&getSegment()); + SegmentNotationHelper helper(*destination); + + RG_DEBUG << "PasteEventsCommand::modifySegment() : paste type = " + << m_pasteType << " - pasteTime = " + << pasteTime << " - origin = " << origin << endl; + + // First check for group IDs, which we want to make unique in the + // copies in the destination segment + + std::map groupIdMap; + for (Segment::iterator i = source->begin(); i != source->end(); ++i) { + long groupId = -1; + if ((*i)->get + (BEAMED_GROUP_ID, groupId)) { + if (groupIdMap.find(groupId) == groupIdMap.end()) { + groupIdMap[groupId] = destination->getNextId(); + } + } + } + + switch (m_pasteType) { + + // Do some preliminary work to make space or whatever; + // we do the actual paste after this switch statement + // (except where individual cases do the work and return) + + case Restricted: + if (!helper.removeRests(pasteTime, duration)) + return ; + break; + + case Simple: + destination->erase(destination->findTime(pasteTime), + destination->findTime(pasteTime + duration)); + break; + + case OpenAndPaste: { + std::vector copies; + for (Segment::iterator i = destination->findTime(pasteTime); + i != destination->end(); ++i) { + Event *e = (*i)->copyMoving(duration); + if (e->has(BEAMED_GROUP_ID)) { + e->set + (BEAMED_GROUP_ID, groupIdMap[e->get + (BEAMED_GROUP_ID)]); + } + copies.push_back(e); + } + + destination->erase(destination->findTime(pasteTime), + destination->end()); + + for (unsigned int i = 0; i < copies.size(); ++i) { + destination->insert(copies[i]); + m_pastedEvents.addEvent(copies[i]); + } + + break; + } + + case NoteOverlay: + for (Segment::iterator i = source->begin(); i != source->end(); ++i) { + if ((*i)->isa(Note::EventRestType)) + continue; + Event *e = (*i)->copyMoving(pasteTime - origin); + if (e->has(BEAMED_GROUP_ID)) { + e->set(BEAMED_GROUP_ID, + groupIdMap[e->get(BEAMED_GROUP_ID)]); + } + if ((*i)->isa(Note::EventType)) { + // e is model event: we retain ownership of it + Segment::iterator i = helper.insertNote(e); + delete e; + if (i != destination->end()) m_pastedEvents.addEvent(*i); + } else { + destination->insert(e); + m_pastedEvents.addEvent(e); + } + } + + return ; + + case MatrixOverlay: + + for (Segment::iterator i = source->begin(); i != source->end(); ++i) { + + if ((*i)->isa(Note::EventRestType)) + continue; + + Event *e = (*i)->copyMoving(pasteTime - origin); + + if (e->has(BEAMED_GROUP_TYPE) && + e->get + (BEAMED_GROUP_TYPE) == GROUP_TYPE_BEAMED) { + e->unset(BEAMED_GROUP_ID); + e->unset(BEAMED_GROUP_TYPE); + } + + if (e->has(BEAMED_GROUP_ID)) { + e->set + (BEAMED_GROUP_ID, groupIdMap[e->get + (BEAMED_GROUP_ID)]); + } + + destination->insert(e); + m_pastedEvents.addEvent(e); + } + + destination->normalizeRests + (source->getStartTime(), source->getEndTime()); + + return ; + } + + RG_DEBUG << "PasteEventsCommand::modifySegment() - inserting\n"; + + for (Segment::iterator i = source->begin(); i != source->end(); ++i) { + Event *e = (*i)->copyMoving(pasteTime - origin); + if (e->has(BEAMED_GROUP_ID)) { + e->set + (BEAMED_GROUP_ID, groupIdMap[e->get + (BEAMED_GROUP_ID)]); + } + destination->insert(e); + m_pastedEvents.addEvent(e); + } + + destination->normalizeRests + (source->getStartTime(), source->getEndTime()); +} + +EventSelection +PasteEventsCommand::getPastedEvents() +{ + return m_pastedEvents; +} + +} diff --git a/src/commands/edit/PasteEventsCommand.h b/src/commands/edit/PasteEventsCommand.h new file mode 100644 index 0000000..3a26b25 --- /dev/null +++ b/src/commands/edit/PasteEventsCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PASTEEVENTSCOMMAND_H_ +#define _RG_PASTEEVENTSCOMMAND_H_ + +#include "document/BasicCommand.h" +#include "base/Selection.h" +#include +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Segment; +class Clipboard; + + +/// Paste from a single-segment clipboard to a segment + +class PasteEventsCommand : public BasicCommand +{ +public: + enum PasteType { + Restricted, // paste into existing gap + Simple, // erase existing events to make room + OpenAndPaste, // bump up existing events to make room + NoteOverlay, // overlay and tie notation-style + MatrixOverlay // overlay raw matrix-style + }; + + typedef std::map PasteTypeMap; + static PasteTypeMap getPasteTypes(); // type, descrip + + /** + * Construct a Paste command from a clipboard that already contains + * the events to be pasted. + */ + PasteEventsCommand(Segment &segment, + Clipboard *clipboard, + timeT pasteTime, + PasteType pasteType); + + /** + * Construct a Paste command from a clipboard that will contain + * the events to be pasted by the time the Paste command is + * executed, but might not do so yet. This is necessary if the + * Paste command is to follow another clipboard-based command + * in a KMacroCommand sequence. pasteEndTime must supply the + * latest time in the destination segment that may be modified + * by the paste. + */ + PasteEventsCommand(Segment &segment, + Clipboard *clipboard, + timeT pasteTime, + timeT pasteEndTime, + PasteType pasteType); + + virtual ~PasteEventsCommand(); + + EventSelection getPastedEvents(); + + static QString getGlobalName() { return i18n("&Paste"); } + + /// Determine whether this paste will succeed (without executing it yet) + bool isPossible(); + + virtual timeT getRelayoutEndTime(); + +protected: + virtual void modifySegment(); + timeT getEffectiveEndTime(Segment &, + Clipboard *, + timeT); + timeT m_relayoutEndTime; + Clipboard *m_clipboard; + PasteType m_pasteType; + EventSelection m_pastedEvents; +}; + + + +} + +#endif diff --git a/src/commands/edit/PasteSegmentsCommand.cpp b/src/commands/edit/PasteSegmentsCommand.cpp new file mode 100644 index 0000000..ab3372a --- /dev/null +++ b/src/commands/edit/PasteSegmentsCommand.cpp @@ -0,0 +1,153 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "PasteSegmentsCommand.h" + +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "base/Segment.h" +#include "base/Track.h" +#include + + +namespace Rosegarden +{ + +PasteSegmentsCommand::PasteSegmentsCommand(Composition *composition, + Clipboard *clipboard, + timeT pasteTime, + TrackId baseTrack, + bool useExactTracks) : + KNamedCommand(getGlobalName()), + m_composition(composition), + m_clipboard(new Clipboard(*clipboard)), + m_pasteTime(pasteTime), + m_baseTrack(baseTrack), + m_exactTracks(useExactTracks), + m_detached(false) +{ + // nothing else +} + +PasteSegmentsCommand::~PasteSegmentsCommand() +{ + if (m_detached) { + for (unsigned int i = 0; i < m_addedSegments.size(); ++i) { + delete m_addedSegments[i]; + } + } + + delete m_clipboard; +} + +void +PasteSegmentsCommand::execute() +{ + if (m_addedSegments.size() > 0) { + // been here before + for (unsigned int i = 0; i < m_addedSegments.size(); ++i) { + m_composition->addSegment(m_addedSegments[i]); + } + return ; + } + + if (m_clipboard->isEmpty()) + return ; + + // We want to paste such that the earliest Segment starts at + // m_pasteTime and the others start at the same times relative to + // that as they did before. Likewise for track, unless + // m_exactTracks is set. + + timeT earliestStartTime = m_clipboard->getBaseTime(); + timeT latestEndTime = 0; + int lowestTrackPos = -1; + + for (Clipboard::iterator i = m_clipboard->begin(); + i != m_clipboard->end(); ++i) { + + int trackPos = m_composition->getTrackPositionById((*i)->getTrack()); + if (trackPos >= 0 && + (lowestTrackPos < 0 || trackPos < lowestTrackPos)) { + lowestTrackPos = trackPos; + } + + if ((*i)->getEndMarkerTime() > latestEndTime) { + latestEndTime = (*i)->getEndMarkerTime(); + } + } + + if (m_exactTracks) + lowestTrackPos = 0; + if (lowestTrackPos < 0) + lowestTrackPos = 0; + timeT offset = m_pasteTime - earliestStartTime; + int baseTrackPos = m_composition->getTrackPositionById(m_baseTrack); + int trackOffset = baseTrackPos - lowestTrackPos; + + for (Clipboard::iterator i = m_clipboard->begin(); + i != m_clipboard->end(); ++i) { + + int newTrackPos = trackOffset + + m_composition->getTrackPositionById((*i)->getTrack()); + + Track *track = m_composition->getTrackByPosition(newTrackPos); + + if (!track) { + newTrackPos = 0; + track = m_composition->getTrackByPosition(newTrackPos); + } + + TrackId newTrackId = track->getId(); + + Segment *segment = new Segment(**i); + segment->setStartTime(segment->getStartTime() + offset); + segment->setTrack(newTrackId); + m_composition->addSegment(segment); + if (m_clipboard->isPartial()) { + segment->normalizeRests(segment->getStartTime(), + segment->getEndMarkerTime()); + } + m_addedSegments.push_back(segment); + } + + // User preference? Update song pointer position on paste + m_composition->setPosition(latestEndTime + + m_pasteTime + - earliestStartTime); + + m_detached = false; +} + +void +PasteSegmentsCommand::unexecute() +{ + for (unsigned int i = 0; i < m_addedSegments.size(); ++i) { + m_composition->detachSegment(m_addedSegments[i]); + } + m_detached = true; +} + +} diff --git a/src/commands/edit/PasteSegmentsCommand.h b/src/commands/edit/PasteSegmentsCommand.h new file mode 100644 index 0000000..3f45914 --- /dev/null +++ b/src/commands/edit/PasteSegmentsCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PASTESEGMENTSCOMMAND_H_ +#define _RG_PASTESEGMENTSCOMMAND_H_ + +#include "base/Track.h" +#include +#include +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Segment; +class Composition; +class Clipboard; + + +/// Paste one or more segments from the clipboard into the composition + +class PasteSegmentsCommand : public KNamedCommand +{ +public: + PasteSegmentsCommand(Composition *composition, + Clipboard *clipboard, + timeT pasteTime, + TrackId baseTrack, + bool useExactTracks); + + virtual ~PasteSegmentsCommand(); + + static QString getGlobalName() { return i18n("&Paste"); } + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + Clipboard *m_clipboard; + timeT m_pasteTime; + TrackId m_baseTrack; + bool m_exactTracks; + std::vector m_addedSegments; + bool m_detached; +}; + + + +} + +#endif diff --git a/src/commands/edit/RemoveMarkerCommand.cpp b/src/commands/edit/RemoveMarkerCommand.cpp new file mode 100644 index 0000000..af3c839 --- /dev/null +++ b/src/commands/edit/RemoveMarkerCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RemoveMarkerCommand.h" + +#include "base/Composition.h" +#include "base/Marker.h" +#include + + +namespace Rosegarden +{ + +RemoveMarkerCommand::RemoveMarkerCommand(Composition *comp, + int id, + timeT time, + const std::string &name, + const std::string &description): + KNamedCommand(getGlobalName()), + m_composition(comp), + m_marker(0), + m_id(id), + m_time(time), + m_name(name), + m_descr(description), + m_detached(false) +{} + +RemoveMarkerCommand::~RemoveMarkerCommand() +{ + if (m_detached) + delete m_marker; +} + +void +RemoveMarkerCommand::execute() +{ + Composition::markercontainer markers = + m_composition->getMarkers(); + + Composition::markerconstiterator it = markers.begin(); + + for (; it != markers.end(); ++it) { + if ((*it)->getID() == m_id) { + m_marker = (*it); + m_composition->detachMarker(m_marker); + m_detached = true; + return ; + } + } +} + +void +RemoveMarkerCommand::unexecute() +{ + if (m_marker) + m_composition->addMarker(m_marker); + m_detached = false; +} + +} diff --git a/src/commands/edit/RemoveMarkerCommand.h b/src/commands/edit/RemoveMarkerCommand.h new file mode 100644 index 0000000..2acaf53 --- /dev/null +++ b/src/commands/edit/RemoveMarkerCommand.h @@ -0,0 +1,75 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_REMOVEMARKERCOMMAND_H_ +#define _RG_REMOVEMARKERCOMMAND_H_ + +#include +#include +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Marker; +class Composition; + + +class RemoveMarkerCommand : public KNamedCommand +{ +public: + RemoveMarkerCommand(Composition *comp, + int id, + timeT time, + const std::string &name, + const std::string &description); + ~RemoveMarkerCommand(); + + static QString getGlobalName() { return i18n("&Remove Marker"); } + + virtual void execute(); + virtual void unexecute(); + +protected: + + Composition *m_composition; + Marker *m_marker; + int m_id; + timeT m_time; + std::string m_name; + std::string m_descr; + bool m_detached; + +}; + + +} + +#endif diff --git a/src/commands/edit/RescaleCommand.cpp b/src/commands/edit/RescaleCommand.cpp new file mode 100644 index 0000000..764969c --- /dev/null +++ b/src/commands/edit/RescaleCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RescaleCommand.h" + +#include "base/Event.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "document/BasicCommand.h" +#include + + +namespace Rosegarden +{ + +RescaleCommand::RescaleCommand(EventSelection &sel, + timeT newDuration, + bool closeGap) : + BasicCommand(getGlobalName(), sel.getSegment(), + sel.getStartTime(), + getAffectedEndTime(sel, newDuration, closeGap), + true), + m_selection(&sel), + m_oldDuration(sel.getTotalDuration()), + m_newDuration(newDuration), + m_closeGap(closeGap) +{ + // nothing else +} + +timeT +RescaleCommand::getAffectedEndTime(EventSelection &sel, + timeT newDuration, + bool closeGap) +{ + timeT preScaleEnd = sel.getEndTime(); + if (closeGap) + preScaleEnd = sel.getSegment().getEndMarkerTime(); + + // dupe of rescale(), but we can't use that here as the m_ + // variables may not have been set + double d = preScaleEnd; + d *= newDuration; + d /= sel.getTotalDuration(); + d += 0.5; + timeT postScaleEnd = (timeT)d; + + return std::max(preScaleEnd, postScaleEnd); +} + +timeT +RescaleCommand::rescale(timeT t) +{ + // avoid overflows by using doubles + double d = t; + d *= m_newDuration; + d /= m_oldDuration; + d += 0.5; + return (timeT)d; +} + +void +RescaleCommand::modifySegment() +{ + if (m_oldDuration == m_newDuration) + return ; + + timeT startTime = m_selection->getStartTime(); + timeT diff = m_newDuration - m_oldDuration; + std::vector toErase; + std::vector toInsert; + + Segment &segment = m_selection->getSegment(); + + for (EventSelection::eventcontainer::iterator i = + m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + toErase.push_back(*i); + + timeT t = (*i)->getAbsoluteTime() - startTime; + timeT d = (*i)->getDuration(); + t = rescale(t); + d = rescale(d); + + toInsert.push_back(new Event(**i, startTime + t, d)); + } + + if (m_closeGap) { + for (Segment::iterator i = segment.findTime(startTime + m_oldDuration); + i != segment.end(); ++i) { + // move all events including any following the end marker + toErase.push_back(*i); + toInsert.push_back((*i)->copyMoving(diff)); + } + } + + for (std::vector::iterator i = toErase.begin(); i != toErase.end(); ++i) { + m_selection->removeEvent(*i); // remove from selection + segment.eraseSingle(*i); + } + + for (std::vector::iterator i = toInsert.begin(); i != toInsert.end(); ++i) { + segment.insert(*i); + m_selection->addEvent(*i); // add to selection + } + + if (m_closeGap && diff > 0) { + segment.setEndMarkerTime(startTime + + rescale(segment.getEndMarkerTime() - startTime)); + } + + segment.normalizeRests(getStartTime(), getEndTime()); +} + +} diff --git a/src/commands/edit/RescaleCommand.h b/src/commands/edit/RescaleCommand.h new file mode 100644 index 0000000..362de24 --- /dev/null +++ b/src/commands/edit/RescaleCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_RESCALECOMMAND_H_ +#define _RG_RESCALECOMMAND_H_ + +#include "document/BasicCommand.h" +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class RescaleCommand : public BasicCommand +{ +public: + RescaleCommand(EventSelection &selection, + timeT newDuration, + bool closeGap); + + static QString getGlobalName() { return i18n("Stretch or S&quash..."); } + +protected: + virtual void modifySegment(); + +private: + timeT rescale(timeT); + timeT getAffectedEndTime(EventSelection &selection, + timeT newDuration, + bool closeGap); + + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + timeT m_oldDuration; + timeT m_newDuration; + bool m_closeGap; +}; + + + +} + +#endif diff --git a/src/commands/edit/RetrogradeCommand.cpp b/src/commands/edit/RetrogradeCommand.cpp new file mode 100644 index 0000000..955f066 --- /dev/null +++ b/src/commands/edit/RetrogradeCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RetrogradeCommand.h" + +#include "base/Event.h" +#include "misc/Debug.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +void +RetrogradeCommand::modifySegment() +{ + std::vector toErase; + std::vector toInsert; + + timeT a0 = m_selection->getStartTime(); + timeT a1 = m_selection->getEndTime(); + + EventSelection::eventcontainer::iterator i; + + bool useNotationTimings = false; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + RG_DEBUG << "RetrogradeCommand::modifySegment: event at " << (*i)->getAbsoluteTime() << " type " << (*i)->getType() << endl; + + if ((*i)->isa(Note::EventRestType)) + continue; + + toErase.push_back(*i); + timeT newTime = a0 + a1 - (*i)->getDuration() - + (useNotationTimings ? + (*i)->getNotationAbsoluteTime() : (*i)->getAbsoluteTime()); + + Event *e; + if (useNotationTimings) { + e = new Event(**i, newTime, (*i)->getDuration(), (*i)->getSubOrdering(), + newTime, (*i)->getNotationDuration()); + } else { + e = new Event(**i, newTime); + } + + toInsert.push_back(e); + } + + Segment &segment(m_selection->getSegment()); + + for (unsigned int j = 0; j < toErase.size(); ++j) { + Segment::iterator jtr(segment.findSingle(toErase[j])); + if (jtr != segment.end()) { + RG_DEBUG << "found event " << j << endl; + segment.erase(jtr); + } else { + RG_DEBUG << "failed to find event " << j << endl; + } + } + + for (unsigned int j = 0; j < toInsert.size(); ++j) { + + Segment::iterator jtr = segment.end(); + + // somewhat like the NoteOverlay part of PasteEventsCommand::modifySegment + /* nah -- let's do a de-counterpoint afterwards perhaps + if (m_useNotationTimings && toInsert[j]->isa(Note::EventType)) { + long pitch = 0; + Accidental explicitAccidental = NoAccidental; + toInsert[j]->get(ACCIDENTAL, explicitAccidental); + if (toInsert[j]->get(PITCH, pitch)) { + jtr = SegmentNotationHelper(segment).insertNote + (toInsert[j]->getAbsoluteTime(), + Note::getNearestNote(toInsert[j]->getDuration()), + pitch, explicitAccidental); + delete toInsert[j]; + toInsert[j] = *jtr; + } + } else { + */ + jtr = segment.insert(toInsert[j]); + // } + + // insert new event back into selection + m_selection->addEvent(toInsert[j]); + + // if (jtr != segment.end()) m_lastInsertedEvent = toInsert[j]; + } + + segment.normalizeRests(a0, a1); +} + +} diff --git a/src/commands/edit/RetrogradeCommand.h b/src/commands/edit/RetrogradeCommand.h new file mode 100644 index 0000000..526a95d --- /dev/null +++ b/src/commands/edit/RetrogradeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_RETROGRADECOMMAND_H_ +#define _RG_RETROGRADECOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class RetrogradeCommand : public BasicSelectionCommand +{ +public: + RetrogradeCommand(int semitones, EventSelection &selection) : + BasicSelectionCommand(getGlobalName(semitones), selection, true), + m_selection(&selection), m_semitones(semitones) { } + + static QString getGlobalName(int semitones = 0) { + switch (semitones) { + default: return i18n("&Retrograde"); + } + } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + int m_semitones; +}; + + + +} + +#endif diff --git a/src/commands/edit/RetrogradeInvertCommand.cpp b/src/commands/edit/RetrogradeInvertCommand.cpp new file mode 100644 index 0000000..3387c9b --- /dev/null +++ b/src/commands/edit/RetrogradeInvertCommand.cpp @@ -0,0 +1,163 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RetrogradeInvertCommand.h" + +#include "base/Event.h" +#include "misc/Debug.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include +#include "base/BaseProperties.h" + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +RetrogradeInvertCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + long highestPitch, lowestPitch; + + bool firstNote = true; + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + try { + long pitch = (*i)->get + (PITCH); + if (firstNote) { + highestPitch = pitch; + lowestPitch = pitch; + firstNote = false; + } else { + if (pitch > highestPitch) + highestPitch = pitch; + else if (pitch < lowestPitch) + lowestPitch = pitch; + } + } catch (...) { } + } + } + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + try { + long pitch = (*i)->get + (PITCH); + pitch = lowestPitch + (highestPitch - pitch); + pitch += m_semitones; + (*i)->set + (PITCH, pitch); + (*i)->unset(ACCIDENTAL); + } catch (...) { } + } + } + std::vector toErase; + std::vector toInsert; + + timeT a0 = m_selection->getStartTime(); + timeT a1 = m_selection->getEndTime(); + + bool useNotationTimings = false; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + RG_DEBUG << "RetrogradeCommand::modifySegment: event at " << (*i)->getAbsoluteTime() << " type " << (*i)->getType() << endl; + + if ((*i)->isa(Note::EventRestType)) + continue; + + toErase.push_back(*i); + timeT newTime = a0 + a1 - (*i)->getDuration() - + (useNotationTimings ? + (*i)->getNotationAbsoluteTime() : (*i)->getAbsoluteTime()); + + Event *e; + if (useNotationTimings) { + e = new Event(**i, newTime, (*i)->getDuration(), (*i)->getSubOrdering(), + newTime, (*i)->getNotationDuration()); + } else { + e = new Event(**i, newTime); + } + + toInsert.push_back(e); + } + + Segment &segment(m_selection->getSegment()); + + for (unsigned int j = 0; j < toErase.size(); ++j) { + Segment::iterator jtr(segment.findSingle(toErase[j])); + if (jtr != segment.end()) { + RG_DEBUG << "found event " << j << endl; + segment.erase(jtr); + } else { + RG_DEBUG << "failed to find event " << j << endl; + } + } + + for (unsigned int j = 0; j < toInsert.size(); ++j) { + + Segment::iterator jtr = segment.end(); + + // somewhat like the NoteOverlay part of PasteEventsCommand::modifySegment + /* nah -- let's do a de-counterpoint afterwards perhaps + if (m_useNotationTimings && toInsert[j]->isa(Note::EventType)) { + long pitch = 0; + Accidental explicitAccidental = NoAccidental; + toInsert[j]->get(ACCIDENTAL, explicitAccidental); + if (toInsert[j]->get(PITCH, pitch)) { + jtr = SegmentNotationHelper(segment).insertNote + (toInsert[j]->getAbsoluteTime(), + Note::getNearestNote(toInsert[j]->getDuration()), + pitch, explicitAccidental); + delete toInsert[j]; + toInsert[j] = *jtr; + } + } else { + */ + jtr = segment.insert(toInsert[j]); + // } + + // insert new event back into selection + m_selection->addEvent(toInsert[j]); + + // if (jtr != segment.end()) m_lastInsertedEvent = toInsert[j]; + } + + segment.normalizeRests(a0, a1); +} + +} diff --git a/src/commands/edit/RetrogradeInvertCommand.h b/src/commands/edit/RetrogradeInvertCommand.h new file mode 100644 index 0000000..ea7d540 --- /dev/null +++ b/src/commands/edit/RetrogradeInvertCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_RETROGRADEINVERTCOMMAND_H_ +#define _RG_RETROGRADEINVERTCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class RetrogradeInvertCommand : public BasicSelectionCommand +{ +public: + RetrogradeInvertCommand(int semitones, EventSelection &selection) : + BasicSelectionCommand(getGlobalName(semitones), selection, true), + m_selection(&selection), m_semitones(semitones) { } + + static QString getGlobalName(int semitones = 0) { + switch (semitones) { + default: return i18n("Re&trograde Invert"); + } + } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + int m_semitones; +}; + + + +} + +#endif diff --git a/src/commands/edit/SelectionPropertyCommand.cpp b/src/commands/edit/SelectionPropertyCommand.cpp new file mode 100644 index 0000000..3501e5b --- /dev/null +++ b/src/commands/edit/SelectionPropertyCommand.cpp @@ -0,0 +1,128 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SelectionPropertyCommand.h" + +#include "base/Event.h" +#include "base/PropertyName.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +SelectionPropertyCommand::SelectionPropertyCommand( + EventSelection *selection, + const PropertyName &property, + PropertyPattern pattern, + int value1, + int value2): + BasicSelectionCommand(getGlobalName(), *selection, true), + m_selection(selection), + m_property(property), + m_pattern(pattern), + m_value1(value1), + m_value2(value2) +{} + +void +SelectionPropertyCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i = + m_selection->getSegmentEvents().begin(); + + int count = 0; + + timeT endTime = 0; + timeT startTime = 0; + bool haveStart = false, haveEnd = false; + + // Get start and end times + // + for (;i != m_selection->getSegmentEvents().end(); ++i) { + if ((*i)->getAbsoluteTime() < startTime || !haveStart) { + startTime = (*i)->getAbsoluteTime(); + haveStart = true; + } + + if ((*i)->getAbsoluteTime() > endTime || !haveEnd) { + endTime = (*i)->getAbsoluteTime(); + haveEnd = true; + } + } + + double step = double(m_value1 - m_value2) / double(endTime - startTime); + double lowStep = double(m_value2) / double(endTime - startTime); + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + // flat + if (m_pattern == FlatPattern) + (*i)->set + (m_property, m_value1); + else if (m_pattern == AlternatingPattern) { + if (count % 2 == 0) + (*i)->set + (m_property, m_value1); + else + (*i)->set + (m_property, m_value2); + + // crescendo, decrescendo + // (determined by step, above, which is in turn influenced by whether + // value1 is greater than value2) + } else if ((m_pattern == CrescendoPattern) || + (m_pattern == DecrescendoPattern)) { + (*i)->set + (m_property, + m_value1 - + int(step * + ((*i)->getAbsoluteTime() - startTime))); + // ringing + } else if (m_pattern == RingingPattern) { + if (count % 2 == 0) + (*i)->set + + (m_property, + m_value1 - int(step * + ((*i)->getAbsoluteTime() - startTime))); + else { + int value = m_value2 - int(lowStep * + ((*i)->getAbsoluteTime() - startTime)); + if (value < 0) + value = 0; + + (*i)->set + (m_property, value); + } + } + + count++; + } +} + +} diff --git a/src/commands/edit/SelectionPropertyCommand.h b/src/commands/edit/SelectionPropertyCommand.h new file mode 100644 index 0000000..34c5352 --- /dev/null +++ b/src/commands/edit/SelectionPropertyCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SELECTIONPROPERTYCOMMAND_H_ +#define _RG_SELECTIONPROPERTYCOMMAND_H_ + +#include "base/PropertyName.h" +#include "document/BasicSelectionCommand.h" +#include +#include + + +class Set; + + +namespace Rosegarden +{ + +class EventSelection; + +// Patterns of properties +// +typedef enum +{ + FlatPattern, // set selection to velocity 1. + AlternatingPattern, // alternate between velocity 1 and 2 on + // subsequent events. + CrescendoPattern, // increasing from velocity 1 to velocity 2. + DecrescendoPattern, // decreasing from velocity 1 to velocity 2. + RingingPattern // between velocity 1 and 2, dying away. +} PropertyPattern; + + +class SelectionPropertyCommand : public BasicSelectionCommand +{ +public: + + SelectionPropertyCommand(EventSelection *selection, + const PropertyName &property, + PropertyPattern pattern, + int value1, + int value2); + + static QString getGlobalName() { return i18n("Set &Property"); } + + virtual void modifySegment(); + +private: + EventSelection *m_selection; + PropertyName m_property; + PropertyPattern m_pattern; + int m_value1; + int m_value2; + +}; + + +} + +#endif diff --git a/src/commands/edit/SetLyricsCommand.cpp b/src/commands/edit/SetLyricsCommand.cpp new file mode 100644 index 0000000..16f5be4 --- /dev/null +++ b/src/commands/edit/SetLyricsCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SetLyricsCommand.h" + +#include "base/Event.h" +#include "misc/Strings.h" +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/BaseProperties.h" +#include +#include +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +SetLyricsCommand::SetLyricsCommand(Segment *segment, int verse, QString newLyricData) : + KNamedCommand(getGlobalName()), + m_segment(segment), + m_verse(verse), + m_newLyricData(newLyricData) +{ + // nothing +} + +SetLyricsCommand::~SetLyricsCommand() +{ + for (std::vector::iterator i = m_oldLyricEvents.begin(); + i != m_oldLyricEvents.end(); ++i) { + delete *i; + } +} + +void +SetLyricsCommand::execute() +{ + // This and LyricEditDialog::unparse() are opposites that will + // need to be kept in sync with any changes to one another. (They + // should really both be in a common lyric management class.) + + // first remove old lyric events + + Segment::iterator i = m_segment->begin(); + + while (i != m_segment->end()) { + + Segment::iterator j = i; + ++j; + + if ((*i)->isa(Text::EventType)) { + std::string textType; + if ((*i)->get(Text::TextTypePropertyName, textType) && + textType == Text::Lyric) { + long verse = 0; + (*i)->get(Text::LyricVersePropertyName, verse); + if (verse == m_verse) { + m_oldLyricEvents.push_back(new Event(**i)); + m_segment->erase(i); + } + } + } + + i = j; + } + + // now parse the new string + + QStringList barStrings = + QStringList::split("/", m_newLyricData, true); // empties ok + + Composition *comp = m_segment->getComposition(); + int barNo = comp->getBarNumber(m_segment->getStartTime()); + + for (QStringList::Iterator bsi = barStrings.begin(); + bsi != barStrings.end(); ++bsi) { + + NOTATION_DEBUG << "Parsing lyrics for bar number " << barNo << ": \"" << *bsi << "\"" << endl; + + std::pair barRange = comp->getBarRange(barNo++); + QString syllables = *bsi; + syllables.replace(QRegExp("\\[\\d+\\] "), " "); + QStringList syllableList = QStringList::split(" ", syllables); // no empties + + i = m_segment->findTime(barRange.first); + timeT laterThan = barRange.first - 1; + + for (QStringList::Iterator ssi = syllableList.begin(); + ssi != syllableList.end(); ++ssi) { + + while (m_segment->isBeforeEndMarker(i) && + (*i)->getAbsoluteTime() < barRange.second && + (!(*i)->isa(Note::EventType) || + (*i)->getNotationAbsoluteTime() <= laterThan || + ((*i)->has(TIED_BACKWARD) && + (*i)->get + (TIED_BACKWARD)))) ++i; + + timeT time = m_segment->getEndMarkerTime(); + timeT notationTime = time; + if (m_segment->isBeforeEndMarker(i)) { + time = (*i)->getAbsoluteTime(); + notationTime = (*i)->getNotationAbsoluteTime(); + } + + QString syllable = *ssi; + syllable.replace(QRegExp("~"), " "); + syllable = syllable.simplifyWhiteSpace(); + if (syllable == "") + continue; + laterThan = notationTime + 1; + if (syllable == ".") + continue; + + NOTATION_DEBUG << "Syllable \"" << syllable << "\" at time " << time << endl; + + Text text(qstrtostr(syllable), Text::Lyric); + Event *event = text.getAsEvent(time); + event->set(Text::LyricVersePropertyName, m_verse); + m_segment->insert(event); + } + } +} + +void +SetLyricsCommand::unexecute() +{ + // Before we inserted the new lyric events (in execute()), we + // removed all the existing ones. That means we know any lyric + // events found now must have been inserted by execute(), so we + // can safely remove them before restoring the old ones. + + Segment::iterator i = m_segment->begin(); + + while (i != m_segment->end()) { + + Segment::iterator j = i; + ++j; + + if ((*i)->isa(Text::EventType)) { + std::string textType; + if ((*i)->get(Text::TextTypePropertyName, textType) && + textType == Text::Lyric) { + long verse = 0; + (*i)->get(Text::LyricVersePropertyName, verse); + if (verse == m_verse) { + m_segment->erase(i); + } + } + } + + i = j; + } + + // Now restore the old ones and clear out the vector. + + for (std::vector::iterator i = m_oldLyricEvents.begin(); + i != m_oldLyricEvents.end(); ++i) { + m_segment->insert(*i); + } + + m_oldLyricEvents.clear(); +} + +} diff --git a/src/commands/edit/SetLyricsCommand.h b/src/commands/edit/SetLyricsCommand.h new file mode 100644 index 0000000..499f12a --- /dev/null +++ b/src/commands/edit/SetLyricsCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SETLYRICSCOMMAND_H_ +#define _RG_SETLYRICSCOMMAND_H_ + +#include +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class SetLyricsCommand : public KNamedCommand +{ +public: + SetLyricsCommand(Segment *segment, int verse, QString newLyricData); + ~SetLyricsCommand(); + + static QString getGlobalName() { return i18n("Edit L&yrics"); } + + virtual void execute(); + virtual void unexecute(); + +private: + Segment *m_segment; + int m_verse; + std::vector m_oldLyricEvents; + QString m_newLyricData; +}; + + + +} + +#endif diff --git a/src/commands/edit/SetNoteTypeCommand.cpp b/src/commands/edit/SetNoteTypeCommand.cpp new file mode 100644 index 0000000..4dc97b1 --- /dev/null +++ b/src/commands/edit/SetNoteTypeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SetNoteTypeCommand.h" + +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +void +SetNoteTypeCommand::modifySegment() +{ + std::vector toErase; + std::vector toInsert; + + EventSelection::eventcontainer::iterator i; + timeT endTime = getEndTime(); + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + toErase.push_back(*i); + + Event *e; + if (m_notationOnly) { + e = new Event(**i, + (*i)->getAbsoluteTime(), + (*i)->getDuration(), + (*i)->getSubOrdering(), + (*i)->getNotationAbsoluteTime(), + Note(m_type).getDuration()); + } else { + e = new Event(**i, + (*i)->getNotationAbsoluteTime(), + Note(m_type).getDuration()); + } + + if (e->getNotationAbsoluteTime() + e->getNotationDuration() > endTime) { + endTime = e->getNotationAbsoluteTime() + e->getNotationDuration(); + } + + toInsert.push_back(e); + } + } + + for (std::vector::iterator i = toErase.begin(); i != toErase.end(); ++i) { + m_selection->getSegment().eraseSingle(*i); + } + + for (std::vector::iterator i = toInsert.begin(); i != toInsert.end(); ++i) { + m_selection->getSegment().insert(*i); + m_selection->addEvent(*i); + } + + m_selection->getSegment().normalizeRests(getStartTime(), endTime); +} + +} diff --git a/src/commands/edit/SetNoteTypeCommand.h b/src/commands/edit/SetNoteTypeCommand.h new file mode 100644 index 0000000..eec4a4c --- /dev/null +++ b/src/commands/edit/SetNoteTypeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SETNOTETYPECOMMAND_H_ +#define _RG_SETNOTETYPECOMMAND_H_ + +#include "base/NotationTypes.h" +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class SetNoteTypeCommand : public BasicSelectionCommand +{ +public: + SetNoteTypeCommand(EventSelection &selection, + Note::Type type, + bool notationOnly) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection), + m_type(type), + m_notationOnly(notationOnly) + { } + + static QString getGlobalName() { + return i18n("&Set Note Type"); + } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + Note::Type m_type; + bool m_notationOnly; +}; + + + +} + +#endif diff --git a/src/commands/edit/SetTriggerCommand.cpp b/src/commands/edit/SetTriggerCommand.cpp new file mode 100644 index 0000000..861796e --- /dev/null +++ b/src/commands/edit/SetTriggerCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SetTriggerCommand.h" + +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "base/TriggerSegment.h" +#include "document/BasicSelectionCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ +using namespace BaseProperties; + +void +SetTriggerCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if (!m_notesOnly || (*i)->isa(Note::EventType)) { + (*i)->set + (TRIGGER_SEGMENT_ID, m_triggerSegmentId); + (*i)->set + (TRIGGER_SEGMENT_RETUNE, m_retune); + (*i)->set + (TRIGGER_SEGMENT_ADJUST_TIMES, m_timeAdjust); + if (m_mark != Marks::NoMark) { + Marks::addMark(**i, m_mark, true); + } + } + } + + // Update the rec references here, without bothering to do so in unexecute + // or in ClearTriggersCommand -- because it doesn't matter if a trigger + // has references to segments that don't actually trigger it, whereas it + // does matter if it loses a reference to something that does + + TriggerSegmentRec *rec = + m_selection->getSegment().getComposition()->getTriggerSegmentRec + (m_triggerSegmentId); + + if (rec) + rec->updateReferences(); +} + +} diff --git a/src/commands/edit/SetTriggerCommand.h b/src/commands/edit/SetTriggerCommand.h new file mode 100644 index 0000000..579bb61 --- /dev/null +++ b/src/commands/edit/SetTriggerCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SETTRIGGERCOMMAND_H_ +#define _RG_SETTRIGGERCOMMAND_H_ + +#include "base/TriggerSegment.h" +#include "document/BasicSelectionCommand.h" +#include +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class SetTriggerCommand : public BasicSelectionCommand +{ +public: + SetTriggerCommand(EventSelection &selection, + TriggerSegmentId triggerSegmentId, + bool notesOnly, + bool retune, + std::string timeAdjust, + Mark mark, + QString name = 0) : + BasicSelectionCommand(name ? name : getGlobalName(), selection, true), + m_selection(&selection), + m_triggerSegmentId(triggerSegmentId), + m_notesOnly(notesOnly), + m_retune(retune), + m_timeAdjust(timeAdjust), + m_mark(mark) + { } + + static QString getGlobalName() { + return i18n("Tri&gger Segment"); + } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + TriggerSegmentId m_triggerSegmentId; + bool m_notesOnly; + bool m_retune; + std::string m_timeAdjust; + Mark m_mark; +}; + + + +} + +#endif diff --git a/src/commands/edit/TransposeCommand.cpp b/src/commands/edit/TransposeCommand.cpp new file mode 100644 index 0000000..4d08079 --- /dev/null +++ b/src/commands/edit/TransposeCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TransposeCommand.h" + +#include +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include +#include "base/BaseProperties.h" + + +namespace Rosegarden +{ + +using namespace BaseProperties; +using namespace Accidentals; + +void +TransposeCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + + if (m_diatonic) + { + + Pitch oldPitch(**i); + + timeT noteTime = (*i)->getAbsoluteTime(); + Key key = m_selection->getSegment().getKeyAtTime(noteTime); + Pitch newPitch = oldPitch.transpose(key, m_semitones, m_steps); + Event * newNoteEvent = newPitch.getAsNoteEvent(0, 0); + Accidental newAccidental; + newNoteEvent->get(BaseProperties::ACCIDENTAL, newAccidental); + + (*i)->set(PITCH, newPitch.getPerformancePitch()); + (*i)->set(ACCIDENTAL, newAccidental); + } + else + { + try { + long pitch = (*i)->get(PITCH); + pitch += m_semitones; + (*i)->set(PITCH, pitch); + if ((m_semitones % 12) != 0) { + (*i)->unset(ACCIDENTAL); + } + } catch (...) { } + } + + } + } +} + +} diff --git a/src/commands/edit/TransposeCommand.h b/src/commands/edit/TransposeCommand.h new file mode 100644 index 0000000..764f72b --- /dev/null +++ b/src/commands/edit/TransposeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_TRANSPOSECOMMAND_H_ +#define _RG_TRANSPOSECOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class TransposeCommand : public BasicSelectionCommand +{ +public: + TransposeCommand(int semitones, EventSelection &selection) : + BasicSelectionCommand(getGlobalName(semitones), selection, true), + m_selection(&selection), m_semitones(semitones), m_diatonic(false) { } + + TransposeCommand(int semitones, int steps, EventSelection &selection) : + BasicSelectionCommand(getDiatonicGlobalName(semitones, steps), selection, true), + m_selection(&selection), m_semitones(semitones), m_steps(steps), m_diatonic(true) { } + + static QString getDiatonicGlobalName(int semitones = 0, int step = 0) { + switch (semitones) { + default: return i18n("Transpose by &Interval..."); + } + } + + static QString getGlobalName(int semitones = 0) { + switch (semitones) { + case 1: return i18n("&Up a Semitone"); + case -1: return i18n("&Down a Semitone"); + case 12: return i18n("Up an &Octave"); + case -12: return i18n("Down an Octa&ve"); + default: return i18n("&Transpose by Semitones..."); + } + } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + int m_semitones; + int m_steps; + bool m_diatonic; +}; + + + +} + +#endif diff --git a/src/commands/matrix/MatrixEraseCommand.cpp b/src/commands/matrix/MatrixEraseCommand.cpp new file mode 100644 index 0000000..c35259e --- /dev/null +++ b/src/commands/matrix/MatrixEraseCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MatrixEraseCommand.h" + +#include +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentMatrixHelper.h" +#include "document/BasicCommand.h" + + +namespace Rosegarden +{ + +MatrixEraseCommand::MatrixEraseCommand(Segment &segment, + Event *event) : + BasicCommand(i18n("Erase Note"), + segment, + event->getAbsoluteTime(), + event->getAbsoluteTime() + event->getDuration(), + true), + m_event(event), + m_relayoutEndTime(getEndTime()) +{ + // nothing +} + +timeT MatrixEraseCommand::getRelayoutEndTime() +{ + return m_relayoutEndTime; +} + +void MatrixEraseCommand::modifySegment() +{ + SegmentMatrixHelper helper(getSegment()); + + std::string eventType = m_event->getType(); + + if (eventType == Note::EventType) { + + helper.deleteNote(m_event, false); + + } +} + +} diff --git a/src/commands/matrix/MatrixEraseCommand.h b/src/commands/matrix/MatrixEraseCommand.h new file mode 100644 index 0000000..244c0f9 --- /dev/null +++ b/src/commands/matrix/MatrixEraseCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MATRIXERASECOMMAND_H_ +#define _RG_MATRIXERASECOMMAND_H_ + +#include "document/BasicCommand.h" +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class MatrixEraseCommand : public BasicCommand +{ +public: + MatrixEraseCommand(Segment &segment, + Event *event); + + virtual timeT getRelayoutEndTime(); + +protected: + virtual void modifySegment(); + + Event *m_event; // only used on 1st execute (cf bruteForceRedo) + timeT m_relayoutEndTime; +}; + +//------------------------------ + + +} + +#endif diff --git a/src/commands/matrix/MatrixInsertionCommand.cpp b/src/commands/matrix/MatrixInsertionCommand.cpp new file mode 100644 index 0000000..5b03d1a --- /dev/null +++ b/src/commands/matrix/MatrixInsertionCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MatrixInsertionCommand.h" + +#include +#include "base/Event.h" +#include "base/Segment.h" +#include "base/SegmentMatrixHelper.h" +#include "document/BasicCommand.h" +#include "base/BaseProperties.h" +#include "misc/Debug.h" + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +MatrixInsertionCommand::MatrixInsertionCommand(Segment &segment, + timeT time, + timeT endTime, + Event *event) : + BasicCommand(i18n("Insert Note"), segment, time, endTime), + m_event(new Event(*event, + std::min(time, endTime), + (time < endTime) ? endTime - time : time - endTime)) +{ + // nothing +} + +MatrixInsertionCommand::~MatrixInsertionCommand() +{ + delete m_event; + // don't want to delete m_lastInsertedEvent, it's just an alias +} + +void MatrixInsertionCommand::modifySegment() +{ + MATRIX_DEBUG << "MatrixInsertionCommand::modifySegment()\n"; + + if (!m_event->has(VELOCITY)) { + m_event->set + (VELOCITY, 100); + } + + SegmentMatrixHelper helper(getSegment()); + m_lastInsertedEvent = new Event(*m_event); + helper.insertNote(m_lastInsertedEvent); +} + +} diff --git a/src/commands/matrix/MatrixInsertionCommand.h b/src/commands/matrix/MatrixInsertionCommand.h new file mode 100644 index 0000000..bca8e0a --- /dev/null +++ b/src/commands/matrix/MatrixInsertionCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MATRIXINSERTIONCOMMAND_H_ +#define _RG_MATRIXINSERTIONCOMMAND_H_ + +#include "document/BasicCommand.h" +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class MatrixInsertionCommand : public BasicCommand +{ +public: + MatrixInsertionCommand(Segment &segment, + timeT time, + timeT endTime, + Event *event); + + virtual ~MatrixInsertionCommand(); + + Event *getLastInsertedEvent() { return m_lastInsertedEvent; } + +protected: + virtual void modifySegment(); + + Event *m_event; + Event *m_lastInsertedEvent; // an alias for another event +}; + + +} + +#endif diff --git a/src/commands/matrix/MatrixModifyCommand.cpp b/src/commands/matrix/MatrixModifyCommand.cpp new file mode 100644 index 0000000..b78d6f3 --- /dev/null +++ b/src/commands/matrix/MatrixModifyCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MatrixModifyCommand.h" + +#include "base/Event.h" +#include +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "document/BasicCommand.h" + + +namespace Rosegarden +{ + +MatrixModifyCommand::MatrixModifyCommand(Segment &segment, + Event *oldEvent, + Event *newEvent, + bool isMove, + bool normalize): + BasicCommand((isMove ? i18n("Move Note") : i18n("Modify Note")), + segment, + std::min(newEvent->getAbsoluteTime(), + oldEvent->getAbsoluteTime()), + std::max(oldEvent->getAbsoluteTime() + + oldEvent->getDuration(), + newEvent->getAbsoluteTime() + + newEvent->getDuration()), + true), + m_normalize(normalize), + m_oldEvent(oldEvent), + m_newEvent(newEvent) +{} + +void MatrixModifyCommand::modifySegment() +{ + std::string eventType = m_oldEvent->getType(); + + if (eventType == Note::EventType) { + + timeT normalizeStart = std::min(m_newEvent->getAbsoluteTime(), + m_oldEvent->getAbsoluteTime()); + + timeT normalizeEnd = std::max(m_newEvent->getAbsoluteTime() + + m_newEvent->getDuration(), + m_oldEvent->getAbsoluteTime() + + m_oldEvent->getDuration()); + + Segment &segment(getSegment()); + segment.insert(m_newEvent); + segment.eraseSingle(m_oldEvent); + + if (m_normalize) { + segment.normalizeRests(normalizeStart, normalizeEnd); + } + } +} + +} diff --git a/src/commands/matrix/MatrixModifyCommand.h b/src/commands/matrix/MatrixModifyCommand.h new file mode 100644 index 0000000..df9bdc0 --- /dev/null +++ b/src/commands/matrix/MatrixModifyCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MATRIXMODIFYCOMMAND_H_ +#define _RG_MATRIXMODIFYCOMMAND_H_ + +#include "document/BasicCommand.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class MatrixModifyCommand : public BasicCommand +{ +public: + MatrixModifyCommand(Segment &segment, + Event *oldEvent, + Event *newEvent, + bool isMove, + bool normalize); + +protected: + virtual void modifySegment(); + + bool m_normalize; + + Event *m_oldEvent; + Event *m_newEvent; +}; + + + +} + +#endif diff --git a/src/commands/matrix/MatrixPercussionInsertionCommand.cpp b/src/commands/matrix/MatrixPercussionInsertionCommand.cpp new file mode 100644 index 0000000..513663d --- /dev/null +++ b/src/commands/matrix/MatrixPercussionInsertionCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MatrixPercussionInsertionCommand.h" + +#include +#include "base/Composition.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentMatrixHelper.h" +#include "document/BasicCommand.h" +#include "base/BaseProperties.h" +#include "misc/Debug.h" + + +namespace Rosegarden +{ +using namespace BaseProperties; + + +MatrixPercussionInsertionCommand::MatrixPercussionInsertionCommand(Segment &segment, + timeT time, + Event *event) : + BasicCommand(i18n("Insert Percussion Note"), segment, + getEffectiveStartTime(segment, time, *event), + getEndTime(segment, time, *event)), + m_event(0), + m_time(time) +{ + timeT endTime = getEndTime(segment, time, *event); + m_event = new Event(*event, time, endTime - time); +} + +MatrixPercussionInsertionCommand::~MatrixPercussionInsertionCommand() +{ + delete m_event; + // don't want to delete m_lastInsertedEvent, it's just an alias +} + +void MatrixPercussionInsertionCommand::modifySegment() +{ + MATRIX_DEBUG << "MatrixPercussionInsertionCommand::modifySegment()\n"; + + if (!m_event->has(VELOCITY)) { + m_event->set + (VELOCITY, 100); + } + + Segment &s = getSegment(); + + Segment::iterator i = s.findTime(m_time); + + int pitch = 0; + if (m_event->has(PITCH)) { + pitch = m_event->get + (PITCH); + } + + while (i != s.begin()) { + + --i; + + if ((*i)->getAbsoluteTime() < m_time && + (*i)->isa(Note::EventType)) { + + if ((*i)->has(PITCH) && + (*i)->get + (PITCH) == pitch) { + + if ((*i)->getAbsoluteTime() + (*i)->getDuration() > m_time) { + Event *newPrevious = new Event + (**i, (*i)->getAbsoluteTime(), m_time - (*i)->getAbsoluteTime()); + s.erase(i); + i = s.insert(newPrevious); + } else { + break; + } + } + } + } + + SegmentMatrixHelper helper(s); + m_lastInsertedEvent = new Event(*m_event); + helper.insertNote(m_lastInsertedEvent); +} + +timeT +MatrixPercussionInsertionCommand::getEffectiveStartTime(Segment &segment, + timeT time, + Event &event) +{ + timeT startTime = time; + + int pitch = 0; + if (event.has(PITCH)) { + pitch = event.get(PITCH); + } + + Segment::iterator i = segment.findTime(time); + while (i != segment.begin()) { + --i; + + if ((*i)->has(PITCH) && + (*i)->get + (PITCH) == pitch) { + + if ((*i)->getAbsoluteTime() < time && + (*i)->isa(Note::EventType)) { + if ((*i)->getAbsoluteTime() + (*i)->getDuration() > time) { + startTime = (*i)->getAbsoluteTime(); + } else { + break; + } + } + } + } + + return startTime; +} + +timeT +MatrixPercussionInsertionCommand::getEndTime(Segment &segment, + timeT time, + Event &event) +{ + timeT endTime = + time + Note(Note::Semibreve, + 0).getDuration(); + timeT barEndTime = segment.getBarEndForTime(time); + timeT segmentEndTime = segment.getEndMarkerTime(); + + if (barEndTime > endTime) + endTime = barEndTime; + if (endTime > segmentEndTime) + endTime = segmentEndTime; + + int pitch = 0; + if (event.has(PITCH)) { + pitch = event.get(PITCH); + } + + for (Segment::iterator i = segment.findTime(time); + segment.isBeforeEndMarker(i); ++i) { + + if ((*i)->has(PITCH) && + (*i)->get + (PITCH) == pitch) { + + if ((*i)->getAbsoluteTime() > time && + (*i)->isa(Note::EventType)) { + endTime = (*i)->getAbsoluteTime(); + } + } + } + + Composition *comp = segment.getComposition(); + std::pair barRange = + comp->getBarRangeForTime(time); + timeT barDuration = barRange.second - barRange.first; + + + if (endTime > time + barDuration) { + endTime = time + barDuration; + } + + return endTime; +} + +} diff --git a/src/commands/matrix/MatrixPercussionInsertionCommand.h b/src/commands/matrix/MatrixPercussionInsertionCommand.h new file mode 100644 index 0000000..513754d --- /dev/null +++ b/src/commands/matrix/MatrixPercussionInsertionCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MATRIXPERCUSSIONINSERTIONCOMMAND_H_ +#define _RG_MATRIXPERCUSSIONINSERTIONCOMMAND_H_ + +#include "document/BasicCommand.h" +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class MatrixPercussionInsertionCommand : public BasicCommand +{ +public: + MatrixPercussionInsertionCommand(Segment &segment, + timeT time, + Event *event); + + virtual ~MatrixPercussionInsertionCommand(); + + Event *getLastInsertedEvent() { return m_lastInsertedEvent; } + +protected: + virtual void modifySegment(); + + timeT getEffectiveStartTime(Segment &segment, + timeT startTime, + Event &event); + timeT getEndTime(Segment &segment, + timeT endTime, + Event &event); + + Event *m_event; + timeT m_time; + Event *m_lastInsertedEvent; // an alias for another event +}; + +//------------------------------ + + +} + +#endif diff --git a/src/commands/notation/AddFingeringMarkCommand.cpp b/src/commands/notation/AddFingeringMarkCommand.cpp new file mode 100644 index 0000000..0c2e895 --- /dev/null +++ b/src/commands/notation/AddFingeringMarkCommand.cpp @@ -0,0 +1,119 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddFingeringMarkCommand.h" + +#include +#include "base/NotationTypes.h" +#include "base/NotationQuantizer.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/Sets.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +QString +AddFingeringMarkCommand::getGlobalName(QString fingering) +{ + if (fingering == "") + return i18n("Add Other &Fingering..."); + else if (fingering == "0") + return i18n("Add Fingering &0 (Thumb)"); + else + return i18n("Add Fingering &%1").arg(fingering); +} + +void +AddFingeringMarkCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + Segment &segment(m_selection->getSegment()); + + std::set + done; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if (done.find(*i) != done.end()) + continue; + if (!(*i)->isa(Note::EventType)) + continue; + + // We should do this on a chord-by-chord basis, considering + // only those notes in a chord that are also in the selection. + // Apply this fingering to the first note in the chord that + // does not already have a fingering. If they all already do, + // then clear them all and start again. + + Chord chord(segment, segment.findSingle(*i), + segment.getComposition()->getNotationQuantizer()); + + int attempt = 0; + + while (attempt < 2) { + + int count = 0; + + for (Chord::iterator ci = chord.begin(); + ci != chord.end(); ++ci) { + + if (!m_selection->contains(**ci)) + continue; + + if (attempt < 2 && + Marks::getFingeringMark(***ci) == + Marks::NoMark) { + Marks::addMark + (***ci, Marks::getFingeringMark(m_text), true); + attempt = 2; + } + + done.insert(**ci); + ++count; + } + + if (attempt < 2) { + if (count == 0) + break; + for (Chord::iterator ci = chord.begin(); + ci != chord.end(); ++ci) { + if (m_selection->contains(**ci)) { + Marks::removeMark + (***ci, + Marks::getFingeringMark(***ci)); + } + } + ++attempt; + } + } + } +} + +} diff --git a/src/commands/notation/AddFingeringMarkCommand.h b/src/commands/notation/AddFingeringMarkCommand.h new file mode 100644 index 0000000..1d95002 --- /dev/null +++ b/src/commands/notation/AddFingeringMarkCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTESMENUADDFINGERINGMARKCOMMAND_H_ +#define _RG_NOTESMENUADDFINGERINGMARKCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class AddFingeringMarkCommand : public BasicSelectionCommand +{ +public: + AddFingeringMarkCommand(std::string text, + EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection), m_text(text) { } + + static QString getGlobalName(QString fingering = ""); + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + std::string m_text; +}; + + + +} + +#endif diff --git a/src/commands/notation/AddIndicationCommand.cpp b/src/commands/notation/AddIndicationCommand.cpp new file mode 100644 index 0000000..717463d --- /dev/null +++ b/src/commands/notation/AddIndicationCommand.cpp @@ -0,0 +1,171 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddIndicationCommand.h" + +#include +#include "misc/Strings.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Selection.h" +#include "document/BasicCommand.h" +#include "gui/editors/notation/NotationProperties.h" +#include + + +namespace Rosegarden +{ + +AddIndicationCommand::AddIndicationCommand(std::string indicationType, + EventSelection &selection) : + BasicCommand(getGlobalName(indicationType), + selection.getSegment(), + selection.getStartTime(), + selection.getEndTime()), + m_indicationType(indicationType), + m_indicationDuration(selection.getEndTime() - selection.getStartTime()), + m_lastInsertedEvent(0) +{ + // nothing else +} + +AddIndicationCommand::~AddIndicationCommand() +{ + // empty +} + +bool +AddIndicationCommand::canExecute() +{ + Segment &s(getSegment()); + + for (Segment::iterator i = s.begin(); s.isBeforeEndMarker(i); ++i) { + + if ((*i)->getAbsoluteTime() >= getStartTime() + m_indicationDuration) { + return true; + } + + if ((*i)->isa(Indication::EventType)) { + + try { + Indication indication(**i); + + if ((*i)->getAbsoluteTime() + indication.getIndicationDuration() <= + getStartTime()) + continue; + + std::string type = indication.getIndicationType(); + + if (type == m_indicationType) { + // for all indications (including slur), we reject an + // exact overlap + if ((*i)->getAbsoluteTime() == getStartTime() && + indication.getIndicationDuration() == m_indicationDuration) { + return false; + } + } else if (m_indicationType == Indication::Slur) { + continue; + } + + // for non-slur indications we reject a partial + // overlap such as this one, if it's an overlap with + // an indication of the same "sort" + + if (m_indicationType == Indication::Crescendo || + m_indicationType == Indication::Decrescendo) { + if (type == Indication::Crescendo || + type == Indication::Decrescendo) + return false; + } + + if (m_indicationType == Indication::QuindicesimaUp || + m_indicationType == Indication::OttavaUp || + m_indicationType == Indication::OttavaDown || + m_indicationType == Indication::QuindicesimaDown) { + if (indication.isOttavaType()) + return false; + } + } catch (...) {} + } + } + + return true; +} + +void +AddIndicationCommand::modifySegment() +{ + SegmentNotationHelper helper(getSegment()); + + Indication indication(m_indicationType, m_indicationDuration); + Event *e = indication.getAsEvent(getStartTime()); + helper.segment().insert(e); + m_lastInsertedEvent = e; + + if (indication.isOttavaType()) { + for (Segment::iterator i = getSegment().findTime(getStartTime()); + i != getSegment().findTime(getStartTime() + m_indicationDuration); + ++i) { + if ((*i)->isa(Note::EventType)) { + (*i)->setMaybe(NotationProperties::OTTAVA_SHIFT, + indication.getOttavaShift()); + } + } + } +} + +QString +AddIndicationCommand::getGlobalName(std::string indicationType) +{ + if (indicationType == Indication::Slur) { + return i18n("Add S&lur"); + } else if (indicationType == Indication::PhrasingSlur) { + return i18n("Add &Phrasing Slur"); + } else if (indicationType == Indication::QuindicesimaUp) { + return i18n("Add Double-Octave Up"); + } else if (indicationType == Indication::OttavaUp) { + return i18n("Add Octave &Up"); + } else if (indicationType == Indication::OttavaDown) { + return i18n("Add Octave &Down"); + } else if (indicationType == Indication::QuindicesimaDown) { + return i18n("Add Double Octave Down"); + + // We used to generate these ones from the internal names plus + // caps, but that makes them untranslateable: + } else if (indicationType == Indication::Crescendo) { + return i18n("Add &Crescendo"); + } else if (indicationType == Indication::Decrescendo) { + return i18n("Add &Decrescendo"); + } else if (indicationType == Indication::Glissando) { + return i18n("Add &Glissando"); + } + + QString n = i18n("Add &%1%2").arg((char)toupper(indicationType[0])).arg(strtoqstr(indicationType.substr(1))); + return n; +} + +} diff --git a/src/commands/notation/AddIndicationCommand.h b/src/commands/notation/AddIndicationCommand.h new file mode 100644 index 0000000..c43dc3a --- /dev/null +++ b/src/commands/notation/AddIndicationCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTESMENUADDINDICATIONCOMMAND_H_ +#define _RG_NOTESMENUADDINDICATIONCOMMAND_H_ + +#include "document/BasicCommand.h" +#include +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class EventSelection; +class Event; + + +class AddIndicationCommand : public BasicCommand +{ +public: + AddIndicationCommand(std::string indicationType, + EventSelection &selection); + virtual ~AddIndicationCommand(); + + // tests whether the indication can be added without overlapping + // another one of the same type + bool canExecute(); + + Event *getLastInsertedEvent() { + return m_lastInsertedEvent; + } + virtual timeT getRelayoutEndTime() { + return getStartTime() + m_indicationDuration; + } + + static QString getGlobalName(std::string indicationType); + +protected: + virtual void modifySegment(); + + std::string m_indicationType; + timeT m_indicationDuration; + Event *m_lastInsertedEvent; +}; + + + +} + +#endif diff --git a/src/commands/notation/AddMarkCommand.cpp b/src/commands/notation/AddMarkCommand.cpp new file mode 100644 index 0000000..5b30431 --- /dev/null +++ b/src/commands/notation/AddMarkCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddMarkCommand.h" + +#include +#include "misc/Strings.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +QString +AddMarkCommand::getGlobalName(Mark markType) +{ + QString m = strtoqstr(markType); + + // Gosh, lots of collisions + if (markType == Marks::Sforzando) + m = i18n("S&forzando"); + else if (markType == Marks::Staccato) + m = i18n("Sta&ccato"); + else if (markType == Marks::Rinforzando) + m = i18n("R&inforzando"); + else if (markType == Marks::Tenuto) + m = i18n("T&enuto"); + else if (markType == Marks::Trill) + m = i18n("Tri&ll"); + else if (markType == Marks::LongTrill) + m = i18n("Trill &with Line"); + else if (markType == Marks::TrillLine) + m = i18n("Trill Line"); + else if (markType == Marks::Turn) + m = i18n("&Turn"); + else if (markType == Marks::Accent) + m = i18n("&Accent"); + else if (markType == Marks::Staccatissimo) + m = i18n("&Staccatissimo"); + else if (markType == Marks::Marcato) + m = i18n("&Marcato"); + else if (markType == Marks::Pause) + m = i18n("&Pause"); + else if (markType == Marks::UpBow) + m = i18n("&Up-Bow"); + else if (markType == Marks::DownBow) + m = i18n("&Down-Bow"); + else if (markType == Marks::Mordent) + m = i18n("Mo&rdent"); + else if (markType == Marks::MordentInverted) + m = i18n("Inverted Mordent"); + else if (markType == Marks::MordentLong) + m = i18n("Long Mordent"); + else if (markType == Marks::MordentLongInverted) + m = i18n("Lon&g Inverted Mordent"); + else + m = i18n("&%1%2").arg(m[0].upper()).arg(m.right(m.length() - 1)); + // FIXME: That last i18n has very little chance of working, unless + // by some miracle the exact same string was translated elsewhere already + // but we'll leave it as a warning + + m = i18n("Add %1").arg(m); + return m; +} + +void +AddMarkCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + long n = 0; + (*i)->get + (MARK_COUNT, n); + (*i)->set + (MARK_COUNT, n + 1); + (*i)->set + (getMarkPropertyName(n), + m_mark); + } +} + +} diff --git a/src/commands/notation/AddMarkCommand.h b/src/commands/notation/AddMarkCommand.h new file mode 100644 index 0000000..5bc36b4 --- /dev/null +++ b/src/commands/notation/AddMarkCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTESMENUADDMARKCOMMAND_H_ +#define _RG_NOTESMENUADDMARKCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class AddMarkCommand : public BasicSelectionCommand +{ +public: + AddMarkCommand(Mark mark, + EventSelection &selection) : + BasicSelectionCommand(getGlobalName(mark), selection, true), + m_selection(&selection), m_mark(mark) { } + + static QString getGlobalName(Mark mark); + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + Mark m_mark; +}; + + + +} + +#endif diff --git a/src/commands/notation/AddSlashesCommand.cpp b/src/commands/notation/AddSlashesCommand.cpp new file mode 100644 index 0000000..1e2e5a6 --- /dev/null +++ b/src/commands/notation/AddSlashesCommand.cpp @@ -0,0 +1,53 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddSlashesCommand.h" + +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "gui/editors/notation/NotationProperties.h" + + +namespace Rosegarden +{ + +void +AddSlashesCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if (m_number < 1) { + (*i)->unset(NotationProperties::SLASHES); + } else { + (*i)->set + (NotationProperties::SLASHES, m_number); + } + } +} + +} diff --git a/src/commands/notation/AddSlashesCommand.h b/src/commands/notation/AddSlashesCommand.h new file mode 100644 index 0000000..4c85cd4 --- /dev/null +++ b/src/commands/notation/AddSlashesCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTESMENUADDSLASHESCOMMAND_H_ +#define _RG_NOTESMENUADDSLASHESCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class AddSlashesCommand : public BasicSelectionCommand +{ +public: + AddSlashesCommand(int number, + EventSelection &selection) : + BasicSelectionCommand(i18n("Slashes"), selection, true), + m_selection(&selection), m_number(number) { } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + int m_number; +}; + + +} + +#endif diff --git a/src/commands/notation/AddTextMarkCommand.cpp b/src/commands/notation/AddTextMarkCommand.cpp new file mode 100644 index 0000000..d3de487 --- /dev/null +++ b/src/commands/notation/AddTextMarkCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddTextMarkCommand.h" + +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +AddTextMarkCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + long n = 0; + (*i)->get + (MARK_COUNT, n); + (*i)->set + (MARK_COUNT, n + 1); + (*i)->set + (getMarkPropertyName(n), + Marks::getTextMark(m_text)); + } +} + +} diff --git a/src/commands/notation/AddTextMarkCommand.h b/src/commands/notation/AddTextMarkCommand.h new file mode 100644 index 0000000..b4037cc --- /dev/null +++ b/src/commands/notation/AddTextMarkCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTESMENUADDTEXTMARKCOMMAND_H_ +#define _RG_NOTESMENUADDTEXTMARKCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class AddTextMarkCommand : public BasicSelectionCommand +{ +public: + AddTextMarkCommand(std::string text, + EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection), m_text(text) { } + + static QString getGlobalName() { return i18n("Add Te&xt Mark..."); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + std::string m_text; +}; + + + +} + +#endif diff --git a/src/commands/notation/AutoBeamCommand.cpp b/src/commands/notation/AutoBeamCommand.cpp new file mode 100644 index 0000000..04c243d --- /dev/null +++ b/src/commands/notation/AutoBeamCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AutoBeamCommand.h" + +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +AutoBeamCommand::modifySegment() +{ + SegmentNotationHelper helper(getSegment()); + helper.autoBeam(getStartTime(), getEndTime(), GROUP_TYPE_BEAMED); +} + +} diff --git a/src/commands/notation/AutoBeamCommand.h b/src/commands/notation/AutoBeamCommand.h new file mode 100644 index 0000000..b26caf1 --- /dev/null +++ b/src/commands/notation/AutoBeamCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTESMENUAUTOBEAMCOMMAND_H_ +#define _RG_NOTESMENUAUTOBEAMCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class Segment; +class EventSelection; + + +class AutoBeamCommand : public BasicSelectionCommand +{ +public: + AutoBeamCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection) { } + + AutoBeamCommand(Segment &segment) : + BasicSelectionCommand(getGlobalName(), segment) { } + + static QString getGlobalName() { return i18n("&Auto-Beam"); } + +protected: + virtual void modifySegment(); +}; + + + +} + +#endif diff --git a/src/commands/notation/BeamCommand.cpp b/src/commands/notation/BeamCommand.cpp new file mode 100644 index 0000000..fb75dcb --- /dev/null +++ b/src/commands/notation/BeamCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "BeamCommand.h" + +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +BeamCommand::modifySegment() +{ + int id = getSegment().getNextId(); + + for (EventSelection::eventcontainer::iterator i = + m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + (*i)->set + (BEAMED_GROUP_ID, id); + (*i)->set + (BEAMED_GROUP_TYPE, GROUP_TYPE_BEAMED); + } + } +} + +} diff --git a/src/commands/notation/BeamCommand.h b/src/commands/notation/BeamCommand.h new file mode 100644 index 0000000..9fd112b --- /dev/null +++ b/src/commands/notation/BeamCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTESMENUBEAMCOMMAND_H_ +#define _RG_NOTESMENUBEAMCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class BeamCommand : public BasicSelectionCommand +{ +public: + BeamCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("&Beam Group"); } + +protected: + virtual void modifySegment(); + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + + +} + +#endif diff --git a/src/commands/notation/BreakCommand.cpp b/src/commands/notation/BreakCommand.cpp new file mode 100644 index 0000000..ae74e37 --- /dev/null +++ b/src/commands/notation/BreakCommand.cpp @@ -0,0 +1,54 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "BreakCommand.h" + +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "gui/editors/notation/NotationProperties.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +BreakCommand::modifySegment() +{ + for (EventSelection::eventcontainer::iterator i = + m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + (*i)->unset(NotationProperties::BEAMED); + (*i)->unset(BEAMED_GROUP_ID); + (*i)->unset(BEAMED_GROUP_TYPE); + (*i)->clearNonPersistentProperties(); + } +} + +} diff --git a/src/commands/notation/BreakCommand.h b/src/commands/notation/BreakCommand.h new file mode 100644 index 0000000..5c95a33 --- /dev/null +++ b/src/commands/notation/BreakCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTESMENUBREAKCOMMAND_H_ +#define _RG_NOTESMENUBREAKCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class BreakCommand : public BasicSelectionCommand +{ +public: + BreakCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("&Unbeam"); } + +protected: + virtual void modifySegment(); + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + + +} + +#endif diff --git a/src/commands/notation/ChangeSlurPositionCommand.cpp b/src/commands/notation/ChangeSlurPositionCommand.cpp new file mode 100644 index 0000000..dc2ff0b --- /dev/null +++ b/src/commands/notation/ChangeSlurPositionCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ChangeSlurPositionCommand.h" + +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "gui/editors/notation/NotationProperties.h" +#include + + +namespace Rosegarden +{ + +void +ChangeSlurPositionCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Indication::EventType)) { + std::string indicationType; + if ((*i)->get + (Indication::IndicationTypePropertyName, indicationType) + && (indicationType == Indication::Slur || + indicationType == Indication::PhrasingSlur)) { + (*i)->set(NotationProperties::SLUR_ABOVE, m_above); + } + } + } +} + +} diff --git a/src/commands/notation/ChangeSlurPositionCommand.h b/src/commands/notation/ChangeSlurPositionCommand.h new file mode 100644 index 0000000..11c4bce --- /dev/null +++ b/src/commands/notation/ChangeSlurPositionCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUCHANGESLURPOSITIONCOMMAND_H_ +#define _RG_ADJUSTMENUCHANGESLURPOSITIONCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + +class Slur; + + +namespace Rosegarden +{ + +class EventSelection; + + +class ChangeSlurPositionCommand : public BasicSelectionCommand +{ +public: + ChangeSlurPositionCommand(bool above, EventSelection &selection) : + BasicSelectionCommand(getGlobalName(above), selection, true), + m_selection(&selection), m_above(above) { } + + static QString getGlobalName(bool above) { + return above ? i18n("Slur &Above") : i18n("Slur &Below"); + } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + bool m_above; +}; + + + +} + +#endif diff --git a/src/commands/notation/ChangeStemsCommand.cpp b/src/commands/notation/ChangeStemsCommand.cpp new file mode 100644 index 0000000..413f9d2 --- /dev/null +++ b/src/commands/notation/ChangeStemsCommand.cpp @@ -0,0 +1,53 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ChangeStemsCommand.h" + +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "gui/editors/notation/NotationProperties.h" +#include + + +namespace Rosegarden +{ + +void +ChangeStemsCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + (*i)->set + (NotationProperties::STEM_UP, m_up); + } + } +} + +} diff --git a/src/commands/notation/ChangeStemsCommand.h b/src/commands/notation/ChangeStemsCommand.h new file mode 100644 index 0000000..40b48bf --- /dev/null +++ b/src/commands/notation/ChangeStemsCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUCHANGESTEMSCOMMAND_H_ +#define _RG_ADJUSTMENUCHANGESTEMSCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + +class Stems; + + +namespace Rosegarden +{ + +class EventSelection; + + +class ChangeStemsCommand : public BasicSelectionCommand +{ +public: + ChangeStemsCommand(bool up, EventSelection &selection) : + BasicSelectionCommand(getGlobalName(up), selection, true), + m_selection(&selection), m_up(up) { } + + static QString getGlobalName(bool up) { + return up ? i18n("Stems &Up") : i18n("Stems &Down"); + } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + bool m_up; +}; + + + +} + +#endif diff --git a/src/commands/notation/ChangeStyleCommand.cpp b/src/commands/notation/ChangeStyleCommand.cpp new file mode 100644 index 0000000..f8d5c04 --- /dev/null +++ b/src/commands/notation/ChangeStyleCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ChangeStyleCommand.h" + +#include "misc/Strings.h" +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "gui/editors/notation/NotationProperties.h" +#include "gui/editors/notation/NoteStyleFactory.h" +#include + + +namespace Rosegarden +{ + +QString +ChangeStyleCommand::getGlobalName(NoteStyleName style) +{ + return strtoqstr(style); +} + +void +ChangeStyleCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + if (m_style == NoteStyleFactory::DefaultStyle) { + (*i)->unset(NotationProperties::NOTE_STYLE); + } else { + (*i)->set + + (NotationProperties::NOTE_STYLE, m_style); + } + } + } +} + +} diff --git a/src/commands/notation/ChangeStyleCommand.h b/src/commands/notation/ChangeStyleCommand.h new file mode 100644 index 0000000..d5edbab --- /dev/null +++ b/src/commands/notation/ChangeStyleCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUCHANGESTYLECOMMAND_H_ +#define _RG_ADJUSTMENUCHANGESTYLECOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include "gui/editors/notation/NoteStyle.h" +#include +#include + + +class Change; + + +namespace Rosegarden +{ + +class EventSelection; + + +class ChangeStyleCommand : public BasicSelectionCommand +{ +public: + ChangeStyleCommand(NoteStyleName style, + EventSelection &selection) : + BasicSelectionCommand(getGlobalName(style), selection, true), + m_selection(&selection), m_style(style) { } + + static QString getGlobalName() { + return i18n("Change &Note Style"); + } + + static QString getGlobalName(NoteStyleName style); + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + NoteStyleName m_style; +}; + + + +} + +#endif diff --git a/src/commands/notation/ChangeTiePositionCommand.cpp b/src/commands/notation/ChangeTiePositionCommand.cpp new file mode 100644 index 0000000..42f67e6 --- /dev/null +++ b/src/commands/notation/ChangeTiePositionCommand.cpp @@ -0,0 +1,54 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ChangeTiePositionCommand.h" + +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "base/BaseProperties.h" +#include "document/BasicSelectionCommand.h" +#include "gui/editors/notation/NotationProperties.h" +#include + + +namespace Rosegarden +{ + +void +ChangeTiePositionCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->has(BaseProperties::TIED_FORWARD) && + (*i)->get(BaseProperties::TIED_FORWARD)) { + (*i)->set(BaseProperties::TIE_IS_ABOVE, m_above); + } + } +} + +} diff --git a/src/commands/notation/ChangeTiePositionCommand.h b/src/commands/notation/ChangeTiePositionCommand.h new file mode 100644 index 0000000..c4c58a7 --- /dev/null +++ b/src/commands/notation/ChangeTiePositionCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUCHANGETIEPOSITIONCOMMAND_H_ +#define _RG_ADJUSTMENUCHANGETIEPOSITIONCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + +namespace Rosegarden +{ + +class EventSelection; + +class ChangeTiePositionCommand : public BasicSelectionCommand +{ +public: + ChangeTiePositionCommand(bool above, EventSelection &selection) : + BasicSelectionCommand(getGlobalName(above), selection, true), + m_selection(&selection), m_above(above) { } + + static QString getGlobalName(bool above) { + return above ? i18n("Tie &Above") : i18n("Tie &Below"); + } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + bool m_above; +}; + + + +} + +#endif diff --git a/src/commands/notation/ClefInsertionCommand.cpp b/src/commands/notation/ClefInsertionCommand.cpp new file mode 100644 index 0000000..f21c1b0 --- /dev/null +++ b/src/commands/notation/ClefInsertionCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ClefInsertionCommand.h" + +#include +#include "misc/Strings.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/BaseProperties.h" +#include "document/BasicCommand.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +ClefInsertionCommand::ClefInsertionCommand(Segment &segment, timeT time, + Clef clef, + bool shouldChangeOctave, + bool shouldTranspose) : + BasicCommand(getGlobalName(&clef), segment, time, + ((shouldChangeOctave || shouldTranspose) ? + segment.getEndTime() : time + 1)), + m_clef(clef), + m_shouldChangeOctave(shouldChangeOctave), + m_shouldTranspose(shouldTranspose), + m_lastInsertedEvent(0) +{ + // nothing +} + +ClefInsertionCommand::~ClefInsertionCommand() +{ + // nothing +} + +QString +ClefInsertionCommand::getGlobalName(Clef *) +{ + /* doesn't handle octave offset -- leave it for now + if (clef) { + QString name(strtoqstr(clef->getClefType())); + name = name.left(1).upper() + name.right(name.length()-1); + return i18n("Change to %1 Cle&f...").arg(name); + } else { + */ + return i18n("Add Cle&f Change..."); + /* + } + */ +} + +timeT +ClefInsertionCommand::getRelayoutEndTime() +{ + // Inserting a clef can change the y-coord of every subsequent note + return getSegment().getEndTime(); +} + +void +ClefInsertionCommand::modifySegment() +{ + SegmentNotationHelper helper(getSegment()); + Clef oldClef(getSegment().getClefAtTime(getStartTime())); + + Segment::iterator i = getSegment().findTime(getStartTime()); + while (getSegment().isBeforeEndMarker(i)) { + if ((*i)->getAbsoluteTime() > getStartTime()) { + break; + } + if ((*i)->isa(Clef::EventType)) { + getSegment().erase(i); + break; + } + ++i; + } + + i = helper.insertClef(getStartTime(), m_clef); + if (i != helper.segment().end()) + m_lastInsertedEvent = *i; + + if (m_clef != oldClef) { + + int semitones = 0; + + if (m_shouldChangeOctave) { + semitones += 12 * (m_clef.getOctave() - oldClef.getOctave()); + } + if (m_shouldTranspose) { + semitones -= m_clef.getPitchOffset() - oldClef.getPitchOffset(); + } + + if (semitones != 0) { + while (i != helper.segment().end()) { + if ((*i)->isa(Note::EventType)) { + long pitch = 0; + if ((*i)->get + (PITCH, pitch)) { + pitch += semitones; + (*i)->set + (PITCH, pitch); + } + } + ++i; + } + } + } +} + +} diff --git a/src/commands/notation/ClefInsertionCommand.h b/src/commands/notation/ClefInsertionCommand.h new file mode 100644 index 0000000..3e9b940 --- /dev/null +++ b/src/commands/notation/ClefInsertionCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CLEFINSERTIONCOMMAND_H_ +#define _RG_CLEFINSERTIONCOMMAND_H_ + +#include "base/NotationTypes.h" +#include "document/BasicCommand.h" +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class ClefInsertionCommand : public BasicCommand +{ +public: + ClefInsertionCommand(Segment &segment, + timeT time, + Clef clef, + bool shouldChangeOctave = false, + bool shouldTranspose = false); + virtual ~ClefInsertionCommand(); + + static QString getGlobalName(Clef *clef = 0); + virtual timeT getRelayoutEndTime(); + + Event *getLastInsertedEvent() { return m_lastInsertedEvent; } + +protected: + virtual void modifySegment(); + + Clef m_clef; + bool m_shouldChangeOctave; + bool m_shouldTranspose; + + Event *m_lastInsertedEvent; +}; + + +} + +#endif diff --git a/src/commands/notation/CollapseRestsCommand.cpp b/src/commands/notation/CollapseRestsCommand.cpp new file mode 100644 index 0000000..655b3b6 --- /dev/null +++ b/src/commands/notation/CollapseRestsCommand.cpp @@ -0,0 +1,54 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CollapseRestsCommand.h" + +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Selection.h" +#include "document/BasicCommand.h" +#include + + +namespace Rosegarden +{ + +CollapseRestsCommand::CollapseRestsCommand +(EventSelection &selection) : + BasicCommand(getGlobalName(), + selection.getSegment(), + selection.getStartTime(), + selection.getEndTime()) +{ + // nothing else +} + +void CollapseRestsCommand::modifySegment() +{ + SegmentNotationHelper helper(getSegment()); + helper.collapseRestsAggressively(getStartTime(), getEndTime()); +} + +} diff --git a/src/commands/notation/CollapseRestsCommand.h b/src/commands/notation/CollapseRestsCommand.h new file mode 100644 index 0000000..337fe04 --- /dev/null +++ b/src/commands/notation/CollapseRestsCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUCOLLAPSERESTSCOMMAND_H_ +#define _RG_ADJUSTMENUCOLLAPSERESTSCOMMAND_H_ + +#include "document/BasicCommand.h" +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Segment; +class EventSelection; + + +class CollapseRestsCommand : public BasicCommand +{ +public: + CollapseRestsCommand(Segment &s, + timeT startTime, + timeT endTime) : + BasicCommand(getGlobalName(), s, startTime, endTime) { } + + CollapseRestsCommand(EventSelection &selection); + + static QString getGlobalName() { return i18n("&Collapse Rests"); } + +protected: + virtual void modifySegment(); +}; + + +} + +#endif diff --git a/src/commands/notation/DeCounterpointCommand.cpp b/src/commands/notation/DeCounterpointCommand.cpp new file mode 100644 index 0000000..1f97476 --- /dev/null +++ b/src/commands/notation/DeCounterpointCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "DeCounterpointCommand.h" + +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +void +DeCounterpointCommand::modifySegment() +{ + Segment &segment(getSegment()); + SegmentNotationHelper helper(segment); + + if (m_selection) { + EventSelection::RangeTimeList ranges(m_selection->getRangeTimes()); + for (EventSelection::RangeTimeList::iterator i = ranges.begin(); + i != ranges.end(); ++i) { + helper.deCounterpoint(i->first, i->second); + segment.normalizeRests(i->first, i->second); + } + } else { + helper.deCounterpoint(getStartTime(), getEndTime()); + segment.normalizeRests(getStartTime(), getEndTime()); + } +} + +} diff --git a/src/commands/notation/DeCounterpointCommand.h b/src/commands/notation/DeCounterpointCommand.h new file mode 100644 index 0000000..e0cda37 --- /dev/null +++ b/src/commands/notation/DeCounterpointCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUDECOUNTERPOINTCOMMAND_H_ +#define _RG_ADJUSTMENUDECOUNTERPOINTCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + +class Overlapping; + + +namespace Rosegarden +{ + +class Segment; +class EventSelection; + + +class DeCounterpointCommand : public BasicSelectionCommand +{ +public: + DeCounterpointCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + DeCounterpointCommand(Segment &segment) : + BasicSelectionCommand(getGlobalName(), segment, true), + m_selection(0) { } + + static QString getGlobalName() { return i18n("Split-and-Tie Overlapping &Chords"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + + +} + +#endif diff --git a/src/commands/notation/EraseEventCommand.cpp b/src/commands/notation/EraseEventCommand.cpp new file mode 100644 index 0000000..e599079 --- /dev/null +++ b/src/commands/notation/EraseEventCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EraseEventCommand.h" + +#include "misc/Strings.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "document/BasicCommand.h" +#include "gui/editors/notation/NotationProperties.h" + + +namespace Rosegarden +{ + +EraseEventCommand::EraseEventCommand(Segment &segment, + Event *event, + bool collapseRest) : + BasicCommand(strtoqstr(makeName(event->getType())), + segment, + event->getAbsoluteTime(), + event->getAbsoluteTime() + event->getDuration(), + true), + m_collapseRest(collapseRest), + m_event(event), + m_relayoutEndTime(getEndTime()) +{ + // nothing +} + +EraseEventCommand::~EraseEventCommand() +{ + // nothing +} + +std::string +EraseEventCommand::makeName(std::string e) +{ + std::string n = "Erase "; + n += (char)toupper(e[0]); + n += e.substr(1); + return n; +} + +timeT +EraseEventCommand::getRelayoutEndTime() +{ + return m_relayoutEndTime; +} + +void +EraseEventCommand::modifySegment() +{ + SegmentNotationHelper helper(getSegment()); + + if (m_event->isa(Clef::EventType) || + m_event->isa(Key ::EventType)) { + + m_relayoutEndTime = helper.segment().getEndTime(); + + } else if (m_event->isa(Indication::EventType)) { + + try { + Indication indication(*m_event); + if (indication.isOttavaType()) { + + for (Segment::iterator i = getSegment().findTime + (m_event->getAbsoluteTime()); + i != getSegment().findTime + (m_event->getAbsoluteTime() + indication.getIndicationDuration()); + ++i) { + (*i)->unset(NotationProperties::OTTAVA_SHIFT); + } + } + } catch (...) {} + } + + helper.deleteEvent(m_event, m_collapseRest); +} + +} diff --git a/src/commands/notation/EraseEventCommand.h b/src/commands/notation/EraseEventCommand.h new file mode 100644 index 0000000..07043fa --- /dev/null +++ b/src/commands/notation/EraseEventCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ERASEEVENTCOMMAND_H_ +#define _RG_ERASEEVENTCOMMAND_H_ + +#include "document/BasicCommand.h" +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class EraseEventCommand : public BasicCommand +{ +public: + EraseEventCommand(Segment &segment, + Event *event, + bool collapseRest); + virtual ~EraseEventCommand(); + + virtual timeT getRelayoutEndTime(); + +protected: + virtual void modifySegment(); + + bool m_collapseRest; + + Event *m_event; // only used on 1st execute (cf bruteForceRedo) + timeT m_relayoutEndTime; + std::string makeName(std::string); +}; + + + +// Group menu commands + + + +} + +#endif diff --git a/src/commands/notation/FixNotationQuantizeCommand.cpp b/src/commands/notation/FixNotationQuantizeCommand.cpp new file mode 100644 index 0000000..014d610 --- /dev/null +++ b/src/commands/notation/FixNotationQuantizeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "FixNotationQuantizeCommand.h" + +#include "base/Event.h" +#include "base/Quantizer.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +void +FixNotationQuantizeCommand::modifySegment() +{ + std::vector toErase; + std::vector toInsert; + Segment &segment(m_selection->getSegment()); + + EventSelection::eventcontainer::iterator i; + + //!!! the Quantizer needs a fixQuantizedValues(EventSelection*) + //method, but it hasn't got one yet so for the moment we're doing + //this by hand. + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + timeT ut = (*i)->getAbsoluteTime(); + timeT ud = (*i)->getDuration(); + timeT qt = (*i)->getNotationAbsoluteTime(); + timeT qd = (*i)->getNotationDuration(); + + if ((ut != qt) || (ud != qd)) { + toErase.push_back(*i); + toInsert.push_back(new Event(**i, qt, qd)); + } + } + + for (unsigned int j = 0; j < toErase.size(); ++j) { + Segment::iterator jtr(segment.findSingle(toErase[j])); + if (jtr != segment.end()) + segment.erase(jtr); + } + + for (unsigned int j = 0; j < toInsert.size(); ++j) { + segment.insert(toInsert[j]); + } + + /*!!! + Segment *segment(&m_selection->getSegment()); + m_quantizer->fixQuantizedValues + (segment, + segment->findTime(m_selection->getStartTime()), + segment->findTime(m_selection->getEndTime())); + */ + + //!!! normalizeRests? +} + +} diff --git a/src/commands/notation/FixNotationQuantizeCommand.h b/src/commands/notation/FixNotationQuantizeCommand.h new file mode 100644 index 0000000..dfce3e9 --- /dev/null +++ b/src/commands/notation/FixNotationQuantizeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUFIXNOTATIONQUANTIZECOMMAND_H_ +#define _RG_ADJUSTMENUFIXNOTATIONQUANTIZECOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class FixNotationQuantizeCommand : public BasicSelectionCommand +{ +public: + FixNotationQuantizeCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("Fi&x Notation Quantization"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + +} + +#endif diff --git a/src/commands/notation/GraceCommand.cpp b/src/commands/notation/GraceCommand.cpp new file mode 100644 index 0000000..99b8e5a --- /dev/null +++ b/src/commands/notation/GraceCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "GraceCommand.h" + +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "document/BasicCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +/*!!! + +GraceCommand::GraceCommand(EventSelection &selection) : + BasicCommand(getGlobalName(), + selection.getSegment(), + selection.getStartTime(), + getEffectiveEndTime(selection), + true), + m_selection(&selection) +{} + +timeT +GraceCommand::getEffectiveEndTime(EventSelection & + selection) +{ + EventSelection::eventcontainer::iterator i = + selection.getSegmentEvents().end(); + if (i == selection.getSegmentEvents().begin()) + return selection.getEndTime(); + --i; + + Segment::iterator si = selection.getSegment().findTime + ((*i)->getAbsoluteTime() + (*i)->getDuration()); + if (si == selection.getSegment().end()) + return selection.getEndTime(); + else + return (*si)->getAbsoluteTime() + 1; +} + +void +GraceCommand::modifySegment() +{ + Segment &s(getSegment()); + timeT startTime = getStartTime(); + timeT endOfLastGraceNote = startTime; + int id = s.getNextId(); + + // first turn the selected events into grace notes + + for (EventSelection::eventcontainer::iterator i = + m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + (*i)->set(IS_GRACE_NOTE, true); + (*i)->set(BEAMED_GROUP_ID, id); + (*i)->set(BEAMED_GROUP_TYPE, GROUP_TYPE_GRACE); + } + + if ((*i)->getAbsoluteTime() + (*i)->getDuration() > + endOfLastGraceNote) { + endOfLastGraceNote = + (*i)->getAbsoluteTime() + (*i)->getDuration(); + } + } + + // then indicate that the following chord has grace notes + + Segment::iterator i0, i1; + s.getTimeSlice(endOfLastGraceNote, i0, i1); + + while (i0 != i1 && i0 != s.end()) { + if (!(*i0)->isa(Note::EventType)) { + ++i0; + continue; + } + (*i0)->set + (HAS_GRACE_NOTES, true); + ++i0; + } +} + +*/ + +} diff --git a/src/commands/notation/GraceCommand.h b/src/commands/notation/GraceCommand.h new file mode 100644 index 0000000..08dcd87 --- /dev/null +++ b/src/commands/notation/GraceCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUGRACECOMMAND_H_ +#define _RG_ADJUSTMENUGRACECOMMAND_H_ + +#include "document/BasicCommand.h" +#include +#include "base/Event.h" +#include + + +class Make; + + +namespace Rosegarden +{ + +class EventSelection; + +/*!!! +class GraceCommand : public BasicCommand +{ +public: + GraceCommand(EventSelection &selection); + + static QString getGlobalName() { return i18n("Make &Grace Notes"); } + +protected: + virtual void modifySegment(); + timeT getEffectiveEndTime(EventSelection &); + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; +*/ + +} + +#endif diff --git a/src/commands/notation/GuitarChordInsertionCommand.cpp b/src/commands/notation/GuitarChordInsertionCommand.cpp new file mode 100644 index 0000000..79e2b44 --- /dev/null +++ b/src/commands/notation/GuitarChordInsertionCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "GuitarChordInsertionCommand.h" + +#include +#include "base/Event.h" +#include "base/Segment.h" +#include "document/BasicCommand.h" + + +namespace Rosegarden +{ + +GuitarChordInsertionCommand::GuitarChordInsertionCommand(Segment &segment, + timeT time, + const Guitar::Chord& chord) : + BasicCommand(i18n("Insert Guitar Chord"), segment, time, time + 1, true), + m_chord(chord) +{ + // nothing +} + +GuitarChordInsertionCommand::~GuitarChordInsertionCommand() +{} + +void + +GuitarChordInsertionCommand::modifySegment() +{ + Segment::iterator i = getSegment().insert(m_chord.getAsEvent(getStartTime())); + if (i != getSegment().end()) { + m_lastInsertedEvent = *i; + } +} + +} diff --git a/src/commands/notation/GuitarChordInsertionCommand.h b/src/commands/notation/GuitarChordInsertionCommand.h new file mode 100644 index 0000000..0a2db02 --- /dev/null +++ b/src/commands/notation/GuitarChordInsertionCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_GUITARCHORDINSERTIONCOMMAND_H_ +#define _RG_GUITARCHORDINSERTIONCOMMAND_H_ + +#include "document/BasicCommand.h" +#include "base/Event.h" +#include "gui/editors/guitar/Chord.h" + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class GuitarChordInsertionCommand : public BasicCommand +{ +public: + GuitarChordInsertionCommand(Segment &segment, + timeT time, + const Guitar::Chord& chord); + virtual ~GuitarChordInsertionCommand(); + + Event *getLastInsertedEvent() { return m_lastInsertedEvent; } + +protected: + virtual void modifySegment(); + + Guitar::Chord m_chord; + Event *m_lastInsertedEvent; +}; + + +} + +#endif diff --git a/src/commands/notation/IncrementDisplacementsCommand.cpp b/src/commands/notation/IncrementDisplacementsCommand.cpp new file mode 100644 index 0000000..2ac44df --- /dev/null +++ b/src/commands/notation/IncrementDisplacementsCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "IncrementDisplacementsCommand.h" + +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +IncrementDisplacementsCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + long prevX = 0, prevY = 0; + (*i)->get + (DISPLACED_X, prevX); + (*i)->get + (DISPLACED_Y, prevY); + (*i)->setMaybe(DISPLACED_X, prevX + long(m_dx)); + (*i)->setMaybe(DISPLACED_Y, prevY + long(m_dy)); + } +} + +} diff --git a/src/commands/notation/IncrementDisplacementsCommand.h b/src/commands/notation/IncrementDisplacementsCommand.h new file mode 100644 index 0000000..4272b6a --- /dev/null +++ b/src/commands/notation/IncrementDisplacementsCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_INCREMENTDISPLACEMENTSCOMMAND_H_ +#define _RG_INCREMENTDISPLACEMENTSCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class IncrementDisplacementsCommand : public BasicSelectionCommand +{ +public: + IncrementDisplacementsCommand(EventSelection &selection, + long dx, long dy) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection), + m_dx(dx), + m_dy(dy) { } + + static QString getGlobalName() { return i18n("Fine Reposition"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + long m_dx; + long m_dy; +}; + + +} + +#endif diff --git a/src/commands/notation/InterpretCommand.cpp b/src/commands/notation/InterpretCommand.cpp new file mode 100644 index 0000000..d8a82cd --- /dev/null +++ b/src/commands/notation/InterpretCommand.cpp @@ -0,0 +1,602 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "InterpretCommand.h" + +#include "base/Composition.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "misc/Debug.h" +#include "base/Quantizer.h" +#include "base/Segment.h" +#include "base/Sets.h" +#include "base/BaseProperties.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +const int InterpretCommand::NoInterpretation = 0; +const int InterpretCommand::GuessDirections = (1<<0); +const int InterpretCommand::ApplyTextDynamics = (1<<1); +const int InterpretCommand::ApplyHairpins = (1<<2); +const int InterpretCommand::StressBeats = (1<<3); +const int InterpretCommand::Articulate = (1<<4); +const int InterpretCommand::AllInterpretations= (1<<5) - 1; + +InterpretCommand::~InterpretCommand() +{ + for (IndicationMap::iterator i = m_indications.begin(); + i != m_indications.end(); ++i) { + delete i->second; + } +} + +void +InterpretCommand::modifySegment() +{ + // Of all the interpretations, Articulate is the only one that + // changes event times or durations. This means we must apply it + // last, as the selection cannot be used after it's been applied, + // because the events in the selection will have been recreated + // with their new timings. + + // The default velocity for new notes is 100, and the range is + // 0-127 (in practice this seems to be roughly logarithmic rather + // than linear, though perhaps that's an illusion). + + // We should only apply interpretation to those events actually + // selected, but when applying things like hairpins and text + // dynamics we need to take into account all dynamics that may + // cover our events even if they're not selected or are not within + // the time range of the selection at all. So first we'd better + // find all the likely indications, starting at (for the sake of + // argument) three bars before the start of the selection: + + Segment &segment(getSegment()); + + timeT t = m_selection->getStartTime(); + for (int i = 0; i < 3; ++i) + t = segment.getBarStartForTime(t); + + Segment::iterator itr = segment.findTime(t); + + while (itr != segment.end()) { + timeT eventTime = (*itr)->getAbsoluteTime(); + if (eventTime > m_selection->getEndTime()) + break; + if ((*itr)->isa(Indication::EventType)) { + m_indications[eventTime] = new Indication(**itr); + } + ++itr; + } + + //!!! need the option of ignoring current velocities or adjusting + //them: at the moment ApplyTextDynamics ignores them and the + //others adjust them + + if (m_interpretations & GuessDirections) + guessDirections(); + if (m_interpretations & ApplyTextDynamics) + applyTextDynamics(); + if (m_interpretations & ApplyHairpins) + applyHairpins(); + if (m_interpretations & StressBeats) + stressBeats(); + if (m_interpretations & Articulate) + articulate(); + + //!!! Finally, in future we should extend this to allow + // indications on one segment (e.g. top line of piano staff) to + // affect another (e.g. bottom line). All together now: "Even + // Rosegarden 2.1 could do that!" +} + +void +InterpretCommand::guessDirections() +{ + //... +} + +void +InterpretCommand::applyTextDynamics() +{ + // laborious + + Segment &segment(getSegment()); + int velocity = 100; + + timeT startTime = m_selection->getStartTime(); + timeT endTime = m_selection->getEndTime(); + + for (Segment::iterator i = segment.begin(); + segment.isBeforeEndMarker(i); ++i) { + + timeT t = (*i)->getAbsoluteTime(); + + if (t > endTime) + break; + + if (Text::isTextOfType(*i, Text::Dynamic)) { + + std::string text; + if ((*i)->get + (Text::TextPropertyName, text)) { + velocity = getVelocityForDynamic(text); + } + } + + if (t >= startTime && + (*i)->isa(Note::EventType) && m_selection->contains(*i)) { + (*i)->set + (VELOCITY, velocity); + } + } +} + +int +InterpretCommand::getVelocityForDynamic(std::string text) +{ + int velocity = 100; + + // should do case-insensitive matching with whitespace + // removed. can surely be cleverer about this too! + + if (text == "ppppp") + velocity = 10; + else if (text == "pppp") + velocity = 20; + else if (text == "ppp") + velocity = 30; + else if (text == "pp") + velocity = 40; + else if (text == "p") + velocity = 60; + else if (text == "mp") + velocity = 80; + else if (text == "mf") + velocity = 90; + else if (text == "f") + velocity = 105; + else if (text == "ff") + velocity = 110; + else if (text == "fff") + velocity = 115; + else if (text == "ffff") + velocity = 120; + else if (text == "fffff") + velocity = 125; + + NOTATION_DEBUG << "InterpretCommand::getVelocityForDynamic: unrecognised dynamic " << text << endl; + + return velocity; +} + +void +InterpretCommand::applyHairpins() +{ + Segment &segment(getSegment()); + int velocityToApply = -1; + + for (EventSelection::eventcontainer::iterator ecitr = + m_selection->getSegmentEvents().begin(); + ecitr != m_selection->getSegmentEvents().end(); ++ecitr) { + + Event *e = *ecitr; + if (Text::isTextOfType(e, Text::Dynamic)) { + velocityToApply = -1; + } + if (!e->isa(Note::EventType)) + continue; + bool crescendo = true; + + IndicationMap::iterator inditr = + findEnclosingIndication(e, Indication::Crescendo); + + // we can't be in both crescendo and decrescendo -- at least, + // not meaningfully + + if (inditr == m_indications.end()) { + inditr = findEnclosingIndication(e, Indication::Decrescendo); + if (inditr == m_indications.end()) { + if (velocityToApply > 0) { + e->set + (VELOCITY, velocityToApply); + } + continue; + } + crescendo = false; + } + + // The starting velocity for the indication is easy -- it's + // just the velocity of the last note at or before the + // indication begins that has a velocity + + timeT hairpinStartTime = inditr->first; + // ensure we scan all of the events at this time: + Segment::iterator itr(segment.findTime(hairpinStartTime + 1)); + while (itr == segment.end() || + (*itr)->getAbsoluteTime() > hairpinStartTime || + !(*itr)->isa(Note::EventType) || + !(*itr)->has(VELOCITY)) { + if (itr == segment.begin()) { + itr = segment.end(); + break; + } + --itr; + } + + long startingVelocity = 100; + if (itr != segment.end()) { + (*itr)->get + (VELOCITY, startingVelocity); + } + + // The ending velocity is harder. If there's a dynamic change + // directly after the hairpin, then we want to use that + // dynamic's velocity unless it opposes the hairpin's + // direction. If there isn't, or it does oppose the hairpin, + // we should probably make the degree of change caused by the + // hairpin depend on its total duration. + + long endingVelocity = startingVelocity; + timeT hairpinEndTime = inditr->first + + inditr->second->getIndicationDuration(); + itr = segment.findTime(hairpinEndTime); + while (itr != segment.end()) { + if (Text::isTextOfType(*itr, Text::Dynamic)) { + std::string text; + if ((*itr)->get + (Text::TextPropertyName, text)) { + endingVelocity = getVelocityForDynamic(text); + break; + } + } + if ((*itr)->getAbsoluteTime() > + (hairpinEndTime + Note(Note::Crotchet).getDuration())) + break; + ++itr; + } + + if (( crescendo && (endingVelocity < startingVelocity)) || + (!crescendo && (endingVelocity > startingVelocity))) { + // we've got it wrong; prefer following the hairpin to + // following whatever direction we got the dynamic from + endingVelocity = startingVelocity; + // and then fall into the next conditional to set up the + // velocities + } + + if (endingVelocity == startingVelocity) { + // calculate an ending velocity based on starting velocity + // and hairpin duration (okay, we'll leave that bit for later) + endingVelocity = startingVelocity * (crescendo ? 120 : 80) / 100; + } + + double proportion = + (double(e->getAbsoluteTime() - hairpinStartTime) / + double(hairpinEndTime - hairpinStartTime)); + long velocity = + int((endingVelocity - startingVelocity) * proportion + + startingVelocity); + + NOTATION_DEBUG << "InterpretCommand::applyHairpins: velocity of note at " << e->getAbsoluteTime() << " is " << velocity << " (" << proportion << " through hairpin from " << startingVelocity << " to " << endingVelocity << ")" << endl; + if (velocity < 10) + velocity = 10; + if (velocity > 127) + velocity = 127; + e->set + (VELOCITY, velocity); + velocityToApply = velocity; + } +} + +void +InterpretCommand::stressBeats() +{ + Composition *c = getSegment().getComposition(); + + for (EventSelection::eventcontainer::iterator itr = + m_selection->getSegmentEvents().begin(); + itr != m_selection->getSegmentEvents().end(); ++itr) { + + Event *e = *itr; + if (!e->isa(Note::EventType)) + continue; + + timeT t = e->getNotationAbsoluteTime(); + TimeSignature timeSig = c->getTimeSignatureAt(t); + timeT barStart = getSegment().getBarStartForTime(t); + int stress = timeSig.getEmphasisForTime(t - barStart); + + // stresses are from 0 to 4, so we add 12% to the velocity + // at the maximum stress, subtract 4% at the minimum + int velocityChange = stress * 4 - 4; + + // do this even if velocityChange == 0, in case the event + // has no velocity yet + long velocity = 100; + e->get + (VELOCITY, velocity); + velocity += velocity * velocityChange / 100; + if (velocity < 10) + velocity = 10; + if (velocity > 127) + velocity = 127; + e->set + (VELOCITY, velocity); + } +} + +void +InterpretCommand::articulate() +{ + // Basic articulations: + // + // -- Anything marked tenuto or within a slur gets 100% of its + // nominal duration (that's what we need the quantizer for, + // to get the display nominal duration), and its velocity + // is unchanged. + // + // -- Anything marked marcato gets 60%, or 70% if slurred (!), + // and gets an extra 15% of velocity. + // + // -- Anything marked staccato gets 55%, or 70% if slurred, + // and unchanged velocity. + // + // -- Anything marked staccatissimo gets 30%, or 50% if slurred (!), + // and loses 5% of velocity. + // + // -- Anything marked sforzando gains 35% of velocity. + // + // -- Anything marked with an accent gains 30% of velocity. + // + // -- Anything marked rinforzando gains 15% of velocity and has + // its full duration. Guess we really need to use some proper + // controllers here. + // + // -- Anything marked down-bow gains 5% of velocity, anything + // marked up-bow loses 5%. + // + // -- Anything unmarked and unslurred, or marked tenuto and + // slurred, gets 90% of duration. + + std::set + toErase; + std::set + toInsert; + Segment &segment(getSegment()); + + for (EventSelection::eventcontainer::iterator ecitr = + m_selection->getSegmentEvents().begin(); + ecitr != m_selection->getSegmentEvents().end(); ++ecitr) { + + Event *e = *ecitr; + if (!e->isa(Note::EventType)) + continue; + Segment::iterator itr = segment.findSingle(e); + Chord chord(segment, itr, m_quantizer); + + // the things that affect duration + bool staccato = false; + bool staccatissimo = false; + bool marcato = false; + bool tenuto = false; + bool rinforzando = false; + bool slurred = false; + + int velocityChange = 0; + + std::vector marks(chord.getMarksForChord()); + + for (std::vector::iterator i = marks.begin(); + i != marks.end(); ++i) { + + if (*i == Marks::Accent) { + velocityChange += 30; + } else if (*i == Marks::Tenuto) { + tenuto = true; + } else if (*i == Marks::Staccato) { + staccato = true; + } else if (*i == Marks::Staccatissimo) { + staccatissimo = true; + velocityChange -= 5; + } else if (*i == Marks::Marcato) { + marcato = true; + velocityChange += 15; + } else if (*i == Marks::Sforzando) { + velocityChange += 35; + } else if (*i == Marks::Rinforzando) { + rinforzando = true; + velocityChange += 15; + } else if (*i == Marks::DownBow) { + velocityChange += 5; + } else if (*i == Marks::UpBow) { + velocityChange -= 5; + } + } + + IndicationMap::iterator inditr = + findEnclosingIndication(e, Indication::Slur); + + if (inditr != m_indications.end()) + slurred = true; + if (slurred) { + // last note in a slur should be treated as if unslurred + timeT slurEnd = + inditr->first + inditr->second->getIndicationDuration(); + if (slurEnd == e->getNotationAbsoluteTime() + e->getNotationDuration() || + slurEnd == e->getAbsoluteTime() + e->getDuration()) { + slurred = false; + } + /*!!! + Segment::iterator slurEndItr = segment.findTime(slurEnd); + if (slurEndItr != segment.end() && + (*slurEndItr)->getNotationAbsoluteTime() <= + e->getNotationAbsoluteTime()) { + slurred = false; + } + */ + } + + int durationChange = 0; + + if (slurred) { + //!!! doesn't seem to be picking up slurs correctly + if (tenuto) + durationChange = -10; + else if (marcato || staccato) + durationChange = -30; + else if (staccatissimo) + durationChange = -50; + else + durationChange = 0; + } else { + if (tenuto) + durationChange = 0; + else if (marcato) + durationChange = -40; + else if (staccato) + durationChange = -45; + else if (staccatissimo) + durationChange = -70; + else if (rinforzando) + durationChange = 0; + else + durationChange = -10; + } + + NOTATION_DEBUG << "InterpretCommand::modifySegment: chord has " << chord.size() << " notes in it" << endl; + + for (Chord::iterator ci = chord.begin(); + ci != chord.end(); ++ci) { + + e = **ci; + + NOTATION_DEBUG << "InterpretCommand::modifySegment: For note at " << e->getAbsoluteTime() << ", velocityChange is " << velocityChange << " and durationChange is " << durationChange << endl; + + // do this even if velocityChange == 0, in case the event + // has no velocity yet + long velocity = 100; + e->get + (VELOCITY, velocity); + velocity += velocity * velocityChange / 100; + if (velocity < 10) + velocity = 10; + if (velocity > 127) + velocity = 127; + e->set + (VELOCITY, velocity); + + timeT duration = e->getNotationDuration(); + + // don't mess with the duration of a tied note + bool tiedForward = false; + if (e->get + (TIED_FORWARD, tiedForward) && tiedForward) { + durationChange = 0; + } + + timeT newDuration = duration + duration * durationChange / 100; + + // this comparison instead of "durationChange != 0" + // because we want to permit the possibility of resetting + // the performance duration of a note (that's perhaps been + // articulated wrongly) based on the notation duration: + + if (e->getDuration() != newDuration) { + + if (toErase.find(e) == toErase.end()) { + + //!!! deal with tuplets + + Event *newEvent = new Event(*e, + e->getAbsoluteTime(), + newDuration, + e->getSubOrdering(), + e->getNotationAbsoluteTime(), + duration); + toInsert.insert(newEvent); + toErase.insert(e); + } + } + } + + // what we want to do here is jump our iterator to the final + // element in the chord -- but that doesn't work because we're + // iterating through the selection, not the segment. So for + // now we just accept the fact that notes in chords might be + // processed multiple times (slow) and added into the toErase + // set more than once (hence the nasty tests in the loop just + // after the close of this loop). + } + + for (std::set + ::iterator j = toErase.begin(); j != toErase.end(); ++j) { + Segment::iterator jtr(segment.findSingle(*j)); + if (jtr != segment.end()) + segment.erase(jtr); + } + + for (std::set + ::iterator j = toInsert.begin(); j != toInsert.end(); ++j) { + segment.insert(*j); + } +} + +InterpretCommand::IndicationMap::iterator + +InterpretCommand::findEnclosingIndication(Event *e, + std::string type) +{ + // a bit slow, but let's wait and see whether it's a bottleneck + // before we worry about that + + timeT t = e->getAbsoluteTime(); + IndicationMap::iterator itr = m_indications.lower_bound(t); + + while (1) { + if (itr != m_indications.end()) { + + if (itr->second->getIndicationType() == type && + itr->first <= t && + itr->first + itr->second->getIndicationDuration() > t) { + return itr; + } + } + if (itr == m_indications.begin()) + break; + --itr; + } + + return m_indications.end(); +} + +} diff --git a/src/commands/notation/InterpretCommand.h b/src/commands/notation/InterpretCommand.h new file mode 100644 index 0000000..e1ace8f --- /dev/null +++ b/src/commands/notation/InterpretCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUINTERPRETCOMMAND_H_ +#define _RG_ADJUSTMENUINTERPRETCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Quantizer; +class Indication; +class EventSelection; +class Event; + + +class InterpretCommand : public BasicSelectionCommand +{ +public: + // bit masks: pass an OR of these to the constructor + static const int NoInterpretation; + static const int GuessDirections; // allegro, rit, pause &c: kinda bogus + static const int ApplyTextDynamics; // mp, ff + static const int ApplyHairpins; // self-evident + static const int StressBeats; // stress bar/beat boundaries + static const int Articulate; // slurs, marks, legato etc + static const int AllInterpretations; // all of the above + + InterpretCommand(EventSelection &selection, + const Quantizer *quantizer, + int interpretations) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection), + m_quantizer(quantizer), + m_interpretations(interpretations) { } + + virtual ~InterpretCommand(); + + static QString getGlobalName() { return i18n("&Interpret..."); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + const Quantizer *m_quantizer; + int m_interpretations; + + typedef std::map IndicationMap; + IndicationMap m_indications; + + void guessDirections(); + void applyTextDynamics(); + void applyHairpins(); + void stressBeats(); + void articulate(); // must be applied last + + // test if the event is within an indication of the given type, return + // an iterator pointing to that indication if so + IndicationMap::iterator findEnclosingIndication(Event *, + std::string type); + int getVelocityForDynamic(std::string dynamic); +}; + + +} + +#endif diff --git a/src/commands/notation/KeyInsertionCommand.cpp b/src/commands/notation/KeyInsertionCommand.cpp new file mode 100644 index 0000000..39b87e2 --- /dev/null +++ b/src/commands/notation/KeyInsertionCommand.cpp @@ -0,0 +1,264 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "KeyInsertionCommand.h" + +#include "misc/Debug.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Studio.h" +#include "document/BasicCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + + +KeyInsertionCommand::KeyInsertionCommand(Segment &segment, timeT time, + Key key, + bool convert, + bool transpose, + bool transposeKey, + bool ignorePercussion) : + BasicCommand(getGlobalName(&key), segment, time, segment.getEndTime()), + m_key(key), + m_lastInsertedEvent(0), + m_convert(convert), + m_transpose(transpose), + m_transposeKey(transposeKey), + m_ignorePercussion(ignorePercussion) + +{ + // nothing +} + +KeyInsertionCommand::~KeyInsertionCommand() +{ + // nothing +} + +void +KeyInsertionCommand::modifySegment() +{ + SegmentNotationHelper helper(getSegment()); + Key oldKey; + + if (m_convert || m_transpose) { + oldKey = getSegment().getKeyAtTime(getStartTime()); + } + + Segment::iterator i = getSegment().findTime(getStartTime()); + while (getSegment().isBeforeEndMarker(i)) { + if ((*i)->getAbsoluteTime() > getStartTime()) { + break; + } + if ((*i)->isa(Key::EventType)) { + getSegment().erase(i); + break; + } + ++i; + } + + // transpose if desired, according to new dialog option + if (m_transposeKey) { + // we don't really care about major/minor for this, so pass it through + // from the original key + bool keyIsMinor = m_key.isMinor(); + + // get whether the original key is flat or sharp, so we know what to + // prefer for the new key + bool keyIsSharp = m_key.isSharp(); + + // get the tonic pitch of the user-specified key, reported as a 0-11 int, then + // add an extra octave to it to avoid winding up with negative numbers + // (the octave will be stripped back off) + int specifiedKeyTonic = m_key.getTonicPitch() + 12; + + // get the transpose factor for the segment we're working on + int segTranspose = getSegment().getTranspose(); + + // subtract the transpose factor from the tonic pitch of the + // user-specified key, because we want to move in the opposite + // direction for notation (eg. notation is in C major concert, at Bb + // transposition, we have -2 from the segment, and want to go +2 for + // the key, from tonic pitch 0 (C) to tonic pitch 2 (D) for the key as + // written for a Bb instrument + // + // sanity check: 0 == C; 0 + 12 == 12; (12 - -2) % 12 == 2; 2 == D + int transposedKeyTonic = (specifiedKeyTonic - segTranspose) % 12; + + // create a new key with the new tonic pitch, and major/minor from the + // original key + std::string newKeyName = ""; + + switch (transposedKeyTonic) { + // 0 C | 1 C# | 2 D | 3 D# | 4 E | 5 F | 6 F# | 7 G | 8 G# | 9 A | 10 A# | 11 B + case 0 : // C + newKeyName = "C"; + break; + case 2 : // D + newKeyName = "D"; + break; + case 4 : // E + newKeyName = "E"; + break; + case 5 : // F + newKeyName = "F"; + break; + case 7 : // G + newKeyName = "G"; + break; + case 9 : // A + newKeyName = "A"; + break; + case 11: // B + newKeyName = "B"; + break; + // the glorious, glorious black keys need special treatment + // again, so we pick flat or sharp spellings based on the + // condition of the original, user-specified key we're + // transposing + case 1 : // C#/Db + newKeyName = (keyIsSharp ? "C#" : "Db"); + break; + case 3 : // D#/Eb + newKeyName = (keyIsSharp ? "D#" : "Eb"); + break; + case 6 : // F#/Gb + newKeyName = (keyIsSharp ? "F#" : "Gb"); + break; + case 8 : // G#/Ab + newKeyName = (keyIsSharp ? "G#" : "Ab"); + break; + case 10: // A#/Bb + newKeyName = (keyIsSharp ? "A#" : "Bb"); + break; + default: + // if this fails, we won't have a valid key name, and + // there will be some crashing exception I don't know how + // to intercept and avoid, so I'm doing this lame failsafe + // instead, which should never, ever actually run under + // any conceivable cirumstance anyway + RG_DEBUG << "KeyInsertionCommand: by the pricking of my thumbs, something wicked this way comes. :(" + << endl; + return ; + } + + newKeyName += (keyIsMinor ? " minor" : " major"); + + //for f in C# D# E# F# G# A# B# Cb Db Eb Fb Gb Ab Bb;do grep "$f + //major" NotationTypes.C > /dev/null||echo "invalid key: $f + //major";grep "$f minor" NotationTypes.C > /dev/null||echo "invalid + //key: $f minor";done|sort + //invalid key: A# major + //invalid key: B# major + //invalid key: B# minor + //invalid key: Cb minor + //invalid key: Db minor + //invalid key: D# major + //invalid key: E# major + //invalid key: E# minor + //invalid key: Fb major + //invalid key: Fb minor + //invalid key: Gb minor + //invalid key: G# major + + // some kludgery to avoid creating invalid key names with some if/then + // swapping to manually respell things generated incorrectly by the + // above, rather than adding all kinds of nonsense to avoid this + // necessity + if (newKeyName == "A# major") + newKeyName = "Bb major"; + else if (newKeyName == "B# major") + newKeyName = "C major"; + else if (newKeyName == "Cb minor") + newKeyName = "B minor"; + else if (newKeyName == "Db minor") + newKeyName = "C# minor"; + else if (newKeyName == "D# major") + newKeyName = "Eb major"; + else if (newKeyName == "E# major") + newKeyName = "F major"; + else if (newKeyName == "E# minor") + newKeyName = "F minor"; + else if (newKeyName == "Fb major") + newKeyName = "E major"; + else if (newKeyName == "Fb minor") + newKeyName = "E minor"; + else if (newKeyName == "Gb minor") + newKeyName = "F# minor"; + else if (newKeyName == "G# major") + newKeyName = "Ab major"; + + // create a new key with the newly derived name, and swap it for the + // user-specified version + Key k(newKeyName); + RG_DEBUG << "KeyInsertCommand: inserting transposed key" << endl + << " user key was: " << m_key.getName() << endl + << " tranposed key is: " << k.getName() << endl; + m_key = k; + } // if (m_transposeKey) + + i = helper.insertKey(getStartTime(), m_key); + + if (i != helper.segment().end()) { + + m_lastInsertedEvent = *i; + if (!m_convert && !m_transpose) + return ; + + while (++i != helper.segment().end()) { + + //!!! what if we get two keys at the same time...? + if ((*i)->isa(Key::EventType)) + break; + + if ((*i)->isa(Note::EventType) && + (*i)->has(PITCH)) { + + long pitch = (*i)->get + (PITCH); + + if (m_convert) { + (*i)->set + (PITCH, m_key.convertFrom(pitch, oldKey)); + } else { + (*i)->set + (PITCH, m_key.transposeFrom(pitch, oldKey)); + } + + (*i)->unset(ACCIDENTAL); + } + } + } +} + +} diff --git a/src/commands/notation/KeyInsertionCommand.h b/src/commands/notation/KeyInsertionCommand.h new file mode 100644 index 0000000..a9caa6a --- /dev/null +++ b/src/commands/notation/KeyInsertionCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_KEYINSERTIONCOMMAND_H_ +#define _RG_KEYINSERTIONCOMMAND_H_ + +#include "base/NotationTypes.h" +#include "document/BasicCommand.h" +#include +#include "base/Event.h" +#include +#include "misc/Strings.h" + + +class Add; + + +namespace Rosegarden +{ + +class Segment; +class Event; + +//!!! Note, the shouldIgnorePercussion parameter probably shouldn't have been +// added to the individual KeyInsertionCommand in the first place, but I haven't +// made up my mind yet for sure, and I already changed all the calls to this +// constructor, so I'm leaving this in until after the new code is field +// tested, and I can determine it really never will be wanted (DMM) +class KeyInsertionCommand : public BasicCommand +{ +public: + KeyInsertionCommand(Segment &segment, + timeT time, + Key key, + bool shouldConvert, + bool shouldTranspose, + bool shouldTransposeKey, + bool shouldIgnorePercussion); + virtual ~KeyInsertionCommand(); + + static QString getGlobalName(Key *key = 0) { + if (key) { + return i18n("Change to &Key %1...").arg(strtoqstr(key->getName())); + } else { + return i18n("Add &Key Change..."); + } + } + + Event *getLastInsertedEvent() { return m_lastInsertedEvent; } + +protected: + virtual void modifySegment(); + + Key m_key; + Event *m_lastInsertedEvent; + bool m_convert; + bool m_transpose; + bool m_transposeKey; + bool m_ignorePercussion; +}; + +/* + * Inserts a key change into multiple segments at the same time, taking + * individual segment transpose into account (fixes #1520716) if desired. + */ + +} + +#endif diff --git a/src/commands/notation/MakeAccidentalsCautionaryCommand.cpp b/src/commands/notation/MakeAccidentalsCautionaryCommand.cpp new file mode 100644 index 0000000..5716a8b --- /dev/null +++ b/src/commands/notation/MakeAccidentalsCautionaryCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MakeAccidentalsCautionaryCommand.h" + +#include +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "gui/editors/notation/NotationProperties.h" +#include + + +namespace Rosegarden +{ + +QString +MakeAccidentalsCautionaryCommand::getGlobalName(bool cautionary) +{ + if (cautionary) + return i18n("Use &Cautionary Accidentals"); + else + return i18n("Cancel C&autionary Accidentals"); +} + +void +MakeAccidentalsCautionaryCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + if (m_cautionary) { + (*i)->set + (NotationProperties::USE_CAUTIONARY_ACCIDENTAL, + true); + } else { + (*i)->unset(NotationProperties::USE_CAUTIONARY_ACCIDENTAL); + } + } + } +} + +} diff --git a/src/commands/notation/MakeAccidentalsCautionaryCommand.h b/src/commands/notation/MakeAccidentalsCautionaryCommand.h new file mode 100644 index 0000000..2745dc7 --- /dev/null +++ b/src/commands/notation/MakeAccidentalsCautionaryCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MAKEACCIDENTALSCAUTIONARYCOMMAND_H_ +#define _RG_MAKEACCIDENTALSCAUTIONARYCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class MakeAccidentalsCautionaryCommand : public BasicSelectionCommand +{ +public: + MakeAccidentalsCautionaryCommand(bool cautionary, + EventSelection &selection) : + BasicSelectionCommand(getGlobalName(cautionary), selection, true), + m_selection(&selection), + m_cautionary(cautionary) { } + + static QString getGlobalName(bool cautionary); + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + bool m_cautionary; +}; + + +} + +#endif diff --git a/src/commands/notation/MakeChordCommand.cpp b/src/commands/notation/MakeChordCommand.cpp new file mode 100644 index 0000000..668e627 --- /dev/null +++ b/src/commands/notation/MakeChordCommand.cpp @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MakeChordCommand.h" + +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +void +MakeChordCommand::modifySegment() +{ + // find all the notes in the selection, and bring them back to align + // with the start of the selection, giving them the same duration as + // the longest note among them + + std::vector toErase; + std::vector toInsert; + Segment &segment(m_selection->getSegment()); + + for (EventSelection::eventcontainer::iterator i = + m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + toErase.push_back(*i); + toInsert.push_back(new Event(**i, m_selection->getStartTime())); + } + } + + for (unsigned int j = 0; j < toErase.size(); ++j) { + Segment::iterator jtr(segment.findSingle(toErase[j])); + if (jtr != segment.end()) + segment.erase(jtr); + } + + for (unsigned int j = 0; j < toInsert.size(); ++j) { + segment.insert(toInsert[j]); + } + + segment.normalizeRests(getStartTime(), getEndTime()); + + //!!! should select all notes in chord now +} + +} diff --git a/src/commands/notation/MakeChordCommand.h b/src/commands/notation/MakeChordCommand.h new file mode 100644 index 0000000..9c85ea2 --- /dev/null +++ b/src/commands/notation/MakeChordCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUMAKECHORDCOMMAND_H_ +#define _RG_ADJUSTMENUMAKECHORDCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + +class Make; + + +namespace Rosegarden +{ + +class EventSelection; + + +class MakeChordCommand : public BasicSelectionCommand +{ +public: + MakeChordCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("Make &Chord"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + +// Transforms menu commands + + + +} + +#endif diff --git a/src/commands/notation/MakeNotesViableCommand.cpp b/src/commands/notation/MakeNotesViableCommand.cpp new file mode 100644 index 0000000..26cff3c --- /dev/null +++ b/src/commands/notation/MakeNotesViableCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MakeNotesViableCommand.h" + +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +void +MakeNotesViableCommand::modifySegment() +{ + Segment &segment(getSegment()); + SegmentNotationHelper helper(segment); + + if (m_selection) { + EventSelection::RangeTimeList ranges(m_selection->getRangeTimes()); + for (EventSelection::RangeTimeList::iterator i = ranges.begin(); + i != ranges.end(); ++i) { + helper.makeNotesViable(i->first, i->second, true); + segment.normalizeRests(i->first, i->second); + } + } else { + helper.makeNotesViable(getStartTime(), getEndTime(), true); + segment.normalizeRests(getStartTime(), getEndTime()); + } +} + +} diff --git a/src/commands/notation/MakeNotesViableCommand.h b/src/commands/notation/MakeNotesViableCommand.h new file mode 100644 index 0000000..f84f76d --- /dev/null +++ b/src/commands/notation/MakeNotesViableCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUMAKENOTESVIABLECOMMAND_H_ +#define _RG_ADJUSTMENUMAKENOTESVIABLECOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class Segment; +class EventSelection; + + +/// MakeNotesViable works on a selection or entire segment +class MakeNotesViableCommand : public BasicSelectionCommand +{ +public: + MakeNotesViableCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + MakeNotesViableCommand(Segment &segment) : + BasicSelectionCommand(getGlobalName(), segment, true), + m_selection(0) { } + + static QString getGlobalName() { return i18n("Tie Notes at &Barlines"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + +} + +#endif diff --git a/src/commands/notation/MakeRegionViableCommand.cpp b/src/commands/notation/MakeRegionViableCommand.cpp new file mode 100644 index 0000000..597b232 --- /dev/null +++ b/src/commands/notation/MakeRegionViableCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MakeRegionViableCommand.h" + +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "document/BasicCommand.h" +#include + + +namespace Rosegarden +{ + +void +MakeRegionViableCommand::modifySegment() +{ + Segment &segment(getSegment()); + if (segment.getType() != Segment::Internal) return; + SegmentNotationHelper helper(segment); + + helper.makeNotesViable(getStartTime(), getEndTime(), true); + segment.normalizeRests(getStartTime(), getEndTime()); +} + +} diff --git a/src/commands/notation/MakeRegionViableCommand.h b/src/commands/notation/MakeRegionViableCommand.h new file mode 100644 index 0000000..64762cb --- /dev/null +++ b/src/commands/notation/MakeRegionViableCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUMAKEREGIONVIABLECOMMAND_H_ +#define _RG_ADJUSTMENUMAKEREGIONVIABLECOMMAND_H_ + +#include "document/BasicCommand.h" +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Segment; + + +/// MakeRegionViable works on part of a segment +class MakeRegionViableCommand : public BasicCommand +{ +public: + MakeRegionViableCommand(Segment &segment, + timeT startTime, + timeT endTime) : + BasicCommand(getGlobalName(), segment, startTime, endTime) { } + + static QString getGlobalName() { return i18n("Tie Notes at &Barlines"); } + +protected: + virtual void modifySegment(); +}; + + + +} + +#endif diff --git a/src/commands/notation/MultiKeyInsertionCommand.cpp b/src/commands/notation/MultiKeyInsertionCommand.cpp new file mode 100644 index 0000000..77bf625 --- /dev/null +++ b/src/commands/notation/MultiKeyInsertionCommand.cpp @@ -0,0 +1,80 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MultiKeyInsertionCommand.h" + +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "KeyInsertionCommand.h" +#include +#include "document/RosegardenGUIDoc.h" +#include "base/Studio.h" +#include "misc/Debug.h" + + +namespace Rosegarden +{ + +MultiKeyInsertionCommand::MultiKeyInsertionCommand(RosegardenGUIDoc* doc, + timeT time, + Key key, + bool convert, + bool transpose, + bool transposeKey, + bool ignorePercussion) : + KMacroCommand(getGlobalName(&key)) +{ + Composition &c = doc->getComposition(); + Studio &s = doc->getStudio(); + + for (Composition::iterator i = c.begin(); i != c.end(); ++i) { + Segment *segment = *i; + + Instrument *instrument = s.getInstrumentFor(segment); + // if (instrument) { + // RG_DEBUG << endl << + // "PERC DEBUG: instrument->isPercussion " << instrument->isPercussion() << + // " ignorePercussion " << ignorePercussion << endl << endl << endl; + //} + if (instrument) if (instrument->isPercussion() && ignorePercussion) continue; + + // no harm in using getEndTime instead of getEndMarkerTime here: + if (segment->getStartTime() <= time && segment->getEndTime() > time) { + addCommand(new KeyInsertionCommand(*segment, time, key, convert, transpose, transposeKey, + ignorePercussion)); + } else if (segment->getStartTime() > time) { + addCommand(new KeyInsertionCommand(*segment, segment->getStartTime(), + key, convert, transpose, transposeKey, ignorePercussion)); + } + } +} + +MultiKeyInsertionCommand::~MultiKeyInsertionCommand() +{ + // nothing +} + +} diff --git a/src/commands/notation/MultiKeyInsertionCommand.h b/src/commands/notation/MultiKeyInsertionCommand.h new file mode 100644 index 0000000..b8ae152 --- /dev/null +++ b/src/commands/notation/MultiKeyInsertionCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MULTIKEYINSERTIONCOMMAND_H_ +#define _RG_MULTIKEYINSERTIONCOMMAND_H_ + +#include "base/NotationTypes.h" +#include +#include "base/Event.h" +#include +#include "misc/Strings.h" +#include +#include "document/RosegardenGUIDoc.h" + + +class Add; + + +namespace Rosegarden +{ + +//class Composition; +class RosegardenGUIDoc; + + +class MultiKeyInsertionCommand : public KMacroCommand +{ +public: + + MultiKeyInsertionCommand(RosegardenGUIDoc* doc, + timeT time, + Key key, + bool shouldConvert, + bool shouldTranspose, + bool shouldTransposeKey, + bool shouldIgnorePercussion); + virtual ~MultiKeyInsertionCommand(); + + static QString getGlobalName(Key *key = 0) { + if (key) { + return i18n("Change all to &Key %1...").arg(strtoqstr(key->getName())); + } else { + return i18n("Add &Key Change..."); + } + } +}; + + +} + +#endif diff --git a/src/commands/notation/NormalizeRestsCommand.cpp b/src/commands/notation/NormalizeRestsCommand.cpp new file mode 100644 index 0000000..9d96586 --- /dev/null +++ b/src/commands/notation/NormalizeRestsCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "NormalizeRestsCommand.h" + +#include "base/Segment.h" +#include "base/Selection.h" +#include "document/BasicCommand.h" +#include + + +namespace Rosegarden +{ + +NormalizeRestsCommand::NormalizeRestsCommand +(EventSelection &selection) : + BasicCommand(getGlobalName(), + selection.getSegment(), + selection.getStartTime(), + selection.getEndTime()) +{ + // nothing else +} + +void NormalizeRestsCommand::modifySegment() +{ + getSegment().normalizeRests(getStartTime(), getEndTime()); +} + +} diff --git a/src/commands/notation/NormalizeRestsCommand.h b/src/commands/notation/NormalizeRestsCommand.h new file mode 100644 index 0000000..db57920 --- /dev/null +++ b/src/commands/notation/NormalizeRestsCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUNORMALIZERESTSCOMMAND_H_ +#define _RG_ADJUSTMENUNORMALIZERESTSCOMMAND_H_ + +#include "document/BasicCommand.h" +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Segment; +class EventSelection; + + +class NormalizeRestsCommand : public BasicCommand +{ +public: + NormalizeRestsCommand(Segment &s, + timeT startTime, + timeT endTime) : + BasicCommand(getGlobalName(), s, startTime, endTime) { } + + NormalizeRestsCommand(EventSelection &selection); + + static QString getGlobalName() { return i18n("&Normalize Rests"); } + +protected: + virtual void modifySegment(); +}; + + + +} + +#endif diff --git a/src/commands/notation/NoteInsertionCommand.cpp b/src/commands/notation/NoteInsertionCommand.cpp new file mode 100644 index 0000000..cadae55 --- /dev/null +++ b/src/commands/notation/NoteInsertionCommand.cpp @@ -0,0 +1,296 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "NoteInsertionCommand.h" + +#include +#include +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentMatrixHelper.h" +#include "base/SegmentNotationHelper.h" +#include "document/BasicCommand.h" +#include "gui/editors/notation/NotationProperties.h" +#include "gui/editors/notation/NoteStyleFactory.h" +#include "base/BaseProperties.h" + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +NoteInsertionCommand::NoteInsertionCommand(Segment &segment, timeT time, + timeT endTime, Note note, int pitch, + Accidental accidental, + AutoBeamMode autoBeam, + MatrixMode matrixType, + GraceMode grace, + float targetSubordering, + NoteStyleName noteStyle) : + BasicCommand(i18n("Insert Note"), segment, + getModificationStartTime(segment, time), + (autoBeam ? segment.getBarEndForTime(endTime) : endTime)), + m_insertionTime(time), + m_note(note), + m_pitch(pitch), + m_accidental(accidental), + m_autoBeam(autoBeam == AutoBeamOn), + m_matrixType(matrixType == MatrixModeOn), + m_grace(grace), + m_targetSubordering(targetSubordering), + m_noteStyle(noteStyle), + m_lastInsertedEvent(0) +{ + // nothing +} + +NoteInsertionCommand::~NoteInsertionCommand() +{ + // nothing +} + +timeT +NoteInsertionCommand::getModificationStartTime(Segment &segment, + timeT time) +{ + // We may be splitting a rest to insert this note, so we'll have + // to record the change from the start of that rest rather than + // just the start of the note + + timeT barTime = segment.getBarStartForTime(time); + Segment::iterator i = segment.findNearestTime(time); + + if (i != segment.end() && + (*i)->getAbsoluteTime() < time && + (*i)->getAbsoluteTime() + (*i)->getDuration() > time && + (*i)->isa(Note::EventRestType)) { + return std::min(barTime, (*i)->getAbsoluteTime()); + } + + return barTime; +} + +void +NoteInsertionCommand::modifySegment() +{ + Segment &segment(getSegment()); + SegmentNotationHelper helper(segment); + Segment::iterator i, j; + + // insert via a model event, so as to apply the note style + + // subordering is always negative for these insertions; round it down + int actualSubordering = lrintf(floorf(m_targetSubordering + 0.01)); + if ((m_grace != GraceModeOff) && actualSubordering >= 0) { + actualSubordering = -1; + } + + // this is true if the subordering is "more or less" an integer, + // as opposed to something like -0.5 + bool suborderingExact = (actualSubordering != + (lrintf(floorf(m_targetSubordering - 0.01)))); + + std::cerr << "actualSubordering = " << actualSubordering + << " suborderingExact = " << suborderingExact << std::endl; + + Event *e; + + if (m_grace == GraceModeOff) { + + e = new Event + (Note::EventType, + m_insertionTime, + m_note.getDuration(), + 0, + m_insertionTime, + m_note.getDuration()); + + } else { + + e = new Event + (Note::EventType, + m_insertionTime, + 0, + actualSubordering == 0 ? -1 : actualSubordering, + m_insertionTime, + m_note.getDuration()); + } + + e->set(PITCH, m_pitch); + e->set(VELOCITY, 100); + + if (m_accidental != Accidentals::NoAccidental) { + e->set(ACCIDENTAL, m_accidental); + } + + if (m_noteStyle != NoteStyleFactory::DefaultStyle) { + e->set(NotationProperties::NOTE_STYLE, m_noteStyle); + } + + if (m_grace != GraceModeOff) { + + if (!suborderingExact) { + + // Adjust suborderings of any existing grace notes, if there + // is at least one with the same subordering and + // suborderingExact is not set + + segment.getTimeSlice(m_insertionTime, i, j); + bool collision = false; + for (Segment::iterator k = i; k != j; ++k) { + if ((*k)->getSubOrdering() == actualSubordering) { + collision = true; + break; + } + } + + if (collision) { + std::vector toInsert, toErase; + for (Segment::iterator k = i; k != j; ++k) { + if ((*k)->isa(Note::EventType) && + (*k)->getSubOrdering() <= actualSubordering) { + toErase.push_back(*k); + toInsert.push_back + (new Event(**k, + (*k)->getAbsoluteTime(), + (*k)->getDuration(), + (*k)->getSubOrdering() - 1, + (*k)->getNotationAbsoluteTime(), + (*k)->getNotationDuration())); + } + } + for (std::vector::iterator k = toErase.begin(); + k != toErase.end(); ++k) segment.eraseSingle(*k); + for (std::vector::iterator k = toInsert.begin(); + k != toInsert.end(); ++k) segment.insert(*k); + } + } + + e->set(IS_GRACE_NOTE, true); + i = segment.insert(e); + + Segment::iterator k; + segment.getTimeSlice(m_insertionTime, j, k); + Segment::iterator bg0 = segment.end(), bg1 = segment.end(); + while (j != k) { + std::cerr << "testing for truthiness: time " << (*j)->getAbsoluteTime() << ", subordering " << (*j)->getSubOrdering() << std::endl; + if ((*j)->isa(Note::EventType) && + (*j)->getSubOrdering() < 0 && + (*j)->has(IS_GRACE_NOTE) && + (*j)->get(IS_GRACE_NOTE)) { + std::cerr << "truthiful" << std::endl; + if (bg0 == segment.end()) bg0 = j; + bg1 = j; + } + ++j; + } + + if (bg0 != segment.end() && bg1 != bg0) { + if (bg1 != segment.end()) ++bg1; + int count = 0; + int pso = 0; + for (Segment::iterator i = bg0; i != bg1; ++i) { + (*i)->unset(BEAMED_GROUP_ID); + (*i)->unset(BEAMED_GROUP_TYPE); + (*i)->unset(BEAMED_GROUP_TUPLED_COUNT); + (*i)->unset(BEAMED_GROUP_UNTUPLED_COUNT); + if ((*i)->getSubOrdering() != pso) { + ++count; + pso = (*i)->getSubOrdering(); + } + } + if (m_grace == GraceAndTripletModesOn) { + helper.makeBeamedGroupExact(bg0, bg1, GROUP_TYPE_TUPLED); + if (count > 1) { + for (Segment::iterator i = bg0; i != bg1; ++i) { + (*i)->set(BEAMED_GROUP_TUPLED_COUNT, count-1); + (*i)->set(BEAMED_GROUP_UNTUPLED_COUNT, count); + } + } + } else { + helper.makeBeamedGroupExact(bg0, bg1, GROUP_TYPE_BEAMED); + } + } + + } else { + + // If we're attempting to insert at the same time and pitch as + // an existing note, then we remove the existing note first + // (so as to change its duration, if the durations differ) + segment.getTimeSlice(m_insertionTime, i, j); + while (i != j) { + if ((*i)->isa(Note::EventType)) { + long pitch; + if ((*i)->get(PITCH, pitch) && pitch == m_pitch) { + helper.deleteNote(*i); + break; + } + } + ++i; + } + + if (m_matrixType) { + i = SegmentMatrixHelper(segment).insertNote(e); + } else { + i = helper.insertNote(e); + // e is just a model for SegmentNotationHelper::insertNote + delete e; + } + } + + if (i != segment.end()) m_lastInsertedEvent = *i; + + if (m_autoBeam) { + + // We auto-beam the bar if it contains no beamed groups + // after the insertion point and if it contains no tupled + // groups at all. + + timeT barStartTime = segment.getBarStartForTime(m_insertionTime); + timeT barEndTime = segment.getBarEndForTime(m_insertionTime); + + for (Segment::iterator j = i; + j != segment.end() && (*j)->getAbsoluteTime() < barEndTime; + ++j) { + if ((*j)->has(BEAMED_GROUP_ID)) + return ; + } + + for (Segment::iterator j = i; + j != segment.end() && (*j)->getAbsoluteTime() >= barStartTime; + --j) { + if ((*j)->has(BEAMED_GROUP_TUPLET_BASE)) + return ; + if (j == segment.begin()) + break; + } + + helper.autoBeam(m_insertionTime, m_insertionTime, GROUP_TYPE_BEAMED); + } +} + +} diff --git a/src/commands/notation/NoteInsertionCommand.h b/src/commands/notation/NoteInsertionCommand.h new file mode 100644 index 0000000..9424f1c --- /dev/null +++ b/src/commands/notation/NoteInsertionCommand.h @@ -0,0 +1,98 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTEINSERTIONCOMMAND_H_ +#define _RG_NOTEINSERTIONCOMMAND_H_ + +#include "base/NotationTypes.h" +#include "document/BasicCommand.h" +#include "base/Event.h" +#include "gui/editors/notation/NoteStyle.h" + + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class NoteInsertionCommand : public BasicCommand +{ +public: + enum AutoBeamMode { + AutoBeamOff, + AutoBeamOn + }; + + enum MatrixMode { + MatrixModeOff, + MatrixModeOn + }; + + enum GraceMode { + GraceModeOff, + GraceModeOn, + GraceAndTripletModesOn + }; + + NoteInsertionCommand(Segment &segment, + timeT time, + timeT endTime, + Note note, + int pitch, + Accidental accidental, + AutoBeamMode autoBeam, + MatrixMode matrixType, + GraceMode grace, + float targetSubordering, + NoteStyleName noteStyle); + virtual ~NoteInsertionCommand(); + + Event *getLastInsertedEvent() { return m_lastInsertedEvent; } + +protected: + virtual void modifySegment(); + + timeT getModificationStartTime(Segment &, timeT); + + timeT m_insertionTime; + Note m_note; + int m_pitch; + Accidental m_accidental; + bool m_autoBeam; + bool m_matrixType; + GraceMode m_grace; + float m_targetSubordering; + NoteStyleName m_noteStyle; + + Event *m_lastInsertedEvent; +}; + + +} + +#endif diff --git a/src/commands/notation/RemoveFingeringMarksCommand.cpp b/src/commands/notation/RemoveFingeringMarksCommand.cpp new file mode 100644 index 0000000..2b66cba --- /dev/null +++ b/src/commands/notation/RemoveFingeringMarksCommand.cpp @@ -0,0 +1,54 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RemoveFingeringMarksCommand.h" + +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +void +RemoveFingeringMarksCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + std::vector marks = Marks::getMarks(**i); + for (std::vector::iterator j = marks.begin(); + j != marks.end(); ++j) { + if (Marks::isFingeringMark(*j)) { + Marks::removeMark(**i, *j); + } + } + } +} + +} diff --git a/src/commands/notation/RemoveFingeringMarksCommand.h b/src/commands/notation/RemoveFingeringMarksCommand.h new file mode 100644 index 0000000..6e02bfc --- /dev/null +++ b/src/commands/notation/RemoveFingeringMarksCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTESMENUREMOVEFINGERINGMARKSCOMMAND_H_ +#define _RG_NOTESMENUREMOVEFINGERINGMARKSCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class RemoveFingeringMarksCommand : public BasicSelectionCommand +{ +public: + RemoveFingeringMarksCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("&Remove Fingerings"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + +} + +#endif diff --git a/src/commands/notation/RemoveMarksCommand.cpp b/src/commands/notation/RemoveMarksCommand.cpp new file mode 100644 index 0000000..29513c2 --- /dev/null +++ b/src/commands/notation/RemoveMarksCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RemoveMarksCommand.h" + +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include +#include "base/BaseProperties.h" + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +RemoveMarksCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + long n = 0; + (*i)->get + (MARK_COUNT, n); + (*i)->unset(MARK_COUNT); + + for (int j = 0; j < n; ++j) { + (*i)->unset(getMarkPropertyName(j)); + } + } +} + +} diff --git a/src/commands/notation/RemoveMarksCommand.h b/src/commands/notation/RemoveMarksCommand.h new file mode 100644 index 0000000..d04a1c9 --- /dev/null +++ b/src/commands/notation/RemoveMarksCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTESMENUREMOVEMARKSCOMMAND_H_ +#define _RG_NOTESMENUREMOVEMARKSCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class RemoveMarksCommand : public BasicSelectionCommand +{ +public: + RemoveMarksCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("&Remove All Marks"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + +} + +#endif diff --git a/src/commands/notation/RemoveNotationQuantizeCommand.cpp b/src/commands/notation/RemoveNotationQuantizeCommand.cpp new file mode 100644 index 0000000..9b500fc --- /dev/null +++ b/src/commands/notation/RemoveNotationQuantizeCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RemoveNotationQuantizeCommand.h" + +#include "base/Event.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +void +RemoveNotationQuantizeCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + std::vector toInsert; + std::vector toErase; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + toInsert.push_back(new Event(**i, + (*i)->getAbsoluteTime(), + (*i)->getDuration(), + (*i)->getSubOrdering(), + (*i)->getAbsoluteTime(), + (*i)->getDuration())); + + toErase.push_back(*i); + } + + for (std::vector::iterator i = toErase.begin(); i != toErase.end(); + ++i) { + m_selection->getSegment().eraseSingle(*i); + } + + for (std::vector::iterator i = toInsert.begin(); i != toInsert.end(); + ++i) { + m_selection->getSegment().insert(*i); + } +} + +} diff --git a/src/commands/notation/RemoveNotationQuantizeCommand.h b/src/commands/notation/RemoveNotationQuantizeCommand.h new file mode 100644 index 0000000..bc61ff8 --- /dev/null +++ b/src/commands/notation/RemoveNotationQuantizeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUREMOVENOTATIONQUANTIZECOMMAND_H_ +#define _RG_ADJUSTMENUREMOVENOTATIONQUANTIZECOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class RemoveNotationQuantizeCommand : public BasicSelectionCommand +{ +public: + RemoveNotationQuantizeCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("Remo&ve Notation Quantization"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + +} + +#endif diff --git a/src/commands/notation/ResetDisplacementsCommand.cpp b/src/commands/notation/ResetDisplacementsCommand.cpp new file mode 100644 index 0000000..dff8549 --- /dev/null +++ b/src/commands/notation/ResetDisplacementsCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ResetDisplacementsCommand.h" + +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +ResetDisplacementsCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + (*i)->unset(DISPLACED_X); + (*i)->unset(DISPLACED_Y); + } +} + +} diff --git a/src/commands/notation/ResetDisplacementsCommand.h b/src/commands/notation/ResetDisplacementsCommand.h new file mode 100644 index 0000000..b1d165b --- /dev/null +++ b/src/commands/notation/ResetDisplacementsCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_RESETDISPLACEMENTSCOMMAND_H_ +#define _RG_RESETDISPLACEMENTSCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class ResetDisplacementsCommand : public BasicSelectionCommand +{ +public: + ResetDisplacementsCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("Restore Positions"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + +} + +#endif diff --git a/src/commands/notation/RespellCommand.cpp b/src/commands/notation/RespellCommand.cpp new file mode 100644 index 0000000..c410707 --- /dev/null +++ b/src/commands/notation/RespellCommand.cpp @@ -0,0 +1,141 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RespellCommand.h" + +#include +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ +using namespace BaseProperties; +using namespace Accidentals; + +QString +RespellCommand::getGlobalName(Type type, Accidental accidental) +{ + switch (type) { + + case Set: { + QString s(i18n("Respell with %1")); + //!!! should be in notationstrings: + if (accidental == DoubleSharp) { + s = s.arg(i18n("Do&uble Sharp")); + } else if (accidental == Sharp) { + s = s.arg(i18n("&Sharp")); + } else if (accidental == Flat) { + s = s.arg(i18n("&Flat")); + } else if (accidental == DoubleFlat) { + s = s.arg(i18n("Dou&ble Flat")); + } else if (accidental == Natural) { + s = s.arg(i18n("&Natural")); + } else { + s = s.arg(i18n("N&one")); + } + return s; + } + + case Up: + return i18n("Respell Accidentals &Upward"); + + case Down: + return i18n("Respell Accidentals &Downward"); + + case Restore: + return i18n("&Restore Accidentals"); + } + + return i18n("Respell Accidentals"); +} + +void +RespellCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + + if (m_type == Up || m_type == Down) { + + Accidental acc = NoAccidental; + (*i)->get + (ACCIDENTAL, acc); + + if (m_type == Down) { + if (acc == DoubleFlat) { + acc = Flat; + } else if (acc == Flat || acc == NoAccidental) { + acc = Sharp; + } else if (acc == Sharp) { + acc = DoubleSharp; + } + } else { + if (acc == Flat) { + acc = DoubleFlat; + } else if (acc == Sharp || acc == NoAccidental) { + acc = Flat; + } else if (acc == DoubleSharp) { + acc = Sharp; + } + } + + (*i)->set + (ACCIDENTAL, acc); + + } else if (m_type == Set) { + + // trap respelling black key notes as natural; which is + // impossible, and makes rawPitchToDisplayPitch() do crazy + // things as a consequence (fixes #1349782) + // 1 = C#, 3 = D#, 6 = F#, 8 = G#, 10 = A# + long pitch; + (*i)->get + (PITCH, pitch); + pitch %= 12; + if ((pitch == 1 || pitch == 3 || pitch == 6 || pitch == 8 || pitch == 10 ) + && m_accidental == Natural) { + // fail silently; is there anything to do here? + } else { + (*i)->set + (ACCIDENTAL, m_accidental); + } + + } else { + + (*i)->unset(ACCIDENTAL); + } + } + } +} + +} diff --git a/src/commands/notation/RespellCommand.h b/src/commands/notation/RespellCommand.h new file mode 100644 index 0000000..e2c414f --- /dev/null +++ b/src/commands/notation/RespellCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_RESPELLCOMMAND_H_ +#define _RG_RESPELLCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class RespellCommand : public BasicSelectionCommand +{ +public: + enum Type { + Set, + Up, + Down, + Restore + }; + + RespellCommand(Type type, Accidental acc, + EventSelection &selection) : + BasicSelectionCommand(getGlobalName(type, acc), selection, true), + m_selection(&selection), + m_type(type), + m_accidental(acc) { } + + static QString getGlobalName(Type type, Accidental acc); + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + Type m_type; + Accidental m_accidental; +}; + + +} + +#endif diff --git a/src/commands/notation/RestInsertionCommand.cpp b/src/commands/notation/RestInsertionCommand.cpp new file mode 100644 index 0000000..8fff336 --- /dev/null +++ b/src/commands/notation/RestInsertionCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RestInsertionCommand.h" + +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "document/BasicCommand.h" +#include "gui/editors/notation/NoteStyleFactory.h" +#include "NoteInsertionCommand.h" + + +namespace Rosegarden +{ + +RestInsertionCommand::RestInsertionCommand(Segment &segment, timeT time, + timeT endTime, Note note) : + NoteInsertionCommand(segment, time, endTime, note, 0, + Accidentals::NoAccidental, + AutoBeamOff, MatrixModeOff, GraceModeOff, 0, + NoteStyleFactory::DefaultStyle) +{ + setName("Insert Rest"); +} + +RestInsertionCommand::~RestInsertionCommand() +{ + // nothing +} + +void +RestInsertionCommand::modifySegment() +{ + SegmentNotationHelper helper(getSegment()); + + Segment::iterator i = helper.insertRest(m_insertionTime, m_note); + if (i != helper.segment().end()) + m_lastInsertedEvent = *i; +} + +} diff --git a/src/commands/notation/RestInsertionCommand.h b/src/commands/notation/RestInsertionCommand.h new file mode 100644 index 0000000..dc3d991 --- /dev/null +++ b/src/commands/notation/RestInsertionCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_RESTINSERTIONCOMMAND_H_ +#define _RG_RESTINSERTIONCOMMAND_H_ + +#include "base/NotationTypes.h" +#include "NoteInsertionCommand.h" +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; + + +class RestInsertionCommand : public NoteInsertionCommand +{ +public: + RestInsertionCommand(Segment &segment, + timeT time, + timeT endTime, + Note note); + virtual ~RestInsertionCommand(); + +protected: + virtual void modifySegment(); +}; + + +} + +#endif diff --git a/src/commands/notation/RestoreSlursCommand.cpp b/src/commands/notation/RestoreSlursCommand.cpp new file mode 100644 index 0000000..4cb2ec8 --- /dev/null +++ b/src/commands/notation/RestoreSlursCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RestoreSlursCommand.h" + +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "gui/editors/notation/NotationProperties.h" +#include + + +namespace Rosegarden +{ + +void +RestoreSlursCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Indication::EventType)) { + std::string indicationType; + if ((*i)->get + (Indication::IndicationTypePropertyName, indicationType) + && (indicationType == Indication::Slur || + indicationType == Indication::PhrasingSlur)) { + (*i)->unset(NotationProperties::SLUR_ABOVE); + } + } + } +} + +} diff --git a/src/commands/notation/RestoreSlursCommand.h b/src/commands/notation/RestoreSlursCommand.h new file mode 100644 index 0000000..687d016 --- /dev/null +++ b/src/commands/notation/RestoreSlursCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENURESTORESLURSCOMMAND_H_ +#define _RG_ADJUSTMENURESTORESLURSCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class RestoreSlursCommand : public BasicSelectionCommand +{ +public: + RestoreSlursCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("&Restore Slur Positions"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + + +} + +#endif diff --git a/src/commands/notation/RestoreStemsCommand.cpp b/src/commands/notation/RestoreStemsCommand.cpp new file mode 100644 index 0000000..99709f3 --- /dev/null +++ b/src/commands/notation/RestoreStemsCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RestoreStemsCommand.h" + +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "gui/editors/notation/NotationProperties.h" +#include + + +namespace Rosegarden +{ + +void +RestoreStemsCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if ((*i)->isa(Note::EventType)) { + (*i)->unset(NotationProperties::STEM_UP); + } + } +} + +} diff --git a/src/commands/notation/RestoreStemsCommand.h b/src/commands/notation/RestoreStemsCommand.h new file mode 100644 index 0000000..94dd0cf --- /dev/null +++ b/src/commands/notation/RestoreStemsCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENURESTORESTEMSCOMMAND_H_ +#define _RG_ADJUSTMENURESTORESTEMSCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class RestoreStemsCommand : public BasicSelectionCommand +{ +public: + RestoreStemsCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("&Restore Stems"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + + +} + +#endif diff --git a/src/commands/notation/RestoreTiesCommand.cpp b/src/commands/notation/RestoreTiesCommand.cpp new file mode 100644 index 0000000..6402361 --- /dev/null +++ b/src/commands/notation/RestoreTiesCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RestoreTiesCommand.h" + +#include "base/NotationTypes.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "base/BaseProperties.h" +#include "gui/editors/notation/NotationProperties.h" +#include + + +namespace Rosegarden +{ + +void +RestoreTiesCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + (*i)->unset(BaseProperties::TIE_IS_ABOVE); + } +} + +} diff --git a/src/commands/notation/RestoreTiesCommand.h b/src/commands/notation/RestoreTiesCommand.h new file mode 100644 index 0000000..d3ffef9 --- /dev/null +++ b/src/commands/notation/RestoreTiesCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENURESTORETIESCOMMAND_H_ +#define _RG_ADJUSTMENURESTORETIESCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class RestoreTiesCommand : public BasicSelectionCommand +{ +public: + RestoreTiesCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("&Restore Tie Positions"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + + +} + +#endif diff --git a/src/commands/notation/SetVisibilityCommand.cpp b/src/commands/notation/SetVisibilityCommand.cpp new file mode 100644 index 0000000..c7f49f3 --- /dev/null +++ b/src/commands/notation/SetVisibilityCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SetVisibilityCommand.h" + +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + + +void +SetVisibilityCommand::modifySegment() +{ + EventSelection::eventcontainer::iterator i; + + for (i = m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + if (m_visible) { + (*i)->unset(INVISIBLE); + } else { + (*i)->set + (INVISIBLE, true); + } + } +} + +} diff --git a/src/commands/notation/SetVisibilityCommand.h b/src/commands/notation/SetVisibilityCommand.h new file mode 100644 index 0000000..6aef5ef --- /dev/null +++ b/src/commands/notation/SetVisibilityCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SETVISIBILITYCOMMAND_H_ +#define _RG_SETVISIBILITYCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class SetVisibilityCommand : public BasicSelectionCommand +{ +public: + SetVisibilityCommand(EventSelection &selection, bool visible) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection), + m_visible(visible) { } + + static QString getGlobalName() { return i18n("Set Visibility"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) + bool m_visible; +}; + + +} + +#endif diff --git a/src/commands/notation/SustainInsertionCommand.cpp b/src/commands/notation/SustainInsertionCommand.cpp new file mode 100644 index 0000000..f3c3917 --- /dev/null +++ b/src/commands/notation/SustainInsertionCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SustainInsertionCommand.h" + +#include "base/Event.h" +#include "base/MidiTypes.h" +#include "base/Segment.h" +#include "document/BasicCommand.h" +#include + + +namespace Rosegarden +{ + +SustainInsertionCommand::SustainInsertionCommand(Segment &segment, timeT time, + bool down, + int controllerNumber) : + BasicCommand(getGlobalName(down), segment, time, time), + m_down(down), + m_controllerNumber(controllerNumber), + m_lastInsertedEvent(0) +{ + // nothing +} + +SustainInsertionCommand::~SustainInsertionCommand() +{ + // nothing +} + +void +SustainInsertionCommand::modifySegment() +{ + Event *e = new Event(Controller::EventType, getStartTime(), 0, + Controller::EventSubOrdering); + e->set + (Controller::NUMBER, m_controllerNumber); + e->set + (Controller::VALUE, m_down ? 127 : 0); + m_lastInsertedEvent = *getSegment().insert(e); +} + +} diff --git a/src/commands/notation/SustainInsertionCommand.h b/src/commands/notation/SustainInsertionCommand.h new file mode 100644 index 0000000..ddb93b4 --- /dev/null +++ b/src/commands/notation/SustainInsertionCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SUSTAININSERTIONCOMMAND_H_ +#define _RG_SUSTAININSERTIONCOMMAND_H_ + +#include "document/BasicCommand.h" +#include +#include "base/Event.h" +#include + + +class Pedal; + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class SustainInsertionCommand : public BasicCommand +{ +public: + SustainInsertionCommand(Segment &segment, + timeT time, + bool down, + int controllerNumber); + virtual ~SustainInsertionCommand(); + + static QString getGlobalName(bool down) { + if (down) { + return i18n("Add Pedal &Press"); + } else { + return i18n("Add Pedal &Release"); + } + } + + Event *getLastInsertedEvent() { return m_lastInsertedEvent; } + +protected: + virtual void modifySegment(); + + bool m_down; + int m_controllerNumber; + Event *m_lastInsertedEvent; +}; + + + +} + +#endif diff --git a/src/commands/notation/TextChangeCommand.cpp b/src/commands/notation/TextChangeCommand.cpp new file mode 100644 index 0000000..e909839 --- /dev/null +++ b/src/commands/notation/TextChangeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TextChangeCommand.h" + +#include +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "document/BasicCommand.h" + + +namespace Rosegarden +{ + +TextChangeCommand::TextChangeCommand(Segment &segment, + Event *event, + Text text) : + BasicCommand(i18n("Edit Text"), segment, + event->getAbsoluteTime(), event->getAbsoluteTime() + 1, + true), // bruteForceRedo + m_event(event), + m_text(text) +{ + // nothing +} + +TextChangeCommand::~TextChangeCommand() +{} + +void +TextChangeCommand::modifySegment() +{ + m_event->set + (Text::TextTypePropertyName, m_text.getTextType()); + m_event->set + (Text::TextPropertyName, m_text.getText()); +} + +} diff --git a/src/commands/notation/TextChangeCommand.h b/src/commands/notation/TextChangeCommand.h new file mode 100644 index 0000000..5dce48e --- /dev/null +++ b/src/commands/notation/TextChangeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_TEXTCHANGECOMMAND_H_ +#define _RG_TEXTCHANGECOMMAND_H_ + +#include "base/NotationTypes.h" +#include "document/BasicCommand.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class TextChangeCommand : public BasicCommand +{ +public: + TextChangeCommand(Segment &segment, + Event *event, + Text text); + virtual ~TextChangeCommand(); + +protected: + virtual void modifySegment(); + Event *m_event; // only used first time through + Text m_text; +}; + +/* + * Inserts a key change into a single segment, taking segment transpose into + * account (fixes #1520716) if desired. + */ + +} + +#endif diff --git a/src/commands/notation/TextInsertionCommand.cpp b/src/commands/notation/TextInsertionCommand.cpp new file mode 100644 index 0000000..8ba94c9 --- /dev/null +++ b/src/commands/notation/TextInsertionCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TextInsertionCommand.h" + +#include +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "document/BasicCommand.h" + + +namespace Rosegarden +{ + +TextInsertionCommand::TextInsertionCommand(Segment &segment, timeT time, + Text text) : + BasicCommand(i18n("Insert Text"), segment, time, time + 1), + m_text(text), + m_lastInsertedEvent(0) +{ + // nothing +} + +TextInsertionCommand::~TextInsertionCommand() +{ + // nothing +} + +void +TextInsertionCommand::modifySegment() +{ + SegmentNotationHelper helper(getSegment()); + + Segment::iterator i = helper.insertText(getStartTime(), m_text); + if (i != helper.segment().end()) + m_lastInsertedEvent = *i; +} + +} diff --git a/src/commands/notation/TextInsertionCommand.h b/src/commands/notation/TextInsertionCommand.h new file mode 100644 index 0000000..34b574f --- /dev/null +++ b/src/commands/notation/TextInsertionCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_TEXTINSERTIONCOMMAND_H_ +#define _RG_TEXTINSERTIONCOMMAND_H_ + +#include "base/NotationTypes.h" +#include "document/BasicCommand.h" +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Event; + + +class TextInsertionCommand : public BasicCommand +{ +public: + TextInsertionCommand(Segment &segment, + timeT time, + Text text); + virtual ~TextInsertionCommand(); + + Event *getLastInsertedEvent() { return m_lastInsertedEvent; } + +protected: + virtual void modifySegment(); + + Text m_text; + Event *m_lastInsertedEvent; +}; + + +} + +#endif diff --git a/src/commands/notation/TieNotesCommand.cpp b/src/commands/notation/TieNotesCommand.cpp new file mode 100644 index 0000000..18b8188 --- /dev/null +++ b/src/commands/notation/TieNotesCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TieNotesCommand.h" + +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +TieNotesCommand::modifySegment() +{ + Segment &segment(getSegment()); + SegmentNotationHelper helper(segment); + + //!!! move part of this to SegmentNotationHelper? + + for (EventSelection::eventcontainer::iterator i = + m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + // bool tiedForward; + // if ((*i)->get(TIED_FORWARD, tiedForward) && tiedForward) { + // continue; + // } + + Segment::iterator si = segment.findSingle(*i); + Segment::iterator sj; + while ((sj = helper.getNextAdjacentNote(si, true, false)) != + segment.end()) { + if (!m_selection->contains(*sj)) + break; + (*si)->set(TIED_FORWARD, true); + (*si)->unset(TIE_IS_ABOVE); + (*sj)->set(TIED_BACKWARD, true); + si = sj; + } + } +} + +} diff --git a/src/commands/notation/TieNotesCommand.h b/src/commands/notation/TieNotesCommand.h new file mode 100644 index 0000000..2f1874f --- /dev/null +++ b/src/commands/notation/TieNotesCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTESMENUTIENOTESCOMMAND_H_ +#define _RG_NOTESMENUTIENOTESCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class TieNotesCommand : public BasicSelectionCommand +{ +public: + TieNotesCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("&Tie"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + + +} + +#endif diff --git a/src/commands/notation/TupletCommand.cpp b/src/commands/notation/TupletCommand.cpp new file mode 100644 index 0000000..b46fff5 --- /dev/null +++ b/src/commands/notation/TupletCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TupletCommand.h" + +#include "base/Event.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "document/BasicCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +TupletCommand::TupletCommand(Segment &segment, + timeT startTime, + timeT unit, + int untupled, int tupled, + bool hasTimingAlready) : + BasicCommand(getGlobalName((untupled == 3) && (tupled == 2)), + segment, startTime, startTime + (unit * untupled)), + m_unit(unit), + m_untupled(untupled), + m_tupled(tupled), + m_hasTimingAlready(hasTimingAlready) +{ + // nothing else +} + +void +TupletCommand::modifySegment() +{ + if (m_hasTimingAlready) { + + int groupId = getSegment().getNextId(); + + for (Segment::iterator i = getSegment().findTime(getStartTime()); + getSegment().isBeforeEndMarker(i); ++i) { + + if ((*i)->getNotationAbsoluteTime() >= + getStartTime() + (m_unit * m_tupled)) + break; + + Event *e = *i; + + e->set + (BEAMED_GROUP_ID, groupId); + e->set + (BEAMED_GROUP_TYPE, GROUP_TYPE_TUPLED); + + e->set + (BEAMED_GROUP_TUPLET_BASE, m_unit); + e->set + (BEAMED_GROUP_TUPLED_COUNT, m_tupled); + e->set + (BEAMED_GROUP_UNTUPLED_COUNT, m_untupled); + } + + } else { + SegmentNotationHelper helper(getSegment()); + helper.makeTupletGroup(getStartTime(), m_untupled, m_tupled, m_unit); + } +} + +} diff --git a/src/commands/notation/TupletCommand.h b/src/commands/notation/TupletCommand.h new file mode 100644 index 0000000..b08a204 --- /dev/null +++ b/src/commands/notation/TupletCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUTUPLETCOMMAND_H_ +#define _RG_ADJUSTMENUTUPLETCOMMAND_H_ + +#include "document/BasicCommand.h" +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Segment; + + +class TupletCommand : public BasicCommand +{ +public: + TupletCommand(Segment &segment, + timeT startTime, + timeT unit, + int untupled = 3, int tupled = 2, + bool groupHasTimingAlready = false); + + static QString getGlobalName(bool simple = true) { + if (simple) return i18n("&Triplet"); + else return i18n("Tu&plet..."); + } + +protected: + virtual void modifySegment(); + +private: + timeT m_unit; + int m_untupled; + int m_tupled; + bool m_hasTimingAlready; +}; + + + +} + +#endif diff --git a/src/commands/notation/UnGraceCommand.cpp b/src/commands/notation/UnGraceCommand.cpp new file mode 100644 index 0000000..7eb0343 --- /dev/null +++ b/src/commands/notation/UnGraceCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "UnGraceCommand.h" + +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include + + +namespace Rosegarden +{ + +void +UnGraceCommand::modifySegment() +{ + //!!! +} + +} diff --git a/src/commands/notation/UnGraceCommand.h b/src/commands/notation/UnGraceCommand.h new file mode 100644 index 0000000..cdaf244 --- /dev/null +++ b/src/commands/notation/UnGraceCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUUNGRACECOMMAND_H_ +#define _RG_ADJUSTMENUUNGRACECOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class UnGraceCommand : public BasicSelectionCommand +{ +public: + UnGraceCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection) { } + + static QString getGlobalName() { return i18n("Ung&race"); } + +protected: + virtual void modifySegment(); +}; + + + +} + +#endif diff --git a/src/commands/notation/UnTupletCommand.cpp b/src/commands/notation/UnTupletCommand.cpp new file mode 100644 index 0000000..0a4b85e --- /dev/null +++ b/src/commands/notation/UnTupletCommand.cpp @@ -0,0 +1,54 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "UnTupletCommand.h" + +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include "base/BaseProperties.h" +#include + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +UnTupletCommand::modifySegment() +{ + for (EventSelection::eventcontainer::iterator i = + m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + (*i)->unset(BEAMED_GROUP_ID); + (*i)->unset(BEAMED_GROUP_TYPE); + (*i)->unset(BEAMED_GROUP_TUPLET_BASE); + (*i)->unset(BEAMED_GROUP_TUPLED_COUNT); + (*i)->unset(BEAMED_GROUP_UNTUPLED_COUNT); + } +} + +} diff --git a/src/commands/notation/UnTupletCommand.h b/src/commands/notation/UnTupletCommand.h new file mode 100644 index 0000000..76aabe4 --- /dev/null +++ b/src/commands/notation/UnTupletCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUUNTUPLETCOMMAND_H_ +#define _RG_ADJUSTMENUUNTUPLETCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class UnTupletCommand : public BasicSelectionCommand +{ +public: + UnTupletCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { + return i18n("&Untuplet"); + } + +protected: + virtual void modifySegment(); + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + + +} + +#endif diff --git a/src/commands/notation/UntieNotesCommand.cpp b/src/commands/notation/UntieNotesCommand.cpp new file mode 100644 index 0000000..e32d605 --- /dev/null +++ b/src/commands/notation/UntieNotesCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "UntieNotesCommand.h" + +#include "base/Selection.h" +#include "document/BasicSelectionCommand.h" +#include +#include "base/BaseProperties.h" + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +void +UntieNotesCommand::modifySegment() +{ + for (EventSelection::eventcontainer::iterator i = + m_selection->getSegmentEvents().begin(); + i != m_selection->getSegmentEvents().end(); ++i) { + + (*i)->unset(TIED_FORWARD); + (*i)->unset(TIE_IS_ABOVE); + (*i)->unset(TIED_BACKWARD); + } +} + +} diff --git a/src/commands/notation/UntieNotesCommand.h b/src/commands/notation/UntieNotesCommand.h new file mode 100644 index 0000000..3f57413 --- /dev/null +++ b/src/commands/notation/UntieNotesCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADJUSTMENUUNTIENOTESCOMMAND_H_ +#define _RG_ADJUSTMENUUNTIENOTESCOMMAND_H_ + +#include "document/BasicSelectionCommand.h" +#include +#include + + + + +namespace Rosegarden +{ + +class EventSelection; + + +class UntieNotesCommand : public BasicSelectionCommand +{ +public: + UntieNotesCommand(EventSelection &selection) : + BasicSelectionCommand(getGlobalName(), selection, true), + m_selection(&selection) { } + + static QString getGlobalName() { return i18n("&Untie"); } + +protected: + virtual void modifySegment(); + +private: + EventSelection *m_selection;// only used on 1st execute (cf bruteForceRedo) +}; + + + +} + +#endif diff --git a/src/commands/segment/AddTempoChangeCommand.cpp b/src/commands/segment/AddTempoChangeCommand.cpp new file mode 100644 index 0000000..6665215 --- /dev/null +++ b/src/commands/segment/AddTempoChangeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddTempoChangeCommand.h" + +#include "base/Composition.h" +#include + + +namespace Rosegarden +{ + +AddTempoChangeCommand::~AddTempoChangeCommand() +{ + // nothing here either +} + +void +AddTempoChangeCommand::execute() +{ + int oldIndex = m_composition->getTempoChangeNumberAt(m_time); + + if (oldIndex >= 0) { + std::pair data = + m_composition->getTempoChange(oldIndex); + + if (data.first == m_time) + m_oldTempo = data.second; + } + + m_tempoChangeIndex = m_composition->addTempoAtTime(m_time, m_tempo, m_target); +} + +void +AddTempoChangeCommand::unexecute() +{ + m_composition->removeTempoChange(m_tempoChangeIndex); + + if (m_oldTempo != 0) { + m_composition->addTempoAtTime(m_time, m_oldTempo); + } +} + +} diff --git a/src/commands/segment/AddTempoChangeCommand.h b/src/commands/segment/AddTempoChangeCommand.h new file mode 100644 index 0000000..07036dd --- /dev/null +++ b/src/commands/segment/AddTempoChangeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADDTEMPOCHANGECOMMAND_H_ +#define _RG_ADDTEMPOCHANGECOMMAND_H_ + +#include +#include +#include "base/Event.h" +#include "base/Composition.h" // for tempoT +#include + + + + +namespace Rosegarden +{ + +class AddTempoChangeCommand : public KNamedCommand +{ +public: + AddTempoChangeCommand(Composition *composition, + timeT time, + tempoT tempo, + tempoT target = -1): + KNamedCommand(getGlobalName()), + m_composition(composition), + m_time(time), + m_tempo(tempo), + m_target(target), + m_oldTempo(0), + m_tempoChangeIndex(0) {} + + virtual ~AddTempoChangeCommand(); + + static QString getGlobalName() { return i18n("Add Te&mpo Change..."); } + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + timeT m_time; + tempoT m_tempo; + tempoT m_target; + tempoT m_oldTempo; + int m_tempoChangeIndex; +}; + + + +} + +#endif diff --git a/src/commands/segment/AddTimeSignatureAndNormalizeCommand.cpp b/src/commands/segment/AddTimeSignatureAndNormalizeCommand.cpp new file mode 100644 index 0000000..45b391b --- /dev/null +++ b/src/commands/segment/AddTimeSignatureAndNormalizeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddTimeSignatureAndNormalizeCommand.h" + +#include "AddTimeSignatureCommand.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "commands/notation/MakeRegionViableCommand.h" + + +namespace Rosegarden +{ + +AddTimeSignatureAndNormalizeCommand::AddTimeSignatureAndNormalizeCommand +(Composition *composition, timeT time, TimeSignature timeSig) : + KMacroCommand(AddTimeSignatureCommand::getGlobalName()) +{ + addCommand(new AddTimeSignatureCommand(composition, time, timeSig)); + + // only up to the next time signature + timeT nextTimeSigTime(composition->getDuration()); + + int index = composition->getTimeSignatureNumberAt(time); + if (composition->getTimeSignatureCount() > index + 1) { + nextTimeSigTime = composition->getTimeSignatureChange(index + 1).first; + } + + for (Composition::iterator i = composition->begin(); + i != composition->end(); ++i) { + + if ((*i)->getType() != Segment::Internal) continue; + + timeT startTime = (*i)->getStartTime(); + timeT endTime = (*i)->getEndTime(); + + if (startTime >= nextTimeSigTime || endTime <= time) + continue; + + // "Make Notes Viable" splits and ties notes at barlines, and + // also does a rest normalize. It's what we normally want + // when adding a time signature. + + addCommand(new MakeRegionViableCommand + (**i, + std::max(startTime, time), + std::min(endTime, nextTimeSigTime))); + } +} + +AddTimeSignatureAndNormalizeCommand::~AddTimeSignatureAndNormalizeCommand() +{ + // well, nothing really +} + +} diff --git a/src/commands/segment/AddTimeSignatureAndNormalizeCommand.h b/src/commands/segment/AddTimeSignatureAndNormalizeCommand.h new file mode 100644 index 0000000..f22faa4 --- /dev/null +++ b/src/commands/segment/AddTimeSignatureAndNormalizeCommand.h @@ -0,0 +1,53 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADDTIMESIGNATUREANDNORMALIZECOMMAND_H_ +#define _RG_ADDTIMESIGNATUREANDNORMALIZECOMMAND_H_ + +#include "base/NotationTypes.h" +#include "base/Event.h" +#include + + +namespace Rosegarden +{ + +class Composition; + + +class AddTimeSignatureAndNormalizeCommand : public KMacroCommand +{ +public: + AddTimeSignatureAndNormalizeCommand(Composition *composition, + timeT time, + TimeSignature timeSig); + virtual ~AddTimeSignatureAndNormalizeCommand(); +}; + + + +} + +#endif diff --git a/src/commands/segment/AddTimeSignatureCommand.cpp b/src/commands/segment/AddTimeSignatureCommand.cpp new file mode 100644 index 0000000..88f2d07 --- /dev/null +++ b/src/commands/segment/AddTimeSignatureCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddTimeSignatureCommand.h" + +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include + + +namespace Rosegarden +{ + +AddTimeSignatureCommand::AddTimeSignatureCommand(Composition *composition, + timeT time, + TimeSignature timeSig) : + KNamedCommand(getGlobalName()), + m_composition(composition), + m_time(time), + m_timeSignature(timeSig), + m_oldTimeSignature(0) +{ + // nothing else +} + +AddTimeSignatureCommand::~AddTimeSignatureCommand() +{ + if (m_oldTimeSignature) + delete m_oldTimeSignature; +} + +void +AddTimeSignatureCommand::execute() +{ + int oldIndex = m_composition->getTimeSignatureNumberAt(m_time); + if (oldIndex >= 0) { + std::pair data = + m_composition->getTimeSignatureChange(oldIndex); + if (data.first == m_time) { + m_oldTimeSignature = new TimeSignature(data.second); + } + } + + m_timeSigIndex = m_composition->addTimeSignature(m_time, m_timeSignature); +} + +void +AddTimeSignatureCommand::unexecute() +{ + m_composition->removeTimeSignature(m_timeSigIndex); + if (m_oldTimeSignature) { + m_composition->addTimeSignature(m_time, *m_oldTimeSignature); + } +} + +} diff --git a/src/commands/segment/AddTimeSignatureCommand.h b/src/commands/segment/AddTimeSignatureCommand.h new file mode 100644 index 0000000..fbc875d --- /dev/null +++ b/src/commands/segment/AddTimeSignatureCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADDTIMESIGNATURECOMMAND_H_ +#define _RG_ADDTIMESIGNATURECOMMAND_H_ + +#include "base/NotationTypes.h" +#include +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Composition; + + +class AddTimeSignatureCommand : public KNamedCommand +{ +public: + AddTimeSignatureCommand(Composition *composition, + timeT time, + TimeSignature timeSig); + virtual ~AddTimeSignatureCommand(); + + static QString getGlobalName() { return i18n("Add Time Si&gnature Change..."); } + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + timeT m_time; + TimeSignature m_timeSignature; + + TimeSignature *m_oldTimeSignature; // for undo + int m_timeSigIndex; // for undo +}; + + + + +} + +#endif diff --git a/src/commands/segment/AddTracksCommand.cpp b/src/commands/segment/AddTracksCommand.cpp new file mode 100644 index 0000000..1f09227 --- /dev/null +++ b/src/commands/segment/AddTracksCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddTracksCommand.h" + +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/MidiProgram.h" +#include "base/Track.h" +#include + + +namespace Rosegarden +{ + +AddTracksCommand::AddTracksCommand(Composition *composition, + unsigned int nbTracks, + InstrumentId id, + int position): + KNamedCommand(getGlobalName()), + m_composition(composition), + m_nbNewTracks(nbTracks), + m_instrumentId(id), + m_position(position), + m_detached(false) + +{ +} + +AddTracksCommand::~AddTracksCommand() +{ + if (m_detached) { + for (unsigned int i = 0; i < m_newTracks.size(); ++i) + delete m_newTracks[i]; + } +} + +void AddTracksCommand::execute() +{ + // Re-attach tracks + // + if (m_detached) { + + for (unsigned int i = 0; i < m_newTracks.size(); i++) { + m_composition->addTrack(m_newTracks[i]); + } + + for (TrackPositionMap::iterator i = m_oldPositions.begin(); + i != m_oldPositions.end(); ++i) { + + int newPosition = i->second + m_nbNewTracks; + Track *track = m_composition->getTrackById(i->first); + if (track) track->setPosition(newPosition); + } + + return; + } + + int highPosition = 0; + + for (Composition::trackiterator it = m_composition->getTracks().begin(); + it != m_composition->getTracks().end(); ++it) { + + int pos = it->second->getPosition(); + + if (pos > highPosition) { + highPosition = pos; + } + } + + if (m_position == -1) m_position = highPosition + 1; + if (m_position < 0) m_position = 0; + if (m_position > highPosition + 1) m_position = highPosition + 1; + + for (Composition::trackiterator it = m_composition->getTracks().begin(); + it != m_composition->getTracks().end(); ++it) { + + int pos = it->second->getPosition(); + + if (pos >= m_position) { + m_oldPositions[it->first] = pos; + it->second->setPosition(pos + m_nbNewTracks); + } + } + + for (unsigned int i = 0; i < m_nbNewTracks; ++i) { + + TrackId trackId = m_composition->getNewTrackId(); + Track *track = new Track(trackId); + + track->setPosition(m_position + i); + track->setInstrument(m_instrumentId); + + m_composition->addTrack(track); + m_newTracks.push_back(track); + } +} + +void AddTracksCommand::unexecute() +{ + for (unsigned int i = 0; i < m_newTracks.size(); i++) { + m_composition->detachTrack(m_newTracks[i]); + } + + for (TrackPositionMap::iterator i = m_oldPositions.begin(); + i != m_oldPositions.end(); ++i) { + + Track *track = m_composition->getTrackById(i->first); + if (track) track->setPosition(i->second); + } + + m_detached = true; +} + +} diff --git a/src/commands/segment/AddTracksCommand.h b/src/commands/segment/AddTracksCommand.h new file mode 100644 index 0000000..d3e09ca --- /dev/null +++ b/src/commands/segment/AddTracksCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADDTRACKSCOMMAND_H_ +#define _RG_ADDTRACKSCOMMAND_H_ + +#include "base/MidiProgram.h" +#include "base/Composition.h" +#include +#include +#include +#include +#include + + + +namespace Rosegarden +{ + +class Track; +class Composition; + + +class AddTracksCommand : public KNamedCommand +{ +public: + AddTracksCommand(Composition *composition, + unsigned int nbTracks, + InstrumentId id, + int position); // -1 -> at end + virtual ~AddTracksCommand(); + + static QString getGlobalName() { return i18n("Add Tracks..."); } + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + unsigned int m_nbNewTracks; + InstrumentId m_instrumentId; + int m_position; + + typedef std::map TrackPositionMap; + + std::vector m_newTracks; + TrackPositionMap m_oldPositions; + + bool m_detached; +}; + + +} + +#endif diff --git a/src/commands/segment/AddTriggerSegmentCommand.cpp b/src/commands/segment/AddTriggerSegmentCommand.cpp new file mode 100644 index 0000000..12d406e --- /dev/null +++ b/src/commands/segment/AddTriggerSegmentCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddTriggerSegmentCommand.h" + +#include +#include "base/Composition.h" +#include "base/Segment.h" +#include "base/TriggerSegment.h" +#include "document/RosegardenGUIDoc.h" + + +namespace Rosegarden +{ + +AddTriggerSegmentCommand::AddTriggerSegmentCommand(RosegardenGUIDoc *doc, + timeT duration, + int basePitch, + int baseVelocity) : + KNamedCommand(i18n("Add Triggered Segment")), + m_composition(&doc->getComposition()), + m_duration(duration), + m_basePitch(basePitch), + m_baseVelocity(baseVelocity), + m_id(0), + m_segment(0), + m_detached(false) +{ + // nothing else +} + +AddTriggerSegmentCommand::~AddTriggerSegmentCommand() +{ + if (m_detached) + delete m_segment; +} + +TriggerSegmentId +AddTriggerSegmentCommand::getId() const +{ + return m_id; +} + +void +AddTriggerSegmentCommand::execute() +{ + if (m_segment) { + m_composition->addTriggerSegment(m_segment, m_id, m_basePitch, m_baseVelocity); + } else { + m_segment = new Segment(); + m_segment->setEndMarkerTime(m_duration); + TriggerSegmentRec *rec = m_composition->addTriggerSegment + (m_segment, m_basePitch, m_baseVelocity); + if (rec) + m_id = rec->getId(); + } + m_detached = false; +} + +void +AddTriggerSegmentCommand::unexecute() +{ + if (m_segment) + m_composition->detachTriggerSegment(m_id); + m_detached = true; +} + +} diff --git a/src/commands/segment/AddTriggerSegmentCommand.h b/src/commands/segment/AddTriggerSegmentCommand.h new file mode 100644 index 0000000..46d23cb --- /dev/null +++ b/src/commands/segment/AddTriggerSegmentCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADDTRIGGERSEGMENTCOMMAND_H_ +#define _RG_ADDTRIGGERSEGMENTCOMMAND_H_ + +#include "base/TriggerSegment.h" +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class RosegardenGUIDoc; +class Composition; + + +class AddTriggerSegmentCommand : public KNamedCommand +{ +public: + AddTriggerSegmentCommand(RosegardenGUIDoc *doc, + timeT duration, // start time always 0 + int basePitch = -1, + int baseVelocity = -1); + virtual ~AddTriggerSegmentCommand(); + + TriggerSegmentId getId() const; // after invocation + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + timeT m_duration; + int m_basePitch; + int m_baseVelocity; + TriggerSegmentId m_id; + Segment *m_segment; + bool m_detached; +}; + + + +} + +#endif diff --git a/src/commands/segment/AudioSegmentAutoSplitCommand.cpp b/src/commands/segment/AudioSegmentAutoSplitCommand.cpp new file mode 100644 index 0000000..d474b64 --- /dev/null +++ b/src/commands/segment/AudioSegmentAutoSplitCommand.cpp @@ -0,0 +1,191 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AudioSegmentAutoSplitCommand.h" + +#include "base/Event.h" +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Composition.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "document/RosegardenGUIDoc.h" +#include "sound/AudioFileManager.h" +#include "sound/PeakFileManager.h" +#include + + +namespace Rosegarden +{ + +struct AutoSplitPoint +{ + timeT time; + timeT lastSoundTime; + Clef clef; + Rosegarden::Key key; + AutoSplitPoint(timeT t, timeT lst, Clef c, Rosegarden::Key k) : + time(t), lastSoundTime(lst), clef(c), key(k) { } +}; + +AudioSegmentAutoSplitCommand::AudioSegmentAutoSplitCommand( + RosegardenGUIDoc *doc, + Segment *segment, + int threshold) : + KNamedCommand(getGlobalName()), + m_segment(segment), + m_composition(segment->getComposition()), + m_audioFileManager(&(doc->getAudioFileManager())), + m_detached(false), + m_threshold(threshold) +{} + +AudioSegmentAutoSplitCommand::~AudioSegmentAutoSplitCommand() +{ + if (m_detached) { + delete m_segment; + } else { + for (unsigned int i = 0; i < m_newSegments.size(); ++i) { + delete m_newSegments[i]; + } + } +} + +void +AudioSegmentAutoSplitCommand::execute() +{ + if (m_newSegments.size() == 0) { + + std::vector splitPoints; + + if (m_segment->getType() != Segment::Audio) + return ; + + // Auto split the audio file - we ask for a minimum + // result file size of 0.2secs - that's probably fair + // enough. + // + std::vector rtSplitPoints; + + try { + rtSplitPoints = + m_audioFileManager-> + getSplitPoints(m_segment->getAudioFileId(), + m_segment->getAudioStartTime(), + m_segment->getAudioEndTime(), + m_threshold, + RealTime(0, 200000000)); + } catch (AudioFileManager::BadAudioPathException e) { + std::cerr << "ERROR: AudioSegmentAutoSplitCommand: Bad audio path: " << e.getMessage() << std::endl; + } catch (PeakFileManager::BadPeakFileException e) { + std::cerr << "ERROR: AudioSegmentAutoSplitCommand: Bad peak file: " << e.getMessage() << std::endl; + } + + std::vector::iterator it; + timeT absStartTime, absEndTime; + + char splitNumber[10]; + int splitCount = 0; + + timeT origStartTime = m_segment->getStartTime(); + RealTime audioStart = m_segment->getAudioStartTime(); + RealTime origStartRT = m_composition->getElapsedRealTime(origStartTime); + + for (it = rtSplitPoints.begin(); it != rtSplitPoints.end(); it++) { + // The start time for the segment is the original + // segment's start time, plus whatever it->first translates + // into as an offset from the original segment's start + // time + + RG_DEBUG << "AudioSegmentAutoSplitCommand::execute: range " << it->first << " -> " << it->second << endl; + + absStartTime = m_composition->getElapsedTimeForRealTime + (origStartRT - audioStart + it->first); + + absEndTime = m_composition->getElapsedTimeForRealTime + (origStartRT - audioStart + it->second); + + // absStartTime = m_segment->getStartTime() + + // m_composition->getElapsedTimeForRealTime(it->first - audioStart); + + // absEndTime = m_segment->getStartTime() + + // m_composition->getElapsedTimeForRealTime(it->second - audioStart); + + Segment *newSegment = new Segment(*m_segment); + + newSegment->setStartTime(absStartTime); + newSegment->setAudioFileId(m_segment->getAudioFileId()); + newSegment->setAudioStartTime(it->first); + newSegment->setAudioEndTime(it->second); + newSegment->setEndMarkerTime(absEndTime); + + // label + sprintf(splitNumber, "%d", splitCount++); + newSegment-> + setLabel(qstrtostr(i18n("%1 (autosplit %2)").arg + (strtoqstr(m_segment->getLabel())).arg + (splitNumber))); + + newSegment->setColourIndex(m_segment->getColourIndex()); + + RG_DEBUG << "AudioSegmentAutoSplitCommand::execute " + << "abs start = " << absStartTime + << ", abs end = " << absEndTime + << ", seg start = " << newSegment->getStartTime() + << ", seg end = " << newSegment->getEndMarkerTime() + << ", audio start = " << newSegment->getAudioStartTime() + << ", audio end = " << newSegment->getAudioEndTime() + << endl; + + m_newSegments.push_back(newSegment); + } + } + + RG_DEBUG << "AudioSegmentAutoSplitCommand::execute: have " << m_newSegments.size() << " new segments" << endl; + + for (unsigned int i = 0; i < m_newSegments.size(); ++i) { + m_composition->addSegment(m_newSegments[i]); + } + + if (m_newSegments.size() > 0) { + m_composition->detachSegment(m_segment); + } + + m_detached = true; +} + +void +AudioSegmentAutoSplitCommand::unexecute() +{ + for (unsigned int i = 0; i < m_newSegments.size(); ++i) { + m_composition->detachSegment(m_newSegments[i]); + } + if (m_newSegments.size() > 0) { // otherwise it was never detached + m_composition->addSegment(m_segment); + } + m_detached = false; +} + +} diff --git a/src/commands/segment/AudioSegmentAutoSplitCommand.h b/src/commands/segment/AudioSegmentAutoSplitCommand.h new file mode 100644 index 0000000..a1c4b48 --- /dev/null +++ b/src/commands/segment/AudioSegmentAutoSplitCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_AUDIOSEGMENTAUTOSPLITCOMMAND_H_ +#define _RG_AUDIOSEGMENTAUTOSPLITCOMMAND_H_ + +#include +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Segment; +class RosegardenGUIDoc; +class Composition; +class AudioFileManager; + + +class AudioSegmentAutoSplitCommand : public KNamedCommand +{ +public: + AudioSegmentAutoSplitCommand(RosegardenGUIDoc *doc, + Segment *segment, + int threshold); + virtual ~AudioSegmentAutoSplitCommand(); + + virtual void execute(); + virtual void unexecute(); + + static QString getGlobalName() { return i18n("&Split on Silence"); } + +private: + Segment *m_segment; + Composition *m_composition; + AudioFileManager *m_audioFileManager; + std::vector m_newSegments; + bool m_detached; + int m_threshold; +}; + + +} + +#endif diff --git a/src/commands/segment/AudioSegmentDistributeCommand.cpp b/src/commands/segment/AudioSegmentDistributeCommand.cpp new file mode 100644 index 0000000..9bdfb97 --- /dev/null +++ b/src/commands/segment/AudioSegmentDistributeCommand.cpp @@ -0,0 +1,156 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AudioSegmentDistributeCommand.h" + +#include "base/Event.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "sound/AudioFile.h" +#include + + +namespace Rosegarden +{ + +AudioSegmentDistributeCommand::AudioSegmentDistributeCommand( + Composition *comp, + SegmentSelection &inputSelection, + Segment *audioSegment): + KNamedCommand(getGlobalName()), + m_composition(comp), + m_selection(inputSelection), + m_audioFile(0), + m_audioSegment(audioSegment), + m_executed(false) +{} + +AudioSegmentDistributeCommand::AudioSegmentDistributeCommand( + Composition *comp, + SegmentSelection &inputSelection, + AudioFile *audioFile): + KNamedCommand(getGlobalName()), + m_composition(comp), + m_selection(inputSelection), + m_audioFile(audioFile), + m_audioSegment(0), + m_executed(false) +{} + +AudioSegmentDistributeCommand::~AudioSegmentDistributeCommand() +{ + if (m_executed) { + for (SegmentSelection::iterator i = m_selection.begin(); + i != m_selection.end(); ++i) { + delete *i; + } + } else { + for (unsigned int i = 0; i < m_newSegments.size(); ++i) + delete m_newSegments[i]; + } +} + +void +AudioSegmentDistributeCommand::execute() +{ + // Store the insert times in a local vector + // + std::vector insertTimes; + + bool addNew = m_newSegments.size() == 0 ? true : false; + + for (SegmentSelection::iterator i = m_selection.begin(); + i != m_selection.end(); ++i) { + // For MIDI (Internal) Segments only of course + // + if ((*i)->getType() == Segment::Internal) { + if (addNew) { + for (Segment::iterator it = (*i)->begin(); + it != (*i)->end(); ++it) { + if ((*it)->isa(Note::EventType)) { + Segment *segment = + new Segment( + Segment::Audio, + (*it)->getAbsoluteTime()); + segment->setTrack((*i)->getTrack()); + + // If we've constructed against an AudioFile + // + if (m_audioFile) { + segment->setAudioFileId(m_audioFile->getId()); + segment->setAudioStartTime( + RealTime::zeroTime); + segment->setAudioEndTime( + m_audioFile->getLength()); + } else // or an audio Segment + { + segment->setAudioFileId( + m_audioSegment->getAudioFileId()); + segment->setAudioStartTime( + m_audioSegment->getAudioStartTime()); + segment->setAudioEndTime( + m_audioSegment->getAudioEndTime()); + } + + m_composition->addSegment(segment); + m_newSegments.push_back(segment); + } + } + } + + // Detach original Segment + // + m_composition->detachSegment(*i); + } + + } + + if (!addNew && m_newSegments.size()) { + // Reattach new segments + // + for (unsigned int i = 0; i < m_newSegments.size(); ++i) + m_composition->addSegment(m_newSegments[i]); + } + + m_executed = true; +} + +void +AudioSegmentDistributeCommand::unexecute() +{ + for (unsigned int i = 0; i < m_newSegments.size(); ++i) + m_composition->detachSegment(m_newSegments[i]); + + for (SegmentSelection::iterator it = m_selection.begin(); + it != m_selection.end(); ++it) + m_composition->addSegment(*it); + + m_executed = false; +} + +} diff --git a/src/commands/segment/AudioSegmentDistributeCommand.h b/src/commands/segment/AudioSegmentDistributeCommand.h new file mode 100644 index 0000000..d325daf --- /dev/null +++ b/src/commands/segment/AudioSegmentDistributeCommand.h @@ -0,0 +1,86 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_AUDIOSEGMENTDISTRIBUTECOMMAND_H_ +#define _RG_AUDIOSEGMENTDISTRIBUTECOMMAND_H_ + +#include "base/Selection.h" +#include +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Segment; +class Composition; +class AudioFile; + + +/** + * AudioSegmentDistributeCommand - Distribute an Audio Segment triggered + * against the MIDI Note ons in a SegmentSelection. + * + * (I think this is actually unused --cc) + */ +class AudioSegmentDistributeCommand : public KNamedCommand +{ +public: + AudioSegmentDistributeCommand(Composition *comp, + SegmentSelection &inputSelection, + Segment *audioSegment); + + AudioSegmentDistributeCommand(Composition *comp, + SegmentSelection &inputSelection, + AudioFile *audioFile); + + virtual ~AudioSegmentDistributeCommand(); + + static QString getGlobalName() + { return i18n("Distribute Audio Segments over MIDI"); } + + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + SegmentSelection m_selection; + AudioFile *m_audioFile; + Segment *m_audioSegment; + std::vector m_newSegments; + bool m_executed; + +}; + + + +} + +#endif diff --git a/src/commands/segment/AudioSegmentInsertCommand.cpp b/src/commands/segment/AudioSegmentInsertCommand.cpp new file mode 100644 index 0000000..b5167f7 --- /dev/null +++ b/src/commands/segment/AudioSegmentInsertCommand.cpp @@ -0,0 +1,136 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AudioSegmentInsertCommand.h" + +#include +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Composition.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/general/GUIPalette.h" +#include "sound/AudioFile.h" +#include "sound/AudioFileManager.h" + + +namespace Rosegarden +{ + +AudioSegmentInsertCommand::AudioSegmentInsertCommand(RosegardenGUIDoc *doc, + TrackId track, + timeT startTime, + AudioFileId audioFileId, + const RealTime &audioStartTime, + const RealTime &audioEndTime): + KNamedCommand(i18n("Create Segment")), + m_composition(&(doc->getComposition())), + m_audioFileManager(&(doc->getAudioFileManager())), + m_segment(0), + m_track(track), + m_startTime(startTime), + m_audioFileId(audioFileId), + m_audioStartTime(audioStartTime), + m_audioEndTime(audioEndTime), + m_detached(false) +{} + +AudioSegmentInsertCommand::~AudioSegmentInsertCommand() +{ + if (m_detached) { + delete m_segment; + } +} + +void +AudioSegmentInsertCommand::execute() +{ + if (!m_segment) { + // Create and insert Segment + // + m_segment = new Segment(Segment::Audio); + m_segment->setTrack(m_track); + m_segment->setStartTime(m_startTime); + m_segment->setAudioStartTime(m_audioStartTime); + m_segment->setAudioEndTime(m_audioEndTime); + m_segment->setAudioFileId(m_audioFileId); + + // Set color for audio segment (DMM) + // + m_segment->setColourIndex(GUIPalette::AudioDefaultIndex); + + // Calculate end time + // + RealTime startTime = + m_composition->getElapsedRealTime(m_startTime); + + RealTime endTime = + startTime + m_audioEndTime - m_audioStartTime; + + timeT endTimeT = m_composition->getElapsedTimeForRealTime(endTime); + + RG_DEBUG << "AudioSegmentInsertCommand::execute : start timeT " + << m_startTime << ", startTime " << startTime << ", audioStartTime " << m_audioStartTime << ", audioEndTime " << m_audioEndTime << ", endTime " << endTime << ", end timeT " << endTimeT << endl; + + m_segment->setEndTime(endTimeT); + + if (endTimeT > m_composition->getEndMarker()) { + m_composition->setEndMarker(m_composition->getBarEndForTime(endTimeT)); + } + + // Label by audio file name + // + std::string label = ""; + + AudioFile *aF = + m_audioFileManager->getAudioFile(m_audioFileId); + + if (aF) + label = qstrtostr(i18n("%1 (inserted)").arg + (strtoqstr(aF->getName()))); + else + label = qstrtostr(i18n("unknown audio file")); + + m_segment->setLabel(label); + + m_composition->addSegment(m_segment); + } else { + m_composition->addSegment(m_segment); + } + + m_detached = false; +} + +void +AudioSegmentInsertCommand::unexecute() +{ + m_composition->detachSegment(m_segment); + m_detached = true; +} + +} diff --git a/src/commands/segment/AudioSegmentInsertCommand.h b/src/commands/segment/AudioSegmentInsertCommand.h new file mode 100644 index 0000000..3510833 --- /dev/null +++ b/src/commands/segment/AudioSegmentInsertCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_AUDIOSEGMENTINSERTCOMMAND_H_ +#define _RG_AUDIOSEGMENTINSERTCOMMAND_H_ + +#include "base/RealTime.h" +#include "base/Track.h" +#include "sound/AudioFile.h" +#include +#include "base/Event.h" + + +namespace Rosegarden +{ + +class Studio; +class Segment; +class RosegardenGUIDoc; +class Composition; +class AudioFileManager; + + +class AudioSegmentInsertCommand : public KNamedCommand +{ +public: + AudioSegmentInsertCommand(RosegardenGUIDoc *doc, + TrackId track, + timeT startTime, + AudioFileId audioFileId, + const RealTime &audioStartTime, + const RealTime &audioEndTime); + virtual ~AudioSegmentInsertCommand(); + + Segment *getNewSegment() { return m_segment; } + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + AudioFileManager *m_audioFileManager; + Segment *m_segment; + int m_track; + timeT m_startTime; + AudioFileId m_audioFileId; + RealTime m_audioStartTime; + RealTime m_audioEndTime; + bool m_detached; +}; + + +} + +#endif diff --git a/src/commands/segment/AudioSegmentRescaleCommand.cpp b/src/commands/segment/AudioSegmentRescaleCommand.cpp new file mode 100644 index 0000000..1386783 --- /dev/null +++ b/src/commands/segment/AudioSegmentRescaleCommand.cpp @@ -0,0 +1,210 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AudioSegmentRescaleCommand.h" + +#include "misc/Strings.h" +#include "base/Event.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "document/RosegardenGUIDoc.h" +#include "sound/AudioFileTimeStretcher.h" +#include "sound/AudioFileManager.h" +#include "gui/widgets/ProgressDialog.h" +#include + + +namespace Rosegarden +{ + +AudioSegmentRescaleCommand::AudioSegmentRescaleCommand(RosegardenGUIDoc *doc, + Segment *s, + float ratio) : + KNamedCommand(getGlobalName()), + m_afm(&doc->getAudioFileManager()), + m_stretcher(new AudioFileTimeStretcher(m_afm)), + m_segment(s), + m_newSegment(0), + m_fid(-1), + m_timesGiven(false), + m_startTime(0), + m_endMarkerTime(0), + m_ratio(ratio), + m_detached(false) +{ + // nothing +} + +AudioSegmentRescaleCommand::AudioSegmentRescaleCommand(RosegardenGUIDoc *doc, + Segment *s, + float ratio, + timeT st, + timeT emt) : + KNamedCommand(getGlobalName()), + m_afm(&doc->getAudioFileManager()), + m_stretcher(new AudioFileTimeStretcher(m_afm)), + m_segment(s), + m_newSegment(0), + m_fid(-1), + m_timesGiven(true), + m_startTime(st), + m_endMarkerTime(emt), + m_ratio(ratio), + m_detached(false) +{ + // nothing +} + +AudioSegmentRescaleCommand::~AudioSegmentRescaleCommand() +{ + delete m_stretcher; + + if (m_detached) { + delete m_segment; + } else { + delete m_newSegment; + } +} + +void +AudioSegmentRescaleCommand::connectProgressDialog(ProgressDialog *dlg) +{ + QObject::connect(m_stretcher, SIGNAL(setProgress(int)), + dlg->progressBar(), SLOT(setValue(int))); + QObject::connect(dlg, SIGNAL(cancelClicked()), + m_stretcher, SLOT(slotStopTimestretch())); +} + +void +AudioSegmentRescaleCommand::disconnectProgressDialog(ProgressDialog *dlg) +{ + QObject::disconnect(m_stretcher, SIGNAL(setProgress(int)), + dlg->progressBar(), SLOT(setValue(int))); + QObject::disconnect(dlg, SIGNAL(cancelClicked()), + m_stretcher, SLOT(slotStopTimestretch())); +} + +void +AudioSegmentRescaleCommand::execute() +{ + timeT startTime = m_segment->getStartTime(); + + if (m_segment->getType() != Segment::Audio) { + return; + } + + bool failed = false; + + if (!m_newSegment) { + + m_newSegment = new Segment(*m_segment); + + QString oldLabel = strtoqstr(m_segment->getLabel()); + if (!oldLabel.endsWith(i18n("(rescaled)"))) { + m_newSegment->setLabel(qstrtostr(i18n("%1 (rescaled)").arg + (oldLabel))); + } + + AudioFileId sourceFileId = m_segment->getAudioFileId(); + float absoluteRatio = m_ratio; + + std::cerr << "AudioSegmentRescaleCommand: segment file id " << sourceFileId << ", given ratio " << m_ratio << std::endl; + + if (m_segment->getStretchRatio() != 1.f && + m_segment->getStretchRatio() != 0.f) { + sourceFileId = m_segment->getUnstretchedFileId(); + absoluteRatio *= m_segment->getStretchRatio(); + std::cerr << "AudioSegmentRescaleCommand: unstretched file id " << sourceFileId << ", prev ratio " << m_segment->getStretchRatio() << ", resulting ratio " << absoluteRatio << std::endl; + } + + if (!m_timesGiven) { + m_endMarkerTime = m_segment->getStartTime() + + (m_segment->getEndMarkerTime() - m_segment->getStartTime()) * m_ratio; + } + + try { + m_fid = m_stretcher->getStretchedAudioFile(sourceFileId, + absoluteRatio); + m_newSegment->setAudioFileId(m_fid); + m_newSegment->setUnstretchedFileId(sourceFileId); + m_newSegment->setStretchRatio(absoluteRatio); + m_newSegment->setAudioStartTime(m_segment->getAudioStartTime() * + m_ratio); + if (m_timesGiven) { + m_newSegment->setStartTime(m_startTime); + m_newSegment->setAudioEndTime(m_segment->getAudioEndTime() * + m_ratio); + m_newSegment->setEndMarkerTime(m_endMarkerTime); + } else { + m_newSegment->setEndMarkerTime(m_endMarkerTime); + m_newSegment->setAudioEndTime(m_segment->getAudioEndTime() * + m_ratio); + } + } catch (SoundFile::BadSoundFileException e) { + std::cerr << "AudioSegmentRescaleCommand: ERROR: BadSoundFileException: " + << e.getMessage() << std::endl; + delete m_newSegment; + m_newSegment = 0; + m_fid = -1; + failed = true; + } catch (AudioFileManager::BadAudioPathException e) { + std::cerr << "AudioSegmentRescaleCommand: ERROR: BadAudioPathException: " + << e.getMessage() << std::endl; + delete m_newSegment; + m_newSegment = 0; + m_fid = -1; + failed = true; + } catch (AudioFileTimeStretcher::CancelledException e) { + std::cerr << "AudioSegmentRescaleCommand: ERROR: Rescale cancelled" << std::endl; + delete m_newSegment; + m_newSegment = 0; + m_fid = -1; + failed = true; + } + } + + if (failed) return; + + m_segment->getComposition()->addSegment(m_newSegment); + m_segment->getComposition()->detachSegment(m_segment); + +// m_newSegment->setEndMarkerTime +// (startTime + rescale(m_segment->getEndMarkerTime() - startTime)); + + m_detached = true; +} + +void +AudioSegmentRescaleCommand::unexecute() +{ + if (m_newSegment) { + m_newSegment->getComposition()->addSegment(m_segment); + m_newSegment->getComposition()->detachSegment(m_newSegment); + m_detached = false; + } +} + +} diff --git a/src/commands/segment/AudioSegmentRescaleCommand.h b/src/commands/segment/AudioSegmentRescaleCommand.h new file mode 100644 index 0000000..a4edb13 --- /dev/null +++ b/src/commands/segment/AudioSegmentRescaleCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_AUDIOSEGMENTRESCALECOMMAND_H_ +#define _RG_AUDIOSEGMENTRESCALECOMMAND_H_ + +#include +#include +#include "base/Event.h" +#include + +namespace Rosegarden +{ + +class Segment; +class AudioFileManager; +class AudioFileTimeStretcher; +class RosegardenGUIDoc; +class ProgressDialog; + +class AudioSegmentRescaleCommand : public KNamedCommand +{ +public: + AudioSegmentRescaleCommand(RosegardenGUIDoc *doc, + Segment *segment, float ratio); + AudioSegmentRescaleCommand(RosegardenGUIDoc *doc, + Segment *segment, float ratio, + timeT newStartTime, + timeT newEndMarkerTime); + virtual ~AudioSegmentRescaleCommand(); + + virtual void execute(); + virtual void unexecute(); + + AudioFileTimeStretcher *getStretcher() { return m_stretcher; } + int getNewAudioFileId() const { return m_fid; } + + void connectProgressDialog(ProgressDialog *dlg); + void disconnectProgressDialog(ProgressDialog *dlg); + + static QString getGlobalName() { return i18n("Stretch or S&quash..."); } + +private: + AudioFileManager *m_afm; + AudioFileTimeStretcher *m_stretcher; + Segment *m_segment; + Segment *m_newSegment; + bool m_timesGiven; + timeT m_startTime; + timeT m_endMarkerTime; + int m_fid; + float m_ratio; + bool m_detached; +}; + + + +} + +#endif diff --git a/src/commands/segment/AudioSegmentResizeFromStartCommand.cpp b/src/commands/segment/AudioSegmentResizeFromStartCommand.cpp new file mode 100644 index 0000000..5045500 --- /dev/null +++ b/src/commands/segment/AudioSegmentResizeFromStartCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AudioSegmentResizeFromStartCommand.h" + +#include +#include "base/Composition.h" +#include "base/RealTime.h" +#include "base/Segment.h" + + +namespace Rosegarden +{ + +AudioSegmentResizeFromStartCommand::AudioSegmentResizeFromStartCommand(Segment *segment, + timeT newStartTime) : + KNamedCommand(i18n("Resize Segment")), + m_segment(segment), + m_newSegment(0), + m_detached(false), + m_oldStartTime(segment->getStartTime()), + m_newStartTime(newStartTime) +{} + +AudioSegmentResizeFromStartCommand::~AudioSegmentResizeFromStartCommand() +{ + if (!m_detached) + delete m_segment; + else + delete m_newSegment; +} + +void +AudioSegmentResizeFromStartCommand::execute() +{ + Composition *c = m_segment->getComposition(); + + if (!m_newSegment) { + RealTime oldRT = c->getElapsedRealTime(m_oldStartTime); + RealTime newRT = c->getElapsedRealTime(m_newStartTime); + + m_newSegment = new Segment(*m_segment); + m_newSegment->setStartTime(m_newStartTime); + m_newSegment->setAudioStartTime(m_segment->getAudioStartTime() - + (oldRT - newRT)); + } + + c->addSegment(m_newSegment); + m_newSegment->setEndMarkerTime(m_segment->getEndMarkerTime()); + c->detachSegment(m_segment); + + m_detached = false; +} + +void +AudioSegmentResizeFromStartCommand::unexecute() +{ + Composition *c = m_newSegment->getComposition(); + c->addSegment(m_segment); + c->detachSegment(m_newSegment); + + m_detached = true; +} + +} diff --git a/src/commands/segment/AudioSegmentResizeFromStartCommand.h b/src/commands/segment/AudioSegmentResizeFromStartCommand.h new file mode 100644 index 0000000..734a6e0 --- /dev/null +++ b/src/commands/segment/AudioSegmentResizeFromStartCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_AUDIOSEGMENTRESIZEFROMSTARTCOMMAND_H_ +#define _RG_AUDIOSEGMENTRESIZEFROMSTARTCOMMAND_H_ + +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; + + +/** + * As above, but for audio segments. + */ +class AudioSegmentResizeFromStartCommand : public KNamedCommand +{ +public: + AudioSegmentResizeFromStartCommand(Segment *segment, + timeT newStartTime); + virtual ~AudioSegmentResizeFromStartCommand(); + + virtual void execute(); + virtual void unexecute(); + +private: + Segment *m_segment; + Segment *m_newSegment; + bool m_detached; + timeT m_oldStartTime; + timeT m_newStartTime; +}; + + + +} + +#endif diff --git a/src/commands/segment/AudioSegmentSplitCommand.cpp b/src/commands/segment/AudioSegmentSplitCommand.cpp new file mode 100644 index 0000000..f0c462e --- /dev/null +++ b/src/commands/segment/AudioSegmentSplitCommand.cpp @@ -0,0 +1,155 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AudioSegmentSplitCommand.h" + +#include +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/RealTime.h" +#include "base/Composition.h" +#include "base/Segment.h" +#include + + +namespace Rosegarden +{ + +AudioSegmentSplitCommand::AudioSegmentSplitCommand(Segment *segment, + timeT splitTime) : + KNamedCommand(i18n("Split Audio Segment")), + m_segment(segment), + m_newSegment(0), + m_splitTime(splitTime), + m_previousEndMarkerTime(0), + m_detached(false) +{} + +AudioSegmentSplitCommand::~AudioSegmentSplitCommand() +{ + if (m_detached) { + delete m_newSegment; + } + delete m_previousEndMarkerTime; +} + +void +AudioSegmentSplitCommand::execute() +{ + if (!m_newSegment) { + + m_newSegment = new Segment(Segment::Audio); + + // Basics + // + m_newSegment->setAudioFileId(m_segment->getAudioFileId()); + m_newSegment->setTrack(m_segment->getTrack()); + + // Get the RealTime split time + // + RealTime splitDiff = + m_segment->getComposition()->getRealTimeDifference( + m_segment->getStartTime(), m_splitTime); + + // Set audio start and end + // + m_newSegment->setAudioStartTime + (m_segment->getAudioStartTime() + splitDiff); + m_newSegment->setAudioEndTime(m_segment->getAudioEndTime()); + + // Insert into composition before setting end time + // + m_segment->getComposition()->addSegment(m_newSegment); + + // Set start and end times + // + m_newSegment->setStartTime(m_splitTime); + m_newSegment->setEndTime(m_segment->getEndTime()); + + // Set original end time + // + // m_previousEndAudioTime = m_segment->getAudioEndTime(); + // m_segment->setAudioEndTime(m_newSegment->getAudioStartTime()); + + RG_DEBUG << "AudioSegmentSplitCommand::execute: Set end audio of left segment to " << m_newSegment->getAudioStartTime() << endl; + + + // Set labels + // + m_segmentLabel = m_segment->getLabel(); + QString newLabel = strtoqstr(m_segmentLabel); + if (!newLabel.endsWith(i18n(" (split)"))) { + newLabel = i18n("%1 (split)").arg(newLabel); + } + m_segment->setLabel(qstrtostr(newLabel)); + m_newSegment->setLabel(m_segment->getLabel()); + + // Set color + // + m_newSegment->setColourIndex(m_segment->getColourIndex()); + } + + // Resize left hand Segment + // + const timeT *emt = m_segment->getRawEndMarkerTime(); + if (emt) { + m_previousEndMarkerTime = new timeT(*emt); + } else { + m_previousEndMarkerTime = 0; + } + + RG_DEBUG << "AudioSegmentSplitCommand::execute: Setting end marker of left segment to " << m_splitTime << endl; + + m_segment->setEndMarkerTime(m_splitTime); + + if (!m_newSegment->getComposition()) { + m_segment->getComposition()->addSegment(m_newSegment); + } + + m_detached = false; + +} + +void +AudioSegmentSplitCommand::unexecute() +{ + if (m_previousEndMarkerTime) { + RG_DEBUG << "AudioSegmentSplitCommand::unexecute: Restoring end marker of left segment to " << *m_previousEndMarkerTime << endl; + + m_segment->setEndMarkerTime(*m_previousEndMarkerTime); + delete m_previousEndMarkerTime; + m_previousEndMarkerTime = 0; + } else { + m_segment->clearEndMarker(); + } + + m_segment->setLabel(m_segmentLabel); + // RG_DEBUG << "AudioSegmentSplitCommand::unexecute: Setting audio end time of left segment to " << m_previousEndAudioTime << endl; + // m_segment->setAudioEndTime(m_previousEndAudioTime); + m_segment->getComposition()->detachSegment(m_newSegment); + m_detached = true; +} + +} diff --git a/src/commands/segment/AudioSegmentSplitCommand.h b/src/commands/segment/AudioSegmentSplitCommand.h new file mode 100644 index 0000000..ce7c59c --- /dev/null +++ b/src/commands/segment/AudioSegmentSplitCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_AUDIOSEGMENTSPLITCOMMAND_H_ +#define _RG_AUDIOSEGMENTSPLITCOMMAND_H_ + +#include +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; + + +class AudioSegmentSplitCommand : public KNamedCommand +{ +public: + AudioSegmentSplitCommand(Segment *segment, + timeT splitTime); + virtual ~AudioSegmentSplitCommand(); + + virtual void execute(); + virtual void unexecute(); + +private: + Segment *m_segment; + Segment *m_newSegment; + timeT m_splitTime; + timeT *m_previousEndMarkerTime; + bool m_detached; + std::string m_segmentLabel; +// RealTime m_previousEndAudioTime; +}; + + +} + +#endif diff --git a/src/commands/segment/ChangeCompositionLengthCommand.cpp b/src/commands/segment/ChangeCompositionLengthCommand.cpp new file mode 100644 index 0000000..bdeb7a5 --- /dev/null +++ b/src/commands/segment/ChangeCompositionLengthCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ChangeCompositionLengthCommand.h" + +#include "base/Composition.h" +#include + + +namespace Rosegarden +{ + +ChangeCompositionLengthCommand::ChangeCompositionLengthCommand( + Composition *composition, + timeT startTime, + timeT endTime): + KNamedCommand(getGlobalName()), + m_composition(composition), + m_startTime(startTime), + m_endTime(endTime), + m_oldStartTime(m_composition->getStartMarker()), + m_oldEndTime(m_composition->getEndMarker()) +{} + +ChangeCompositionLengthCommand::~ChangeCompositionLengthCommand() +{} + +void +ChangeCompositionLengthCommand::execute() +{ + m_composition->setStartMarker(m_startTime); + m_composition->setEndMarker(m_endTime); +} + +void +ChangeCompositionLengthCommand::unexecute() +{ + m_composition->setStartMarker(m_oldStartTime); + m_composition->setEndMarker(m_oldEndTime); +} + +} diff --git a/src/commands/segment/ChangeCompositionLengthCommand.h b/src/commands/segment/ChangeCompositionLengthCommand.h new file mode 100644 index 0000000..9e0db9b --- /dev/null +++ b/src/commands/segment/ChangeCompositionLengthCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CHANGECOMPOSITIONLENGTHCOMMAND_H_ +#define _RG_CHANGECOMPOSITIONLENGTHCOMMAND_H_ + +#include +#include +#include "base/Event.h" +#include + + +class Change; + + +namespace Rosegarden +{ + +class Composition; + + +class ChangeCompositionLengthCommand : public KNamedCommand +{ +public: + ChangeCompositionLengthCommand(Composition *composition, + timeT startTime, + timeT endTime); + virtual ~ChangeCompositionLengthCommand(); + + static QString getGlobalName() + { return i18n("Change &Composition Start and End..."); } + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + timeT m_startTime; + timeT m_endTime; + timeT m_oldStartTime; + timeT m_oldEndTime; + +}; + + +} + +#endif diff --git a/src/commands/segment/CreateTempoMapFromSegmentCommand.cpp b/src/commands/segment/CreateTempoMapFromSegmentCommand.cpp new file mode 100644 index 0000000..e548875 --- /dev/null +++ b/src/commands/segment/CreateTempoMapFromSegmentCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CreateTempoMapFromSegmentCommand.h" + +#include +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/RealTime.h" +#include "base/Segment.h" + + +namespace Rosegarden +{ + +CreateTempoMapFromSegmentCommand::CreateTempoMapFromSegmentCommand(Segment *groove) : + KNamedCommand(i18n("Set Tempos from Beat Segment")), + m_composition(groove->getComposition()) +{ + initialise(groove); +} + +CreateTempoMapFromSegmentCommand::~CreateTempoMapFromSegmentCommand() +{ + // nothing +} + +void +CreateTempoMapFromSegmentCommand::execute() +{ + for (TempoMap::iterator i = m_oldTempi.begin(); i != m_oldTempi.end(); ++i) { + int n = m_composition->getTempoChangeNumberAt(i->first); + if (n < m_composition->getTempoChangeCount()) { + m_composition->removeTempoChange(n); + } + } + + for (TempoMap::iterator i = m_newTempi.begin(); i != m_newTempi.end(); ++i) { + m_composition->addTempoAtTime(i->first, i->second); + } +} + +void +CreateTempoMapFromSegmentCommand::unexecute() +{ + for (TempoMap::iterator i = m_newTempi.begin(); i != m_newTempi.end(); ++i) { + int n = m_composition->getTempoChangeNumberAt(i->first); + if (n < m_composition->getTempoChangeCount()) { + m_composition->removeTempoChange(n); + } + } + + for (TempoMap::iterator i = m_oldTempi.begin(); i != m_oldTempi.end(); ++i) { + m_composition->addTempoAtTime(i->first, i->second); + } +} + +void +CreateTempoMapFromSegmentCommand::initialise(Segment *s) +{ + m_oldTempi.clear(); + m_newTempi.clear(); + + //!!! need an additional option: per-chord, per-beat, per-bar. + // Let's work per-beat for the moment. Even for this, we should + // probably use TimeSignature.getDivisions() + + std::vector beatTimeTs; + std::vector beatRealTimes; + + int startBar = m_composition->getBarNumber(s->getStartTime()); + int barNo = startBar; + int beat = 0; + + for (Segment::iterator i = s->begin(); s->isBeforeEndMarker(i); ++i) { + if ((*i)->isa(Note::EventType)) { + + bool isNew; + TimeSignature sig = + m_composition->getTimeSignatureInBar(barNo, isNew); + + beatTimeTs.push_back(m_composition->getBarStart(barNo) + + beat * sig.getBeatDuration()); + + if (++beat >= sig.getBeatsPerBar()) { + ++barNo; + beat = 0; + } + + beatRealTimes.push_back(s->getComposition()->getElapsedRealTime + ((*i)->getAbsoluteTime())); + } + } + + if (beatTimeTs.size() < 2) + return ; + + tempoT prevTempo = 0; + + // set up m_oldTempi and prevTempo + + for (int i = m_composition->getTempoChangeNumberAt(*beatTimeTs.begin() - 1) + 1; + i <= m_composition->getTempoChangeNumberAt(*beatTimeTs.end() - 1); ++i) { + + std::pair tempoChange = + m_composition->getTempoChange(i); + m_oldTempi[tempoChange.first] = tempoChange.second; + if (prevTempo == 0) + prevTempo = tempoChange.second; + } + + RG_DEBUG << "starting tempo: " << prevTempo << endl; + + timeT quarter = Note(Note::Crotchet).getDuration(); + + for (int beat = 1; beat < beatTimeTs.size(); ++beat) { + + timeT beatTime = beatTimeTs[beat] - beatTimeTs[beat - 1]; + RealTime beatRealTime = beatRealTimes[beat] - beatRealTimes[beat - 1]; + + // Calculate tempo to nearest qpm. + // This is 60 / {quarter note duration in seconds} + // = 60 / ( {beat in seconds} * {quarter in ticks} / { beat in ticks} ) + // = ( 60 * {beat in ticks} ) / ( {beat in seconds} * {quarter in ticks} ) + // Precision is deliberately limited to qpm to avoid silly values. + + double beatSec = double(beatRealTime.sec) + + double(beatRealTime.usec() / 1000000.0); + double qpm = (60.0 * beatTime) / (beatSec * quarter); + tempoT tempo = Composition::getTempoForQpm(int(qpm + 0.001)); + + RG_DEBUG << "prev beat: " << beatTimeTs[beat] << ", prev beat real time " << beatRealTimes[beat] << endl; + RG_DEBUG << "time " << beatTime << ", rt " << beatRealTime << ", beatSec " << beatSec << ", tempo " << tempo << endl; + + if (tempo != prevTempo) { + m_newTempi[beatTimeTs[beat - 1]] = tempo; + prevTempo = tempo; + } + } + +} + +} diff --git a/src/commands/segment/CreateTempoMapFromSegmentCommand.h b/src/commands/segment/CreateTempoMapFromSegmentCommand.h new file mode 100644 index 0000000..f6ea4d5 --- /dev/null +++ b/src/commands/segment/CreateTempoMapFromSegmentCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CREATETEMPOMAPFROMSEGMENTCOMMAND_H_ +#define _RG_CREATETEMPOMAPFROMSEGMENTCOMMAND_H_ + +#include +#include +#include "base/Event.h" +#include "base/Composition.h" // for tempoT + + + + +namespace Rosegarden +{ + + +/** + * CreateTempoMapFromSegment applies timings found in a reference + * segment to the composition as a whole via the tempo map. + */ + +class CreateTempoMapFromSegmentCommand : public KNamedCommand +{ +public: + CreateTempoMapFromSegmentCommand(Segment *grooveSegment); + virtual ~CreateTempoMapFromSegmentCommand(); + + virtual void execute(); + virtual void unexecute(); + +private: + void initialise(Segment *s); + + Composition *m_composition; + + typedef std::map TempoMap; + TempoMap m_oldTempi; + TempoMap m_newTempi; +}; + + + +} + +#endif diff --git a/src/commands/segment/CutRangeCommand.cpp b/src/commands/segment/CutRangeCommand.cpp new file mode 100644 index 0000000..df91398 --- /dev/null +++ b/src/commands/segment/CutRangeCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CutRangeCommand.h" + +#include +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "commands/edit/CopyCommand.h" +#include "DeleteRangeCommand.h" + + +namespace Rosegarden +{ + +CutRangeCommand::CutRangeCommand(Composition *composition, + timeT t0, timeT t1, + Clipboard *clipboard) : + KMacroCommand(i18n("Cut Range")) +{ + addCommand(new CopyCommand(composition, t0, t1, clipboard)); + addCommand(new DeleteRangeCommand(composition, t0, t1)); +} + +} diff --git a/src/commands/segment/CutRangeCommand.h b/src/commands/segment/CutRangeCommand.h new file mode 100644 index 0000000..f2b3402 --- /dev/null +++ b/src/commands/segment/CutRangeCommand.h @@ -0,0 +1,53 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CUTRANGECOMMAND_H_ +#define _RG_CUTRANGECOMMAND_H_ + +#include "base/Event.h" +#include + + +namespace Rosegarden +{ + +class Composition; +class Clipboard; + + +class CutRangeCommand : public KMacroCommand +{ +public: + CutRangeCommand(Composition *composition, + timeT begin, + timeT end, + Clipboard *clipboard); +}; + + + +} + +#endif diff --git a/src/commands/segment/DeleteRangeCommand.cpp b/src/commands/segment/DeleteRangeCommand.cpp new file mode 100644 index 0000000..05ec79f --- /dev/null +++ b/src/commands/segment/DeleteRangeCommand.cpp @@ -0,0 +1,127 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "DeleteRangeCommand.h" + +#include "AudioSegmentSplitCommand.h" +#include "base/Composition.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "EraseSegmentsStartingInRangeCommand.h" +#include "OpenOrCloseRangeCommand.h" +#include "SegmentJoinCommand.h" +#include "SegmentSplitCommand.h" + + +namespace Rosegarden +{ + +DeleteRangeCommand::DeleteRangeCommand(Composition *composition, + timeT t0, timeT t1) : + KMacroCommand(i18n("Delete Range")) +{ + // First add commands to split the segments up. Make a note of + // segments that will need rejoining with their neighbours + // afterwards. + + std::vector rejoins; + + for (int e = 0; e < 2; ++e) { + + // Split all segments at the range end first, then the range + // begin afterwards. This is because the split commands create + // new segments for the right part and leave the left parts in + // the original segments, so that we can use the same segment + // pointer to do the left split as we did for the right + + timeT t = t1; + if (e == 1) + t = t0; + + for (Composition::iterator i = composition->begin(); + i != composition->end(); ++i) { + + if ((*i)->getStartTime() >= t || (*i)->getEndMarkerTime() <= t) { + continue; + } + + if ((*i)->getType() == Segment::Audio) { + addCommand(new AudioSegmentSplitCommand(*i, t)); + } else { + addCommand(new SegmentSplitCommand(*i, t)); + + if (t == t0 && (*i)->getEndMarkerTime() > t1) { + rejoins.push_back(*i); + } + } + } + } + + // Then commands to do the rest of the work + + addCommand(new EraseSegmentsStartingInRangeCommand(composition, t0, t1)); + + addCommand(new OpenOrCloseRangeCommand(composition, t0, t1, false)); + + for (std::vector::iterator i = rejoins.begin(); + i != rejoins.end(); ++i) { + addCommand(new RejoinCommand(composition, *i, + (*i)->getEndMarkerTime() + t0 - t1)); + } +} + +DeleteRangeCommand::~DeleteRangeCommand() +{} + +void +DeleteRangeCommand::RejoinCommand::execute() +{ + if (m_joinCommand) { + m_joinCommand->execute(); + return ; + } + + //!!! Need to remove the "(split)" names from the segment bits + + for (Composition::iterator i = m_composition->begin(); + i != m_composition->end(); ++i) { + if ((*i) == m_segment) + continue; + if ((*i)->getTrack() != m_segment->getTrack()) + continue; + if ((*i)->getEndMarkerTime() != m_endMarkerTime) + continue; + if ((*i)->getStartTime() <= m_segment->getStartTime()) + continue; + SegmentSelection selection; + selection.insert(m_segment); + selection.insert(*i); + m_joinCommand = new SegmentJoinCommand(selection); + m_joinCommand->execute(); + break; + } +} + +} diff --git a/src/commands/segment/DeleteRangeCommand.h b/src/commands/segment/DeleteRangeCommand.h new file mode 100644 index 0000000..6b198ea --- /dev/null +++ b/src/commands/segment/DeleteRangeCommand.h @@ -0,0 +1,84 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_DELETERANGECOMMAND_H_ +#define _RG_DELETERANGECOMMAND_H_ + +#include +#include "base/Event.h" +#include + +#include "SegmentJoinCommand.h" + + +namespace Rosegarden +{ + +class Segment; +class Composition; + + +class DeleteRangeCommand : public KMacroCommand +{ +public: + DeleteRangeCommand(Composition *composition, + timeT begin, + timeT end); + virtual ~DeleteRangeCommand(); + +private: + class RejoinCommand : public KNamedCommand + { + public: + // This command rejoins s on to a subsequent segment on the same + // track that ends at endMarkerTime (presumably the original end + // marker time of s, with the range duration subtracted). + + RejoinCommand(Composition *c, + Segment *s, + timeT endMarkerTime) : + KNamedCommand(i18n("Rejoin Command")), + m_composition(c), m_segment(s), m_endMarkerTime(endMarkerTime), + m_joinCommand(0) { } + + ~RejoinCommand() { delete m_joinCommand; } + + void execute(); + void unexecute() { if (m_joinCommand) m_joinCommand->unexecute(); } + + private: + Composition *m_composition; + Segment *m_segment; + timeT m_endMarkerTime; + + SegmentJoinCommand *m_joinCommand; + }; +}; + + + +} + +#endif diff --git a/src/commands/segment/DeleteTracksCommand.cpp b/src/commands/segment/DeleteTracksCommand.cpp new file mode 100644 index 0000000..bad2be0 --- /dev/null +++ b/src/commands/segment/DeleteTracksCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "DeleteTracksCommand.h" + +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/Segment.h" +#include + + +namespace Rosegarden +{ + +DeleteTracksCommand::DeleteTracksCommand(Composition *composition, + std::vector tracks): + KNamedCommand(getGlobalName()), + m_composition(composition), + m_tracks(tracks), + m_detached(false) +{} + +DeleteTracksCommand::~DeleteTracksCommand() +{ + if (m_detached) { + for (unsigned int i = 0; i < m_oldTracks.size(); ++i) + delete m_oldTracks[i]; + + for (unsigned int i = 0; i < m_oldSegments.size(); ++i) + delete m_oldSegments[i]; + + m_oldTracks.clear(); + m_oldSegments.clear(); + } +} + +void DeleteTracksCommand::execute() +{ + Track *track = 0; + const Composition::segmentcontainer &segments = + m_composition->getSegments(); + + //cout << "DeleteTracksCommand::execute()" << endl; + + m_oldSegments.clear(); + m_oldTracks.clear(); + + // Remap positions and track numbers + // + + Composition::trackiterator tit; + Composition::trackcontainer + &tracks = m_composition->getTracks(); + + for (unsigned int i = 0; i < m_tracks.size(); ++i) { + // detach segments and store tracks somewhere + track = m_composition->getTrackById(m_tracks[i]); + + if (track) { + // detach all segments for that track + // + for (Composition::segmentcontainer::const_iterator + it = segments.begin(); + it != segments.end(); ++it) { + if ((*it)->getTrack() == m_tracks[i]) { + m_oldSegments.push_back(*it); + m_composition->detachSegment(*it); + } + } + + // store old tracks + m_oldTracks.push_back(track); + if (m_composition->detachTrack(track) == false) { + RG_DEBUG << "DeleteTracksCommand::execute - can't detach track" << endl; + } + } + } + + std::vector::iterator otIt; + for (otIt = m_oldTracks.begin(); otIt != m_oldTracks.end(); ++otIt) { + for (tit = tracks.begin(); tit != tracks.end(); ++tit) { + if ((*tit).second->getPosition() > (*otIt)->getPosition()) { + // If the track we've removed was after the current + // track then decrement the track position. + // + int newPosition = (*tit).second->getPosition() - 1; + + (*tit).second->setPosition(newPosition); + + } + } + } + + m_detached = true; +} + +void DeleteTracksCommand::unexecute() +{ + // Add the tracks and the segments back in + // + + // Remap positions and track numbers + // + Composition::trackcontainer + &tracks = m_composition->getTracks(); + Composition::trackiterator tit; + + std::vector::iterator otIt; + for (otIt = m_oldTracks.begin(); otIt != m_oldTracks.end(); ++otIt) { + // From the back we shuffle out the tracks to allow the new + // (old) track some space to come back in. + // + tit = tracks.end(); + while (true) { + --tit; + + if ((*tit).second->getPosition() >= (*otIt)->getPosition()) { + // If the track we're adding has position after the + // current track then increment position. + // + int newPosition = (*tit).second->getPosition() + 1; + + (*tit).second->setPosition(newPosition); + } + + if (tit == tracks.begin()) + break; + } + + m_composition->addTrack(*otIt); + } + + for (unsigned int i = 0; i < m_oldSegments.size(); ++i) + m_composition->addSegment(m_oldSegments[i]); + + m_detached = false; +} + +} diff --git a/src/commands/segment/DeleteTracksCommand.h b/src/commands/segment/DeleteTracksCommand.h new file mode 100644 index 0000000..d1d2332 --- /dev/null +++ b/src/commands/segment/DeleteTracksCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_DELETETRACKSCOMMAND_H_ +#define _RG_DELETETRACKSCOMMAND_H_ + +#include +#include +#include +#include +#include "base/Track.h" + + +namespace Rosegarden +{ + +class Track; +class Segment; +class Composition; + + +class DeleteTracksCommand : public KNamedCommand +{ +public: + DeleteTracksCommand(Composition *composition, + std::vector tracks); + virtual ~DeleteTracksCommand(); + + static QString getGlobalName() { return i18n("Delete Tracks..."); } + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + std::vector m_tracks; + + std::vector m_oldTracks; + std::vector m_oldSegments; + bool m_detached; +}; + + +} + +#endif diff --git a/src/commands/segment/DeleteTriggerSegmentCommand.cpp b/src/commands/segment/DeleteTriggerSegmentCommand.cpp new file mode 100644 index 0000000..3ddb4af --- /dev/null +++ b/src/commands/segment/DeleteTriggerSegmentCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "DeleteTriggerSegmentCommand.h" + +#include +#include "base/Composition.h" +#include "base/Segment.h" +#include "base/TriggerSegment.h" +#include "document/RosegardenGUIDoc.h" + + +namespace Rosegarden +{ + +DeleteTriggerSegmentCommand::DeleteTriggerSegmentCommand(RosegardenGUIDoc *doc, + TriggerSegmentId id) : + KNamedCommand(i18n("Delete Triggered Segment")), + m_composition(&doc->getComposition()), + m_id(id), + m_segment(0), + m_basePitch( -1), + m_baseVelocity( -1), + m_detached(true) +{ + // nothing else +} + +DeleteTriggerSegmentCommand::~DeleteTriggerSegmentCommand() +{ + if (m_detached) + delete m_segment; +} + +void +DeleteTriggerSegmentCommand::execute() +{ + TriggerSegmentRec *rec = m_composition->getTriggerSegmentRec(m_id); + if (!rec) + return ; + m_segment = rec->getSegment(); + m_basePitch = rec->getBasePitch(); + m_baseVelocity = rec->getBaseVelocity(); + m_composition->detachTriggerSegment(m_id); + m_detached = true; +} + +void +DeleteTriggerSegmentCommand::unexecute() +{ + if (m_segment) + m_composition->addTriggerSegment(m_segment, m_id, m_basePitch, m_baseVelocity); + m_detached = false; +} + +} diff --git a/src/commands/segment/DeleteTriggerSegmentCommand.h b/src/commands/segment/DeleteTriggerSegmentCommand.h new file mode 100644 index 0000000..30986f7 --- /dev/null +++ b/src/commands/segment/DeleteTriggerSegmentCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_DELETETRIGGERSEGMENTCOMMAND_H_ +#define _RG_DELETETRIGGERSEGMENTCOMMAND_H_ + +#include "base/TriggerSegment.h" +#include + + + + +namespace Rosegarden +{ + +class Segment; +class RosegardenGUIDoc; +class Composition; + + +class DeleteTriggerSegmentCommand : public KNamedCommand +{ +public: + DeleteTriggerSegmentCommand(RosegardenGUIDoc *doc, + TriggerSegmentId); + virtual ~DeleteTriggerSegmentCommand(); + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + TriggerSegmentId m_id; + Segment *m_segment; + int m_basePitch; + int m_baseVelocity; + bool m_detached; +}; + + + +} + +#endif diff --git a/src/commands/segment/EraseSegmentsStartingInRangeCommand.cpp b/src/commands/segment/EraseSegmentsStartingInRangeCommand.cpp new file mode 100644 index 0000000..35f7f10 --- /dev/null +++ b/src/commands/segment/EraseSegmentsStartingInRangeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EraseSegmentsStartingInRangeCommand.h" + +#include +#include "base/Composition.h" +#include "base/Segment.h" + + +namespace Rosegarden +{ + +EraseSegmentsStartingInRangeCommand::EraseSegmentsStartingInRangeCommand( + Composition *composition, + timeT t0, timeT t1) : + KNamedCommand(i18n("Delete Range")), + m_composition(composition), + m_beginTime(t0), + m_endTime(t1), + m_detached(false) +{} + +EraseSegmentsStartingInRangeCommand::~EraseSegmentsStartingInRangeCommand() +{ + if (m_detached) { + for (std::vector::iterator i = m_detaching.begin(); + i != m_detaching.end(); ++i) { + delete *i; + } + } +} + +void +EraseSegmentsStartingInRangeCommand::execute() +{ + if (m_detaching.empty()) { + + for (Composition::iterator i = m_composition->begin(); + i != m_composition->end(); ++i) { + + if ((*i)->getStartTime() >= m_beginTime && + (*i)->getStartTime() < m_endTime) { + m_detaching.push_back(*i); + } + } + } + + for (std::vector::iterator i = m_detaching.begin(); + i != m_detaching.end(); ++i) { + m_composition->detachSegment(*i); + } + + m_detached = true; +} + +void +EraseSegmentsStartingInRangeCommand::unexecute() +{ + for (std::vector::iterator i = m_detaching.begin(); + i != m_detaching.end(); ++i) { + + m_composition->addSegment(*i); + + //!!! see horrible code in SegmentEraseCommand::unexecute() + // to restore the audio file ID association in audio file mgr + // when an audio segment is restored. Why is this necessary? + // What is the agency that removed the audio file association + // in the first place, and why? Need to investigate that + // before heedlessly duplicating the same horrors here. + + } + + m_detached = false; +} + +} diff --git a/src/commands/segment/EraseSegmentsStartingInRangeCommand.h b/src/commands/segment/EraseSegmentsStartingInRangeCommand.h new file mode 100644 index 0000000..70b6978 --- /dev/null +++ b/src/commands/segment/EraseSegmentsStartingInRangeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ERASESEGMENTSSTARTINGINRANGECOMMAND_H_ +#define _RG_ERASESEGMENTSSTARTINGINRANGECOMMAND_H_ + +#include +#include +#include "base/Event.h" +#include "gui/application/RosegardenDCOP.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Composition; + + +class EraseSegmentsStartingInRangeCommand : public KNamedCommand +{ +public: + EraseSegmentsStartingInRangeCommand(Composition *composition, + timeT begin, + timeT end); + virtual ~EraseSegmentsStartingInRangeCommand(); + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + timeT m_beginTime; + timeT m_endTime; + + bool m_detached; + std::vector m_detaching; +}; + + +} + +#endif diff --git a/src/commands/segment/InsertRangeCommand.cpp b/src/commands/segment/InsertRangeCommand.cpp new file mode 100644 index 0000000..73dc5fd --- /dev/null +++ b/src/commands/segment/InsertRangeCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "InsertRangeCommand.h" + +#include +#include "AudioSegmentSplitCommand.h" +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "base/Segment.h" +#include "OpenOrCloseRangeCommand.h" +#include "SegmentSplitCommand.h" + + +namespace Rosegarden +{ + +InsertRangeCommand::InsertRangeCommand(Composition *composition, + timeT t0, timeT duration) : + KMacroCommand(i18n("Insert Range")) +{ + // Need to split segments before opening, at t0 + + for (Composition::iterator i = composition->begin(); + i != composition->end(); ++i) { + + if ((*i)->getStartTime() >= t0 || (*i)->getEndMarkerTime() <= t0) { + continue; + } + + if ((*i)->getType() == Segment::Audio) { + addCommand(new AudioSegmentSplitCommand(*i, t0)); + } else { + addCommand(new SegmentSplitCommand(*i, t0)); + } + } + + addCommand(new OpenOrCloseRangeCommand(composition, t0, t0 + duration, true)); +} + +} diff --git a/src/commands/segment/InsertRangeCommand.h b/src/commands/segment/InsertRangeCommand.h new file mode 100644 index 0000000..e3960c8 --- /dev/null +++ b/src/commands/segment/InsertRangeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_INSERTRANGECOMMAND_H_ +#define _RG_INSERTRANGECOMMAND_H_ + +#include "base/Event.h" +#include + +namespace Rosegarden +{ + +class Composition; + +class InsertRangeCommand : public KMacroCommand +{ +public: + InsertRangeCommand(Composition *composition, + timeT startTime, timeT duration); +}; + + +} + +#endif diff --git a/src/commands/segment/ModifyDefaultTempoCommand.cpp b/src/commands/segment/ModifyDefaultTempoCommand.cpp new file mode 100644 index 0000000..88360a4 --- /dev/null +++ b/src/commands/segment/ModifyDefaultTempoCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ModifyDefaultTempoCommand.h" + +#include "base/Composition.h" +#include + + +namespace Rosegarden +{ + +void +ModifyDefaultTempoCommand::execute() +{ + m_oldTempo = m_composition->getCompositionDefaultTempo(); + m_composition->setCompositionDefaultTempo(m_tempo); +} + +void +ModifyDefaultTempoCommand::unexecute() +{ + m_composition->setCompositionDefaultTempo(m_oldTempo); +} + +} diff --git a/src/commands/segment/ModifyDefaultTempoCommand.h b/src/commands/segment/ModifyDefaultTempoCommand.h new file mode 100644 index 0000000..50f736d --- /dev/null +++ b/src/commands/segment/ModifyDefaultTempoCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MODIFYDEFAULTTEMPOCOMMAND_H_ +#define _RG_MODIFYDEFAULTTEMPOCOMMAND_H_ + +#include +#include +#include +#include "base/Composition.h" // for tempoT + + + +namespace Rosegarden +{ + + +class ModifyDefaultTempoCommand : public KNamedCommand +{ +public: + ModifyDefaultTempoCommand(Composition *composition, + tempoT tempo): + KNamedCommand(getGlobalName()), + m_composition(composition), + m_tempo(tempo) {} + + virtual ~ModifyDefaultTempoCommand() {} + + static QString getGlobalName() { return i18n("Modify &Default Tempo..."); } + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + tempoT m_tempo; + tempoT m_oldTempo; +}; + + + +} + +#endif diff --git a/src/commands/segment/MoveTracksCommand.cpp b/src/commands/segment/MoveTracksCommand.cpp new file mode 100644 index 0000000..49c089b --- /dev/null +++ b/src/commands/segment/MoveTracksCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MoveTracksCommand.h" + +#include "base/Composition.h" +#include "base/Track.h" +#include + + +namespace Rosegarden +{ + +MoveTracksCommand::MoveTracksCommand(Composition *composition, + TrackId srcTrack, + TrackId destTrack): + KNamedCommand(getGlobalName()), + m_composition(composition), + m_srcTrack(srcTrack), + m_destTrack(destTrack) +{} + +MoveTracksCommand::~MoveTracksCommand() +{} + +void +MoveTracksCommand::execute() +{ + Track *srcTrack = m_composition->getTrackById(m_srcTrack); + Track *destTrack = m_composition->getTrackById(m_destTrack); + + int srcPosition = srcTrack->getPosition(); + + srcTrack->setPosition(destTrack->getPosition()); + destTrack->setPosition(srcPosition); + + m_composition->updateRefreshStatuses(); +} + +void +MoveTracksCommand::unexecute() +{ + Track *srcTrack = m_composition->getTrackById(m_srcTrack); + Track *destTrack = m_composition->getTrackById(m_destTrack); + + int srcPosition = srcTrack->getPosition(); + + srcTrack->setPosition(destTrack->getPosition()); + destTrack->setPosition(srcPosition); + + m_composition->updateRefreshStatuses(); +} + +} diff --git a/src/commands/segment/MoveTracksCommand.h b/src/commands/segment/MoveTracksCommand.h new file mode 100644 index 0000000..e35af5e --- /dev/null +++ b/src/commands/segment/MoveTracksCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MOVETRACKSCOMMAND_H_ +#define _RG_MOVETRACKSCOMMAND_H_ + +#include "base/Track.h" +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Composition; + + +class MoveTracksCommand : public KNamedCommand +{ +public: + MoveTracksCommand(Composition *composition, + TrackId srcTrack, + TrackId destTrack); + virtual ~MoveTracksCommand(); + + static QString getGlobalName() { return i18n("Move Tracks..."); } + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + + TrackId m_srcTrack; + TrackId m_destTrack; +}; + + +} + +#endif diff --git a/src/commands/segment/OpenOrCloseRangeCommand.cpp b/src/commands/segment/OpenOrCloseRangeCommand.cpp new file mode 100644 index 0000000..ad1db2c --- /dev/null +++ b/src/commands/segment/OpenOrCloseRangeCommand.cpp @@ -0,0 +1,181 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "OpenOrCloseRangeCommand.h" + +#include +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Selection.h" + + +namespace Rosegarden +{ + +OpenOrCloseRangeCommand::OpenOrCloseRangeCommand(Composition *composition, + timeT rangeBegin, + timeT rangeEnd, + bool open) : + KNamedCommand(i18n("Open or Close Range")), + m_composition(composition), + m_beginTime(rangeBegin), + m_endTime(rangeEnd), + m_prepared(false), + m_opening(open) +{} + +OpenOrCloseRangeCommand::~OpenOrCloseRangeCommand() +{} + +void +OpenOrCloseRangeCommand::execute() +{ + timeT offset = m_endTime - m_beginTime; + if (!m_opening) + offset = -offset; + + if (m_opening) { + if (offset + m_composition->getDuration() > + m_composition->getEndMarker()) { + m_composition->setEndMarker + (m_composition->getBarEndForTime + (m_composition->getDuration() + offset)); + } + } + + if (!m_prepared) { + + timeT movingFrom = m_beginTime; + if (!m_opening) + movingFrom = m_endTime; + + for (Composition::iterator i = m_composition->begin(); + i != m_composition->end(); ++i) { + + if ((*i)->getStartTime() >= movingFrom) { + m_moving.push_back(*i); + } + } + + m_timesigsPre = TimeSignatureSelection + (*m_composition, movingFrom, + m_composition->getEndMarker(), + false); + + m_temposPre = TempoSelection + (*m_composition, movingFrom, + m_composition->getEndMarker(), + false); + + for (TimeSignatureSelection::timesigcontainer::const_iterator i = + m_timesigsPre.begin(); i != m_timesigsPre.end(); ++i) { + + timeT t = i->first; + TimeSignature sig = i->second; + m_timesigsPost.addTimeSignature(t + offset, sig); + } + + for (TempoSelection::tempocontainer::const_iterator i = + m_temposPre.begin(); i != m_temposPre.end(); ++i) { + + timeT t = i->first; + TempoSelection::tempochange change = i->second; + m_temposPost.addTempo(t + offset, change.first, change.second); + } + + m_prepared = true; + } + + for (std::vector::iterator i = m_moving.begin(); + i != m_moving.end(); ++i) { + // RG_DEBUG << "Moving segment on track " << (*i)->getTrack() << " from " << (*i)->getStartTime() << " to " << ((*i)->getStartTime() + offset) << " (current end time is " << (*i)->getEndTime() << ", end marker is " << (*i)->getEndMarkerTime() << ")" << endl; + (*i)->setStartTime((*i)->getStartTime() + offset); + } + + for (TimeSignatureSelection::timesigcontainer::const_iterator i = + m_timesigsPre.begin(); i != m_timesigsPre.end(); ++i) { + int n = m_composition->getTimeSignatureNumberAt(i->first); + if (n >= 0) + m_composition->removeTimeSignature(n); + } + + for (TimeSignatureSelection::timesigcontainer::const_iterator i = + m_timesigsPost.begin(); i != m_timesigsPost.end(); ++i) { + m_composition->addTimeSignature(i->first, i->second); + } + + for (TempoSelection::tempocontainer::const_iterator i = + m_temposPre.begin(); i != m_temposPre.end(); ++i) { + int n = m_composition->getTempoChangeNumberAt(i->first); + if (n >= 0) + m_composition->removeTempoChange(n); + } + + for (TempoSelection::tempocontainer::const_iterator i = + m_temposPost.begin(); i != m_temposPost.end(); ++i) { + m_composition->addTempoAtTime(i->first, i->second.first, i->second.second); + } +} + +void +OpenOrCloseRangeCommand::unexecute() +{ + timeT offset = m_beginTime - m_endTime; + if (!m_opening) + offset = -offset; + + for (std::vector::iterator i = m_moving.begin(); + i != m_moving.end(); ++i) { + (*i)->setStartTime((*i)->getStartTime() + offset); + } + + for (TimeSignatureSelection::timesigcontainer::const_iterator i = + m_timesigsPost.begin(); i != m_timesigsPost.end(); ++i) { + int n = m_composition->getTimeSignatureNumberAt(i->first); + if (n >= 0) + m_composition->removeTimeSignature(n); + } + + for (TimeSignatureSelection::timesigcontainer::const_iterator i = + m_timesigsPre.begin(); i != m_timesigsPre.end(); ++i) { + m_composition->addTimeSignature(i->first, i->second); + } + + for (TempoSelection::tempocontainer::const_iterator i = + m_temposPost.begin(); i != m_temposPost.end(); ++i) { + int n = m_composition->getTempoChangeNumberAt(i->first); + if (n >= 0) + m_composition->removeTempoChange(n); + } + + for (TempoSelection::tempocontainer::const_iterator i = + m_temposPre.begin(); i != m_temposPre.end(); ++i) { + m_composition->addTempoAtTime(i->first, i->second.first, i->second.second); + } +} + +} diff --git a/src/commands/segment/OpenOrCloseRangeCommand.h b/src/commands/segment/OpenOrCloseRangeCommand.h new file mode 100644 index 0000000..6cb0f16 --- /dev/null +++ b/src/commands/segment/OpenOrCloseRangeCommand.h @@ -0,0 +1,84 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_OPENORCLOSERANGECOMMAND_H_ +#define _RG_OPENORCLOSERANGECOMMAND_H_ + +#include "base/Selection.h" +#include +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Composition; + + +/** + * Pull all segments, time sigs, tempos etc starting after the end of + * a given range back by the duration of that range, so as to fill in + * the (presumably empty) range itself. + * + * This does not actually split any segments etc, it just moves them. + */ +class OpenOrCloseRangeCommand : public KNamedCommand +{ +public: + OpenOrCloseRangeCommand(Composition *composition, + timeT rangeBegin, + timeT rangeEnd, + bool open); + virtual ~OpenOrCloseRangeCommand(); + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + timeT m_beginTime; + timeT m_endTime; + + bool m_prepared; + bool m_opening; + + std::vector m_moving; + + TimeSignatureSelection m_timesigsPre; + TimeSignatureSelection m_timesigsPost; + + TempoSelection m_temposPre; + TempoSelection m_temposPost; +}; + + + +} + +#endif diff --git a/src/commands/segment/PasteConductorDataCommand.cpp b/src/commands/segment/PasteConductorDataCommand.cpp new file mode 100644 index 0000000..6db082f --- /dev/null +++ b/src/commands/segment/PasteConductorDataCommand.cpp @@ -0,0 +1,128 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "PasteConductorDataCommand.h" + +#include +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Selection.h" + + +namespace Rosegarden +{ + +PasteConductorDataCommand::PasteConductorDataCommand(Composition *composition, + Clipboard *clipboard, + timeT t) : + KNamedCommand(i18n("Paste Tempos and Time Signatures")), + m_composition(composition), + m_clipboard(new Clipboard(*clipboard)), + m_t0(t) +{} + +PasteConductorDataCommand::~PasteConductorDataCommand() +{ + delete m_clipboard; +} + +void +PasteConductorDataCommand::execute() +{ + //!!! current implementation of execute and unexecute require + // that the target area of the composition be empty of tempo and + // timesig data before the command is executed + + if (m_clipboard->hasTimeSignatureSelection()) { + + for (TimeSignatureSelection::timesigcontainer::const_iterator i = + m_clipboard->getTimeSignatureSelection().begin(); + i != m_clipboard->getTimeSignatureSelection().end(); ++i) { + + timeT t = i->first; + t = t - m_clipboard->getBaseTime() + m_t0; + TimeSignature sig = i->second; + + if (i == m_clipboard->getTimeSignatureSelection().begin() && + m_composition->getTimeSignatureAt(t) == sig) + continue; + + m_composition->addTimeSignature(t, sig); + } + } + + if (m_clipboard->hasTempoSelection()) { + + for (TempoSelection::tempocontainer::const_iterator i = + m_clipboard->getTempoSelection().begin(); + i != m_clipboard->getTempoSelection().end(); ++i) { + + timeT t = i->first; + t = t - m_clipboard->getBaseTime() + m_t0; + tempoT tempo = i->second.first; + tempoT targetTempo = i->second.second; + + if (i == m_clipboard->getTempoSelection().begin() && + targetTempo < 0 && + m_composition->getTempoAtTime(t) == tempo) + continue; + + m_composition->addTempoAtTime(t, tempo, targetTempo); + } + } +} + +void +PasteConductorDataCommand::unexecute() +{ + //!!! see note above + + for (TimeSignatureSelection::timesigcontainer::const_iterator i = + m_clipboard->getTimeSignatureSelection().begin(); + i != m_clipboard->getTimeSignatureSelection().end(); ++i) { + + timeT t = i->first; + int n = m_composition->getTimeSignatureNumberAt(t); + + if (n >= 0 && m_composition->getTimeSignatureChange(n).first == t) { + m_composition->removeTimeSignature(n); + } + } + + for (TempoSelection::tempocontainer::const_iterator i = + m_clipboard->getTempoSelection().begin(); + i != m_clipboard->getTempoSelection().end(); ++i) { + + timeT t = i->first; + int n = m_composition->getTempoChangeNumberAt(t); + + if (n >= 0 && m_composition->getTempoChange(n).first == t) { + m_composition->removeTempoChange(n); + } + } +} + +} diff --git a/src/commands/segment/PasteConductorDataCommand.h b/src/commands/segment/PasteConductorDataCommand.h new file mode 100644 index 0000000..15b6e54 --- /dev/null +++ b/src/commands/segment/PasteConductorDataCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PASTECONDUCTORDATACOMMAND_H_ +#define _RG_PASTECONDUCTORDATACOMMAND_H_ + +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Composition; +class Clipboard; + + +/** + * Paste time signature and tempo data from the given clipboard into + * the given composition starting at the given time. + */ +class PasteConductorDataCommand : public KNamedCommand +{ +public: + PasteConductorDataCommand(Composition *composition, + Clipboard *clipboard, + timeT t); + virtual ~PasteConductorDataCommand(); + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + Clipboard *m_clipboard; + timeT m_t0; +}; + + + +} + +#endif diff --git a/src/commands/segment/PasteRangeCommand.cpp b/src/commands/segment/PasteRangeCommand.cpp new file mode 100644 index 0000000..c2f9a0e --- /dev/null +++ b/src/commands/segment/PasteRangeCommand.cpp @@ -0,0 +1,97 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "PasteRangeCommand.h" + +#include +#include "AudioSegmentSplitCommand.h" +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "base/Segment.h" +#include "commands/edit/PasteSegmentsCommand.h" +#include "OpenOrCloseRangeCommand.h" +#include "PasteConductorDataCommand.h" +#include "SegmentSplitCommand.h" + + +namespace Rosegarden +{ + +PasteRangeCommand::PasteRangeCommand(Composition *composition, + Clipboard *clipboard, + timeT t0) : + KMacroCommand(i18n("Paste Range")) +{ + timeT clipBeginTime = clipboard->getBaseTime(); + + timeT t1 = t0; + + if (clipboard->hasNominalRange()) { + + clipboard->getNominalRange(clipBeginTime, t1); + t1 = t0 + (t1 - clipBeginTime); + + } else { + + timeT duration = 0; + + for (Clipboard::iterator i = clipboard->begin(); + i != clipboard->end(); ++i) { + timeT durationHere = (*i)->getEndMarkerTime() - clipBeginTime; + if (i == clipboard->begin() || durationHere > duration) { + duration = durationHere; + } + } + + if (duration <= 0) + return ; + t1 = t0 + duration; + } + + // Need to split segments before opening, at t0 + + for (Composition::iterator i = composition->begin(); + i != composition->end(); ++i) { + + if ((*i)->getStartTime() >= t0 || (*i)->getEndMarkerTime() <= t0) { + continue; + } + + if ((*i)->getType() == Segment::Audio) { + addCommand(new AudioSegmentSplitCommand(*i, t0)); + } else { + addCommand(new SegmentSplitCommand(*i, t0)); + } + } + + addCommand(new OpenOrCloseRangeCommand(composition, t0, t1, true)); + addCommand(new PasteSegmentsCommand + (composition, clipboard, t0, + composition->getTrackByPosition(0)->getId(), + true)); + addCommand(new PasteConductorDataCommand(composition, clipboard, t0)); +} + +} diff --git a/src/commands/segment/PasteRangeCommand.h b/src/commands/segment/PasteRangeCommand.h new file mode 100644 index 0000000..f64da24 --- /dev/null +++ b/src/commands/segment/PasteRangeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PASTERANGECOMMAND_H_ +#define _RG_PASTERANGECOMMAND_H_ + +#include "base/Event.h" +#include + + +namespace Rosegarden +{ + +class Composition; +class Clipboard; + + +class PasteRangeCommand : public KMacroCommand +{ +public: + PasteRangeCommand(Composition *composition, + Clipboard *clipboard, + timeT pasteTime); +}; + + + +} + +#endif diff --git a/src/commands/segment/PasteToTriggerSegmentCommand.cpp b/src/commands/segment/PasteToTriggerSegmentCommand.cpp new file mode 100644 index 0000000..0447c40 --- /dev/null +++ b/src/commands/segment/PasteToTriggerSegmentCommand.cpp @@ -0,0 +1,129 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "PasteToTriggerSegmentCommand.h" + +#include "base/Event.h" +#include +#include "misc/Strings.h" +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/TriggerSegment.h" +#include + + +namespace Rosegarden +{ + +PasteToTriggerSegmentCommand::PasteToTriggerSegmentCommand(Composition *composition, + Clipboard *clipboard, + QString label, + int basePitch, + int baseVelocity) : + KNamedCommand(i18n("Paste as New Triggered Segment")), + m_composition(composition), + m_clipboard(new Clipboard(*clipboard)), + m_label(label), + m_basePitch(basePitch), + m_baseVelocity(baseVelocity), + m_segment(0), + m_detached(false) +{ + // nothing else +} + +PasteToTriggerSegmentCommand::~PasteToTriggerSegmentCommand() +{ + if (m_detached) + delete m_segment; + delete m_clipboard; +} + +void +PasteToTriggerSegmentCommand::execute() +{ + if (m_segment) { + + m_composition->addTriggerSegment(m_segment, m_id, m_basePitch, m_baseVelocity); + + } else { + + if (m_clipboard->isEmpty()) + return ; + + m_segment = new Segment(); + + timeT earliestStartTime = 0; + timeT latestEndTime = 0; + + for (Clipboard::iterator i = m_clipboard->begin(); + i != m_clipboard->end(); ++i) { + + if (i == m_clipboard->begin() || + (*i)->getStartTime() < earliestStartTime) { + earliestStartTime = (*i)->getStartTime(); + } + + if ((*i)->getEndMarkerTime() > latestEndTime) + latestEndTime = (*i)->getEndMarkerTime(); + } + + for (Clipboard::iterator i = m_clipboard->begin(); + i != m_clipboard->end(); ++i) { + + for (Segment::iterator si = (*i)->begin(); + (*i)->isBeforeEndMarker(si); ++si) { + if (!(*si)->isa(Note::EventRestType)) { + m_segment->insert + (new Event(**si, + (*si)->getAbsoluteTime() - earliestStartTime)); + } + } + } + + m_segment->setLabel(qstrtostr(m_label)); + + TriggerSegmentRec *rec = + m_composition->addTriggerSegment(m_segment, m_basePitch, m_baseVelocity); + if (rec) + m_id = rec->getId(); + } + + m_composition->getTriggerSegmentRec(m_id)->updateReferences(); + + m_detached = false; +} + +void +PasteToTriggerSegmentCommand::unexecute() +{ + if (m_segment) + m_composition->detachTriggerSegment(m_id); + m_detached = true; +} + +} diff --git a/src/commands/segment/PasteToTriggerSegmentCommand.h b/src/commands/segment/PasteToTriggerSegmentCommand.h new file mode 100644 index 0000000..32f95e7 --- /dev/null +++ b/src/commands/segment/PasteToTriggerSegmentCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PASTETOTRIGGERSEGMENTCOMMAND_H_ +#define _RG_PASTETOTRIGGERSEGMENTCOMMAND_H_ + +#include "base/TriggerSegment.h" +#include +#include + + + + +namespace Rosegarden +{ + +class Segment; +class Composition; +class Clipboard; + + +class PasteToTriggerSegmentCommand : public KNamedCommand +{ +public: + /// If basePitch is -1, the first pitch in the selection will be used + PasteToTriggerSegmentCommand(Composition *composition, + Clipboard *clipboard, + QString label, + int basePitch = -1, + int baseVelocity = -1); + virtual ~PasteToTriggerSegmentCommand(); + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + Clipboard *m_clipboard; + QString m_label; + int m_basePitch; + int m_baseVelocity; + Segment *m_segment; + TriggerSegmentId m_id; + bool m_detached; +}; + + + +} + +#endif diff --git a/src/commands/segment/RemoveTempoChangeCommand.cpp b/src/commands/segment/RemoveTempoChangeCommand.cpp new file mode 100644 index 0000000..11a89ad --- /dev/null +++ b/src/commands/segment/RemoveTempoChangeCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RemoveTempoChangeCommand.h" + +#include "base/Composition.h" +#include + + +namespace Rosegarden +{ + +void +RemoveTempoChangeCommand::execute() +{ + if (m_tempoChangeIndex >= 0) { + std::pair data = + m_composition->getTempoChange(m_tempoChangeIndex); + + // store + m_oldTime = data.first; + m_oldTempo = data.second; + } + + // do we need to (re)store the index number? + // + m_composition->removeTempoChange(m_tempoChangeIndex); + +} + +void +RemoveTempoChangeCommand::unexecute() +{ + m_composition->addTempoAtTime(m_oldTime, m_oldTempo); +} + +} diff --git a/src/commands/segment/RemoveTempoChangeCommand.h b/src/commands/segment/RemoveTempoChangeCommand.h new file mode 100644 index 0000000..1900a8b --- /dev/null +++ b/src/commands/segment/RemoveTempoChangeCommand.h @@ -0,0 +1,75 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_REMOVETEMPOCHANGECOMMAND_H_ +#define _RG_REMOVETEMPOCHANGECOMMAND_H_ + +#include +#include +#include "base/Event.h" +#include "base/Composition.h" // for tempoT +#include + + +class Remove; + + +namespace Rosegarden +{ + +class Composition; + + +class RemoveTempoChangeCommand : public KNamedCommand +{ +public: + RemoveTempoChangeCommand(Composition *composition, + int index): + KNamedCommand(getGlobalName()), + m_composition(composition), + m_tempoChangeIndex(index), + m_oldTime(0), + m_oldTempo(0){} + + virtual ~RemoveTempoChangeCommand() {} + + static QString getGlobalName() { return i18n("Remove &Tempo Change..."); } + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + int m_tempoChangeIndex; + timeT m_oldTime; + tempoT m_oldTempo; +}; + + + + +} + +#endif diff --git a/src/commands/segment/RemoveTimeSignatureCommand.cpp b/src/commands/segment/RemoveTimeSignatureCommand.cpp new file mode 100644 index 0000000..c6b59b1 --- /dev/null +++ b/src/commands/segment/RemoveTimeSignatureCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RemoveTimeSignatureCommand.h" + +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include + + +namespace Rosegarden +{ + +void +RemoveTimeSignatureCommand::execute() +{ + if (m_timeSigIndex >= 0) { + std::pair data = + m_composition->getTimeSignatureChange(m_timeSigIndex); + + // store + m_oldTime = data.first; + m_oldTimeSignature = data.second; + } + + // do we need to (re)store the index number? + // + m_composition->removeTimeSignature(m_timeSigIndex); + +} + +void +RemoveTimeSignatureCommand::unexecute() +{ + m_composition->addTimeSignature(m_oldTime, m_oldTimeSignature); +} + +} diff --git a/src/commands/segment/RemoveTimeSignatureCommand.h b/src/commands/segment/RemoveTimeSignatureCommand.h new file mode 100644 index 0000000..d29d666 --- /dev/null +++ b/src/commands/segment/RemoveTimeSignatureCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_REMOVETIMESIGNATURECOMMAND_H_ +#define _RG_REMOVETIMESIGNATURECOMMAND_H_ + +#include "base/NotationTypes.h" +#include +#include +#include "base/Event.h" +#include + + +class Remove; + + +namespace Rosegarden +{ + +class Composition; + + +class RemoveTimeSignatureCommand : public KNamedCommand +{ +public: + RemoveTimeSignatureCommand(Composition *composition, + int index): + KNamedCommand(getGlobalName()), + m_composition(composition), + m_timeSigIndex(index), + m_oldTime(0), + m_oldTimeSignature() { } + + virtual ~RemoveTimeSignatureCommand() {} + + static QString getGlobalName() { return i18n("Remove &Time Signature Change..."); } + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + int m_timeSigIndex; + timeT m_oldTime; + TimeSignature m_oldTimeSignature; +}; + + + +} + +#endif diff --git a/src/commands/segment/RenameTrackCommand.cpp b/src/commands/segment/RenameTrackCommand.cpp new file mode 100644 index 0000000..62d1d7e --- /dev/null +++ b/src/commands/segment/RenameTrackCommand.cpp @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RenameTrackCommand.h" + +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/Track.h" +#include + + +namespace Rosegarden +{ + +RenameTrackCommand::RenameTrackCommand(Composition *composition, + TrackId trackId, + std::string name) : + KNamedCommand(getGlobalName()), + m_composition(composition), + m_track(trackId), + m_newName(name) +{ + Track *track = composition->getTrackById(m_track); + if (!track) { + RG_DEBUG << "Hey! No Track in RenameTrackCommand (track id " << track + << ")!" << endl; + return ; + } + m_oldName = track->getLabel(); +} + +RenameTrackCommand::~RenameTrackCommand() +{} + +void +RenameTrackCommand::execute() +{ + Track *track = m_composition->getTrackById(m_track); + if (!track) + return ; + track->setLabel(m_newName); +} + +void +RenameTrackCommand::unexecute() +{ + Track *track = m_composition->getTrackById(m_track); + if (!track) + return ; + track->setLabel(m_oldName); +} + +} diff --git a/src/commands/segment/RenameTrackCommand.h b/src/commands/segment/RenameTrackCommand.h new file mode 100644 index 0000000..8e63a88 --- /dev/null +++ b/src/commands/segment/RenameTrackCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_RENAMETRACKCOMMAND_H_ +#define _RG_RENAMETRACKCOMMAND_H_ + +#include "base/Track.h" +#include +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Composition; + + +class RenameTrackCommand : public KNamedCommand +{ +public: + RenameTrackCommand(Composition *composition, + TrackId track, + std::string name); + virtual ~RenameTrackCommand(); + + static QString getGlobalName() { return i18n("Rename Track"); } + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + TrackId m_track; + std::string m_oldName; + std::string m_newName; +}; + + +} + +#endif diff --git a/src/commands/segment/SegmentAutoSplitCommand.cpp b/src/commands/segment/SegmentAutoSplitCommand.cpp new file mode 100644 index 0000000..fbd6daa --- /dev/null +++ b/src/commands/segment/SegmentAutoSplitCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentAutoSplitCommand.h" + +#include "base/Event.h" +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include + + +namespace Rosegarden +{ + +struct AutoSplitPoint +{ + timeT time; + timeT lastSoundTime; + Clef clef; + Rosegarden::Key key; + AutoSplitPoint(timeT t, timeT lst, Clef c, Rosegarden::Key k) : + time(t), lastSoundTime(lst), clef(c), key(k) { } +}; + +SegmentAutoSplitCommand::SegmentAutoSplitCommand(Segment *segment) : + KNamedCommand(getGlobalName()), + m_segment(segment), + m_composition(segment->getComposition()), + m_detached(false) +{} + +SegmentAutoSplitCommand::~SegmentAutoSplitCommand() +{ + if (m_detached) { + delete m_segment; + } else { + for (unsigned int i = 0; i < m_newSegments.size(); ++i) { + delete m_newSegments[i]; + } + } +} + +void +SegmentAutoSplitCommand::execute() +{ + if (m_newSegments.size() == 0) { + + std::vector splitPoints; + + Clef clef; + Key key; + timeT segmentStart = m_segment->getStartTime(); + timeT lastSoundTime = segmentStart; + timeT lastSplitTime = segmentStart - 1; + + for (Segment::iterator i = m_segment->begin(); + m_segment->isBeforeEndMarker(i); ++i) { + + timeT myTime = (*i)->getAbsoluteTime(); + int barNo = m_composition->getBarNumber(myTime); + + if ((*i)->isa(Clef::EventType)) { + clef = Clef(**i); + } else if ((*i)->isa(Key::EventType)) { + key = Key(**i); + } + + if (myTime <= lastSplitTime) + continue; + + bool newTimeSig = false; + TimeSignature tsig = + m_composition->getTimeSignatureInBar(barNo, newTimeSig); + + if (newTimeSig) { + + // If there's a new time sig in this bar and we haven't + // already made a split in this bar, make one + + if (splitPoints.size() == 0 || + m_composition->getBarNumber + (splitPoints[splitPoints.size() - 1].time) < barNo) { + + splitPoints.push_back(AutoSplitPoint(myTime, lastSoundTime, + clef, key)); + lastSoundTime = lastSplitTime = myTime; + } + + } else if ((*i)->isa(Note::EventRestType)) { + + // Otherwise never start a subsegment on a rest + + continue; + + } else { + + // When we meet a non-rest event, start a new split + // if an entire bar has passed since the last one + + int lastSoundBarNo = m_composition->getBarNumber(lastSoundTime); + + if (lastSoundBarNo < barNo - 1 || + (lastSoundBarNo == barNo - 1 && + m_composition->getBarStartForTime(lastSoundTime) == + lastSoundTime && + lastSoundTime > segmentStart)) { + + splitPoints.push_back + (AutoSplitPoint + (m_composition->getBarStartForTime(myTime), lastSoundTime, + clef, key)); + lastSplitTime = myTime; + } + } + + lastSoundTime = std::max(lastSoundTime, myTime + (*i)->getDuration()); + } + + for (unsigned int split = 0; split <= splitPoints.size(); ++split) { + + Segment *newSegment = new Segment(); + newSegment->setTrack(m_segment->getTrack()); + newSegment->setLabel(qstrtostr(i18n("%1 (part)").arg + (strtoqstr(m_segment->getLabel())))); + newSegment->setColourIndex(m_segment->getColourIndex()); + + timeT startTime = segmentStart; + if (split > 0) { + + RG_DEBUG << "Auto-split point " << split - 1 << ": time " + << splitPoints[split - 1].time << ", lastSoundTime " + << splitPoints[split - 1].lastSoundTime << endl; + + startTime = splitPoints[split - 1].time; + newSegment->insert(splitPoints[split - 1].clef.getAsEvent(startTime)); + newSegment->insert(splitPoints[split - 1].key.getAsEvent(startTime)); + } + + Segment::iterator i = m_segment->findTime(startTime); + + // A segment has to contain at least one note to be a worthy + // candidate for adding back into the composition + bool haveNotes = false; + + while (m_segment->isBeforeEndMarker(i)) { + timeT t = (*i)->getAbsoluteTime(); + if (split < splitPoints.size() && + t >= splitPoints[split].lastSoundTime) + break; + if ((*i)->isa(Note::EventType)) + haveNotes = true; + newSegment->insert(new Event(**i)); + ++i; + } + + if (haveNotes) + m_newSegments.push_back(newSegment); + else + delete newSegment; + } + } + + m_composition->detachSegment(m_segment); + for (unsigned int i = 0; i < m_newSegments.size(); ++i) { + m_composition->addSegment(m_newSegments[i]); + } + m_detached = true; +} + +void +SegmentAutoSplitCommand::unexecute() +{ + for (unsigned int i = 0; i < m_newSegments.size(); ++i) { + m_composition->detachSegment(m_newSegments[i]); + } + m_composition->addSegment(m_segment); + m_detached = false; +} + +} diff --git a/src/commands/segment/SegmentAutoSplitCommand.h b/src/commands/segment/SegmentAutoSplitCommand.h new file mode 100644 index 0000000..a7e54c7 --- /dev/null +++ b/src/commands/segment/SegmentAutoSplitCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTAUTOSPLITCOMMAND_H_ +#define _RG_SEGMENTAUTOSPLITCOMMAND_H_ + +#include +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Segment; +class Composition; + + +class SegmentAutoSplitCommand : public KNamedCommand +{ +public: + SegmentAutoSplitCommand(Segment *segment); + virtual ~SegmentAutoSplitCommand(); + + virtual void execute(); + virtual void unexecute(); + + static QString getGlobalName() { return i18n("&Split on Silence"); } + +private: + Segment *m_segment; + Composition *m_composition; + std::vector m_newSegments; + bool m_detached; +}; + + + +} + +#endif diff --git a/src/commands/segment/SegmentChangePlayableRangeCommand.cpp b/src/commands/segment/SegmentChangePlayableRangeCommand.cpp new file mode 100644 index 0000000..b4d5d3a --- /dev/null +++ b/src/commands/segment/SegmentChangePlayableRangeCommand.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-2007 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentChangePlayableRangeCommand.h" + +#include "base/Segment.h" +#include "gui/editors/notation/NotationStrings.h" +#include + + +namespace Rosegarden +{ + +SegmentChangePlayableRangeCommand::SegmentChangePlayableRangeCommand(int low, int high, Segment *segment) : + KNamedCommand(getGlobalName(low, high)), + m_lowestPlayableNote(low), + m_highestPlayableNote(high), + m_segment(segment) +{ + // nothing +} + +SegmentChangePlayableRangeCommand::~SegmentChangePlayableRangeCommand() +{ + // nothing +} + +void +SegmentChangePlayableRangeCommand::execute() +{ + m_oldLowestPlayableNote = m_segment->getLowestPlayable(); + m_oldHighestPlayableNote = m_segment->getHighestPlayable(); + m_segment->setLowestPlayable(m_lowestPlayableNote); + m_segment->setHighestPlayable(m_highestPlayableNote); +} + +void +SegmentChangePlayableRangeCommand::unexecute() +{ + m_segment->setLowestPlayable(m_oldLowestPlayableNote); + m_segment->setHighestPlayable(m_oldHighestPlayableNote); +} + +QString +SegmentChangePlayableRangeCommand::getGlobalName(int low, int high) +{ + bool unit = false; // fake code to allow trunk/ to compile! + if (!unit) { + return "Undo change playable range"; + } else { + return QString("Change playable range to %1-%2").arg(low, high); + } +} + +} diff --git a/src/commands/segment/SegmentChangePlayableRangeCommand.h b/src/commands/segment/SegmentChangePlayableRangeCommand.h new file mode 100644 index 0000000..d78aa5c --- /dev/null +++ b/src/commands/segment/SegmentChangePlayableRangeCommand.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-2007 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTCHANGEPLAYABLERANGECOMMAND_H_ +#define _RG_SEGMENTCHANGEPLAYABLERANGECOMMAND_H_ + +#include +#include +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; + + +class SegmentChangePlayableRangeCommand : public KNamedCommand +{ +public: + /// Set transpose on segments. + SegmentChangePlayableRangeCommand(int /*low*/, int /*high*/, Segment *segment); + virtual ~SegmentChangePlayableRangeCommand(); + + virtual void execute(); + virtual void unexecute(); + + static QString getGlobalName(int, int); + +private: + Segment *m_segment; + int m_oldLowestPlayableNote; + int m_oldHighestPlayableNote; + int m_lowestPlayableNote; + int m_highestPlayableNote; +}; + + + +} + +#endif diff --git a/src/commands/segment/SegmentChangeQuantizationCommand.cpp b/src/commands/segment/SegmentChangeQuantizationCommand.cpp new file mode 100644 index 0000000..22d09f4 --- /dev/null +++ b/src/commands/segment/SegmentChangeQuantizationCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentChangeQuantizationCommand.h" + +#include "base/Segment.h" +#include "base/BasicQuantizer.h" +#include "gui/editors/notation/NotationStrings.h" +#include + + +namespace Rosegarden +{ + +SegmentChangeQuantizationCommand::SegmentChangeQuantizationCommand(timeT unit) : + KNamedCommand(getGlobalName(unit)), + m_unit(unit) +{ + // nothing +} + +SegmentChangeQuantizationCommand::~SegmentChangeQuantizationCommand() +{ + // nothing +} + +void +SegmentChangeQuantizationCommand::execute() +{ + for (unsigned int i = 0; i < m_records.size(); ++i) { + + SegmentRec &rec = m_records[i]; + + if (m_unit) { + + rec.oldUnit = rec.segment->getQuantizer()->getUnit(); + rec.segment->setQuantizeLevel(m_unit); + + rec.wasQuantized = rec.segment->hasQuantization(); + rec.segment->setQuantization(true); + + } else { + + rec.wasQuantized = rec.segment->hasQuantization(); + rec.segment->setQuantization(false); + } + } +} + +void +SegmentChangeQuantizationCommand::unexecute() +{ + for (unsigned int i = 0; i < m_records.size(); ++i) { + + SegmentRec &rec = m_records[i]; + + if (m_unit) { + + if (!rec.wasQuantized) + rec.segment->setQuantization(false); + rec.segment->setQuantizeLevel(rec.oldUnit); + + } else { + + if (rec.wasQuantized) + rec.segment->setQuantization(true); + } + } +} + +void +SegmentChangeQuantizationCommand::addSegment(Segment *s) +{ + SegmentRec rec; + rec.segment = s; + rec.oldUnit = 0; // shouldn't matter what we initialise this to + rec.wasQuantized = false; // shouldn't matter what we initialise this to + m_records.push_back(rec); +} + +QString +SegmentChangeQuantizationCommand::getGlobalName(timeT unit) +{ + if (!unit) { + return "Unquantize"; + } else { + timeT error = 0; + QString label = NotationStrings::makeNoteMenuLabel(unit, true, error); + return QString("Quantize to %1").arg(label); + } +} + +} diff --git a/src/commands/segment/SegmentChangeQuantizationCommand.h b/src/commands/segment/SegmentChangeQuantizationCommand.h new file mode 100644 index 0000000..e5d7a36 --- /dev/null +++ b/src/commands/segment/SegmentChangeQuantizationCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTCHANGEQUANTIZATIONCOMMAND_H_ +#define _RG_SEGMENTCHANGEQUANTIZATIONCOMMAND_H_ + +#include +#include +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; + + +class SegmentChangeQuantizationCommand : public KNamedCommand +{ +public: + /// Set quantization on segments. If unit is zero, switch quantization off + SegmentChangeQuantizationCommand(timeT); + virtual ~SegmentChangeQuantizationCommand(); + + void addSegment(Segment *s); + + virtual void execute(); + virtual void unexecute(); + + static QString getGlobalName(timeT); + +private: + struct SegmentRec { + Segment *segment; + timeT oldUnit; + bool wasQuantized; + }; + typedef std::vector SegmentRecSet; + SegmentRecSet m_records; + + timeT m_unit; +}; + + + +} + +#endif diff --git a/src/commands/segment/SegmentChangeTransposeCommand.cpp b/src/commands/segment/SegmentChangeTransposeCommand.cpp new file mode 100644 index 0000000..452c2d7 --- /dev/null +++ b/src/commands/segment/SegmentChangeTransposeCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentChangeTransposeCommand.h" + +#include "base/Segment.h" +#include "gui/editors/notation/NotationStrings.h" +#include + + +namespace Rosegarden +{ + +SegmentChangeTransposeCommand::SegmentChangeTransposeCommand(int unit, Segment *segment) : + KNamedCommand(getGlobalName(unit)), + m_unit(unit), + m_segment(segment) +{ + // nothing +} + +SegmentChangeTransposeCommand::~SegmentChangeTransposeCommand() +{ + // nothing +} + +void +SegmentChangeTransposeCommand::execute() +{ + m_oldUnit = m_segment->getTranspose(); + m_segment->setTranspose(m_unit); +} + +void +SegmentChangeTransposeCommand::unexecute() +{ + m_segment->setTranspose(m_oldUnit); +} + +QString +SegmentChangeTransposeCommand::getGlobalName(int unit) +{ + if (!unit) { + return "Undo change transposition"; + } else { + return QString("Change transposition to %1").arg(unit); + } +} + +} diff --git a/src/commands/segment/SegmentChangeTransposeCommand.h b/src/commands/segment/SegmentChangeTransposeCommand.h new file mode 100644 index 0000000..64bd75a --- /dev/null +++ b/src/commands/segment/SegmentChangeTransposeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTCHANGETRANSPOSECOMMAND_H_ +#define _RG_SEGMENTCHANGETRANSPOSECOMMAND_H_ + +#include +#include +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; + + +class SegmentChangeTransposeCommand : public KNamedCommand +{ +public: + /// Set transpose on segments. + SegmentChangeTransposeCommand(int, Segment *segment); + virtual ~SegmentChangeTransposeCommand(); + + virtual void execute(); + virtual void unexecute(); + + static QString getGlobalName(int); + +private: + Segment *m_segment; + int m_oldUnit; + int m_unit; +}; + + + +} + +#endif diff --git a/src/commands/segment/SegmentColourCommand.cpp b/src/commands/segment/SegmentColourCommand.cpp new file mode 100644 index 0000000..7dffc8f --- /dev/null +++ b/src/commands/segment/SegmentColourCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentColourCommand.h" + +#include "base/Segment.h" +#include "base/Selection.h" +#include + + +namespace Rosegarden +{ + +SegmentColourCommand::SegmentColourCommand( + SegmentSelection &segments, + const unsigned int index): + KNamedCommand(i18n("Change Segment Color")), + m_newColourIndex(index) +{ + for (SegmentSelection::iterator i = segments.begin(); i != segments.end(); ++i) + m_segments.push_back(*i); +} + +SegmentColourCommand::~SegmentColourCommand() +{} + +void +SegmentColourCommand::execute() +{ + for (unsigned int i = 0; i < m_segments.size(); ++i) { + m_oldColourIndexes.push_back(m_segments[i]->getColourIndex()); + m_segments[i]->setColourIndex(m_newColourIndex); + } +} + +void +SegmentColourCommand::unexecute() +{ + for (unsigned int i = 0; i < m_segments.size(); ++i) + m_segments[i]->setColourIndex(m_oldColourIndexes[i]); +} + +} diff --git a/src/commands/segment/SegmentColourCommand.h b/src/commands/segment/SegmentColourCommand.h new file mode 100644 index 0000000..79b7d3d --- /dev/null +++ b/src/commands/segment/SegmentColourCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTCOLOURCOMMAND_H_ +#define _RG_SEGMENTCOLOURCOMMAND_H_ + +#include "base/Segment.h" +#include +#include +#include +#include + + + + +namespace Rosegarden +{ + +class SegmentSelection; + + +class SegmentColourCommand : public KNamedCommand +{ +public: + SegmentColourCommand(SegmentSelection &segments, + const unsigned int index); + virtual ~SegmentColourCommand(); + + static QString getGlobalName() + { return i18n("Change Segment Color..."); } + + virtual void execute(); + virtual void unexecute(); +protected: + + std::vector m_segments; + std::vector m_oldColourIndexes; + unsigned int m_newColourIndex; +}; + + +} + +#endif diff --git a/src/commands/segment/SegmentColourMapCommand.cpp b/src/commands/segment/SegmentColourMapCommand.cpp new file mode 100644 index 0000000..0e09d4b --- /dev/null +++ b/src/commands/segment/SegmentColourMapCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentColourMapCommand.h" + +#include "base/ColourMap.h" +#include "base/Segment.h" +#include "document/RosegardenGUIDoc.h" +#include + + +namespace Rosegarden +{ + +SegmentColourMapCommand::SegmentColourMapCommand( + RosegardenGUIDoc *doc, + const ColourMap &map): + KNamedCommand(i18n("Change Segment Color Map")), + m_doc(doc), + m_oldMap(m_doc->getComposition().getSegmentColourMap()), + m_newMap(map) +{ +} + +SegmentColourMapCommand::~SegmentColourMapCommand() +{} + +void +SegmentColourMapCommand::execute() +{ + m_doc->getComposition().setSegmentColourMap(m_newMap); + m_doc->slotDocColoursChanged(); +} + +void +SegmentColourMapCommand::unexecute() +{ + m_doc->getComposition().setSegmentColourMap(m_oldMap); + m_doc->slotDocColoursChanged(); +} + +} diff --git a/src/commands/segment/SegmentColourMapCommand.h b/src/commands/segment/SegmentColourMapCommand.h new file mode 100644 index 0000000..257cdce --- /dev/null +++ b/src/commands/segment/SegmentColourMapCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTCOLOURMAPCOMMAND_H_ +#define _RG_SEGMENTCOLOURMAPCOMMAND_H_ + +#include "base/ColourMap.h" +#include "base/Segment.h" +#include +#include +#include + + + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +class SegmentColourMapCommand : public KNamedCommand +{ +public: + SegmentColourMapCommand( RosegardenGUIDoc* doc, + const ColourMap& map); + virtual ~SegmentColourMapCommand(); + + static QString getGlobalName() + { return i18n("Change Segment Color Map..."); } + + virtual void execute(); + virtual void unexecute(); +protected: + RosegardenGUIDoc * m_doc; + ColourMap m_oldMap; + ColourMap m_newMap; +}; + + +// Trigger Segment commands. These are the commands that create +// and manage the triggered segments themselves. See editcommands.h +// for SetTriggerCommand and ClearTriggersCommand which manipulate +// the events that do the triggering. + + +} + +#endif diff --git a/src/commands/segment/SegmentCommand.cpp b/src/commands/segment/SegmentCommand.cpp new file mode 100644 index 0000000..68ff846 --- /dev/null +++ b/src/commands/segment/SegmentCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentCommand.h" + +#include "base/Segment.h" +#include + + +namespace Rosegarden +{ + +SegmentCommand::SegmentCommand(QString name, const std::vector& segments) + : KNamedCommand(name) +{ + m_segments.resize(segments.size()); + std::copy(segments.begin(), segments.end(), m_segments.begin()); +} + +} diff --git a/src/commands/segment/SegmentCommand.h b/src/commands/segment/SegmentCommand.h new file mode 100644 index 0000000..da28a04 --- /dev/null +++ b/src/commands/segment/SegmentCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTCOMMAND_H_ +#define _RG_SEGMENTCOMMAND_H_ + +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Segment; + + +/** + * Base class for commands from the SegmentParameterBox + */ +class SegmentCommand : public KNamedCommand +{ +public: + SegmentCommand(QString name, const std::vector&); + + typedef std::vector segmentlist; + +protected: + segmentlist m_segments; +}; + + +} + +#endif diff --git a/src/commands/segment/SegmentCommandRepeat.cpp b/src/commands/segment/SegmentCommandRepeat.cpp new file mode 100644 index 0000000..4b707cb --- /dev/null +++ b/src/commands/segment/SegmentCommandRepeat.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentCommandRepeat.h" + +#include +#include "base/Segment.h" +#include "SegmentCommand.h" +#include + + +namespace Rosegarden +{ + +SegmentCommandRepeat::SegmentCommandRepeat(const std::vector& segments, + bool repeat) + : SegmentCommand(i18n("Repeat Segments"), segments), + m_repeatState(repeat) +{} + +void SegmentCommandRepeat::execute() +{ + segmentlist::iterator it; + + for (it = m_segments.begin(); it != m_segments.end(); it++) + (*it)->setRepeating(m_repeatState); +} + +void SegmentCommandRepeat::unexecute() +{ + segmentlist::iterator it; + + for (it = m_segments.begin(); it != m_segments.end(); it++) + (*it)->setRepeating(!m_repeatState); +} + +} diff --git a/src/commands/segment/SegmentCommandRepeat.h b/src/commands/segment/SegmentCommandRepeat.h new file mode 100644 index 0000000..0234070 --- /dev/null +++ b/src/commands/segment/SegmentCommandRepeat.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTCOMMANDREPEAT_H_ +#define _RG_SEGMENTCOMMANDREPEAT_H_ + +#include "SegmentCommand.h" +#include + + + + +namespace Rosegarden +{ + +class Segment; + + +/** + * Repeat segment command from the SegmentParameterBox + */ +class SegmentCommandRepeat : public SegmentCommand +{ +public: + SegmentCommandRepeat(const std::vector&, + bool repeat); + + virtual void execute(); + virtual void unexecute(); + +protected: + bool m_repeatState; +}; + +// Disabled until we find a better solution +// +// As it is, command creation happens on every change of the text +// value of the combo box +// +// +// class SegmentCommandChangeTransposeValue : public SegmentCommand +// { +// public: +// SegmentCommandChangeTransposeValue(const std::vector&, +// int transposeValue); + +// virtual void execute(); +// virtual void unexecute(); + +// protected: +// int m_transposeValue; +// std::vector m_savedValues; +// }; + + + +} + +#endif diff --git a/src/commands/segment/SegmentEraseCommand.cpp b/src/commands/segment/SegmentEraseCommand.cpp new file mode 100644 index 0000000..b15788f --- /dev/null +++ b/src/commands/segment/SegmentEraseCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentEraseCommand.h" + +#include +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/Segment.h" +#include "sound/AudioFile.h" +#include "sound/AudioFileManager.h" + + +namespace Rosegarden +{ + +SegmentEraseCommand::SegmentEraseCommand(Segment *segment) : + KNamedCommand(i18n("Erase Segment")), + m_composition(segment->getComposition()), + m_segment(segment), + m_mgr(0), + m_audioFileName(""), + m_detached(false) +{ + // nothing else +} + +SegmentEraseCommand::SegmentEraseCommand(Segment *segment, + AudioFileManager *mgr) : + KNamedCommand(i18n("Erase Segment")), + m_composition(segment->getComposition()), + m_segment(segment), + m_mgr(mgr), + m_detached(false) +{ + // If this is an audio segment, we want to make a note of + // its associated file name in case we need to undo and restore + // the file. + if (m_segment->getType() == Segment::Audio) { + unsigned int id = m_segment->getAudioFileId(); + AudioFile *file = mgr->getAudioFile(id); + if (file) + m_audioFileName = file->getFilename(); + } +} + +SegmentEraseCommand::~SegmentEraseCommand() +{ + // This is the only place in this command that the Segment can + // safely be deleted, and then only if it is not in the + // Composition (i.e. if we executed more recently than we + // unexecuted). Can't safely call through the m_segment pointer + // here; someone else might have got to it first + + if (m_detached) { + delete m_segment; + } +} + +void +SegmentEraseCommand::execute() +{ + m_composition->detachSegment(m_segment); + m_detached = true; +} + +void +SegmentEraseCommand::unexecute() +{ + m_composition->addSegment(m_segment); + m_detached = false; + + if (m_segment->getType() == Segment::Audio && + m_audioFileName != "" && + m_mgr) { + int id = m_mgr->fileExists(m_audioFileName); + + RG_DEBUG << "SegmentEraseCommand::unexecute: file is " << m_audioFileName << endl; + + if (id == -1) + id = (int)m_mgr->addFile(m_audioFileName); + m_segment->setAudioFileId(id); + } +} + +} diff --git a/src/commands/segment/SegmentEraseCommand.h b/src/commands/segment/SegmentEraseCommand.h new file mode 100644 index 0000000..493cd14 --- /dev/null +++ b/src/commands/segment/SegmentEraseCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTERASECOMMAND_H_ +#define _RG_SEGMENTERASECOMMAND_H_ + +#include +#include + + + + +namespace Rosegarden +{ + +class Segment; +class Composition; +class AudioFileManager; + + +//////////////////////////////////////////////////////////// + +class SegmentEraseCommand : public KNamedCommand +{ +public: + /// for removing segment normally + SegmentEraseCommand(Segment *segment); + + /// for removing audio segment when removing an audio file + SegmentEraseCommand(Segment *segment, + AudioFileManager *mgr); + virtual ~SegmentEraseCommand(); + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + Segment *m_segment; + AudioFileManager *m_mgr; + std::string m_audioFileName; + bool m_detached; +}; + + +} + +#endif diff --git a/src/commands/segment/SegmentInsertCommand.cpp b/src/commands/segment/SegmentInsertCommand.cpp new file mode 100644 index 0000000..f10ef45 --- /dev/null +++ b/src/commands/segment/SegmentInsertCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentInsertCommand.h" + +#include +#include "base/Composition.h" +#include "base/Segment.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "document/RosegardenGUIDoc.h" + + +namespace Rosegarden +{ + +SegmentInsertCommand::SegmentInsertCommand(RosegardenGUIDoc *doc, + TrackId track, + timeT startTime, + timeT endTime): + KNamedCommand(i18n("Create Segment")), + m_composition(&(doc->getComposition())), + m_studio(&(doc->getStudio())), + m_segment(0), + m_track(track), + m_startTime(startTime), + m_endTime(endTime), + m_detached(false) +{} + +SegmentInsertCommand::SegmentInsertCommand(Composition *composition, + Segment *segment, + TrackId track): + KNamedCommand(i18n("Create Segment")), + m_composition(composition), + m_studio(0), + m_segment(segment), + m_track(track), + m_startTime(0), + m_endTime(0), + m_detached(false) +{} + +SegmentInsertCommand::~SegmentInsertCommand() +{ + if (m_detached) { + delete m_segment; + } +} + +Segment * +SegmentInsertCommand::getSegment() const +{ + return m_segment; +} + +void +SegmentInsertCommand::execute() +{ + if (!m_segment) { + // Create and insert Segment + // + m_segment = new Segment(); + m_segment->setTrack(m_track); + m_segment->setStartTime(m_startTime); + m_composition->addSegment(m_segment); + m_segment->setEndTime(m_endTime); + + // Do our best to label the Segment with whatever is currently + // showing against it. + // + Track *track = m_composition->getTrackById(m_track); + std::string label; + + if (track) { + // try to get a reasonable Segment name by Instrument + // + label = m_studio->getSegmentName(track->getInstrument()); + + // if not use the track label + // + if (label == "") + label = track->getLabel(); + + m_segment->setLabel(label); + } + } else { + m_segment->setTrack(m_track); + m_composition->addSegment(m_segment); + } + + m_detached = false; +} + +void +SegmentInsertCommand::unexecute() +{ + m_composition->detachSegment(m_segment); + m_detached = true; +} + +} diff --git a/src/commands/segment/SegmentInsertCommand.h b/src/commands/segment/SegmentInsertCommand.h new file mode 100644 index 0000000..932919a --- /dev/null +++ b/src/commands/segment/SegmentInsertCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTINSERTCOMMAND_H_ +#define _RG_SEGMENTINSERTCOMMAND_H_ + +#include "base/Track.h" +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Studio; +class Segment; +class RosegardenGUIDoc; +class Composition; + + +class SegmentInsertCommand : public KNamedCommand +{ +public: + SegmentInsertCommand(RosegardenGUIDoc *doc, + TrackId track, + timeT startTime, + timeT endTime); + SegmentInsertCommand(Composition *composition, + Segment *segment, + TrackId track); + virtual ~SegmentInsertCommand(); + + Segment *getSegment() const; // after invocation + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + Studio *m_studio; + Segment *m_segment; + int m_track; + timeT m_startTime; + timeT m_endTime; + bool m_detached; +}; + + + +} + +#endif diff --git a/src/commands/segment/SegmentJoinCommand.cpp b/src/commands/segment/SegmentJoinCommand.cpp new file mode 100644 index 0000000..27b1bb8 --- /dev/null +++ b/src/commands/segment/SegmentJoinCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentJoinCommand.h" + +#include "base/Composition.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include + + +namespace Rosegarden +{ + +SegmentJoinCommand::SegmentJoinCommand(SegmentSelection & + segments) : + KNamedCommand(getGlobalName()), + m_newSegment(0), + m_detached(false) // true if the old segments are detached, not the new +{ + for (SegmentSelection::iterator i = segments.begin(); + i != segments.end(); ++i) + { + m_oldSegments.push_back(*i); + } + assert(m_oldSegments.size() > 0); +} + +SegmentJoinCommand::~SegmentJoinCommand() +{ + if (m_detached) { + for (unsigned int i = 0; i < m_oldSegments.size(); ++i) { + delete m_oldSegments[i]; + } + } else { + delete m_newSegment; + } +} + +void +SegmentJoinCommand::execute() +{ + Composition *composition = m_oldSegments[0]->getComposition(); + if (!composition) { + std::cerr + << "SegmentJoinCommand::execute: ERROR: old segments are not in composition!" + << std::endl; + return ; + } + + // we normalize rests in any overlapping areas + timeT overlapStart = 0, overlapEnd = 0; + bool haveOverlap = false; + + if (!m_newSegment) { + + m_newSegment = new Segment(*m_oldSegments[0]); + + // that duplicated segment 0; now do the rest + + for (unsigned int i = 1; i < m_oldSegments.size(); ++i) { + + Segment *s = m_oldSegments[i]; + + timeT start = s->getStartTime(), end = s->getEndMarkerTime(); + + timeT os = 0, oe = 0; + bool haveOverlapHere = false; + + if (start < m_newSegment->getEndMarkerTime() && + end > m_newSegment->getStartTime()) { + haveOverlapHere = true; + os = std::max(start, m_newSegment->getStartTime()); + oe = std::min(end, m_newSegment->getEndMarkerTime()); + std::cerr << "overlap here, os = " << os << ", oe = " << oe << std::endl; + } + + if (haveOverlapHere) { + if (haveOverlap) { + overlapStart = std::min(overlapStart, os); + overlapEnd = std::max(overlapEnd, oe); + } else { + overlapStart = os; + overlapEnd = oe; + haveOverlap = true; + } + } + + if (start > m_newSegment->getEndMarkerTime()) { + m_newSegment->setEndMarkerTime(start); + } + + for (Segment::iterator si = s->begin(); + s->isBeforeEndMarker(si); ++si) { + + // weed out duplicate clefs and keys + + if ((*si)->isa(Clef::EventType)) { + try { + Clef newClef(**si); + if (m_newSegment->getClefAtTime + ((*si)->getAbsoluteTime() + 1) == newClef) { + continue; + } + } catch (...) { } + } + + if ((*si)->isa(Key::EventType)) { + try { + Key newKey(**si); + if (m_newSegment->getKeyAtTime + ((*si)->getAbsoluteTime() + 1) == newKey) { + continue; + } + } catch (...) { } + } + + m_newSegment->insert(new Event(**si)); + } + + if (end > m_newSegment->getEndMarkerTime()) { + m_newSegment->setEndMarkerTime(end); + } + } + } + + composition->addSegment(m_newSegment); + + if (haveOverlap) { + m_newSegment->normalizeRests(overlapStart, overlapEnd); + } + + for (unsigned int i = 0; i < m_oldSegments.size(); ++i) { + composition->detachSegment(m_oldSegments[i]); + } + + m_detached = true; +} + +void +SegmentJoinCommand::unexecute() +{ + for (unsigned int i = 0; i < m_oldSegments.size(); ++i) { + m_newSegment->getComposition()->addSegment(m_oldSegments[i]); + } + + m_newSegment->getComposition()->detachSegment(m_newSegment); + m_detached = false; +} + +} diff --git a/src/commands/segment/SegmentJoinCommand.h b/src/commands/segment/SegmentJoinCommand.h new file mode 100644 index 0000000..97c7924 --- /dev/null +++ b/src/commands/segment/SegmentJoinCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTJOINCOMMAND_H_ +#define _RG_SEGMENTJOINCOMMAND_H_ + +#include +#include +#include +#include + + + + +namespace Rosegarden +{ + +class SegmentSelection; +class Segment; + + +class SegmentJoinCommand : public KNamedCommand +{ +public: + SegmentJoinCommand(SegmentSelection &segments); + virtual ~SegmentJoinCommand(); + + virtual void execute(); + virtual void unexecute(); + + static QString getGlobalName() { return i18n("&Join"); } + +private: + std::vector m_oldSegments; + Segment *m_newSegment; + bool m_detached; +}; + + + +} + +#endif diff --git a/src/commands/segment/SegmentLabelCommand.cpp b/src/commands/segment/SegmentLabelCommand.cpp new file mode 100644 index 0000000..8bc0ff4 --- /dev/null +++ b/src/commands/segment/SegmentLabelCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentLabelCommand.h" + +#include "misc/Strings.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include + + +namespace Rosegarden +{ + +SegmentLabelCommand::SegmentLabelCommand( + SegmentSelection &segments, + const QString &label): + KNamedCommand(i18n("Label Segments")), + m_newLabel(label) +{ + for (SegmentSelection::iterator i = segments.begin(); + i != segments.end(); ++i) + m_segments.push_back(*i); +} + +SegmentLabelCommand::~SegmentLabelCommand() +{} + +void +SegmentLabelCommand::execute() +{ + bool addLabels = false; + if (m_labels.size() == 0) + addLabels = true; + + for (unsigned int i = 0; i < m_segments.size(); ++i) { + if (addLabels) + m_labels.push_back(strtoqstr(m_segments[i]->getLabel())); + + m_segments[i]->setLabel(qstrtostr(m_newLabel)); + } +} + +void +SegmentLabelCommand::unexecute() +{ + for (unsigned int i = 0; i < m_segments.size(); ++i) + m_segments[i]->setLabel(qstrtostr(m_labels[i])); +} + +} diff --git a/src/commands/segment/SegmentLabelCommand.h b/src/commands/segment/SegmentLabelCommand.h new file mode 100644 index 0000000..1c55f3b --- /dev/null +++ b/src/commands/segment/SegmentLabelCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTLABELCOMMAND_H_ +#define _RG_SEGMENTLABELCOMMAND_H_ + +#include +#include +#include +#include + + + + +namespace Rosegarden +{ + +class SegmentSelection; +class Segment; + + +class SegmentLabelCommand : public KNamedCommand +{ +public: + SegmentLabelCommand(SegmentSelection &segments, + const QString &label); + virtual ~SegmentLabelCommand(); + + static QString getGlobalName() + { return i18n("Re&label..."); } + + virtual void execute(); + virtual void unexecute(); +protected: + + std::vector m_segments; + std::vector m_labels; + QString m_newLabel; +}; + + + +} + +#endif diff --git a/src/commands/segment/SegmentQuickCopyCommand.cpp b/src/commands/segment/SegmentQuickCopyCommand.cpp new file mode 100644 index 0000000..1ce432c --- /dev/null +++ b/src/commands/segment/SegmentQuickCopyCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentQuickCopyCommand.h" + +#include "misc/Strings.h" +#include "base/Composition.h" +#include "base/Segment.h" +#include + + +namespace Rosegarden +{ + +SegmentQuickCopyCommand::SegmentQuickCopyCommand(Segment *segment): + KNamedCommand(getGlobalName()), + m_composition(segment->getComposition()), + m_segmentToCopy(segment), + m_segment(0), + m_detached(false) +{} + +SegmentQuickCopyCommand::~SegmentQuickCopyCommand() +{ + if (m_detached) { + delete m_segment; + } +} + +void +SegmentQuickCopyCommand::execute() +{ + if (!m_segment) { + m_segment = new Segment(*m_segmentToCopy); + m_segment->setLabel(qstrtostr(i18n("%1 (copied)").arg + (strtoqstr(m_segment->getLabel())))); + } + m_composition->addSegment(m_segment); + m_detached = false; +} + +void +SegmentQuickCopyCommand::unexecute() +{ + m_composition->detachSegment(m_segment); + m_detached = true; +} + +} diff --git a/src/commands/segment/SegmentQuickCopyCommand.h b/src/commands/segment/SegmentQuickCopyCommand.h new file mode 100644 index 0000000..638b16a --- /dev/null +++ b/src/commands/segment/SegmentQuickCopyCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTQUICKCOPYCOMMAND_H_ +#define _RG_SEGMENTQUICKCOPYCOMMAND_H_ + +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Segment; +class Composition; + + +class SegmentQuickCopyCommand : public KNamedCommand +{ +public: + SegmentQuickCopyCommand(Segment *segment); + virtual ~SegmentQuickCopyCommand(); + + virtual void execute(); + virtual void unexecute(); + + // return pointer to new copy + Segment* getCopy() { return m_segment; } + + static QString getGlobalName() { return i18n("Quick-Copy Segment"); } + +private: + Composition *m_composition; + Segment *m_segmentToCopy; + Segment *m_segment; + bool m_detached; +}; + + + +} + +#endif diff --git a/src/commands/segment/SegmentReconfigureCommand.cpp b/src/commands/segment/SegmentReconfigureCommand.cpp new file mode 100644 index 0000000..ec9d1bd --- /dev/null +++ b/src/commands/segment/SegmentReconfigureCommand.cpp @@ -0,0 +1,114 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentReconfigureCommand.h" + +#include "base/Segment.h" +#include "base/Track.h" +#include + + +namespace Rosegarden +{ + +SegmentReconfigureCommand::SegmentReconfigureCommand(QString name) : + KNamedCommand(name) +{} + +SegmentReconfigureCommand::~SegmentReconfigureCommand() +{} + +void +SegmentReconfigureCommand::addSegment(Segment *segment, + timeT startTime, + timeT endMarkerTime, + TrackId track) +{ + SegmentRec record; + record.segment = segment; + record.startTime = startTime; + record.endMarkerTime = endMarkerTime; + record.track = track; + m_records.push_back(record); +} + +void +SegmentReconfigureCommand::addSegments(const SegmentRecSet &records) +{ + for (SegmentRecSet::const_iterator i = records.begin(); i != records.end(); ++i) { + m_records.push_back(*i); + } +} + +void +SegmentReconfigureCommand::execute() +{ + swap(); +} + +void +SegmentReconfigureCommand::unexecute() +{ + swap(); +} + +void +SegmentReconfigureCommand::swap() +{ + for (SegmentRecSet::iterator i = m_records.begin(); + i != m_records.end(); ++i) { + + // set the segment's values from the record, but set the + // previous values back in to the record for use in the + // next iteration of the execute/unexecute cycle. + + // #1083496: look up both of the "old" values before we set + // anything, as setting the start time is likely to change the + // end marker time. + + timeT prevStartTime = i->segment->getStartTime(); + timeT prevEndMarkerTime = i->segment->getEndMarkerTime(); + + if (i->segment->getStartTime() != i->startTime) { + i->segment->setStartTime(i->startTime); + } + + if (i->segment->getEndMarkerTime() != i->endMarkerTime) { + i->segment->setEndMarkerTime(i->endMarkerTime); + } + + i->startTime = prevStartTime; + i->endMarkerTime = prevEndMarkerTime; + + TrackId currentTrack = i->segment->getTrack(); + + if (currentTrack != i->track) { + i->segment->setTrack(i->track); + i->track = currentTrack; + } + } +} + +} diff --git a/src/commands/segment/SegmentReconfigureCommand.h b/src/commands/segment/SegmentReconfigureCommand.h new file mode 100644 index 0000000..be82e47 --- /dev/null +++ b/src/commands/segment/SegmentReconfigureCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTRECONFIGURECOMMAND_H_ +#define _RG_SEGMENTRECONFIGURECOMMAND_H_ + +#include "base/Track.h" +#include +#include +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; + + +/** + * SegmentReconfigureCommand is a general-purpose command for + * moving, resizing or changing the track of one or more segments + */ +class SegmentReconfigureCommand : public KNamedCommand +{ +public: + SegmentReconfigureCommand(QString name); + virtual ~SegmentReconfigureCommand(); + + struct SegmentRec { + Segment *segment; + timeT startTime; + timeT endMarkerTime; + TrackId track; + }; + typedef std::vector SegmentRecSet; + + void addSegment(Segment *segment, + timeT startTime, + timeT endMarkerTime, + TrackId track); + + void addSegments(const SegmentRecSet &records); + + void execute(); + void unexecute(); + +private: + SegmentRecSet m_records; + void swap(); +}; + + + +} + +#endif diff --git a/src/commands/segment/SegmentRecordCommand.cpp b/src/commands/segment/SegmentRecordCommand.cpp new file mode 100644 index 0000000..f457f32 --- /dev/null +++ b/src/commands/segment/SegmentRecordCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentRecordCommand.h" + +#include +#include "base/Composition.h" +#include "base/Segment.h" + + +namespace Rosegarden +{ + +SegmentRecordCommand::SegmentRecordCommand(Segment *s) : + KNamedCommand(i18n("Record")), + m_composition(s->getComposition()), + m_segment(s), + m_detached(false) +{} + +SegmentRecordCommand::~SegmentRecordCommand() +{ + if (m_detached) { + delete m_segment; + } +} + +void +SegmentRecordCommand::execute() +{ + if (!m_segment->getComposition()) { + m_composition->addSegment(m_segment); + } + + m_detached = false; +} + +void +SegmentRecordCommand::unexecute() +{ + m_composition->detachSegment(m_segment); + m_detached = true; +} + +} diff --git a/src/commands/segment/SegmentRecordCommand.h b/src/commands/segment/SegmentRecordCommand.h new file mode 100644 index 0000000..c3f4ffe --- /dev/null +++ b/src/commands/segment/SegmentRecordCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTRECORDCOMMAND_H_ +#define _RG_SEGMENTRECORDCOMMAND_H_ + +#include + + + + +namespace Rosegarden +{ + +class Segment; +class Composition; + + +/** + * SegmentRecordCommand pretends to insert a Segment that is actually + * already in the Composition (the currently-being-recorded one). It's + * used at the end of recording, to ensure that GUI updates happen + * correctly, and it provides the ability to undo recording. (The + * unexecute does remove the segment, it doesn't just pretend to.) + */ +class SegmentRecordCommand : public KNamedCommand +{ +public: + SegmentRecordCommand(Segment *segment); + virtual ~SegmentRecordCommand(); + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + Segment *m_segment; + bool m_detached; +}; + + + +} + +#endif diff --git a/src/commands/segment/SegmentRepeatToCopyCommand.cpp b/src/commands/segment/SegmentRepeatToCopyCommand.cpp new file mode 100644 index 0000000..a0e0e43 --- /dev/null +++ b/src/commands/segment/SegmentRepeatToCopyCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentRepeatToCopyCommand.h" + +#include "base/Event.h" +#include +#include "base/Composition.h" +#include "base/Segment.h" + + +namespace Rosegarden +{ + +SegmentRepeatToCopyCommand::SegmentRepeatToCopyCommand( + Segment *segment): + KNamedCommand(i18n("Turn Repeats into Copies")), + m_composition(segment->getComposition()), + m_segment(segment), + m_detached(false) +{} + +SegmentRepeatToCopyCommand::~SegmentRepeatToCopyCommand() +{ + if (m_detached) { + std::vector::iterator it = + m_newSegments.begin(); + + for (; it != m_newSegments.end(); it++) + delete (*it); + } +} + +void +SegmentRepeatToCopyCommand::execute() +{ + if (m_newSegments.size() == 0) { + timeT newStartTime = m_segment->getEndMarkerTime(); + timeT newDuration = + m_segment->getEndMarkerTime() - m_segment->getStartTime(); + Segment *newSegment; + timeT repeatEndTime = m_segment->getRepeatEndTime(); + + while (newStartTime + newDuration < repeatEndTime) { + // Create new segment, transpose and turn off repeat + // + newSegment = new Segment(*m_segment); + newSegment->setStartTime(newStartTime); + newSegment->setRepeating(false); + + // Insert and store + m_composition->addSegment(newSegment); + m_newSegments.push_back(newSegment); + + // Move onto next + newStartTime += newDuration; + } + + // fill remaining partial segment + } else { + std::vector::iterator it = + m_newSegments.begin(); + + for (; it != m_newSegments.end(); it++) + m_composition->addSegment(*it); + } + m_segment->setRepeating(false); + m_detached = false; +} + +void +SegmentRepeatToCopyCommand::unexecute() +{ + std::vector::iterator it = + m_newSegments.begin(); + + for (; it != m_newSegments.end(); it++) + m_composition->detachSegment(*it); + + m_detached = true; + m_segment->setRepeating(true); +} + +} diff --git a/src/commands/segment/SegmentRepeatToCopyCommand.h b/src/commands/segment/SegmentRepeatToCopyCommand.h new file mode 100644 index 0000000..d0cc565 --- /dev/null +++ b/src/commands/segment/SegmentRepeatToCopyCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTREPEATTOCOPYCOMMAND_H_ +#define _RG_SEGMENTREPEATTOCOPYCOMMAND_H_ + +#include +#include + + + + +namespace Rosegarden +{ + +class Segment; +class Composition; + + +class SegmentRepeatToCopyCommand : public KNamedCommand +{ +public: + SegmentRepeatToCopyCommand(Segment *segment); + virtual ~SegmentRepeatToCopyCommand(); + + virtual void execute(); + virtual void unexecute(); + +private: + + Composition *m_composition; + Segment *m_segment; + std::vector m_newSegments; + bool m_detached; +}; + + +} + +#endif diff --git a/src/commands/segment/SegmentRescaleCommand.cpp b/src/commands/segment/SegmentRescaleCommand.cpp new file mode 100644 index 0000000..d7a7c80 --- /dev/null +++ b/src/commands/segment/SegmentRescaleCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentRescaleCommand.h" + +#include "misc/Strings.h" +#include "base/Event.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include + + +namespace Rosegarden +{ + +SegmentRescaleCommand::SegmentRescaleCommand(Segment *s, + int multiplier, + int divisor) : + KNamedCommand(getGlobalName()), + m_segment(s), + m_newSegment(0), + m_startTimeGiven(false), + m_startTime(s->getStartTime()), + m_multiplier(multiplier), + m_divisor(divisor), + m_detached(false) +{ + // nothing +} + +SegmentRescaleCommand::SegmentRescaleCommand(Segment *s, + int multiplier, + int divisor, + timeT st) : + KNamedCommand(getGlobalName()), + m_segment(s), + m_newSegment(0), + m_startTimeGiven(true), + m_startTime(st), + m_multiplier(multiplier), + m_divisor(divisor), + m_detached(false) +{ + // nothing +} + +SegmentRescaleCommand::~SegmentRescaleCommand() +{ + if (m_detached) { + delete m_segment; + } else { + delete m_newSegment; + } +} + +timeT +SegmentRescaleCommand::rescale(timeT t) +{ + // avoid overflows by using doubles + double d = t; + d *= m_multiplier; + d /= m_divisor; + d += 0.5; + return (timeT)d; +} + +void +SegmentRescaleCommand::execute() +{ + timeT startTime = m_segment->getStartTime(); + + if (m_startTimeGiven) startTime = m_startTime; + + if (!m_newSegment) { + + m_newSegment = new Segment(); + m_newSegment->setTrack(m_segment->getTrack()); + QString oldLabel = strtoqstr(m_segment->getLabel()); + if (oldLabel.endsWith(i18n("(rescaled)"))) { + m_newSegment->setLabel(m_segment->getLabel()); + } else { + m_newSegment->setLabel(qstrtostr(i18n("%1 (rescaled)").arg + (oldLabel))); + } + m_newSegment->setColourIndex(m_segment->getColourIndex()); + + for (Segment::iterator i = m_segment->begin(); + m_segment->isBeforeEndMarker(i); ++i) { + + if ((*i)->isa(Note::EventRestType)) + continue; + + timeT dt = (*i)->getAbsoluteTime() - startTime; + timeT duration = (*i)->getDuration(); + + //!!! use doubles for this calculation where necessary + + m_newSegment->insert + (new Event(**i, + startTime + rescale(dt), + rescale(duration))); + } + } + + m_segment->getComposition()->addSegment(m_newSegment); + m_segment->getComposition()->detachSegment(m_segment); + m_newSegment->normalizeRests(m_newSegment->getStartTime(), + m_newSegment->getEndTime()); + + m_newSegment->setEndMarkerTime + (startTime + rescale(m_segment->getEndMarkerTime() - + m_segment->getStartTime())); + + m_detached = true; +} + +void +SegmentRescaleCommand::unexecute() +{ + m_newSegment->getComposition()->addSegment(m_segment); + m_newSegment->getComposition()->detachSegment(m_newSegment); + m_detached = false; +} + +} diff --git a/src/commands/segment/SegmentRescaleCommand.h b/src/commands/segment/SegmentRescaleCommand.h new file mode 100644 index 0000000..7a34727 --- /dev/null +++ b/src/commands/segment/SegmentRescaleCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTRESCALECOMMAND_H_ +#define _RG_SEGMENTRESCALECOMMAND_H_ + +#include +#include +#include "base/Event.h" +#include + + + + +namespace Rosegarden +{ + +class Segment; + + +class SegmentRescaleCommand : public KNamedCommand +{ +public: + SegmentRescaleCommand(Segment *segment, + int multiplier, + int divisor); + SegmentRescaleCommand(Segment *segment, + int multiplier, + int divisor, + timeT newStartTime); + virtual ~SegmentRescaleCommand(); + + virtual void execute(); + virtual void unexecute(); + + static QString getGlobalName() { return i18n("Stretch or S&quash..."); } + +private: + Segment *m_segment; + Segment *m_newSegment; + bool m_startTimeGiven; + timeT m_startTime; + int m_multiplier; + int m_divisor; + bool m_detached; + + timeT rescale(timeT); +}; + + + +} + +#endif diff --git a/src/commands/segment/SegmentResizeFromStartCommand.cpp b/src/commands/segment/SegmentResizeFromStartCommand.cpp new file mode 100644 index 0000000..a4157bb --- /dev/null +++ b/src/commands/segment/SegmentResizeFromStartCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentResizeFromStartCommand.h" + +#include +#include "base/Event.h" +#include "base/Segment.h" +#include "document/BasicCommand.h" + + +namespace Rosegarden +{ + +SegmentResizeFromStartCommand::SegmentResizeFromStartCommand(Segment *s, + timeT time) : + BasicCommand(i18n("Resize Segment"), *s, + std::min(time, s->getStartTime()), + std::max(time, s->getStartTime())), + m_segment(s), + m_oldStartTime(s->getStartTime()), + m_newStartTime(time) +{ + // nothing else +} + +SegmentResizeFromStartCommand::~SegmentResizeFromStartCommand() +{ + // nothing +} + +void +SegmentResizeFromStartCommand::modifySegment() +{ + if (m_newStartTime < m_oldStartTime) { + m_segment->fillWithRests(m_newStartTime, m_oldStartTime); + } else { + + for (Segment::iterator i = m_segment->begin(); + m_segment->isBeforeEndMarker(i); ) { + + Segment::iterator j = i; + ++j; + + if ((*i)->getAbsoluteTime() >= m_newStartTime) + break; + + if ((*i)->getAbsoluteTime() + (*i)->getDuration() <= m_newStartTime) { + m_segment->erase(i); + } else { + Event *e = new Event + (**i, m_newStartTime, + (*i)->getAbsoluteTime() + (*i)->getDuration() - m_newStartTime); + m_segment->erase(i); + m_segment->insert(e); + } + + i = j; + } + } +} + +} diff --git a/src/commands/segment/SegmentResizeFromStartCommand.h b/src/commands/segment/SegmentResizeFromStartCommand.h new file mode 100644 index 0000000..8f0dc89 --- /dev/null +++ b/src/commands/segment/SegmentResizeFromStartCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTRESIZEFROMSTARTCOMMAND_H_ +#define _RG_SEGMENTRESIZEFROMSTARTCOMMAND_H_ + +#include "document/BasicCommand.h" +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; + + +/** + * SegmentResizeFromStartCommand moves the start time of a segment + * leaving the events in it at the same absolute times as before, so + * padding with rests or deleting events as appropriate. (Distinct + * from Segment::setStartTime, as used by SegmentReconfigureCommand, + * which moves all the events in the segment.) + * Not for use on audio segments (see AudioSegmentResizeFromStartCommand). + */ +class SegmentResizeFromStartCommand : public BasicCommand +{ +public: + SegmentResizeFromStartCommand(Segment *segment, + timeT newStartTime); + virtual ~SegmentResizeFromStartCommand(); + +protected: + virtual void modifySegment(); + +private: + Segment *m_segment; + timeT m_oldStartTime; + timeT m_newStartTime; +}; + + + +} + +#endif diff --git a/src/commands/segment/SegmentSingleRepeatToCopyCommand.cpp b/src/commands/segment/SegmentSingleRepeatToCopyCommand.cpp new file mode 100644 index 0000000..c9b92fb --- /dev/null +++ b/src/commands/segment/SegmentSingleRepeatToCopyCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentSingleRepeatToCopyCommand.h" + +#include +#include "base/Composition.h" +#include "base/Segment.h" + + +namespace Rosegarden +{ + +SegmentSingleRepeatToCopyCommand::SegmentSingleRepeatToCopyCommand( + Segment *segment, + timeT time): + KNamedCommand(i18n("Turn Single Repeat into Copy")), + m_composition(segment->getComposition()), + m_segment(segment), + m_newSegment(0), + m_time(time), + m_detached(false) +{} + +SegmentSingleRepeatToCopyCommand::~SegmentSingleRepeatToCopyCommand() +{ + if (m_detached) + delete m_newSegment; +} + +void +SegmentSingleRepeatToCopyCommand::execute() +{ + if (!m_newSegment) { + m_newSegment = new Segment(*m_segment); + m_newSegment->setStartTime(m_time); + m_newSegment->setRepeating(true); + } + + m_composition->addSegment(m_newSegment); + m_detached = false; +} + +void +SegmentSingleRepeatToCopyCommand::unexecute() +{ + m_composition->detachSegment(m_newSegment); + m_detached = true; +} + +} diff --git a/src/commands/segment/SegmentSingleRepeatToCopyCommand.h b/src/commands/segment/SegmentSingleRepeatToCopyCommand.h new file mode 100644 index 0000000..0c4984e --- /dev/null +++ b/src/commands/segment/SegmentSingleRepeatToCopyCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTSINGLEREPEATTOCOPYCOMMAND_H_ +#define _RG_SEGMENTSINGLEREPEATTOCOPYCOMMAND_H_ + +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Composition; + + +class SegmentSingleRepeatToCopyCommand : public KNamedCommand +{ +public: + SegmentSingleRepeatToCopyCommand(Segment *segment, + timeT time); + virtual ~SegmentSingleRepeatToCopyCommand(); + + virtual void execute(); + virtual void unexecute(); + + Segment *getNewSegment() const { return m_newSegment; } + +private: + Composition *m_composition; + Segment *m_segment; + Segment *m_newSegment; + timeT m_time; + bool m_detached; +}; + + +} + +#endif diff --git a/src/commands/segment/SegmentSplitByPitchCommand.cpp b/src/commands/segment/SegmentSplitByPitchCommand.cpp new file mode 100644 index 0000000..2000a35 --- /dev/null +++ b/src/commands/segment/SegmentSplitByPitchCommand.cpp @@ -0,0 +1,280 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentSplitByPitchCommand.h" + +#include "base/BaseProperties.h" +#include "base/Sets.h" +#include "misc/Strings.h" +#include "base/Composition.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/NotationQuantizer.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include + + +namespace Rosegarden +{ + +SegmentSplitByPitchCommand::SegmentSplitByPitchCommand(Segment *segment, + int p, bool r, bool d, + ClefHandling c) : + KNamedCommand(i18n("Split by Pitch")), + m_composition(segment->getComposition()), + m_segment(segment), + m_newSegmentA(0), + m_newSegmentB(0), + m_splitPitch(p), + m_ranging(r), + m_dupNonNoteEvents(d), + m_clefHandling(c), + m_executed(false) +{} + +SegmentSplitByPitchCommand::~SegmentSplitByPitchCommand() +{ + if (m_executed) { + delete m_segment; + } else { + delete m_newSegmentA; + delete m_newSegmentB; + } +} + +void +SegmentSplitByPitchCommand::execute() +{ + if (!m_newSegmentA) { + + m_newSegmentA = new Segment; + m_newSegmentB = new Segment; + + m_newSegmentA->setTrack(m_segment->getTrack()); + m_newSegmentA->setStartTime(m_segment->getStartTime()); + + m_newSegmentB->setTrack(m_segment->getTrack()); + m_newSegmentB->setStartTime(m_segment->getStartTime()); + + int splitPitch(m_splitPitch); + + for (Segment::iterator i = m_segment->begin(); + m_segment->isBeforeEndMarker(i); ++i) { + + if ((*i)->isa(Note::EventRestType)) + continue; + if ((*i)->isa(Clef::EventType) && + m_clefHandling != LeaveClefs) + continue; + + if ((*i)->isa(Note::EventType)) { + + if (m_ranging) { + splitPitch = getSplitPitchAt(i, splitPitch); + } + + if ((*i)->has(BaseProperties::PITCH) && + (*i)->get + (BaseProperties::PITCH) < + splitPitch) { + if (m_newSegmentB->empty()) { + m_newSegmentB->fillWithRests((*i)->getAbsoluteTime()); + } + m_newSegmentB->insert(new Event(**i)); + } + else { + if (m_newSegmentA->empty()) { + m_newSegmentA->fillWithRests((*i)->getAbsoluteTime()); + } + m_newSegmentA->insert(new Event(**i)); + } + + } else { + + m_newSegmentA->insert(new Event(**i)); + + if (m_dupNonNoteEvents) { + m_newSegmentB->insert(new Event(**i)); + } + } + } + + //!!! m_newSegmentA->fillWithRests(m_segment->getEndMarkerTime()); + // m_newSegmentB->fillWithRests(m_segment->getEndMarkerTime()); + m_newSegmentA->normalizeRests(m_segment->getStartTime(), + m_segment->getEndMarkerTime()); + m_newSegmentB->normalizeRests(m_segment->getStartTime(), + m_segment->getEndMarkerTime()); + } + + m_composition->addSegment(m_newSegmentA); + m_composition->addSegment(m_newSegmentB); + + SegmentNotationHelper helperA(*m_newSegmentA); + SegmentNotationHelper helperB(*m_newSegmentB); + + if (m_clefHandling == RecalculateClefs) { + + m_newSegmentA->insert + (helperA.guessClef(m_newSegmentA->begin(), + m_newSegmentA->end()).getAsEvent + (m_newSegmentA->getStartTime())); + + m_newSegmentB->insert + (helperB.guessClef(m_newSegmentB->begin(), + m_newSegmentB->end()).getAsEvent + (m_newSegmentB->getStartTime())); + + } else if (m_clefHandling == UseTrebleAndBassClefs) { + + m_newSegmentA->insert + (Clef(Clef::Treble).getAsEvent + (m_newSegmentA->getStartTime())); + + m_newSegmentB->insert + (Clef(Clef::Bass).getAsEvent + (m_newSegmentB->getStartTime())); + } + + //!!! m_composition->getNotationQuantizer()->quantize(m_newSegmentA); + // m_composition->getNotationQuantizer()->quantize(m_newSegmentB); + helperA.autoBeam(m_newSegmentA->begin(), m_newSegmentA->end(), + BaseProperties::GROUP_TYPE_BEAMED); + helperB.autoBeam(m_newSegmentB->begin(), m_newSegmentB->end(), + BaseProperties::GROUP_TYPE_BEAMED); + + std::string label = m_segment->getLabel(); + m_newSegmentA->setLabel(qstrtostr(i18n("%1 (upper)").arg + (strtoqstr(label)))); + m_newSegmentB->setLabel(qstrtostr(i18n("%1 (lower)").arg + (strtoqstr(label)))); + m_newSegmentA->setColourIndex(m_segment->getColourIndex()); + m_newSegmentB->setColourIndex(m_segment->getColourIndex()); + + m_composition->detachSegment(m_segment); + m_executed = true; +} + +void +SegmentSplitByPitchCommand::unexecute() +{ + m_composition->addSegment(m_segment); + m_composition->detachSegment(m_newSegmentA); + m_composition->detachSegment(m_newSegmentB); + m_executed = false; +} + +int +SegmentSplitByPitchCommand::getSplitPitchAt(Segment::iterator i, + int lastSplitPitch) +{ + typedef std::set::iterator PitchItr; + std::set pitches; + + // when this algorithm appears to be working ok, we should be + // able to make it much quicker + + const Quantizer *quantizer + (m_segment->getComposition()->getNotationQuantizer()); + + int myHighest, myLowest; + int prevHighest = 0, prevLowest = 0; + bool havePrev = false; + + Chord c0(*m_segment, i, quantizer); + std::vector c0p(c0.getPitches()); + pitches.insert::iterator>(c0p.begin(), c0p.end()); + + myLowest = c0p[0]; + myHighest = c0p[c0p.size() - 1]; + + Segment::iterator j(c0.getPreviousNote()); + if (j != m_segment->end()) { + + havePrev = true; + + Chord c1(*m_segment, j, quantizer); + std::vector c1p(c1.getPitches()); + pitches.insert::iterator>(c1p.begin(), c1p.end()); + + prevLowest = c1p[0]; + prevHighest = c1p[c1p.size() - 1]; + } + + if (pitches.size() < 2) + return lastSplitPitch; + + PitchItr pi = pitches.begin(); + int lowest(*pi); + + pi = pitches.end(); + --pi; + int highest(*pi); + + if ((pitches.size() == 2 || highest - lowest <= 18) && + myHighest > lastSplitPitch && + myLowest < lastSplitPitch && + prevHighest > lastSplitPitch && + prevLowest < lastSplitPitch) { + + if (havePrev) { + if ((myLowest > prevLowest && myHighest > prevHighest) || + (myLowest < prevLowest && myHighest < prevHighest)) { + int avgDiff = ((myLowest - prevLowest) + + (myHighest - prevHighest)) / 2; + if (avgDiff < -5) + avgDiff = -5; + if (avgDiff > 5) + avgDiff = 5; + return lastSplitPitch + avgDiff; + } + } + + return lastSplitPitch; + } + + int middle = (highest - lowest) / 2 + lowest; + + while (lastSplitPitch > middle && lastSplitPitch > m_splitPitch - 12) { + if (lastSplitPitch - lowest < 12) + return lastSplitPitch; + if (lastSplitPitch <= m_splitPitch - 12) + return lastSplitPitch; + --lastSplitPitch; + } + + while (lastSplitPitch < middle && lastSplitPitch < m_splitPitch + 12) { + if (highest - lastSplitPitch < 12) + return lastSplitPitch; + if (lastSplitPitch >= m_splitPitch + 12) + return lastSplitPitch; + ++lastSplitPitch; + } + + return lastSplitPitch; +} + +} diff --git a/src/commands/segment/SegmentSplitByPitchCommand.h b/src/commands/segment/SegmentSplitByPitchCommand.h new file mode 100644 index 0000000..e536be6 --- /dev/null +++ b/src/commands/segment/SegmentSplitByPitchCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTSPLITBYPITCHCOMMAND_H_ +#define _RG_SEGMENTSPLITBYPITCHCOMMAND_H_ + +#include "base/Segment.h" +#include +#include +#include +#include "gui/general/ClefIndex.h" + + + + +namespace Rosegarden +{ + +class Composition; + + +class SegmentSplitByPitchCommand : public KNamedCommand +{ +public: + enum ClefHandling { + LeaveClefs, + RecalculateClefs, + UseTrebleAndBassClefs + }; + + SegmentSplitByPitchCommand(Segment *segment, + int splitPitch, + bool ranging, + bool duplicateNonNoteEvents, + ClefHandling clefHandling); + virtual ~SegmentSplitByPitchCommand(); + + static QString getGlobalName() + { return i18n("Split by &Pitch..."); } + + virtual void execute(); + virtual void unexecute(); + +private: + int getSplitPitchAt(Segment::iterator i, int lastSplitPitch); + + Composition *m_composition; + Segment *m_segment; + Segment *m_newSegmentA; + Segment *m_newSegmentB; + int m_splitPitch; + bool m_ranging; + bool m_dupNonNoteEvents; + ClefHandling m_clefHandling; + bool m_executed; +}; + + +} + +#endif diff --git a/src/commands/segment/SegmentSplitByRecordingSrcCommand.cpp b/src/commands/segment/SegmentSplitByRecordingSrcCommand.cpp new file mode 100644 index 0000000..fbb3c1b --- /dev/null +++ b/src/commands/segment/SegmentSplitByRecordingSrcCommand.cpp @@ -0,0 +1,153 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentSplitByRecordingSrcCommand.h" + +#include "base/BaseProperties.h" +#include "misc/Strings.h" +#include "base/Composition.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include + + +namespace Rosegarden +{ + +SegmentSplitByRecordingSrcCommand::SegmentSplitByRecordingSrcCommand ( + Segment *segment, int channel, int device ) : + KNamedCommand(i18n("Split by Recording Source")), + m_composition(segment->getComposition()), + m_segment(segment), + m_newSegmentA(0), + m_channel(channel), + m_device(device), + m_executed(false) +{} + +void +SegmentSplitByRecordingSrcCommand::execute() +{ + if (!m_newSegmentA) { + + m_newSegmentA = new Segment; + m_newSegmentB = new Segment; + + m_newSegmentA->setTrack(m_segment->getTrack()); + m_newSegmentA->setStartTime(m_segment->getStartTime()); + + m_newSegmentB->setTrack(m_segment->getTrack()); + m_newSegmentB->setStartTime(m_segment->getStartTime()); + + bool selectedC = false; + bool selectedD = false; + + for (Segment::iterator i = m_segment->begin(); + m_segment->isBeforeEndMarker(i); ++i) { + + if ((*i)->isa(Note::EventRestType)) + continue; + + if ( (*i)->isa(Clef::EventType) || + (*i)->isa(Key::EventType) ) { + + m_newSegmentA->insert(new Event(**i)); + m_newSegmentB->insert(new Event(**i)); + continue; + } + + selectedC = false; + selectedD = false; + + if ((*i)->has(BaseProperties::RECORDED_CHANNEL)) { + selectedC = true; + if (m_channel > -1) + selectedC = ( m_channel == + (*i)->get + (BaseProperties::RECORDED_CHANNEL) ); + } + + if ((*i)->has(BaseProperties::RECORDED_PORT)) { + selectedD = true; + if (m_device > -1) + selectedD = ( m_device == + (*i)->get + (BaseProperties::RECORDED_PORT) ); + } + + if (selectedC & selectedD) { + if (m_newSegmentB->empty()) { + m_newSegmentB->fillWithRests((*i)->getAbsoluteTime()); + } + m_newSegmentB->insert(new Event(**i)); + } else { + if (m_newSegmentA->empty()) { + m_newSegmentA->fillWithRests((*i)->getAbsoluteTime()); + } + m_newSegmentA->insert(new Event(**i)); + } + } + + m_newSegmentA->normalizeRests(m_segment->getStartTime(), + m_segment->getEndMarkerTime()); + m_newSegmentB->normalizeRests(m_segment->getStartTime(), + m_segment->getEndMarkerTime()); + + std::string label = m_segment->getLabel(); + m_newSegmentA->setLabel(qstrtostr(i18n("%1 (split)").arg + (strtoqstr(label)))); + m_newSegmentB->setLabel(qstrtostr(i18n("%1 (split)").arg + (strtoqstr(label)))); + m_newSegmentA->setColourIndex(m_segment->getColourIndex()); + m_newSegmentB->setColourIndex(m_segment->getColourIndex()); + } + + m_composition->addSegment(m_newSegmentA); + m_composition->addSegment(m_newSegmentB); + m_composition->detachSegment(m_segment); + m_executed = true; +} + +void +SegmentSplitByRecordingSrcCommand::unexecute() +{ + m_composition->addSegment(m_segment); + m_composition->detachSegment(m_newSegmentA); + m_composition->detachSegment(m_newSegmentB); + m_executed = false; +} + +SegmentSplitByRecordingSrcCommand::~SegmentSplitByRecordingSrcCommand() +{ + if (m_executed) { + delete m_segment; + } else { + delete m_newSegmentA; + delete m_newSegmentB; + } +} + +} diff --git a/src/commands/segment/SegmentSplitByRecordingSrcCommand.h b/src/commands/segment/SegmentSplitByRecordingSrcCommand.h new file mode 100644 index 0000000..3b087ab --- /dev/null +++ b/src/commands/segment/SegmentSplitByRecordingSrcCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTSPLITBYRECORDINGSRCCOMMAND_H_ +#define _RG_SEGMENTSPLITBYRECORDINGSRCCOMMAND_H_ + +#include +#include +#include +#include "gui/application/RosegardenDCOP.h" + + + + +namespace Rosegarden +{ + +class Segment; +class Composition; + + +class SegmentSplitByRecordingSrcCommand : public KNamedCommand +{ +public: + SegmentSplitByRecordingSrcCommand(Segment *segment, + int channel, int device); + virtual ~SegmentSplitByRecordingSrcCommand(); + + static QString getGlobalName() + { return i18n("Split by &Recording Source..."); } + + virtual void execute(); + virtual void unexecute(); + +private: + Composition *m_composition; + Segment *m_segment; + Segment *m_newSegmentA; + Segment *m_newSegmentB; + int m_channel; + int m_device; + bool m_executed; +}; + + +} + +#endif diff --git a/src/commands/segment/SegmentSplitCommand.cpp b/src/commands/segment/SegmentSplitCommand.cpp new file mode 100644 index 0000000..450ad3e --- /dev/null +++ b/src/commands/segment/SegmentSplitCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentSplitCommand.h" + +#include +#include "misc/Strings.h" +#include "base/Event.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include + + +namespace Rosegarden +{ + +SegmentSplitCommand::SegmentSplitCommand(Segment *segment, + timeT splitTime) : + KNamedCommand(i18n("Split Segment")), + m_segment(segment), + m_newSegmentA(0), + m_newSegmentB(0), + m_splitTime(splitTime), + m_previousEndMarkerTime(0), + m_detached(true) +{} + +SegmentSplitCommand::~SegmentSplitCommand() +{ + if (m_detached) { + delete m_newSegmentA; + delete m_newSegmentB; + } + delete m_previousEndMarkerTime; +} + +void +SegmentSplitCommand::execute() +{ + if (m_newSegmentA) { + + m_segment->getComposition()->addSegment(m_newSegmentA); + m_segment->getComposition()->addSegment(m_newSegmentB); + + m_segment->getComposition()->detachSegment(m_segment); + + m_detached = false; // i.e. new segments are not detached + return; + } + + m_newSegmentA = new Segment(*m_segment); + m_newSegmentB = new Segment(); + + m_newSegmentB->setTrack(m_segment->getTrack()); + m_newSegmentB->setStartTime(m_splitTime); + + m_segment->getComposition()->addSegment(m_newSegmentA); + m_segment->getComposition()->addSegment(m_newSegmentB); + + Event *clefEvent = 0; + Event *keyEvent = 0; + + // Copy the last occurrence of clef and key + // from the left hand side of the split (nb. timesig events + // don't appear in segments, only in composition) + // + Segment::iterator it = m_segment->findTime(m_splitTime); + + while (it != m_segment->begin()) { + + --it; + + if (!clefEvent && (*it)->isa(Clef::EventType)) { + clefEvent = new Event(**it, m_splitTime); + } + + if (!keyEvent && (*it)->isa(Key::EventType)) { + keyEvent = new Event(**it, m_splitTime); + } + + if (clefEvent && keyEvent) + break; + } + + // Insert relevant meta info if we've found some + // + if (clefEvent) + m_newSegmentB->insert(clefEvent); + + if (keyEvent) + m_newSegmentB->insert(keyEvent); + + // Copy through the Events + // + it = m_segment->findTime(m_splitTime); + + if (it != m_segment->end() && (*it)->getAbsoluteTime() > m_splitTime) { + m_newSegmentB->fillWithRests((*it)->getAbsoluteTime()); + } + + while (it != m_segment->end()) { + m_newSegmentB->insert(new Event(**it)); + ++it; + } + m_newSegmentB->setEndTime(m_segment->getEndTime()); + m_newSegmentB->setEndMarkerTime(m_segment->getEndMarkerTime()); + + // Set labels + // + m_segmentLabel = m_segment->getLabel(); + QString newLabel = strtoqstr(m_segmentLabel); + if (!newLabel.endsWith(i18n(" (split)"))) { + newLabel = i18n("%1 (split)").arg(newLabel); + } + m_newSegmentA->setLabel(newLabel); + m_newSegmentB->setLabel(newLabel); + + m_newSegmentB->setColourIndex(m_segment->getColourIndex()); + m_newSegmentB->setTranspose(m_segment->getTranspose()); + m_newSegmentB->setDelay(m_segment->getDelay()); + + // Resize left hand Segment + // + std::vector toErase, toInsert; + for (Segment::iterator i = m_newSegmentA->findTime(m_splitTime); + i != m_newSegmentA->end(); ++i) { + if ((*i)->getAbsoluteTime() >= m_splitTime) break; + if ((*i)->getAbsoluteTime() + (*i)->getDuration() > m_splitTime) { + Event *e = new Event(**i, (*i)->getAbsoluteTime(), + m_splitTime - (*i)->getAbsoluteTime()); + toErase.push_back(*i); + toInsert.push_back(e); + } + } + + for (int i = 0; i < toErase.size(); ++i) { + m_newSegmentA->eraseSingle(toErase[i]); + delete toErase[i]; + } + for (int i = 0; i < toInsert.size(); ++i) { + m_newSegmentA->insert(toInsert[i]); + } + + m_newSegmentA->setEndTime(m_splitTime); + + m_segment->getComposition()->detachSegment(m_segment); + + m_detached = false; // i.e. new segments are not detached +} + +void +SegmentSplitCommand::unexecute() +{ + m_newSegmentA->getComposition()->addSegment(m_segment); + + m_segment->getComposition()->detachSegment(m_newSegmentA); + m_segment->getComposition()->detachSegment(m_newSegmentB); + + m_detached = true; // i.e. new segments are not detached +} + +} diff --git a/src/commands/segment/SegmentSplitCommand.h b/src/commands/segment/SegmentSplitCommand.h new file mode 100644 index 0000000..8b59b76 --- /dev/null +++ b/src/commands/segment/SegmentSplitCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTSPLITCOMMAND_H_ +#define _RG_SEGMENTSPLITCOMMAND_H_ + +#include +#include +#include "base/Event.h" + + + + +namespace Rosegarden +{ + +class Segment; + + +class SegmentSplitCommand : public KNamedCommand +{ +public: + SegmentSplitCommand(Segment *segment, + timeT splitTime); + virtual ~SegmentSplitCommand(); + + virtual void execute(); + virtual void unexecute(); + +private: + Segment *m_segment; + Segment *m_newSegmentA; + Segment *m_newSegmentB; + timeT m_splitTime; + timeT *m_previousEndMarkerTime; + bool m_detached; + std::string m_segmentLabel; +}; + + +} + +#endif diff --git a/src/commands/segment/SegmentSyncClefCommand.cpp b/src/commands/segment/SegmentSyncClefCommand.cpp new file mode 100644 index 0000000..5e3560b --- /dev/null +++ b/src/commands/segment/SegmentSyncClefCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentSyncClefCommand.h" + +#include "base/Selection.h" +#include "commands/notation/ClefInsertionCommand.h" + +namespace Rosegarden +{ +SegmentSyncClefCommand::SegmentSyncClefCommand(Segment &segment, const Clef& clef) : + KMacroCommand(i18n("Sync segment clef")) +{ + processSegment(segment, clef); +} + +void +SegmentSyncClefCommand::processSegment(Segment &segment, const Clef& clef) +{ + KMacroCommand * macroCommand = this; + + // TODO delete it somewhere. + EventSelection * wholeSegment = new EventSelection(segment, segment.getStartTime(), segment.getEndMarkerTime()); + + EventSelection::eventcontainer::iterator i; + + for (i = wholeSegment->getSegmentEvents().begin(); + i != wholeSegment->getSegmentEvents().end(); ++i) { + if ((*i)->isa(Rosegarden::Clef::EventType)) { + macroCommand->addCommand + (new ClefInsertionCommand + (segment, + (*i)->getAbsoluteTime(), + clef, false, false)); + } + } + +} + + +SegmentSyncClefCommand::~SegmentSyncClefCommand() +{} + + +} diff --git a/src/commands/segment/SegmentSyncClefCommand.h b/src/commands/segment/SegmentSyncClefCommand.h new file mode 100644 index 0000000..01b97fd --- /dev/null +++ b/src/commands/segment/SegmentSyncClefCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTSYNCCLEFCOMMAND_H_ +#define _RG_SEGMENTSYNCCLEFCOMMAND_H_ + +#include +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "document/MultiViewCommandHistory.h" +#include + +namespace Rosegarden +{ + +class Segment; +class SegmentSelection; + + +class SegmentSyncClefCommand : public KMacroCommand +{ +public: + SegmentSyncClefCommand(Segment &segment, const Clef& clef); + + virtual ~SegmentSyncClefCommand(); + +protected: + void processSegment(Segment &segment, const Clef& clef); +}; + +} + +#endif diff --git a/src/commands/segment/SegmentSyncCommand.cpp b/src/commands/segment/SegmentSyncCommand.cpp new file mode 100644 index 0000000..d5e9734 --- /dev/null +++ b/src/commands/segment/SegmentSyncCommand.cpp @@ -0,0 +1,103 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2007 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentSyncCommand.h" + +#include "base/Selection.h" +#include "commands/notation/KeyInsertionCommand.h" +#include "commands/edit/TransposeCommand.h" +#include "commands/segment/SegmentChangeTransposeCommand.h" +#include "commands/segment/SegmentTransposeCommand.h" +#include "commands/segment/SegmentSyncClefCommand.h" + +namespace Rosegarden +{ +SegmentSyncCommand::SegmentSyncCommand(Segment &segment, int newTranspose, int lowRange, int highRange, const Clef& clef) : + KMacroCommand(i18n("Sync segment parameters")) +{ + processSegment(segment, newTranspose, lowRange, highRange, clef); +} + +SegmentSyncCommand::SegmentSyncCommand(SegmentSelection selection, int newTranspose, int lowRange, int highRange, const Clef& clef) : + KMacroCommand(i18n("Sync segment parameters")) +{ + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) + { + Segment &segment = **i; + processSegment(segment, newTranspose, lowRange, highRange, clef); + } +} + +SegmentSyncCommand::SegmentSyncCommand(std::vector segments, int newTranspose, int lowRange, int highRange, const Clef& clef) : + KMacroCommand(i18n("Sync segment parameters")) +{ + for (int i = 0; i < segments.size(); i++) { + processSegment(*(segments[i]), newTranspose, lowRange, highRange, clef); + } +} + +SegmentSyncCommand::SegmentSyncCommand(Composition::segmentcontainer& segments, TrackId selectedTrack, int newTranspose, int lowRange, int highRange, const Clef& clef) : + KMacroCommand(i18n("Sync segment parameters")) +{ + for (Composition::segmentcontainer::const_iterator si = segments.begin(); + si != segments.end(); ++si) { + if ((*si)->getTrack() == selectedTrack) { + processSegment(**si, newTranspose, lowRange, highRange, clef); + } + } +} + +void +SegmentSyncCommand::processSegment(Segment &segment, int newTranspose, int lowRange, int highRange, const Clef& clef) +{ + KMacroCommand * macroCommand = this; + + // if the new desired setting for 'transpose' is higher, we need to + // transpose the notes upwards to compensate: + int semitones = segment.getTranspose() - newTranspose; + + // Say the old transpose was -2 and the new is 0, this corresponds to + // Bb and C. The height of the old transpose is 1 below the height of the new. + int oldHeight = Pitch(segment.getTranspose()).getHeightOnStaff(Clef::Treble, Key("C major")); + int newHeight = Pitch(newTranspose).getHeightOnStaff(Clef::Treble, Key("C major")); + int steps = oldHeight - newHeight; + + SegmentTransposeCommand* command = new SegmentTransposeCommand(segment, true, steps, semitones, true); + macroCommand->addCommand(command); + + // TODO do this in an undoable fashion: + segment.setLowestPlayable(lowRange); + segment.setHighestPlayable(highRange); + + macroCommand->addCommand(new SegmentSyncClefCommand(segment, clef)); +} + + +SegmentSyncCommand::~SegmentSyncCommand() +{} + + +} diff --git a/src/commands/segment/SegmentSyncCommand.h b/src/commands/segment/SegmentSyncCommand.h new file mode 100644 index 0000000..0d8e189 --- /dev/null +++ b/src/commands/segment/SegmentSyncCommand.h @@ -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-2007 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTSYNCCOMMAND_H_ +#define _RG_SEGMENTSYNCCOMMAND_H_ + +#include +#include "base/Composition.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "document/MultiViewCommandHistory.h" +#include + +namespace Rosegarden +{ + +class Segment; +class SegmentSelection; + + +class SegmentSyncCommand : public KMacroCommand +{ +public: + SegmentSyncCommand(Segment &segment, + int newTranspose, int lowRange, int highRange, const Clef& clef); + + SegmentSyncCommand(SegmentSelection selection, + int newTranspose, int lowRange, int highRange, const Clef& clef); + + SegmentSyncCommand(std::vector segments, + int newTranspose, int lowRange, int highRange, const Clef& clef); + + SegmentSyncCommand(Composition::segmentcontainer& segments, TrackId track, + int newTranspose, int lowRange, int highRange, const Clef& clef); + + virtual ~SegmentSyncCommand(); + +protected: + void processSegment(Segment &segment, int newTranspose, int lowRange, int highRange, const Clef& clef); +}; + +} + +#endif diff --git a/src/commands/segment/SegmentTransposeCommand.cpp b/src/commands/segment/SegmentTransposeCommand.cpp new file mode 100644 index 0000000..d3e4221 --- /dev/null +++ b/src/commands/segment/SegmentTransposeCommand.cpp @@ -0,0 +1,123 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SegmentTransposeCommand.h" + +#include "base/Selection.h" +#include "commands/notation/KeyInsertionCommand.h" +#include "commands/edit/TransposeCommand.h" +#include "commands/segment/SegmentChangeTransposeCommand.h" + +namespace Rosegarden +{ +SegmentTransposeCommand::SegmentTransposeCommand(Segment &segment, bool changeKey, int steps, int semitones, bool transposeSegmentBack) : + KMacroCommand(i18n("Change segment transposition")) +{ + processSegment(segment, changeKey, steps, semitones, transposeSegmentBack); +} + +SegmentTransposeCommand::SegmentTransposeCommand(SegmentSelection selection, bool changeKey, int steps, int semitones, bool transposeSegmentBack) : + KMacroCommand(i18n("Change segment transposition")) +{ + //SegmentSelection selection(m_view->getSelection()); + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) + { + Segment &segment = **i; + processSegment(segment, changeKey, steps, semitones, transposeSegmentBack); + } +} + +void +SegmentTransposeCommand::processSegment(Segment &segment, bool changeKey, int steps, int semitones, bool transposeSegmentBack) +{ + KMacroCommand * macroCommand = this; + + // TODO delete it somewhere. + EventSelection * wholeSegment = new EventSelection(segment, segment.getStartTime(), segment.getEndMarkerTime()); + macroCommand->addCommand(new TransposeCommand + (semitones, steps, *wholeSegment)); + + // Key insertion can do transposition, but a C4 to D becomes a D4, while + // a C4 to G becomes a G3. Because we let the user specify an explicit number + // of octaves to move the notes up/down, we add the keys without transposing + // and handle the transposition seperately: + if (changeKey) + { + Rosegarden::Key initialKey = segment.getKeyAtTime(segment.getStartTime()); + Rosegarden::Key newInitialKey = initialKey.transpose(semitones, steps); + + EventSelection::eventcontainer::iterator i; + //std::list commands; + + for (i = wholeSegment->getSegmentEvents().begin(); + i != wholeSegment->getSegmentEvents().end(); ++i) { + // transpose key + if ((*i)->isa(Rosegarden::Key::EventType)) { + Rosegarden::Key trKey = (Rosegarden::Key (**i)).transpose(semitones, steps); + //commands.push_front + macroCommand->addCommand + (new KeyInsertionCommand + (segment, + (*i)->getAbsoluteTime(), + trKey, + false, + false, + false, + true)); + } + } + std::list::iterator ci; + //for (ci=commands.begin(); ci!=commands.end(); ci++) + //{ + // commandHistory->addCommand(*ci); + //} + + KeyInsertionCommand *firstKeyCommand = new KeyInsertionCommand + (segment, + segment.getStartTime(), + newInitialKey, + false, + false, + false, + true); + //commandHistory->addCommand(firstKeyCommand); + macroCommand->addCommand(firstKeyCommand); + } + + if (transposeSegmentBack) + { + // Transpose segment in opposite direction + int newTranspose = segment.getTranspose() - semitones; + macroCommand->addCommand(new SegmentChangeTransposeCommand(newTranspose, &segment)); + } +} + + +SegmentTransposeCommand::~SegmentTransposeCommand() +{} + + +} diff --git a/src/commands/segment/SegmentTransposeCommand.h b/src/commands/segment/SegmentTransposeCommand.h new file mode 100644 index 0000000..74af8d4 --- /dev/null +++ b/src/commands/segment/SegmentTransposeCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SEGMENTTRANSPOSECOMMAND_H_ +#define _RG_SEGMENTTRANSPOSECOMMAND_H_ + +#include +#include "base/Event.h" +#include "document/MultiViewCommandHistory.h" +#include + +namespace Rosegarden +{ + +class Segment; +class SegmentSelection; + + +class SegmentTransposeCommand : public KMacroCommand +{ +public: + SegmentTransposeCommand(Segment &segment, + bool changeKey, int steps, int semitones, bool transposeSegmentBack); + + SegmentTransposeCommand(SegmentSelection selection, + bool changeKey, int steps, int semitones, bool transposeSegmentBack); + + virtual ~SegmentTransposeCommand(); + + static QString getGlobalName(int semitones = 0, int step = 0) { + switch (semitones) { + default: return i18n("Transpose by &Interval..."); + } + } + +protected: + void processSegment(Segment &segment, bool changeKey, int steps, int semitones, bool transposeSegmentBack); +}; + +} + +#endif diff --git a/src/commands/segment/SetTriggerSegmentBasePitchCommand.cpp b/src/commands/segment/SetTriggerSegmentBasePitchCommand.cpp new file mode 100644 index 0000000..9bb1bc3 --- /dev/null +++ b/src/commands/segment/SetTriggerSegmentBasePitchCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SetTriggerSegmentBasePitchCommand.h" + +#include +#include "base/Composition.h" +#include "base/TriggerSegment.h" + + +namespace Rosegarden +{ + +SetTriggerSegmentBasePitchCommand::SetTriggerSegmentBasePitchCommand(Composition *composition, + TriggerSegmentId id, + int newPitch) : + KNamedCommand(i18n("Set Base Pitch")), + m_composition(composition), + m_id(id), + m_newPitch(newPitch), + m_oldPitch( -1) +{ + // nothing +} + +SetTriggerSegmentBasePitchCommand::~SetTriggerSegmentBasePitchCommand() +{ + // nothing +} + +void +SetTriggerSegmentBasePitchCommand::execute() +{ + TriggerSegmentRec *rec = m_composition->getTriggerSegmentRec(m_id); + if (!rec) + return ; + if (m_oldPitch == -1) { + m_oldPitch = rec->getBasePitch(); + } + rec->setBasePitch(m_newPitch); +} + +void +SetTriggerSegmentBasePitchCommand::unexecute() +{ + TriggerSegmentRec *rec = m_composition->getTriggerSegmentRec(m_id); + if (!rec) + return ; + rec->setBasePitch(m_oldPitch); +} + +} diff --git a/src/commands/segment/SetTriggerSegmentBasePitchCommand.h b/src/commands/segment/SetTriggerSegmentBasePitchCommand.h new file mode 100644 index 0000000..0d79c7c --- /dev/null +++ b/src/commands/segment/SetTriggerSegmentBasePitchCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SETTRIGGERSEGMENTBASEPITCHCOMMAND_H_ +#define _RG_SETTRIGGERSEGMENTBASEPITCHCOMMAND_H_ + +#include "base/TriggerSegment.h" +#include + + + + +namespace Rosegarden +{ + +class Composition; + + +class SetTriggerSegmentBasePitchCommand : public KNamedCommand +{ +public: + SetTriggerSegmentBasePitchCommand(Composition *composition, + TriggerSegmentId id, + int newPitch); + virtual ~SetTriggerSegmentBasePitchCommand(); + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + TriggerSegmentId m_id; + int m_newPitch; + int m_oldPitch; +}; + + + +} + +#endif diff --git a/src/commands/segment/SetTriggerSegmentBaseVelocityCommand.cpp b/src/commands/segment/SetTriggerSegmentBaseVelocityCommand.cpp new file mode 100644 index 0000000..f1bba10 --- /dev/null +++ b/src/commands/segment/SetTriggerSegmentBaseVelocityCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SetTriggerSegmentBaseVelocityCommand.h" + +#include +#include "base/Composition.h" +#include "base/TriggerSegment.h" + + +namespace Rosegarden +{ + +SetTriggerSegmentBaseVelocityCommand::SetTriggerSegmentBaseVelocityCommand(Composition *composition, + TriggerSegmentId id, + int newVelocity) : + KNamedCommand(i18n("Set Base Velocity")), + m_composition(composition), + m_id(id), + m_newVelocity(newVelocity), + m_oldVelocity( -1) +{ + // nothing +} + +SetTriggerSegmentBaseVelocityCommand::~SetTriggerSegmentBaseVelocityCommand() +{ + // nothing +} + +void +SetTriggerSegmentBaseVelocityCommand::execute() +{ + TriggerSegmentRec *rec = m_composition->getTriggerSegmentRec(m_id); + if (!rec) + return ; + if (m_oldVelocity == -1) { + m_oldVelocity = rec->getBaseVelocity(); + } + rec->setBaseVelocity(m_newVelocity); +} + +void +SetTriggerSegmentBaseVelocityCommand::unexecute() +{ + TriggerSegmentRec *rec = m_composition->getTriggerSegmentRec(m_id); + if (!rec) + return ; + rec->setBaseVelocity(m_oldVelocity); +} + +} diff --git a/src/commands/segment/SetTriggerSegmentBaseVelocityCommand.h b/src/commands/segment/SetTriggerSegmentBaseVelocityCommand.h new file mode 100644 index 0000000..26875ec --- /dev/null +++ b/src/commands/segment/SetTriggerSegmentBaseVelocityCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SETTRIGGERSEGMENTBASEVELOCITYCOMMAND_H_ +#define _RG_SETTRIGGERSEGMENTBASEVELOCITYCOMMAND_H_ + +#include "base/TriggerSegment.h" +#include + + + + +namespace Rosegarden +{ + +class Composition; + + +class SetTriggerSegmentBaseVelocityCommand : public KNamedCommand +{ +public: + SetTriggerSegmentBaseVelocityCommand(Composition *composition, + TriggerSegmentId id, + int newVelocity); + virtual ~SetTriggerSegmentBaseVelocityCommand(); + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + TriggerSegmentId m_id; + int m_newVelocity; + int m_oldVelocity; +}; + + + +} + +#endif diff --git a/src/commands/segment/SetTriggerSegmentDefaultRetuneCommand.cpp b/src/commands/segment/SetTriggerSegmentDefaultRetuneCommand.cpp new file mode 100644 index 0000000..35ae878 --- /dev/null +++ b/src/commands/segment/SetTriggerSegmentDefaultRetuneCommand.cpp @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SetTriggerSegmentDefaultRetuneCommand.h" + +#include +#include "base/Composition.h" +#include "base/TriggerSegment.h" + + +namespace Rosegarden +{ + +SetTriggerSegmentDefaultRetuneCommand::SetTriggerSegmentDefaultRetuneCommand(Composition *composition, + TriggerSegmentId id, + bool newDefaultRetune) : + KNamedCommand(i18n("Set Default Retune")), + m_composition(composition), + m_id(id), + m_newDefaultRetune(newDefaultRetune), + m_oldDefaultRetune(false), + m_haveOldDefaultRetune(false) +{ + // nothing +} + +SetTriggerSegmentDefaultRetuneCommand::~SetTriggerSegmentDefaultRetuneCommand() +{ + // nothing +} + +void +SetTriggerSegmentDefaultRetuneCommand::execute() +{ + TriggerSegmentRec *rec = m_composition->getTriggerSegmentRec(m_id); + if (!rec) + return ; + if (!m_haveOldDefaultRetune) { + m_oldDefaultRetune = rec->getDefaultRetune(); + } + rec->setDefaultRetune(m_newDefaultRetune); +} + +void +SetTriggerSegmentDefaultRetuneCommand::unexecute() +{ + TriggerSegmentRec *rec = m_composition->getTriggerSegmentRec(m_id); + if (!rec) + return ; + rec->setDefaultRetune(m_oldDefaultRetune); +} + +} diff --git a/src/commands/segment/SetTriggerSegmentDefaultRetuneCommand.h b/src/commands/segment/SetTriggerSegmentDefaultRetuneCommand.h new file mode 100644 index 0000000..4563594 --- /dev/null +++ b/src/commands/segment/SetTriggerSegmentDefaultRetuneCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SETTRIGGERSEGMENTDEFAULTRETUNECOMMAND_H_ +#define _RG_SETTRIGGERSEGMENTDEFAULTRETUNECOMMAND_H_ + +#include "base/TriggerSegment.h" +#include + + + + +namespace Rosegarden +{ + +class Composition; + + +class SetTriggerSegmentDefaultRetuneCommand : public KNamedCommand +{ +public: + SetTriggerSegmentDefaultRetuneCommand(Composition *composition, + TriggerSegmentId id, + bool newDefaultRetune); + virtual ~SetTriggerSegmentDefaultRetuneCommand(); + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + TriggerSegmentId m_id; + bool m_newDefaultRetune; + bool m_oldDefaultRetune; + bool m_haveOldDefaultRetune; +}; + + + +} + +#endif diff --git a/src/commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.cpp b/src/commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.cpp new file mode 100644 index 0000000..deb49a1 --- /dev/null +++ b/src/commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SetTriggerSegmentDefaultTimeAdjustCommand.h" + +#include +#include "base/Composition.h" +#include "base/TriggerSegment.h" + + +namespace Rosegarden +{ + +SetTriggerSegmentDefaultTimeAdjustCommand::SetTriggerSegmentDefaultTimeAdjustCommand(Composition *composition, + TriggerSegmentId id, + std::string newDefaultTimeAdjust) : + KNamedCommand(i18n("Set Default Time Adjust")), + m_composition(composition), + m_id(id), + m_newDefaultTimeAdjust(newDefaultTimeAdjust), + m_oldDefaultTimeAdjust("") +{ + // nothing +} + +SetTriggerSegmentDefaultTimeAdjustCommand::~SetTriggerSegmentDefaultTimeAdjustCommand() +{ + // nothing +} + +void +SetTriggerSegmentDefaultTimeAdjustCommand::execute() +{ + TriggerSegmentRec *rec = m_composition->getTriggerSegmentRec(m_id); + if (!rec) + return ; + if (m_oldDefaultTimeAdjust == "") { + m_oldDefaultTimeAdjust = rec->getDefaultTimeAdjust(); + } + rec->setDefaultTimeAdjust(m_newDefaultTimeAdjust); +} + +void +SetTriggerSegmentDefaultTimeAdjustCommand::unexecute() +{ + TriggerSegmentRec *rec = m_composition->getTriggerSegmentRec(m_id); + if (!rec) + return ; + rec->setDefaultTimeAdjust(m_oldDefaultTimeAdjust); +} + +} diff --git a/src/commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.h b/src/commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.h new file mode 100644 index 0000000..7d31b26 --- /dev/null +++ b/src/commands/segment/SetTriggerSegmentDefaultTimeAdjustCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SETTRIGGERSEGMENTDEFAULTTIMEADJUSTCOMMAND_H_ +#define _RG_SETTRIGGERSEGMENTDEFAULTTIMEADJUSTCOMMAND_H_ + +#include "base/TriggerSegment.h" +#include +#include + + + + +namespace Rosegarden +{ + +class Composition; + + +class SetTriggerSegmentDefaultTimeAdjustCommand : public KNamedCommand +{ +public: + SetTriggerSegmentDefaultTimeAdjustCommand(Composition *composition, + TriggerSegmentId id, + std::string newDefaultTimeAdjust); + virtual ~SetTriggerSegmentDefaultTimeAdjustCommand(); + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + TriggerSegmentId m_id; + std::string m_newDefaultTimeAdjust; + std::string m_oldDefaultTimeAdjust; +}; + + + +} + +#endif diff --git a/src/commands/studio/AddControlParameterCommand.cpp b/src/commands/studio/AddControlParameterCommand.cpp new file mode 100644 index 0000000..35ac62a --- /dev/null +++ b/src/commands/studio/AddControlParameterCommand.cpp @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddControlParameterCommand.h" + +#include "base/ControlParameter.h" +#include "base/Device.h" +#include "base/MidiDevice.h" +#include "base/Studio.h" +#include +#include + + +namespace Rosegarden +{ + +AddControlParameterCommand::~AddControlParameterCommand() +{} + +void +AddControlParameterCommand::execute() +{ + MidiDevice *md = dynamic_cast + (m_studio->getDevice(m_device)); + if (!md) { + std::cerr << "WARNING: AddControlParameterCommand::execute: device " + << m_device << " is not a MidiDevice in current studio" + << std::endl; + return ; + } + + md->addControlParameter(m_control); + + // store id of the new control + m_id = md->getControlParameters().size() - 1; +} + +void +AddControlParameterCommand::unexecute() +{ + MidiDevice *md = dynamic_cast + (m_studio->getDevice(m_device)); + if (!md) { + std::cerr << "WARNING: AddControlParameterCommand::unexecute: device " + << m_device << " is not a MidiDevice in current studio" + << std::endl; + return ; + } + + md->removeControlParameter(m_id); +} + +} diff --git a/src/commands/studio/AddControlParameterCommand.h b/src/commands/studio/AddControlParameterCommand.h new file mode 100644 index 0000000..fa614ac --- /dev/null +++ b/src/commands/studio/AddControlParameterCommand.h @@ -0,0 +1,75 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADDCONTROLPARAMETERCOMMAND_H_ +#define _RG_ADDCONTROLPARAMETERCOMMAND_H_ + +#include "base/ControlParameter.h" +#include "base/Device.h" +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Studio; + + +class AddControlParameterCommand : public KNamedCommand +{ +public: + AddControlParameterCommand(Studio *studio, + DeviceId device, + ControlParameter control): + KNamedCommand(getGlobalName()), + m_studio(studio), + m_device(device), + m_control(control), + m_id(0) { } + + ~AddControlParameterCommand(); + + virtual void execute(); + virtual void unexecute(); + + static QString getGlobalName() { return i18n("&Add Control Parameter"); } + +protected: + Studio *m_studio; + DeviceId m_device; + ControlParameter m_control; + int m_id; + +}; + + + +} + +#endif diff --git a/src/commands/studio/CreateOrDeleteDeviceCommand.cpp b/src/commands/studio/CreateOrDeleteDeviceCommand.cpp new file mode 100644 index 0000000..48dc6c1 --- /dev/null +++ b/src/commands/studio/CreateOrDeleteDeviceCommand.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CreateOrDeleteDeviceCommand.h" + +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Device.h" +#include "base/MidiDevice.h" +#include "base/Studio.h" +#include +#include +#include +#include "gui/application/RosegardenApplication.h" + + +namespace Rosegarden +{ + +CreateOrDeleteDeviceCommand::CreateOrDeleteDeviceCommand(Studio *studio, + DeviceId id) : + KNamedCommand(getGlobalName(true)), + m_studio(studio), + m_deviceId(id), + m_deviceCreated(true) +{ + Device *device = m_studio->getDevice(m_deviceId); + + if (device) { + m_name = device->getName(); + m_type = device->getType(); + m_direction = MidiDevice::Play; + MidiDevice *md = + dynamic_cast(device); + if (md) + m_direction = md->getDirection(); + m_connection = device->getConnection(); + } else { + RG_DEBUG << "CreateOrDeleteDeviceCommand: No such device as " + << m_deviceId << endl; + } +} + +void +CreateOrDeleteDeviceCommand::execute() +{ + if (!m_deviceCreated) { + + // Create + + // don't want to do this again on undo even if it fails -- only on redo + m_deviceCreated = true; + + + QByteArray data; + QByteArray replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + + arg << (int)m_type; + arg << (unsigned int)m_direction; + + if (!rgapp->sequencerCall("addDevice(int, unsigned int)", + replyType, replyData, data)) { + SEQMAN_DEBUG << "CreateDeviceCommand::execute - " + << "failure in sequencer addDevice" << endl; + return ; + } + + QDataStream reply(replyData, IO_ReadOnly); + reply >> m_deviceId; + + if (m_deviceId == Device::NO_DEVICE) { + SEQMAN_DEBUG << "CreateDeviceCommand::execute - " + << "sequencer addDevice failed" << endl; + return ; + } + + SEQMAN_DEBUG << "CreateDeviceCommand::execute - " + << " added device " << m_deviceId << endl; + + arg.device()->reset(); + arg << (unsigned int)m_deviceId; + arg << strtoqstr(m_connection); + + if (!rgapp->sequencerCall("setConnection(unsigned int, QString)", + replyType, replyData, data)) { + SEQMAN_DEBUG << "CreateDeviceCommand::execute - " + << "failure in sequencer setConnection" << endl; + return ; + } + + SEQMAN_DEBUG << "CreateDeviceCommand::execute - " + << " reconnected device " << m_deviceId + << " to " << m_connection << endl; + + // Add the device to the Studio now, so that we can name it -- + // otherwise the name will be lost + m_studio->addDevice(m_name, m_deviceId, m_type); + Device *device = m_studio->getDevice(m_deviceId); + if (device) { + MidiDevice *md = dynamic_cast + (device); + if (md) + md->setDirection(m_direction); + } + + } else { + + // Delete + + QByteArray data; + QByteArray replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + + if (m_deviceId == Device::NO_DEVICE) + return ; + + arg << (int)m_deviceId; + + if (!rgapp->sequencerCall("removeDevice(unsigned int)", + replyType, replyData, data)) { + SEQMAN_DEBUG << "CreateDeviceCommand::execute - " + << "failure in sequencer addDevice" << endl; + return ; + } + + SEQMAN_DEBUG << "CreateDeviceCommand::unexecute - " + << " removed device " << m_deviceId << endl; + + m_studio->removeDevice(m_deviceId); + + m_deviceId = Device::NO_DEVICE; + m_deviceCreated = false; + } +} + +} diff --git a/src/commands/studio/CreateOrDeleteDeviceCommand.h b/src/commands/studio/CreateOrDeleteDeviceCommand.h new file mode 100644 index 0000000..2fe69a3 --- /dev/null +++ b/src/commands/studio/CreateOrDeleteDeviceCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CREATEORDELETEDEVICECOMMAND_H_ +#define _RG_CREATEORDELETEDEVICECOMMAND_H_ + +#include "base/Device.h" +#include "base/MidiDevice.h" +#include +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Studio; + + +class CreateOrDeleteDeviceCommand : public KNamedCommand +{ +public: + // Creation constructor + CreateOrDeleteDeviceCommand(Studio *studio, + std::string name, + Device::DeviceType type, + MidiDevice::DeviceDirection direction, + std::string connection) : + KNamedCommand(getGlobalName(false)), + m_studio(studio), + m_name(name), + m_type(type), + m_direction(direction), + m_connection(connection), + m_deviceId(Device::NO_DEVICE), + m_deviceCreated(false) { } + + // Deletion constructor + CreateOrDeleteDeviceCommand(Studio *studio, + DeviceId deviceId); + + static QString getGlobalName(bool deletion) { + return (deletion ? i18n("Delete Device") : i18n("Create Device")); + } + + virtual void execute(); + virtual void unexecute() { execute(); } + +protected: + Studio *m_studio; + std::string m_name; + Device::DeviceType m_type; + MidiDevice::DeviceDirection m_direction; + std::string m_connection; + DeviceId m_deviceId; + bool m_deviceCreated; +}; + + + +} + +#endif diff --git a/src/commands/studio/ModifyControlParameterCommand.cpp b/src/commands/studio/ModifyControlParameterCommand.cpp new file mode 100644 index 0000000..5c8c1a2 --- /dev/null +++ b/src/commands/studio/ModifyControlParameterCommand.cpp @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ModifyControlParameterCommand.h" + +#include "base/ControlParameter.h" +#include "base/Device.h" +#include "base/MidiDevice.h" +#include "base/Studio.h" +#include +#include + + +namespace Rosegarden +{ + +ModifyControlParameterCommand::~ModifyControlParameterCommand() +{} + +void +ModifyControlParameterCommand::execute() +{ + MidiDevice *md = dynamic_cast + (m_studio->getDevice(m_device)); + if (!md) { + std::cerr << "WARNING: ModifyControlParameterCommand::execute: device " + << m_device << " is not a MidiDevice in current studio" + << std::endl; + return ; + } + + ControlParameter *param = md->getControlParameter(m_id); + if (param) + m_originalControl = *param; + md->modifyControlParameter(m_control, m_id); +} + +void +ModifyControlParameterCommand::unexecute() +{ + MidiDevice *md = dynamic_cast + (m_studio->getDevice(m_device)); + if (!md) { + std::cerr << "WARNING: ModifyControlParameterCommand::execute: device " + << m_device << " is not a MidiDevice in current studio" + << std::endl; + return ; + } + + md->modifyControlParameter(m_originalControl, m_id); +} + +} diff --git a/src/commands/studio/ModifyControlParameterCommand.h b/src/commands/studio/ModifyControlParameterCommand.h new file mode 100644 index 0000000..cd705d6 --- /dev/null +++ b/src/commands/studio/ModifyControlParameterCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MODIFYCONTROLPARAMETERCOMMAND_H_ +#define _RG_MODIFYCONTROLPARAMETERCOMMAND_H_ + +#include "base/ControlParameter.h" +#include "base/Device.h" +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Studio; + + +class ModifyControlParameterCommand : public KNamedCommand +{ +public: + ModifyControlParameterCommand(Studio *studio, + DeviceId device, + ControlParameter control, + int id): + KNamedCommand(getGlobalName()), + m_studio(studio), + m_device(device), + m_control(control), + m_id(id) { } + ~ModifyControlParameterCommand(); + + virtual void execute(); + virtual void unexecute(); + + static QString getGlobalName() { return i18n("&Modify Control Parameter"); } + +protected: + Studio *m_studio; + DeviceId m_device; + ControlParameter m_control; + int m_id; + ControlParameter m_originalControl; + +}; + +} + +#endif diff --git a/src/commands/studio/ModifyDeviceCommand.cpp b/src/commands/studio/ModifyDeviceCommand.cpp new file mode 100644 index 0000000..d3323ac --- /dev/null +++ b/src/commands/studio/ModifyDeviceCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ModifyDeviceCommand.h" + +#include "base/Device.h" +#include "base/MidiDevice.h" +#include "base/Studio.h" +#include +#include + + +namespace Rosegarden +{ + +ModifyDeviceCommand::ModifyDeviceCommand( + Studio *studio, + DeviceId device, + const std::string &name, + const std::string &librarianName, + const std::string &librarianEmail): + KNamedCommand(getGlobalName()), + m_studio(studio), + m_device(device), + m_name(name), + m_librarianName(librarianName), + m_librarianEmail(librarianEmail), + m_overwrite(true), + m_rename(true), + m_changeVariation(false), + m_changeBanks(false), + m_changePrograms(false), + m_changeControls(false), + m_changeKeyMappings(false), + m_clearBankAndProgramList(false) +{} + +void ModifyDeviceCommand::setVariation(MidiDevice::VariationType variationType) +{ + m_variationType = variationType; + m_changeVariation = true; +} + +void ModifyDeviceCommand::setBankList(const BankList &bankList) +{ + m_bankList = bankList; + m_changeBanks = true; +} + +void ModifyDeviceCommand::setProgramList(const ProgramList &programList) +{ + m_programList = programList; + m_changePrograms = true; +} + +void ModifyDeviceCommand::setControlList(const ControlList &controlList) +{ + m_controlList = controlList; + m_changeControls = true; +} + +void ModifyDeviceCommand::setKeyMappingList(const KeyMappingList &keyMappingList) +{ + m_keyMappingList = keyMappingList; + m_changeKeyMappings = true; +} + +void +ModifyDeviceCommand::execute() +{ + Device *device = m_studio->getDevice(m_device); + MidiDevice *midiDevice = + dynamic_cast(device); + + if (device) { + if (!midiDevice) { + std::cerr << "ERROR: ModifyDeviceCommand::execute: device " + << m_device << " is not a MIDI device" << std::endl; + return ; + } + } else { + std::cerr + << "ERROR: ModifyDeviceCommand::execute: no such device as " + << m_device << std::endl; + return ; + } + + m_oldName = midiDevice->getName(); + m_oldBankList = midiDevice->getBanks(); + m_oldProgramList = midiDevice->getPrograms(); + m_oldControlList = midiDevice->getControlParameters(); + m_oldKeyMappingList = midiDevice->getKeyMappings(); + m_oldLibrarianName = midiDevice->getLibrarianName(); + m_oldLibrarianEmail = midiDevice->getLibrarianEmail(); + m_oldVariationType = midiDevice->getVariationType(); + + if (m_changeVariation) + midiDevice->setVariationType(m_variationType); + + if (m_overwrite) { + if (m_clearBankAndProgramList) { + midiDevice->clearBankList(); + midiDevice->clearProgramList(); + } else { + if (m_changeBanks) + midiDevice->replaceBankList(m_bankList); + if (m_changePrograms) + midiDevice->replaceProgramList(m_programList); + } + + if (m_changeKeyMappings) { + midiDevice->replaceKeyMappingList(m_keyMappingList); + } + + if (m_rename) + midiDevice->setName(m_name); + midiDevice->setLibrarian(m_librarianName, m_librarianEmail); + } else { + if (m_clearBankAndProgramList) { + midiDevice->clearBankList(); + midiDevice->clearProgramList(); + } else { + if (m_changeBanks) + midiDevice->mergeBankList(m_bankList); + if (m_changePrograms) + midiDevice->mergeProgramList(m_programList); + } + + if (m_changeKeyMappings) { + midiDevice->mergeKeyMappingList(m_keyMappingList); + } + + if (m_rename) { + std::string mergeName = midiDevice->getName() + + std::string("/") + m_name; + midiDevice->setName(mergeName); + } + } + + //!!! merge option? + if (m_changeControls) + midiDevice->replaceControlParameters(m_controlList); +} + +void +ModifyDeviceCommand::unexecute() +{ + Device *device = m_studio->getDevice(m_device); + MidiDevice *midiDevice = + dynamic_cast(device); + + if (device) { + if (!midiDevice) { + std::cerr << "ERROR: ModifyDeviceCommand::unexecute: device " + << m_device << " is not a MIDI device" << std::endl; + return ; + } + } else { + std::cerr + << "ERROR: ModifyDeviceCommand::unexecute: no such device as " + << m_device << std::endl; + return ; + } + + if (m_rename) + midiDevice->setName(m_oldName); + midiDevice->replaceBankList(m_oldBankList); + midiDevice->replaceProgramList(m_oldProgramList); + midiDevice->replaceControlParameters(m_oldControlList); + midiDevice->replaceKeyMappingList(m_oldKeyMappingList); + midiDevice->setLibrarian(m_oldLibrarianName, m_oldLibrarianEmail); + if (m_changeVariation) + midiDevice->setVariationType(m_oldVariationType); +} + +} diff --git a/src/commands/studio/ModifyDeviceCommand.h b/src/commands/studio/ModifyDeviceCommand.h new file mode 100644 index 0000000..f8f820e --- /dev/null +++ b/src/commands/studio/ModifyDeviceCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MODIFYDEVICECOMMAND_H_ +#define _RG_MODIFYDEVICECOMMAND_H_ + +#include "base/Device.h" +#include "base/MidiDevice.h" +#include +#include +#include +#include + + +class Modify; + + +namespace Rosegarden +{ + +class Studio; + + +class ModifyDeviceCommand : public KNamedCommand +{ +public: + // Any of the arguments passed by pointer may be null (except for + // the Studio) -- in which case they will not be changed in the device. + ModifyDeviceCommand(Studio *studio, + DeviceId device, + const std::string &name, + const std::string &librarianName, + const std::string &librarianEmail); + + void setVariation (MidiDevice::VariationType variationType); + void setBankList (const BankList &bankList); + void setProgramList(const ProgramList &programList); + void setControlList(const ControlList &controlList); + void setKeyMappingList(const KeyMappingList &keyMappingList); + void setOverwrite (bool overwrite) { m_overwrite = overwrite; } + void setRename (bool rename) { m_rename = rename; } + + /// supersedes setBankList() and setProgramList() + void clearBankAndProgramList() { m_clearBankAndProgramList = true; } + + static QString getGlobalName() { return i18n("Modify &MIDI Bank"); } + + virtual void execute(); + virtual void unexecute(); + +protected: + + Studio *m_studio; + int m_device; + std::string m_name; + std::string m_librarianName; + std::string m_librarianEmail; + MidiDevice::VariationType m_variationType; + BankList m_bankList; + ProgramList m_programList; + ControlList m_controlList; + KeyMappingList m_keyMappingList; + + std::string m_oldName; + std::string m_oldLibrarianName; + std::string m_oldLibrarianEmail; + MidiDevice::VariationType m_oldVariationType; + BankList m_oldBankList; + ProgramList m_oldProgramList; + ControlList m_oldControlList; + KeyMappingList m_oldKeyMappingList; + + bool m_overwrite; + bool m_rename; + bool m_changeVariation; + bool m_changeBanks; + bool m_changePrograms; + bool m_changeControls; + bool m_changeKeyMappings; + bool m_clearBankAndProgramList; + +}; + + +} + +#endif diff --git a/src/commands/studio/ModifyDeviceMappingCommand.cpp b/src/commands/studio/ModifyDeviceMappingCommand.cpp new file mode 100644 index 0000000..6f02d8d --- /dev/null +++ b/src/commands/studio/ModifyDeviceMappingCommand.cpp @@ -0,0 +1,147 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ModifyDeviceMappingCommand.h" + +#include "misc/Debug.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 "document/RosegardenGUIDoc.h" +#include + + +namespace Rosegarden +{ + +ModifyDeviceMappingCommand::ModifyDeviceMappingCommand( + RosegardenGUIDoc *doc, + DeviceId fromDevice, + DeviceId toDevice): + KNamedCommand(getGlobalName()), + m_composition(&doc->getComposition()), + m_studio(&doc->getStudio()), + m_fromDevice(fromDevice), + m_toDevice(toDevice) +{} + +void +ModifyDeviceMappingCommand::execute() +{ + Composition::trackcontainer &tracks = + m_composition->getTracks(); + Composition::trackcontainer::iterator it = tracks.begin(); + Instrument *instr = 0; + int index = 0; + + for (; it != tracks.end(); it++) { + instr = m_studio->getInstrumentById(it->second->getInstrument()); + if (!instr || !instr->getDevice()) + continue; + + if (instr->getDevice()->getId() == m_fromDevice) { + // if source and target are MIDI + if (m_studio->getDevice(m_fromDevice)->getType() == + Device::Midi && + m_studio->getDevice(m_toDevice)->getType() == + Device::Midi) { + // Try to match channels on the target device + // + MidiByte channel = instr->getMidiChannel(); + + InstrumentList destList = m_studio-> + getDevice(m_toDevice)->getPresentationInstruments(); + + InstrumentList::iterator dIt = destList.begin(); + + for (; dIt != destList.end(); dIt++) { + if ((*dIt)->getMidiChannel() == channel) { + break; + } + } + + // Failure to match anything and there's no Instruments + // at all in the destination. Skip to next source Instrument. + // + if (dIt == destList.end() || destList.size() == 0) + continue; + + + RG_DEBUG << " Track " << it->first + << ", setting Instrument to " + << (*dIt)->getId() << endl; + + // store "to" and "from" values + // + m_mapping.push_back( + std::pair < TrackId, + InstrumentId > + (it->first, + instr->getId())); + + it->second->setInstrument((*dIt)->getId()); + } else // audio is involved in the mapping - use indexes + { + // assign by index numbers + InstrumentList destList = m_studio-> + getDevice(m_toDevice)->getPresentationInstruments(); + + // skip if we can't match + // + if (index > (int)(destList.size() - 1)) + continue; + + m_mapping.push_back( + std::pair < TrackId, + InstrumentId > + (it->first, + instr->getId())); + + it->second->setInstrument(destList[index]->getId()); + } + + index++; + } + } + +} + +void +ModifyDeviceMappingCommand::unexecute() +{ + std::vector > + ::iterator it = m_mapping.begin(); + Track *track = 0; + + for (; it != m_mapping.end(); it++) { + track = m_composition->getTrackById(it->first); + track->setInstrument(it->second); + } +} + +} diff --git a/src/commands/studio/ModifyDeviceMappingCommand.h b/src/commands/studio/ModifyDeviceMappingCommand.h new file mode 100644 index 0000000..150275d --- /dev/null +++ b/src/commands/studio/ModifyDeviceMappingCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MODIFYDEVICEMAPPINGCOMMAND_H_ +#define _RG_MODIFYDEVICEMAPPINGCOMMAND_H_ + +#include "base/Device.h" +#include "base/Track.h" +#include +#include +#include +#include + + +class Modify; + + +namespace Rosegarden +{ + +class Studio; +class RosegardenGUIDoc; +class Composition; + + +class ModifyDeviceMappingCommand : public KNamedCommand +{ +public: + ModifyDeviceMappingCommand(RosegardenGUIDoc *doc, + DeviceId fromDevice, + DeviceId toDevice); + + static QString getGlobalName() { return i18n("Modify &Device Mapping"); } + + virtual void execute(); + virtual void unexecute(); +protected: + Composition *m_composition; + Studio *m_studio; + DeviceId m_fromDevice; + DeviceId m_toDevice; + + std::vector > m_mapping; +}; + + +} + +#endif diff --git a/src/commands/studio/ModifyInstrumentMappingCommand.cpp b/src/commands/studio/ModifyInstrumentMappingCommand.cpp new file mode 100644 index 0000000..e6369b6 --- /dev/null +++ b/src/commands/studio/ModifyInstrumentMappingCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ModifyInstrumentMappingCommand.h" + +#include "base/Composition.h" +#include "base/MidiProgram.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "document/RosegardenGUIDoc.h" +#include + + +namespace Rosegarden +{ + +ModifyInstrumentMappingCommand::ModifyInstrumentMappingCommand( + RosegardenGUIDoc *doc, + InstrumentId fromInstrument, + InstrumentId toInstrument): + KNamedCommand(getGlobalName()), + m_composition(&doc->getComposition()), + m_studio(&doc->getStudio()), + m_fromInstrument(fromInstrument), + m_toInstrument(toInstrument) +{} + +void +ModifyInstrumentMappingCommand::execute() +{ + Composition::trackcontainer &tracks = + m_composition->getTracks(); + Composition::trackcontainer::iterator it = tracks.begin(); + + for (; it != tracks.end(); it++) { + if (it->second->getInstrument() == m_fromInstrument) { + m_mapping.push_back(it->first); + it->second->setInstrument(m_toInstrument); + } + } + +} + +void +ModifyInstrumentMappingCommand::unexecute() +{ + std::vector::iterator it = m_mapping.begin(); + Track *track = 0; + + for (; it != m_mapping.end(); it++) { + track = m_composition->getTrackById(*it); + track->setInstrument(m_fromInstrument); + } +} + +} diff --git a/src/commands/studio/ModifyInstrumentMappingCommand.h b/src/commands/studio/ModifyInstrumentMappingCommand.h new file mode 100644 index 0000000..224459b --- /dev/null +++ b/src/commands/studio/ModifyInstrumentMappingCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MODIFYINSTRUMENTMAPPINGCOMMAND_H_ +#define _RG_MODIFYINSTRUMENTMAPPINGCOMMAND_H_ + +#include "base/MidiProgram.h" +#include "base/Track.h" +#include +#include +#include +#include + + +class Modify; + + +namespace Rosegarden +{ + +class Studio; +class RosegardenGUIDoc; +class Composition; + + +class ModifyInstrumentMappingCommand : public KNamedCommand +{ +public: + ModifyInstrumentMappingCommand(RosegardenGUIDoc *doc, + InstrumentId fromInstrument, + InstrumentId toInstrument); + + static QString getGlobalName() { return i18n("Modify &Instrument Mapping"); } + + virtual void execute(); + virtual void unexecute(); + +protected: + Composition *m_composition; + Studio *m_studio; + InstrumentId m_fromInstrument; + InstrumentId m_toInstrument; + + std::vector m_mapping; + +}; + + +// because ModifyDeviceCommand is overkill for this + + +} + +#endif diff --git a/src/commands/studio/ReconnectDeviceCommand.cpp b/src/commands/studio/ReconnectDeviceCommand.cpp new file mode 100644 index 0000000..6d40ede --- /dev/null +++ b/src/commands/studio/ReconnectDeviceCommand.cpp @@ -0,0 +1,98 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ReconnectDeviceCommand.h" + +#include "misc/Strings.h" +#include "misc/Debug.h" +#include "base/Device.h" +#include "base/Studio.h" +#include +#include +#include +#include "gui/application/RosegardenApplication.h" + + +namespace Rosegarden +{ + +void +ReconnectDeviceCommand::execute() +{ + Device *device = m_studio->getDevice(m_deviceId); + + if (device) { + m_oldConnection = device->getConnection(); + + QByteArray data; + QByteArray replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + + arg << (unsigned int)m_deviceId; + arg << strtoqstr(m_newConnection); + + if (!rgapp->sequencerCall("setConnection(unsigned int, QString)", + replyType, replyData, data)) { + SEQMAN_DEBUG << "ReconnectDeviceCommand::execute - " + << "failure in sequencer setConnection" << endl; + return ; + } + + SEQMAN_DEBUG << "ReconnectDeviceCommand::execute - " + << " reconnected device " << m_deviceId + << " to " << m_newConnection << endl; + } +} + +void +ReconnectDeviceCommand::unexecute() +{ + Device *device = m_studio->getDevice(m_deviceId); + + if (device) { + + QByteArray data; + QByteArray replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + + arg << (unsigned int)m_deviceId; + arg << strtoqstr(m_oldConnection); + + if (!rgapp->sequencerCall("setConnection(unsigned int, QString)", + replyType, replyData, data)) { + SEQMAN_DEBUG << "ReconnectDeviceCommand::unexecute - " + << "failure in sequencer setConnection" << endl; + return ; + } + + SEQMAN_DEBUG << "ReconnectDeviceCommand::unexecute - " + << " reconnected device " << m_deviceId + << " to " << m_oldConnection << endl; + } +} + +} diff --git a/src/commands/studio/ReconnectDeviceCommand.h b/src/commands/studio/ReconnectDeviceCommand.h new file mode 100644 index 0000000..910bdbf --- /dev/null +++ b/src/commands/studio/ReconnectDeviceCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_RECONNECTDEVICECOMMAND_H_ +#define _RG_RECONNECTDEVICECOMMAND_H_ + +#include "base/Device.h" +#include +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Studio; + + +class ReconnectDeviceCommand : public KNamedCommand +{ +public: + ReconnectDeviceCommand(Studio *studio, + DeviceId deviceId, + std::string newConnection) : + KNamedCommand(getGlobalName()), + m_studio(studio), + m_deviceId(deviceId), + m_newConnection(newConnection) { } + + static QString getGlobalName() { return i18n("Reconnect Device"); } + + virtual void execute(); + virtual void unexecute(); + +protected: + Studio *m_studio; + DeviceId m_deviceId; + std::string m_newConnection; + std::string m_oldConnection; +}; + + +} + +#endif diff --git a/src/commands/studio/RemoveControlParameterCommand.cpp b/src/commands/studio/RemoveControlParameterCommand.cpp new file mode 100644 index 0000000..5f596b5 --- /dev/null +++ b/src/commands/studio/RemoveControlParameterCommand.cpp @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RemoveControlParameterCommand.h" + +#include "base/ControlParameter.h" +#include "base/Device.h" +#include "base/MidiDevice.h" +#include "base/Studio.h" +#include +#include + + +namespace Rosegarden +{ + +RemoveControlParameterCommand::~RemoveControlParameterCommand() +{} + +void +RemoveControlParameterCommand::execute() +{ + MidiDevice *md = dynamic_cast + (m_studio->getDevice(m_device)); + if (!md) { + std::cerr << "WARNING: RemoveControlParameterCommand::execute: device " + << m_device << " is not a MidiDevice in current studio" + << std::endl; + return ; + } + + ControlParameter *param = md->getControlParameter(m_id); + if (param) + m_oldControl = *param; + md->removeControlParameter(m_id); +} + +void +RemoveControlParameterCommand::unexecute() +{ + MidiDevice *md = dynamic_cast + (m_studio->getDevice(m_device)); + if (!md) { + std::cerr << "WARNING: RemoveControlParameterCommand::execute: device " + << m_device << " is not a MidiDevice in current studio" + << std::endl; + return ; + } + + md->addControlParameter(m_oldControl, m_id); +} + +} diff --git a/src/commands/studio/RemoveControlParameterCommand.h b/src/commands/studio/RemoveControlParameterCommand.h new file mode 100644 index 0000000..3143739 --- /dev/null +++ b/src/commands/studio/RemoveControlParameterCommand.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_REMOVECONTROLPARAMETERCOMMAND_H_ +#define _RG_REMOVECONTROLPARAMETERCOMMAND_H_ + +#include "base/ControlParameter.h" +#include "base/Device.h" +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Studio; + + +class RemoveControlParameterCommand : public KNamedCommand +{ +public: + RemoveControlParameterCommand(Studio *studio, + DeviceId device, + int id): + KNamedCommand(getGlobalName()), + m_studio(studio), + m_device(device), + m_id(id) { } + + ~RemoveControlParameterCommand(); + + virtual void execute(); + virtual void unexecute(); + + static QString getGlobalName() { return i18n("&Remove Control Parameter"); } + +protected: + Studio *m_studio; + DeviceId m_device; + int m_id; + ControlParameter m_oldControl; + +}; + + +} + +#endif diff --git a/src/commands/studio/RenameDeviceCommand.cpp b/src/commands/studio/RenameDeviceCommand.cpp new file mode 100644 index 0000000..1d6c382 --- /dev/null +++ b/src/commands/studio/RenameDeviceCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RenameDeviceCommand.h" + +#include "base/Device.h" +#include "base/Studio.h" +#include + + +namespace Rosegarden +{ + +void +RenameDeviceCommand::execute() +{ + Device *device = m_studio->getDevice(m_deviceId); + if (m_oldName == "") + m_oldName = device->getName(); + device->setName(m_name); +} + +void +RenameDeviceCommand::unexecute() +{ + Device *device = m_studio->getDevice(m_deviceId); + device->setName(m_oldName); +} + +} diff --git a/src/commands/studio/RenameDeviceCommand.h b/src/commands/studio/RenameDeviceCommand.h new file mode 100644 index 0000000..d767bca --- /dev/null +++ b/src/commands/studio/RenameDeviceCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_RENAMEDEVICECOMMAND_H_ +#define _RG_RENAMEDEVICECOMMAND_H_ + +#include "base/Device.h" +#include +#include +#include +#include + + + + +namespace Rosegarden +{ + +class Studio; + + +class RenameDeviceCommand : public KNamedCommand +{ +public: + RenameDeviceCommand(Studio *studio, + DeviceId deviceId, + std::string name) : + KNamedCommand(getGlobalName()), + m_studio(studio), + m_deviceId(deviceId), + m_name(name) { } + + static QString getGlobalName() { return i18n("Rename Device"); } + + virtual void execute(); + virtual void unexecute(); + +protected: + Studio *m_studio; + DeviceId m_deviceId; + std::string m_name; + std::string m_oldName; +}; + + + +} + +#endif diff --git a/src/document/BasicCommand.cpp b/src/document/BasicCommand.cpp new file mode 100644 index 0000000..db56564 --- /dev/null +++ b/src/document/BasicCommand.cpp @@ -0,0 +1,171 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "BasicCommand.h" + +#include "base/Segment.h" +#include + +namespace Rosegarden +{ + +BasicCommand::BasicCommand(const QString &name, Segment &segment, + timeT start, timeT end, bool bruteForceRedo) : + KNamedCommand(name), + m_startTime(calculateStartTime(start, segment)), + m_endTime(calculateEndTime(end, segment)), + m_segment(segment), + m_savedEvents(segment.getType(), m_startTime), + m_doBruteForceRedo(false), + m_redoEvents(0) +{ + if (m_endTime == m_startTime) ++m_endTime; + + if (bruteForceRedo) { + m_redoEvents = new Segment(segment.getType(), m_startTime); + } +} + +BasicCommand::~BasicCommand() +{ + m_savedEvents.clear(); + if (m_redoEvents) m_redoEvents->clear(); + delete m_redoEvents; +} + +timeT +BasicCommand::calculateStartTime(timeT given, Segment &segment) +{ + timeT actual = given; + Segment::iterator i = segment.findTime(given); + + while (i != segment.end() && (*i)->getAbsoluteTime() == given) { + timeT notation = (*i)->getNotationAbsoluteTime(); + if (notation < given) actual = notation; + ++i; + } + + return actual; +} + +timeT +BasicCommand::calculateEndTime(timeT given, Segment &segment) +{ + timeT actual = given; + Segment::iterator i = segment.findTime(given); + + while (i != segment.end() && (*i)->getAbsoluteTime() == given) { + timeT notation = (*i)->getNotationAbsoluteTime(); + if (notation > given) actual = notation; + ++i; + } + + return actual; +} + +Rosegarden::Segment& BasicCommand::getSegment() +{ + return m_segment; +} + +Rosegarden::timeT BasicCommand::getRelayoutEndTime() +{ + return getEndTime(); +} + +void +BasicCommand::beginExecute() +{ + copyTo(&m_savedEvents); +} + +void +BasicCommand::execute() +{ + beginExecute(); + + if (!m_doBruteForceRedo) { + + modifySegment(); + + } else { + copyFrom(m_redoEvents); + } + + m_segment.updateRefreshStatuses(getStartTime(), getRelayoutEndTime()); + RG_DEBUG << "BasicCommand(" << name() << "): updated refresh statuses " + << getStartTime() << " -> " << getRelayoutEndTime() << endl; +} + +void +BasicCommand::unexecute() +{ + if (m_redoEvents) { + copyTo(m_redoEvents); + m_doBruteForceRedo = true; + } + + copyFrom(&m_savedEvents); + + m_segment.updateRefreshStatuses(getStartTime(), getRelayoutEndTime()); +} + +void +BasicCommand::copyTo(Rosegarden::Segment *events) +{ + RG_DEBUG << "BasicCommand(" << name() << ")::copyTo: " << &m_segment << " to " + << events << ", range (" + << m_startTime << "," << m_endTime + << ")" << endl; + + Segment::iterator from = m_segment.findTime(m_startTime); + Segment::iterator to = m_segment.findTime(m_endTime); + + for (Segment::iterator i = from; i != m_segment.end() && i != to; ++i) { +// RG_DEBUG << "Found event of type " << (*i)->getType() << " and duration " << (*i)->getDuration() << endl; + events->insert(new Event(**i)); + } +} + +void +BasicCommand::copyFrom(Rosegarden::Segment *events) +{ + RG_DEBUG << "BasicCommand(" << name() << ")::copyFrom: " << events << " to " + << &m_segment << ", range (" + << m_startTime << "," << m_endTime + << ")" << endl; + + m_segment.erase(m_segment.findTime(m_startTime), + m_segment.findTime(m_endTime)); + + for (Segment::iterator i = events->begin(); i != events->end(); ++i) { +// RG_DEBUG << "Found event of type " << (*i)->getType() << " and duration " << (*i)->getDuration() << endl; + m_segment.insert(new Event(**i)); + } + + events->clear(); +} + +} diff --git a/src/document/BasicCommand.h b/src/document/BasicCommand.h new file mode 100644 index 0000000..eab9ea0 --- /dev/null +++ b/src/document/BasicCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_BASICCOMMAND_H_ +#define _RG_BASICCOMMAND_H_ + +#include "base/Segment.h" +#include +#include "base/Event.h" +#include "misc/Debug.h" + +class QString; + + +namespace Rosegarden +{ + + + +/** + * BasicCommand is an abstract subclass of Command that manages undo, + * redo and notification of changes within a contiguous region of a + * single Rosegarden Segment, by brute force. When a subclass + * of BasicCommand executes, it stores a copy of the events that are + * modified by the command, ready to be restored verbatim on undo. + */ + +class BasicCommand : public KNamedCommand +{ +public: + virtual ~BasicCommand(); + + virtual void execute(); + virtual void unexecute(); + + virtual Segment &getSegment(); + + timeT getStartTime() { return m_startTime; } + timeT getEndTime() { return m_endTime; } + virtual timeT getRelayoutEndTime(); + +protected: + /** + * You should pass "bruteForceRedoRequired = true" if your + * subclass's implementation of modifySegment uses discrete + * event pointers or segment iterators to determine which + * events to modify, in which case it won't work when + * replayed for redo because the pointers may no longer be + * valid. In which case, BasicCommand will implement redo + * much like undo, and will only call your modifySegment + * the very first time the command object is executed. + * + * It is always safe to pass bruteForceRedoRequired true, + * it's just normally a waste of memory. + */ + BasicCommand(const QString &name, + Segment &segment, + timeT start, timeT end, + bool bruteForceRedoRequired = false); + + virtual void modifySegment() = 0; + + virtual void beginExecute(); + +private: + //--------------- Data members --------------------------------- + + void copyTo(Segment *); + void copyFrom(Segment *); + + timeT calculateStartTime(timeT given, + Segment &segment); + timeT calculateEndTime(timeT given, + Segment &segment); + + timeT m_startTime; + timeT m_endTime; + + Segment &m_segment; + Segment m_savedEvents; + + bool m_doBruteForceRedo; + Segment *m_redoEvents; +}; + + + +} + +#endif diff --git a/src/document/BasicSelectionCommand.cpp b/src/document/BasicSelectionCommand.cpp new file mode 100644 index 0000000..2216223 --- /dev/null +++ b/src/document/BasicSelectionCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "BasicSelectionCommand.h" + +#include "base/Segment.h" +#include "base/Selection.h" +#include "BasicCommand.h" +#include + + +namespace Rosegarden +{ + +BasicSelectionCommand::BasicSelectionCommand(const QString &name, + EventSelection &selection, + bool bruteForceRedo) : + BasicCommand(name, + selection.getSegment(), + selection.getStartTime(), + selection.getEndTime(), + bruteForceRedo) +{ + // nothing +} + +BasicSelectionCommand::BasicSelectionCommand(const QString &name, + Segment &segment, + bool bruteForceRedo) : + BasicCommand(name, + segment, + segment.getStartTime(), + segment.getEndMarkerTime(), + bruteForceRedo) +{ + // nothing +} + +BasicSelectionCommand::~BasicSelectionCommand() +{ + // nothing +} + +} diff --git a/src/document/BasicSelectionCommand.h b/src/document/BasicSelectionCommand.h new file mode 100644 index 0000000..825955d --- /dev/null +++ b/src/document/BasicSelectionCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_BASICSELECTIONCOMMAND_H_ +#define _RG_BASICSELECTIONCOMMAND_H_ + +#include "BasicCommand.h" + + +class QString; + + +namespace Rosegarden +{ + +class Segment; +class EventSelection; + + +/** + * Subclass of BasicCommand that manages the brute-force undo and redo + * extends based on a given selection. + */ + +class BasicSelectionCommand : public BasicCommand +{ +public: + virtual ~BasicSelectionCommand(); + +protected: + /// selection from segment + BasicSelectionCommand(const QString &name, + EventSelection &selection, + bool bruteForceRedoRequired = false); + + /// entire segment + BasicSelectionCommand(const QString &name, + Segment &segment, + bool bruteForceRedoRequired = false); +}; + + +} + +#endif diff --git a/src/document/ConfigGroups.cpp b/src/document/ConfigGroups.cpp new file mode 100644 index 0000000..8e164c3 --- /dev/null +++ b/src/document/ConfigGroups.cpp @@ -0,0 +1,53 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "ConfigGroups.h" + +namespace Rosegarden +{ + + // + // KConfig group names + // + const char* const GeneralOptionsConfigGroup = "General Options"; + const char* const LatencyOptionsConfigGroup = "Latency Options"; + const char* const SequencerOptionsConfigGroup = "Sequencer Options"; + const char* const NotationViewConfigGroup = "Notation Options"; + const char* const AudioManagerDialogConfigGroup = "AudioManagerDialog"; + const char* const SynthPluginManagerConfigGroup = "Synth Plugin Manager"; + const char* const BankEditorConfigGroup = "Bank Editor"; + const char* const ColoursConfigGroup = "coloursconfiggroup"; + const char* const ControlEditorConfigGroup = "Control Editor"; + const char* const DeviceManagerConfigGroup = "Device Manager"; + const char* const EventFilterDialogConfigGroup = "EventFilter Dialog"; + const char* const EventViewLayoutConfigGroupName = "EventList Layout"; + const char* const EventViewConfigGroup = "EventList Options"; + const char* const MarkerEditorConfigGroup = "Marker Editor"; + const char* const MatrixViewConfigGroup = "Matrix Options"; + const char* const PlayListConfigGroup = "PLAY_LIST"; + const char* const MainWindowConfigGroup = "MainView"; + const char* const TransportDialogConfigGroup = "Transport Controls"; + const char* const TempoViewLayoutConfigGroupName = "TempoView Layout"; + const char* const TempoViewConfigGroup = "TempoView Options"; + const char* const TriggerManagerConfigGroup = "Trigger Editor"; + const char* const EditViewConfigGroup = "Edit View"; + +} diff --git a/src/document/ConfigGroups.h b/src/document/ConfigGroups.h new file mode 100644 index 0000000..d960e4d --- /dev/null +++ b/src/document/ConfigGroups.h @@ -0,0 +1,56 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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. +*/ + +/** + * Miscellaneous constants + */ + + +#ifndef _CONSTANTS_H_ +#define _CONSTANTS_H_ + +namespace Rosegarden +{ + extern const char* const GeneralOptionsConfigGroup; + extern const char* const LatencyOptionsConfigGroup; + extern const char* const SequencerOptionsConfigGroup; + extern const char* const NotationViewConfigGroup; + extern const char* const AudioManagerDialogConfigGroup; + extern const char* const SynthPluginManagerConfigGroup; + extern const char* const BankEditorConfigGroup; + extern const char* const ColoursConfigGroup; + extern const char* const ControlEditorConfigGroup; + extern const char* const DeviceManagerConfigGroup; + extern const char* const EventFilterDialogConfigGroup; + extern const char* const EventViewLayoutConfigGroupName; + extern const char* const EventViewConfigGroup; + extern const char* const MarkerEditorConfigGroup; + extern const char* const MatrixViewConfigGroup; + extern const char* const PlayListConfigGroup; + extern const char* const MainWindowConfigGroup; + extern const char* const TransportDialogConfigGroup; + extern const char* const TempoViewLayoutConfigGroupName; + extern const char* const TempoViewConfigGroup; + extern const char* const TriggerManagerConfigGroup; + extern const char* const EditViewConfigGroup; +} + +#endif diff --git a/src/document/MultiViewCommandHistory.cpp b/src/document/MultiViewCommandHistory.cpp new file mode 100644 index 0000000..f8cddeb --- /dev/null +++ b/src/document/MultiViewCommandHistory.cpp @@ -0,0 +1,386 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MultiViewCommandHistory.h" + +#include +#include "misc/Debug.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +MultiViewCommandHistory::MultiViewCommandHistory() : + m_undoLimit(50), + m_redoLimit(50), + m_savedAt(0) +{ + // nothing +} + +MultiViewCommandHistory::~MultiViewCommandHistory() +{ + m_savedAt = -1; + clearStack(m_undoStack); + clearStack(m_redoStack); +} + +void +MultiViewCommandHistory::clear() +{ + m_savedAt = -1; + clearStack(m_undoStack); + clearStack(m_redoStack); +} + +void +MultiViewCommandHistory::attachView(KActionCollection *collection) +{ + if (m_views.find(collection) != m_views.end()) + return ; + + RG_DEBUG << "MultiViewCommandHistory::attachView() : setting up undo/redo actions\n"; + + KToolBarPopupAction *undo = dynamic_cast(collection->action(KStdAction::stdName(KStdAction::Undo))); + + if (undo) { + connect(undo, SIGNAL(activated()), + this, SLOT(slotUndo())); + + connect + (undo->popupMenu(), + SIGNAL(aboutToShow()), + this, + SLOT(slotUndoAboutToShow())); + + connect + (undo->popupMenu(), + SIGNAL(activated(int)), + this, + SLOT(slotUndoActivated(int))); + } + + KToolBarPopupAction *redo = dynamic_cast(collection->action(KStdAction::stdName(KStdAction::Redo))); + + if (redo) { + + connect(redo, SIGNAL(activated()), + this, SLOT(slotRedo())); + + connect + (redo->popupMenu(), + SIGNAL(aboutToShow()), + this, + SLOT(slotRedoAboutToShow())); + + connect + (redo->popupMenu(), + SIGNAL(activated(int)), + this, + SLOT(slotRedoActivated(int))); + } + + m_views.insert(collection); + updateButtons(); + +} + +void +MultiViewCommandHistory::detachView(KActionCollection *collection) +{ + ViewSet::iterator i = m_views.find(collection); + if (i != m_views.end()) + m_views.erase(collection); +} + +void +MultiViewCommandHistory::addCommand(KCommand *command, bool execute) +{ + if (!command) + return ; + + RG_DEBUG << "MultiViewCommandHistory::addCommand: " << command->name() << endl; + + // We can't redo after adding a command + clearStack(m_redoStack); + + // can we reach savedAt? + if ((int)m_undoStack.size() < m_savedAt) + m_savedAt = -1; // nope + + m_undoStack.push(command); + clipCommands(); + + if (execute) { + command->execute(); + emit commandExecuted(); + emit commandExecuted(command); + } + + updateButtons(); +} + +void +MultiViewCommandHistory::slotUndo() +{ + if (m_undoStack.empty()) + return ; + + KCommand *command = m_undoStack.top(); + command->unexecute(); + emit commandExecuted(); + emit commandExecuted(command); + + m_redoStack.push(command); + m_undoStack.pop(); + + clipCommands(); + updateButtons(); + + if ((int)m_undoStack.size() == m_savedAt) + emit documentRestored(); +} + +void +MultiViewCommandHistory::slotRedo() +{ + if (m_redoStack.empty()) + return ; + + KCommand *command = m_redoStack.top(); + command->execute(); + emit commandExecuted(); + emit commandExecuted(command); + + m_undoStack.push(command); + m_redoStack.pop(); + // no need to clip + updateButtons(); +} + +void +MultiViewCommandHistory::setUndoLimit(int limit) +{ + if (limit > 0 && limit != m_undoLimit) { + m_undoLimit = limit; + clipCommands(); + } +} + +void +MultiViewCommandHistory::setRedoLimit(int limit) +{ + if (limit > 0 && limit != m_redoLimit) { + m_redoLimit = limit; + clipCommands(); + } +} + +void +MultiViewCommandHistory::documentSaved() +{ + m_savedAt = m_undoStack.size(); +} + +void +MultiViewCommandHistory::clipCommands() +{ + if ((int)m_undoStack.size() > m_undoLimit) { + m_savedAt -= (m_undoStack.size() - m_undoLimit); + } + + clipStack(m_undoStack, m_undoLimit); + clipStack(m_redoStack, m_redoLimit); +} + +void +MultiViewCommandHistory::clipStack(CommandStack &stack, int limit) +{ + int i; + + if ((int)stack.size() > limit) { + + CommandStack tempStack; + for (i = 0; i < limit; ++i) { + KCommand *togo = stack.top(); + KNamedCommand *named = dynamic_cast(togo); + if (named) { + RG_DEBUG << "MVCH::clipStack: Saving recent command: " << named->name() << " at " << togo << endl; + } else { + RG_DEBUG << "MVCH::clipStack: Saving recent unnamed command" << " at " << togo << endl; + } + tempStack.push(stack.top()); + stack.pop(); + } + clearStack(stack); + for (i = 0; i < m_undoLimit; ++i) { + stack.push(tempStack.top()); + tempStack.pop(); + } + } +} + +void +MultiViewCommandHistory::clearStack(CommandStack &stack) +{ + while (!stack.empty()) { + KCommand *togo = stack.top(); + KNamedCommand *named = dynamic_cast(togo); + if (named) { + RG_DEBUG << "MVCH::clearStack: About to delete command: " << named->name() << " at " << togo << endl; + } else { + RG_DEBUG << "MVCH::clearStack: About to delete unnamed command" << " at " << togo << endl; + } + delete togo; + stack.pop(); + } +} + +void +MultiViewCommandHistory::slotUndoActivated(int pos) +{ + for (int i = 0 ; i <= pos; ++i) + slotUndo(); +} + +void +MultiViewCommandHistory::slotRedoActivated(int pos) +{ + for (int i = 0 ; i <= pos; ++i) + slotRedo(); +} + +void +MultiViewCommandHistory::slotUndoAboutToShow() +{ + updateMenu(true, KStdAction::stdName(KStdAction::Undo), m_undoStack); +} + +void +MultiViewCommandHistory::slotRedoAboutToShow() +{ + updateMenu(false, KStdAction::stdName(KStdAction::Redo), m_redoStack); +} + +void +MultiViewCommandHistory::updateButtons() +{ + updateButton(true, KStdAction::stdName(KStdAction::Undo), m_undoStack); + updateButton(false, KStdAction::stdName(KStdAction::Redo), m_redoStack); +} + +void +MultiViewCommandHistory::updateButton(bool undo, + const QString &name, + CommandStack &stack) +{ + for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) { + + KAction *action = (*i)->action(name); + if (!action) + continue; + QString text; + + if (stack.empty()) { + action->setEnabled(false); + if (undo) + text = i18n("Nothing to undo"); + else + text = i18n("Nothing to redo"); + action->setText(text); + } else { + action->setEnabled(true); + QString commandName = stack.top()->name(); + commandName.replace(QRegExp("&"), ""); + commandName.replace(QRegExp("\\.\\.\\.$"), ""); + if (undo) + text = i18n("Und&o %1").arg(commandName); + else + text = i18n("Re&do %1").arg(commandName); + action->setText(text); + } + } +} + +void +MultiViewCommandHistory::updateMenu(bool undo, + const QString &name, + CommandStack &stack) +{ + for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) { + + KAction *action = (*i)->action(name); + if (!action) + continue; + + KToolBarPopupAction *popupAction = + dynamic_cast(action); + if (!popupAction) + continue; + + QPopupMenu *menu = popupAction->popupMenu(); + if (!menu) + continue; + menu->clear(); + + CommandStack tempStack; + int j = 0; + + while (j < 10 && !stack.empty()) { + + KCommand *command = stack.top(); + tempStack.push(command); + stack.pop(); + + QString commandName = command->name(); + commandName.replace(QRegExp("&"), ""); + commandName.replace(QRegExp("\\.\\.\\.$"), ""); + + QString text; + if (undo) + text = i18n("Und&o %1").arg(commandName); + else + text = i18n("Re&do %1").arg(commandName); + menu->insertItem(text, j++); + } + + while (!tempStack.empty()) { + stack.push(tempStack.top()); + tempStack.pop(); + } + } +} + +} +#include "MultiViewCommandHistory.moc" diff --git a/src/document/MultiViewCommandHistory.h b/src/document/MultiViewCommandHistory.h new file mode 100644 index 0000000..d122d08 --- /dev/null +++ b/src/document/MultiViewCommandHistory.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MULTIVIEWCOMMANDHISTORY_H_ +#define _RG_MULTIVIEWCOMMANDHISTORY_H_ + +#include +#include +#include + + +class QString; +class KCommand; +class KActionCollection; + + +namespace Rosegarden +{ + + + +/** + * The MultiViewCommandHistory class is much like KCommandHistory in + * that it stores a list of executed commands and maintains Undo and + * Redo actions synchronised with those commands. + * + * The difference is that MultiViewCommandHistory allows you to + * associate more than one Undo and Redo action with the same command + * history, and it keeps them all up-to-date at once. This makes it + * effective in systems where multiple views may be editing the same + * data at once. + */ + +class MultiViewCommandHistory : public QObject +{ + Q_OBJECT +public: + + MultiViewCommandHistory(); + virtual ~MultiViewCommandHistory(); + + void clear(); + + void attachView(KActionCollection *collection); + void detachView(KActionCollection *collection); + + void addCommand(KCommand *command, bool execute = true); + + /// @return the maximum number of items in the undo history + int undoLimit() { return m_undoLimit; } + + /// Set the maximum number of items in the undo history + void setUndoLimit(int limit); + + /// @return the maximum number of items in the redo history + int redoLimit() { return m_redoLimit; } + + /// Set the maximum number of items in the redo history + void setRedoLimit(int limit); + +public slots: + /** + * Remember when you saved the document. + * Call this right after saving the document. As soon as + * the history reaches the current index again (via some + * undo/redo operations) it will emit @ref documentRestored + * If you implemented undo/redo properly the document is + * the same you saved before. + */ + virtual void documentSaved(); + +protected slots: + void slotUndo(); + void slotRedo(); + void slotUndoAboutToShow(); + void slotUndoActivated(int); + void slotRedoAboutToShow(); + void slotRedoActivated(int); + +signals: + /** + * This is emitted every time a command is executed + * (whether by addCommand, undo or redo). + * You can use this to update the GUI, for instance. + */ + void commandExecuted(KCommand *); + + /** + * This is emitted every time a command is executed + * (whether by addCommand, undo or redo). + * + * It should be connected to the update() slot of widgets + * which need to repaint after a command + */ + void commandExecuted(); + + /** + * This is emitted every time we reach the index where you + * saved the document for the last time. See @ref documentSaved + */ + void documentRestored(); + +private: + //--------------- Data members --------------------------------- + + typedef std::set ViewSet; + ViewSet m_views; + + typedef std::stack CommandStack; + CommandStack m_undoStack; + CommandStack m_redoStack; + + int m_undoLimit; + int m_redoLimit; + int m_savedAt; + + void updateButtons(); + void updateButton(bool undo, const QString &name, CommandStack &stack); + void updateMenu(bool undo, const QString &name, CommandStack &stack); + void clipCommands(); + + void clipStack(CommandStack &stack, int limit); + void clearStack(CommandStack &stack); +}; + + + +} + +#endif diff --git a/src/document/RoseXmlHandler.cpp b/src/document/RoseXmlHandler.cpp new file mode 100644 index 0000000..028c89a --- /dev/null +++ b/src/document/RoseXmlHandler.cpp @@ -0,0 +1,2368 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RoseXmlHandler.h" + +#include "sound/Midi.h" +#include +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/AudioLevel.h" +#include "base/AudioPluginInstance.h" +#include "base/BaseProperties.h" +#include "base/Colour.h" +#include "base/ColourMap.h" +#include "base/Composition.h" +#include "base/ControlParameter.h" +#include "base/Device.h" +#include "base/Instrument.h" +#include "base/Marker.h" +#include "base/MidiDevice.h" +#include "base/MidiProgram.h" +#include "base/MidiTypes.h" +#include "base/NotationTypes.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "base/TriggerSegment.h" +#include "gui/application/RosegardenGUIApp.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/dialogs/FileLocateDialog.h" +#include "gui/general/ProgressReporter.h" +#include "gui/kdeext/KStartupLogo.h" +#include "gui/studio/AudioPlugin.h" +#include "gui/studio/AudioPluginManager.h" +#include "gui/widgets/CurrentProgressDialog.h" +#include "gui/widgets/ProgressDialog.h" +#include "RosegardenGUIDoc.h" +#include "sound/AudioFileManager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "XmlStorableEvent.h" +#include "XmlSubHandler.h" + +namespace Rosegarden +{ + +using namespace BaseProperties; + +class ConfigurationXmlSubHandler : public XmlSubHandler +{ +public: + ConfigurationXmlSubHandler(const QString &elementName, + Rosegarden::Configuration *configuration); + + 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, + bool& finished); + + virtual bool characters(const QString& ch); + + //--------------- Data members --------------------------------- + + Rosegarden::Configuration *m_configuration; + + QString m_elementName; + QString m_propertyName; + QString m_propertyType; +}; + +ConfigurationXmlSubHandler::ConfigurationXmlSubHandler(const QString &elementName, + Rosegarden::Configuration *configuration) + : m_configuration(configuration), + m_elementName(elementName) +{ +} + +bool ConfigurationXmlSubHandler::startElement(const QString&, const QString&, + const QString& lcName, + const QXmlAttributes& atts) +{ + m_propertyName = lcName; + m_propertyType = atts.value("type"); + + if (m_propertyName == "property") { + // handle alternative encoding for properties with arbitrary names + m_propertyName = atts.value("name"); + QString value = atts.value("value"); + if (value) { + m_propertyType = "String"; + m_configuration->set(qstrtostr(m_propertyName), + qstrtostr(value)); + } + } + + return true; +} + +bool ConfigurationXmlSubHandler::characters(const QString& chars) +{ + QString ch = chars.stripWhiteSpace(); + // this method is also called on newlines - skip these cases + if (ch.isEmpty()) return true; + + + if (m_propertyType == "Int") { + long i = ch.toInt(); + RG_DEBUG << "\"" << m_propertyName << "\" " + << "value = " << i << endl; + m_configuration->set(qstrtostr(m_propertyName), i); + + return true; + } + + if (m_propertyType == "RealTime") { + Rosegarden::RealTime rt; + int sepIdx = ch.find(','); + + rt.sec = ch.left(sepIdx).toInt(); + rt.nsec = ch.mid(sepIdx + 1).toInt(); + + RG_DEBUG << "\"" << m_propertyName << "\" " + << "sec = " << rt.sec << ", nsec = " << rt.nsec << endl; + + m_configuration->set(qstrtostr(m_propertyName), rt); + + return true; + } + + if (m_propertyType == "Bool") { + QString chLc = ch.lower(); + + bool b = (chLc == "true" || + chLc == "1" || + chLc == "on"); + + m_configuration->set(qstrtostr(m_propertyName), b); + + return true; + } + + if (!m_propertyType || + m_propertyType == "String") { + + m_configuration->set(qstrtostr(m_propertyName), + qstrtostr(ch)); + + return true; + } + + + return true; +} + +bool +ConfigurationXmlSubHandler::endElement(const QString&, + const QString&, + const QString& lcName, + bool& finished) +{ + m_propertyName = ""; + m_propertyType = ""; + finished = (lcName == m_elementName); + return true; +} + + +//---------------------------------------- + + + +RoseXmlHandler::RoseXmlHandler(RosegardenGUIDoc *doc, + unsigned int elementCount, + bool createNewDevicesWhenNeeded) + : ProgressReporter(0), + m_doc(doc), + m_currentSegment(0), + m_currentEvent(0), + m_currentTime(0), + m_chordDuration(0), + m_segmentEndMarkerTime(0), + m_inChord(false), + m_inGroup(false), + m_inComposition(false), + m_groupId(0), + m_foundTempo(false), + m_section(NoSection), + m_device(0), + m_deviceRunningId(Device::NO_DEVICE), + m_msb(0), + m_lsb(0), + m_instrument(0), + m_plugin(0), + m_pluginInBuss(false), + m_colourMap(0), + m_keyMapping(0), + m_pluginId(0), + m_totalElements(elementCount), + m_elementsSoFar(0), + m_subHandler(0), + m_deprecation(false), + m_createDevices(createNewDevicesWhenNeeded), + m_haveControls(false), + m_cancelled(false), + m_skipAllAudio(false), + m_hasActiveAudio(false) +{} + +RoseXmlHandler::~RoseXmlHandler() +{ + delete m_subHandler; +} + +Composition & +RoseXmlHandler::getComposition() +{ + return m_doc->getComposition(); +} + +Studio & +RoseXmlHandler::getStudio() +{ + return m_doc->getStudio(); +} + +AudioFileManager & +RoseXmlHandler::getAudioFileManager() +{ + return m_doc->getAudioFileManager(); +} + +AudioPluginManager * +RoseXmlHandler::getAudioPluginManager() +{ + return m_doc->getPluginManager(); +} + +bool +RoseXmlHandler::startDocument() +{ + // Clear tracks + // + getComposition().clearTracks(); + + // And the loop + // + getComposition().setLoopStart(0); + getComposition().setLoopEnd(0); + + // All plugins + // + m_doc->clearAllPlugins(); + + // reset state + return true; +} + +bool +RoseXmlHandler::startElement(const QString& namespaceURI, + const QString& localName, + const QString& qName, const QXmlAttributes& atts) +{ + // First check if user pressed cancel button on the progress + // dialog + // + if (isOperationCancelled()) { + // Ideally, we'd throw here, but at this point Qt is in the stack + // and Qt is very often compiled without exception support. + // + m_cancelled = true; + return false; + } + + QString lcName = qName.lower(); + + if (getSubHandler()) { + return getSubHandler()->startElement(namespaceURI, localName, lcName, atts); + } + + if (lcName == "event") { + + // RG_DEBUG << "RoseXmlHandler::startElement: found event, current time is " << m_currentTime << endl; + + if (m_currentEvent) { + RG_DEBUG << "RoseXmlHandler::startElement: Warning: new event found at time " << m_currentTime << " before previous event has ended; previous event will be lost" << endl; + delete m_currentEvent; + } + + m_currentEvent = new XmlStorableEvent(atts, m_currentTime); + + if (m_currentEvent->has(BEAMED_GROUP_ID)) { + + // remap -- we want to ensure that the segment's nextId + // is always used (and incremented) in preference to the + // stored id + + if (!m_currentSegment) { + m_errorString = "Got grouped event outside of a segment"; + return false; + } + + long storedId = m_currentEvent->get + (BEAMED_GROUP_ID); + + if (m_groupIdMap.find(storedId) == m_groupIdMap.end()) { + m_groupIdMap[storedId] = m_currentSegment->getNextId(); + } + + m_currentEvent->set + (BEAMED_GROUP_ID, m_groupIdMap[storedId]); + + } else if (m_inGroup) { + m_currentEvent->set + (BEAMED_GROUP_ID, m_groupId); + m_currentEvent->set + (BEAMED_GROUP_TYPE, m_groupType); + if (m_groupType == GROUP_TYPE_TUPLED) { + m_currentEvent->set + + (BEAMED_GROUP_TUPLET_BASE, m_groupTupletBase); + m_currentEvent->set + + (BEAMED_GROUP_TUPLED_COUNT, m_groupTupledCount); + m_currentEvent->set + + (BEAMED_GROUP_UNTUPLED_COUNT, m_groupUntupledCount); + } + } + + timeT duration = m_currentEvent->getDuration(); + + if (!m_inChord) { + + m_currentTime = m_currentEvent->getAbsoluteTime() + duration; + + // RG_DEBUG << "RoseXmlHandler::startElement: (we're not in a chord) " << endl; + + } else if (duration != 0) { + + // set chord duration to the duration of the shortest + // element with a non-null duration (if no such elements, + // leave it as 0). + + if (m_chordDuration == 0 || duration < m_chordDuration) { + m_chordDuration = duration; + } + } + + } else if (lcName == "property") { + + if (!m_currentEvent) { + RG_DEBUG << "RoseXmlHandler::startElement: Warning: Found property outside of event at time " << m_currentTime << ", ignoring" << endl; + } else { + m_currentEvent->setPropertyFromAttributes(atts, true); + } + + } else if (lcName == "nproperty") { + + if (!m_currentEvent) { + RG_DEBUG << "RoseXmlHandler::startElement: Warning: Found nproperty outside of event at time " << m_currentTime << ", ignoring" << endl; + } else { + m_currentEvent->setPropertyFromAttributes(atts, false); + } + + } else if (lcName == "chord") { + + m_inChord = true; + + } else if (lcName == "group") { + + if (!m_currentSegment) { + m_errorString = "Got group outside of a segment"; + return false; + } + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"group\". We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + m_inGroup = true; + m_groupId = m_currentSegment->getNextId(); + m_groupType = qstrtostr(atts.value("type")); + + if (m_groupType == GROUP_TYPE_TUPLED) { + m_groupTupletBase = atts.value("base").toInt(); + m_groupTupledCount = atts.value("tupled").toInt(); + m_groupUntupledCount = atts.value("untupled").toInt(); + } + + } else if (lcName == "rosegarden-data") { + + // FILE FORMAT VERSIONING -- see comments in + // rosegardenguidoc.cpp. We only care about major and minor + // here, not point. + + QString version = atts.value("version"); + QString smajor = atts.value("format-version-major"); + QString sminor = atts.value("format-version-minor"); + +// std::cerr << "\n\n\nRosegarden file version = \"" << version << "\"\n\n\n" << std::endl; + + if (smajor) { + + int major = smajor.toInt(); + int minor = sminor.toInt(); + + if (major > RosegardenGUIDoc::FILE_FORMAT_VERSION_MAJOR) { + m_errorString = i18n("This file was written by Rosegarden %1, and it uses\na different file format that cannot be read by this version.").arg(version); + return false; + } + + if (major == RosegardenGUIDoc::FILE_FORMAT_VERSION_MAJOR && + minor > RosegardenGUIDoc::FILE_FORMAT_VERSION_MINOR) { + + CurrentProgressDialog::freeze(); + KStartupLogo::hideIfStillThere(); + + KMessageBox::information(0, i18n("This file was written by Rosegarden %1, which is more recent than this version.\nThere may be some incompatibilities with the file format.").arg(version)); + + CurrentProgressDialog::thaw(); + } + } + + } else if (lcName == "studio") { + + if (m_section != NoSection) { + m_errorString = "Found Studio in another section"; + return false; + } + + // In the Studio we clear down everything apart from Devices and + // Instruments before we reload. Instruments are derived from + // the Sequencer, the bank/program information is loaded from + // the file we're currently examining. + // + getStudio().clearMidiBanksAndPrograms(); + getStudio().clearBusses(); + getStudio().clearRecordIns(); + + m_section = InStudio; // set top level section + + // Get and set MIDI filters + // + QString thruStr = atts.value("thrufilter"); + + if (thruStr) + getStudio().setMIDIThruFilter(thruStr.toInt()); + + QString recordStr = atts.value("recordfilter"); + + if (recordStr) + getStudio().setMIDIRecordFilter(recordStr.toInt()); + + QString inputStr = atts.value("audioinputpairs"); + + if (inputStr) { + int inputs = inputStr.toInt(); + if (inputs < 1) + inputs = 1; // we simply don't permit no inputs + while (int(getStudio().getRecordIns().size()) < inputs) { + getStudio().addRecordIn(new RecordIn()); + } + } + + QString mixerStr = atts.value("mixerdisplayoptions"); + + if (mixerStr) { + unsigned int mixer = mixerStr.toUInt(); + getStudio().setMixerDisplayOptions(mixer); + } + + QString metronomeStr = atts.value("metronomedevice"); + + if (metronomeStr) { + DeviceId metronome = metronomeStr.toUInt(); + getStudio().setMetronomeDevice(metronome); + } + + } else if (lcName == "timesignature") { + + if (m_inComposition == false) { + m_errorString = "TimeSignature object found outside Composition"; + return false; + } + + timeT t = 0; + QString timeStr = atts.value("time"); + if (timeStr) + t = timeStr.toInt(); + + int num = 4; + QString numStr = atts.value("numerator"); + if (numStr) + num = numStr.toInt(); + + int denom = 4; + QString denomStr = atts.value("denominator"); + if (denomStr) + denom = denomStr.toInt(); + + bool common = false; + QString commonStr = atts.value("common"); + if (commonStr) + common = (commonStr == "true"); + + bool hidden = false; + QString hiddenStr = atts.value("hidden"); + if (hiddenStr) + hidden = (hiddenStr == "true"); + + bool hiddenBars = false; + QString hiddenBarsStr = atts.value("hiddenbars"); + if (hiddenBarsStr) + hiddenBars = (hiddenBarsStr == "true"); + + getComposition().addTimeSignature + (t, TimeSignature(num, denom, common, hidden, hiddenBars)); + + } else if (lcName == "tempo") { + + timeT t = 0; + QString timeStr = atts.value("time"); + if (timeStr) + t = timeStr.toInt(); + + tempoT tempo = Composition::getTempoForQpm(120.0); + QString tempoStr = atts.value("tempo"); + QString targetStr = atts.value("target"); + QString bphStr = atts.value("bph"); + if (tempoStr) { + tempo = tempoStr.toInt(); + } else if (bphStr) { + tempo = Composition::getTempoForQpm + (double(bphStr.toInt()) / 60.0); + } + + if (targetStr) { + getComposition().addTempoAtTime(t, tempo, targetStr.toInt()); + } else { + getComposition().addTempoAtTime(t, tempo); + } + + } else if (lcName == "composition") { + + if (m_section != NoSection) { + m_errorString = "Found Composition in another section"; + return false; + } + + // set Segment + m_section = InComposition; + + // Get and set the record track + // + QString recordStr = atts.value("recordtrack"); + if (recordStr) { + getComposition().setTrackRecording(recordStr.toInt(), true); + } + + QString recordPlStr = atts.value("recordtracks"); + if (recordPlStr) { + RG_DEBUG << "Record tracks: " << recordPlStr << endl; + QStringList recordList = QStringList::split(',', recordPlStr); + for (QStringList::iterator i = recordList.begin(); + i != recordList.end(); ++i) { + RG_DEBUG << "Record track: " << (*i).toInt() << endl; + getComposition().setTrackRecording((*i).toInt(), true); + } + } + + // Get and set the position pointer + // + int position = 0; + QString positionStr = atts.value("pointer"); + if (positionStr) { + position = positionStr.toInt(); + } + + getComposition().setPosition(position); + + + // Get and (eventually) set the default tempo. + // We prefer the new compositionDefaultTempo over the + // older defaultTempo. + // + QString tempoStr = atts.value("compositionDefaultTempo"); + if (tempoStr) { + tempoT tempo = tempoT(tempoStr.toInt()); + getComposition().setCompositionDefaultTempo(tempo); + } else { + tempoStr = atts.value("defaultTempo"); + if (tempoStr) { + double tempo = qstrtodouble(tempoStr); + getComposition().setCompositionDefaultTempo + (Composition::getTempoForQpm(tempo)); + } + } + + // set the composition flag + m_inComposition = true; + + + // Set the loop + // + QString loopStartStr = atts.value("loopstart"); + QString loopEndStr = atts.value("loopend"); + + if (loopStartStr && loopEndStr) { + int loopStart = loopStartStr.toInt(); + int loopEnd = loopEndStr.toInt(); + + getComposition().setLoopStart(loopStart); + getComposition().setLoopEnd(loopEnd); + } + + QString selectedTrackStr = atts.value("selected"); + + if (selectedTrackStr) { + TrackId selectedTrack = + (TrackId)selectedTrackStr.toInt(); + + getComposition().setSelectedTrack(selectedTrack); + } + + QString soloTrackStr = atts.value("solo"); + if (soloTrackStr) { + if (soloTrackStr.toInt() == 1) + getComposition().setSolo(true); + else + getComposition().setSolo(false); + } + + + QString playMetStr = atts.value("playmetronome"); + if (playMetStr) { + if (playMetStr.toInt()) + getComposition().setPlayMetronome(true); + else + getComposition().setPlayMetronome(false); + } + + QString recMetStr = atts.value("recordmetronome"); + if (recMetStr) { + if (recMetStr.toInt()) + getComposition().setRecordMetronome(true); + else + getComposition().setRecordMetronome(false); + } + + QString nextTriggerIdStr = atts.value("nexttriggerid"); + if (nextTriggerIdStr) { + getComposition().setNextTriggerSegmentId(nextTriggerIdStr.toInt()); + } + + QString copyrightStr = atts.value("copyright"); + if (copyrightStr) { + getComposition().setCopyrightNote(qstrtostr(copyrightStr)); + } + + QString startMarkerStr = atts.value("startMarker"); + QString endMarkerStr = atts.value("endMarker"); + + if (startMarkerStr) { + getComposition().setStartMarker(startMarkerStr.toInt()); + } + + if (endMarkerStr) { + getComposition().setEndMarker(endMarkerStr.toInt()); + } + + } else if (lcName == "track") { + + if (m_section != InComposition) { + m_errorString = "Track object found outside Composition"; + return false; + } + + int id = -1; + int position = -1; + int instrument = -1; + std::string label; + bool muted = false; + + QString trackNbStr = atts.value("id"); + if (trackNbStr) { + id = trackNbStr.toInt(); + } + + QString labelStr = atts.value("label"); + if (labelStr) { + label = qstrtostr(labelStr); + } + + QString mutedStr = atts.value("muted"); + if (mutedStr) { + if (mutedStr == "true") + muted = true; + else + muted = false; + } + + QString positionStr = atts.value("position"); + if (positionStr) { + position = positionStr.toInt(); + } + + QString instrumentStr = atts.value("instrument"); + if (instrumentStr) { + instrument = instrumentStr.toInt(); + } + + Track *track = new Track(id, + instrument, + position, + label, + muted); + + // track properties affecting newly created segments are initialized + // to default values in the ctor, so they don't need to be initialized + // here + + QString presetLabelStr = atts.value("defaultLabel"); + if (labelStr) { + track->setPresetLabel(presetLabelStr); + } + + QString clefStr = atts.value("defaultClef"); + if (clefStr) { + track->setClef(clefStr.toInt()); + } + + QString transposeStr = atts.value("defaultTranspose"); + if (transposeStr) { + track->setTranspose(transposeStr.toInt()); + } + + QString colorStr = atts.value("defaultColour"); + if (colorStr) { + track->setColor(colorStr.toInt()); + } + + QString highplayStr = atts.value("defaultHighestPlayable"); + if (highplayStr) { + track->setHighestPlayable(highplayStr.toInt()); + } + + QString lowplayStr = atts.value("defaultLowestPlayable"); + if (lowplayStr) { + track->setLowestPlayable(lowplayStr.toInt()); + } + + QString staffSizeStr = atts.value("staffSize"); + if (staffSizeStr) { + track->setStaffSize(staffSizeStr.toInt()); + } + + QString staffBracketStr = atts.value("staffBracket"); + if (staffBracketStr) { + track->setStaffBracket(staffBracketStr.toInt()); + } + + getComposition().addTrack(track); + + + } else if (lcName == "segment") { + + if (m_section != NoSection) { + m_errorString = "Found Segment in another section"; + return false; + } + + // set Segment + m_section = InSegment; + + int track = -1, startTime = 0; + unsigned int colourindex = 0; + QString trackNbStr = atts.value("track"); + if (trackNbStr) { + track = trackNbStr.toInt(); + } + + QString startIdxStr = atts.value("start"); + if (startIdxStr) { + startTime = startIdxStr.toInt(); + } + + QString segmentType = (atts.value("type")).lower(); + if (segmentType) { + if (segmentType == "audio") { + int audioFileId = atts.value("file").toInt(); + + // check this file id exists on the AudioFileManager + + if (getAudioFileManager().fileExists(audioFileId) == false) { + // We don't report an error as this audio file might've + // been excluded deliberately as we could't actually + // find the audio file itself. + // + return true; + } + + // Create an Audio segment and add its reference + // + m_currentSegment = new Segment(Segment::Audio); + m_currentSegment->setAudioFileId(audioFileId); + m_currentSegment->setStartTime(startTime); + } else { + // Create a (normal) internal Segment + m_currentSegment = new Segment(Segment::Internal); + } + + } else { + // for the moment we default + m_currentSegment = new Segment(Segment::Internal); + } + + QString repeatStr = atts.value("repeat"); + if (repeatStr.lower() == "true") { + m_currentSegment->setRepeating(true); + } + + QString delayStr = atts.value("delay"); + if (delayStr) { + RG_DEBUG << "Delay string is \"" << delayStr << "\"" << endl; + long delay = delayStr.toLong(); + RG_DEBUG << "Delay is " << delay << endl; + m_currentSegment->setDelay(delay); + } + + QString rtDelaynSec = atts.value("rtdelaynsec"); + QString rtDelayuSec = atts.value("rtdelayusec"); + QString rtDelaySec = atts.value("rtdelaysec"); + if (rtDelaySec && (rtDelaynSec || rtDelayuSec)) { + if (rtDelaynSec) { + m_currentSegment->setRealTimeDelay + (RealTime(rtDelaySec.toInt(), + rtDelaynSec.toInt())); + } else { + m_currentSegment->setRealTimeDelay + (RealTime(rtDelaySec.toInt(), + rtDelayuSec.toInt() * 1000)); + } + } + + QString transposeStr = atts.value("transpose"); + if (transposeStr) + m_currentSegment->setTranspose(transposeStr.toInt()); + + // fill in the label + QString labelStr = atts.value("label"); + if (labelStr) + m_currentSegment->setLabel(qstrtostr(labelStr)); + + m_currentSegment->setTrack(track); + //m_currentSegment->setStartTime(startTime); + + QString colourIndStr = atts.value("colourindex"); + if (colourIndStr) { + colourindex = colourIndStr.toInt(); + } + + m_currentSegment->setColourIndex(colourindex); + + QString snapGridSizeStr = atts.value("snapgridsize"); + if (snapGridSizeStr) { + m_currentSegment->setSnapGridSize(snapGridSizeStr.toInt()); + } + + QString viewFeaturesStr = atts.value("viewfeatures"); + if (viewFeaturesStr) { + m_currentSegment->setViewFeatures(viewFeaturesStr.toInt()); + } + + m_currentTime = startTime; + + QString triggerIdStr = atts.value("triggerid"); + QString triggerPitchStr = atts.value("triggerbasepitch"); + QString triggerVelocityStr = atts.value("triggerbasevelocity"); + QString triggerRetuneStr = atts.value("triggerretune"); + QString triggerAdjustTimeStr = atts.value("triggeradjusttimes"); + + if (triggerIdStr) { + int pitch = -1; + if (triggerPitchStr) + pitch = triggerPitchStr.toInt(); + int velocity = -1; + if (triggerVelocityStr) + velocity = triggerVelocityStr.toInt(); + TriggerSegmentRec *rec = + getComposition().addTriggerSegment(m_currentSegment, + triggerIdStr.toInt(), + pitch, velocity); + if (rec) { + if (triggerRetuneStr) + rec->setDefaultRetune(triggerRetuneStr.lower() == "true"); + if (triggerAdjustTimeStr) + rec->setDefaultTimeAdjust(qstrtostr(triggerAdjustTimeStr)); + } + m_currentSegment->setStartTimeDataMember(startTime); + } else { + getComposition().addSegment(m_currentSegment); + getComposition().setSegmentStartTime(m_currentSegment, startTime); + } + + QString endMarkerStr = atts.value("endmarker"); + if (endMarkerStr) { + delete m_segmentEndMarkerTime; + m_segmentEndMarkerTime = new timeT(endMarkerStr.toInt()); + } + + m_groupIdMap.clear(); + + } else if (lcName == "gui") { + + if (m_section != InSegment) { + m_errorString = "Found GUI element outside Segment"; + return false; + } + + } else if (lcName == "controller") { + + if (m_section != InSegment) { + m_errorString = "Found Controller element outside Segment"; + return false; + } + + QString type = atts.value("type"); + //RG_DEBUG << "RoseXmlHandler::startElement - controller type = " << type << endl; + + if (type == strtoqstr(PitchBend::EventType)) + m_currentSegment->addEventRuler(PitchBend::EventType); + else if (type == strtoqstr(Controller::EventType)) { + QString value = atts.value("value"); + + if (value != "") + m_currentSegment->addEventRuler(Controller::EventType, value.toInt()); + } + + } else if (lcName == "resync") { + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"resync\". We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + QString time(atts.value("time")); + bool isNumeric; + int numTime = time.toInt(&isNumeric); + if (isNumeric) + m_currentTime = numTime; + + } else if (lcName == "audio") { + + if (m_section != InAudioFiles) { + m_errorString = "Audio object found outside Audio section"; + return false; + } + + if (m_skipAllAudio) { + std::cout << "SKIPPING audio file" << std::endl; + return true; + } + + QString id(atts.value("id")); + QString file(atts.value("file")); + QString label(atts.value("label")); + + if (id.isEmpty() || file.isEmpty() || label.isEmpty()) { + m_errorString = "Audio object has empty parameters"; + return false; + } + + m_hasActiveAudio = true; + + // attempt to insert file into AudioFileManager + // (this checks the integrity of the file at the + // same time) + // + if (getAudioFileManager().insertFile(qstrtostr(label), + qstrtostr(file), + id.toInt()) == false) { + // Ok, now attempt to use the KFileDialog saved default + // value for the AudioPath. + // + QString thing; + KURL url = KFileDialog::getStartURL(QString(":WAVS"), thing); + getAudioFileManager().setAudioPath(url.path().latin1()); + + /* + RG_DEBUG << "ATTEMPTING TO FIND IN PATH = " + << url.path() << endl; + */ + + if (getAudioFileManager(). + insertFile(qstrtostr(label), + qstrtostr(file), id.toInt()) == false) { + + // Freeze the progress dialog + CurrentProgressDialog::freeze(); + + // Hide splash screen if present on startup + KStartupLogo::hideIfStillThere(); + + // Create a locate file dialog - give it the file name + // and the AudioFileManager path that we've already + // tried. If we manually locate the file then we reset + // the audiofilepath to the new value and see if this + // helps us locate the rest of the files. + // + + QString newFilename = ""; + QString newPath = ""; + + do { + + FileLocateDialog fL((RosegardenGUIApp *)m_doc->parent(), + file, + QString(getAudioFileManager().getAudioPath().c_str())); + int result = fL.exec(); + + if (result == QDialog::Accepted) { + newFilename = fL.getFilename(); + newPath = fL.getDirectory(); + } else if (result == QDialog::Rejected) { + // just skip the file + break; + } else { + // don't process any more audio files + m_skipAllAudio = true; + CurrentProgressDialog::thaw(); + return true; + } + + + } while (getAudioFileManager().insertFile(qstrtostr(label), + qstrtostr(newFilename), + id.toInt()) == false); + + if (newPath != "") { + getAudioFileManager().setAudioPath(qstrtostr(newPath)); + // Set a document post-modify flag + //m_doc->setModified(true); + } + + getAudioFileManager().print(); + + // Restore progress dialog's normal state + CurrentProgressDialog::thaw(); + } else { + // AudioPath is modified so set a document post modify flag + // + //m_doc->setModified(true); + } + + } + + } else if (lcName == "audiopath") { + + if (m_section != InAudioFiles) { + m_errorString = "Audiopath object found outside AudioFiles section"; + return false; + } + + QString search(atts.value("value")); + + if (search.isEmpty()) { + m_errorString = "Audiopath has no value"; + return false; + } + + if (!search.startsWith("/") && !search.startsWith("~")) { + QString docPath = m_doc->getAbsFilePath(); + QString dirPath = QFileInfo(docPath).dirPath(); + if (QFileInfo(dirPath).exists()) { + search = dirPath + "/" + search; + } + } + + getAudioFileManager().setAudioPath(qstrtostr(search)); + + } else if (lcName == "begin") { + + double marker = qstrtodouble(atts.value("index")); + + if (!m_currentSegment) { + // Don't fail - as this segment could be defunct if we + // skipped loading the audio file + // + return true; + } + + if (m_currentSegment->getType() != Segment::Audio) { + m_errorString = "Found audio begin index in non audio segment"; + return false; + } + + // convert to RealTime from float + int sec = (int)marker; + int usec = (int)((marker - ((double)sec)) * 1000000.0); + m_currentSegment->setAudioStartTime(RealTime(sec, usec * 1000)); + + + } else if (lcName == "end") { + + double marker = qstrtodouble(atts.value("index")); + + if (!m_currentSegment) { + // Don't fail - as this segment could be defunct if we + // skipped loading the audio file + // + return true; + } + + if (m_currentSegment->getType() != Segment::Audio) { + m_errorString = "found audio end index in non audio segment"; + return false; + } + + int sec = (int)marker; + int usec = (int)((marker - ((double)sec)) * 1000000.0); + RealTime markerTime(sec, usec * 1000); + + if (markerTime < m_currentSegment->getAudioStartTime()) { + m_errorString = "Audio end index before audio start marker"; + return false; + } + + m_currentSegment->setAudioEndTime(markerTime); + + // Ensure we set end time according to correct RealTime end of Segment + // + RealTime realEndTime = getComposition(). + getElapsedRealTime(m_currentSegment->getStartTime()) + + m_currentSegment->getAudioEndTime() - + m_currentSegment->getAudioStartTime(); + + timeT absEnd = getComposition().getElapsedTimeForRealTime(realEndTime); + m_currentSegment->setEndTime(absEnd); + + } else if (lcName == "fadein") { + + if (!m_currentSegment) { + // Don't fail - as this segment could be defunct if we + // skipped loading the audio file + // + return true; + } + + if (m_currentSegment->getType() != Segment::Audio) { + m_errorString = "found fade in time in non audio segment"; + return false; + } + + double marker = qstrtodouble(atts.value("time")); + int sec = (int)marker; + int usec = (int)((marker - ((double)sec)) * 1000000.0); + RealTime markerTime(sec, usec * 1000); + + m_currentSegment->setFadeInTime(markerTime); + m_currentSegment->setAutoFade(true); + + + } else if (lcName == "fadeout") { + + if (!m_currentSegment) { + // Don't fail - as this segment could be defunct if we + // skipped loading the audio file + // + return true; + } + + if (m_currentSegment->getType() != Segment::Audio) { + m_errorString = "found fade out time in non audio segment"; + return false; + } + + double marker = qstrtodouble(atts.value("time")); + int sec = (int)marker; + int usec = (int)((marker - ((double)sec)) * 1000000.0); + RealTime markerTime(sec, usec * 1000); + + m_currentSegment->setFadeOutTime(markerTime); + m_currentSegment->setAutoFade(true); + + } else if (lcName == "device") { + + if (m_section != InStudio) { + m_errorString = "Found Device outside Studio"; + return false; + } + + m_haveControls = false; + + QString type = (atts.value("type")).lower(); + QString idString = atts.value("id"); + QString nameStr = atts.value("name"); + + if (idString.isNull()) { + m_errorString = "No ID on Device tag"; + return false; + } + int id = idString.toInt(); + + if (type == "midi") { + QString direction = atts.value("direction").lower(); + + if (direction.isNull() || + direction == "" || + direction == "play") { // ignore inputs + + // This will leave m_device set only if there is a + // valid play midi device to modify: + skipToNextPlayDevice(); + + if (m_device) { + if (nameStr && nameStr != "") { + m_device->setName(qstrtostr(nameStr)); + } + } else if (nameStr && nameStr != "") { + addMIDIDevice(nameStr, m_createDevices); // also sets m_device + } + } + + QString connection = atts.value("connection"); + if (m_createDevices && m_device && + !connection.isNull() && connection != "") { + setMIDIDeviceConnection(connection); + } + + setMIDIDeviceName(nameStr); + + QString vstr = atts.value("variation").lower(); + MidiDevice::VariationType variation = + MidiDevice::NoVariations; + if (!vstr.isNull()) { + if (vstr == "lsb") { + variation = MidiDevice::VariationFromLSB; + } else if (vstr == "msb") { + variation = MidiDevice::VariationFromMSB; + } else if (vstr == "") { + variation = MidiDevice::NoVariations; + } + } + MidiDevice *md = dynamic_cast + (m_device); + if (md) { + md->setVariationType(variation); + } + } else if (type == "softsynth") { + m_device = getStudio().getDevice(id); + + if (m_device && m_device->getType() == Device::SoftSynth) + m_device->setName(qstrtostr(nameStr)); + } else if (type == "audio") { + m_device = getStudio().getDevice(id); + + if (m_device && m_device->getType() == Device::Audio) + m_device->setName(qstrtostr(nameStr)); + } else { + m_errorString = "Found unknown Device type"; + return false; + } + + } else if (lcName == "librarian") { + + // The contact details for the maintainer of the banks/programs + // information. + // + if (m_device && m_device->getType() == Device::Midi) { + QString name = atts.value("name"); + QString email = atts.value("email"); + + dynamic_cast(m_device)-> + setLibrarian(qstrtostr(name), qstrtostr(email)); + } + + } else if (lcName == "bank") { + + if (m_device) // only if we have a device + { + if (m_section != InStudio && m_section != InInstrument) + { + m_errorString = "Found Bank outside Studio or Instrument"; + return false; + } + + QString nameStr = atts.value("name"); + m_percussion = (atts.value("percussion").lower() == "true"); + m_msb = (atts.value("msb")).toInt(); + m_lsb = (atts.value("lsb")).toInt(); + + // To actually create a bank + // + if (m_section == InStudio) + { + // Create a new bank + MidiBank bank(m_percussion, + m_msb, + m_lsb, + qstrtostr(nameStr)); + + if (m_device->getType() == Device::Midi) { + // Insert the bank + // + dynamic_cast(m_device)->addBank(bank); + } + } else // otherwise we're referencing it in an instrument + if (m_section == InInstrument) + { + if (m_instrument) { + m_instrument->setPercussion(m_percussion); + m_instrument->setMSB(m_msb); + m_instrument->setLSB(m_lsb); + m_instrument->setSendBankSelect(true); + } + } + } + + } else if (lcName == "program") { + + if (m_device) // only if we have a device + { + if (m_section == InStudio) + { + QString nameStr = (atts.value("name")); + MidiByte pc = atts.value("id").toInt(); + QString keyMappingStr = (atts.value("keymapping")); + + // Create a new program + MidiProgram program + (MidiBank(m_percussion, + m_msb, + m_lsb), + pc, + qstrtostr(nameStr), + keyMappingStr ? qstrtostr(keyMappingStr) : ""); + + if (m_device->getType() == Device::Midi) { + // Insert the program + // + dynamic_cast(m_device)-> + addProgram(program); + } + + } else if (m_section == InInstrument) + { + if (m_instrument) { + MidiByte id = atts.value("id").toInt(); + m_instrument->setProgramChange(id); + m_instrument->setSendProgramChange(true); + } + } else + { + m_errorString = "Found Program outside Studio and Instrument"; + return false; + } + } + + } else if (lcName == "keymapping") { + + if (m_section == InInstrument) { + RG_DEBUG << "Old-style keymapping in instrument found, ignoring" << endl; + } else { + + if (m_section != InStudio) { + m_errorString = "Found Keymapping outside Studio"; + return false; + } + + if (m_device && (m_device->getType() == Device::Midi)) { + QString name = atts.value("name"); + m_keyMapping = new MidiKeyMapping(qstrtostr(name)); + m_keyNameMap.clear(); + } + } + + } else if (lcName == "key") { + + if (m_keyMapping) { + QString numStr = atts.value("number"); + QString namStr = atts.value("name"); + if (numStr && namStr) { + m_keyNameMap[numStr.toInt()] = qstrtostr(namStr); + } + } + + } else if (lcName == "controls") { + + // Only clear down the controllers list if we have found some controllers in the RG file + // + if (m_device) { + dynamic_cast(m_device)->clearControlList(); + } + + m_haveControls = true; + + } else if (lcName == "control") { + + if (m_section != InStudio) { + m_errorString = "Found ControlParameter outside Studio"; + return false; + } + + if (!m_device) { + //!!! ach no, we can't give this warning -- we might be in a elt + // but have no sequencer support, for example. we need a separate m_inDevice + // flag + // m_deprecation = true; + // std::cerr << "WARNING: This Rosegarden file uses a deprecated control parameter structure. We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + + } else if (m_device->getType() == Device::Midi) { + + if (!m_haveControls) { + m_errorString = "Found ControlParameter outside Controls block"; + return false; + } + + QString name = atts.value("name"); + QString type = atts.value("type"); + QString descr = atts.value("description"); + QString min = atts.value("min"); + QString max = atts.value("max"); + QString def = atts.value("default"); + QString conVal = atts.value("controllervalue"); + QString colour = atts.value("colourindex"); + QString ipbPosition = atts.value("ipbposition"); + + ControlParameter con(qstrtostr(name), + qstrtostr(type), + qstrtostr(descr), + min.toInt(), + max.toInt(), + def.toInt(), + MidiByte(conVal.toInt()), + colour.toInt(), + ipbPosition.toInt()); + + dynamic_cast(m_device)-> + addControlParameter(con); + } + + } else if (lcName == "reverb") { // deprecated but we still read 'em + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"reverb\" (now replaced by a control parameter). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + if (m_section != InInstrument) { + m_errorString = "Found Reverb outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) + m_instrument->setControllerValue(MIDI_CONTROLLER_REVERB, value); + + + } else if (lcName == "chorus") { // deprecated but we still read 'em + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"chorus\" (now replaced by a control parameter). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + if (m_section != InInstrument) { + m_errorString = "Found Chorus outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) + m_instrument->setControllerValue(MIDI_CONTROLLER_CHORUS, value); + + } else if (lcName == "filter") { // deprecated but we still read 'em + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"filter\" (now replaced by a control parameter). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + if (m_section != InInstrument) { + m_errorString = "Found Filter outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) + m_instrument->setControllerValue(MIDI_CONTROLLER_FILTER, value); + + + } else if (lcName == "resonance") { // deprecated but we still read 'em + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"resonance\" (now replaced by a control parameter). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + if (m_section != InInstrument) { + m_errorString = "Found Resonance outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) + m_instrument->setControllerValue(MIDI_CONTROLLER_RESONANCE, value); + + + } else if (lcName == "attack") { // deprecated but we still read 'em + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"attack\" (now replaced by a control parameter). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + if (m_section != InInstrument) { + m_errorString = "Found Attack outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) + m_instrument->setControllerValue(MIDI_CONTROLLER_ATTACK, value); + + } else if (lcName == "release") { // deprecated but we still read 'em + + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"release\" (now replaced by a control parameter). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + + if (m_section != InInstrument) { + m_errorString = "Found Release outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) + m_instrument->setControllerValue(MIDI_CONTROLLER_RELEASE, value); + + } else if (lcName == "pan") { + + if (m_section != InInstrument && m_section != InBuss) { + m_errorString = "Found Pan outside Instrument or Buss"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_section == InInstrument) { + if (m_instrument) { + m_instrument->setPan(value); + m_instrument->setSendPan(true); + } + } else if (m_section == InBuss) { + if (m_buss) { + m_buss->setPan(value); + } + } + + // keep "velocity" so we're backwards compatible + } else if (lcName == "velocity" || lcName == "volume") { + + if (lcName == "velocity") { + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"velocity\" for an overall MIDI instrument level (now replaced by \"volume\"). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + } + + if (m_section != InInstrument) { + m_errorString = "Found Volume outside Instrument"; + return false; + } + + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) { + if (m_instrument->getType() == Instrument::Audio || + m_instrument->getType() == Instrument::SoftSynth) { + // Backward compatibility: "volume" was in a 0-127 + // range and we now store "level" (float dB) instead. + // Note that we have no such compatibility for + // "recordLevel", whose range has changed silently. + if (!m_deprecation) + std::cerr << "WARNING: This Rosegarden file uses the deprecated element \"volume\" for an audio instrument (now replaced by \"level\"). We recommend re-saving the file from this version of Rosegarden to assure your ability to re-load it in future versions" << std::endl; + m_deprecation = true; + m_instrument->setLevel + (AudioLevel::multiplier_to_dB(float(value) / 100.0)); + } else { + m_instrument->setVolume(value); + m_instrument->setSendVolume(true); + } + } + + } else if (lcName == "level") { + + if (m_section != InBuss && + (m_section != InInstrument || + (m_instrument && + m_instrument->getType() != Instrument::Audio && + m_instrument->getType() != Instrument::SoftSynth))) { + m_errorString = "Found Level outside (audio) Instrument or Buss"; + return false; + } + + double value = qstrtodouble(atts.value("value")); + + if (m_section == InBuss) { + if (m_buss) + m_buss->setLevel(value); + } else { + if (m_instrument) + m_instrument->setLevel(value); + } + + } else if (lcName == "controlchange") { + + if (m_section != InInstrument) { + m_errorString = "Found ControlChange outside Instrument"; + return false; + } + + MidiByte type = atts.value("type").toInt(); + MidiByte value = atts.value("value").toInt(); + + if (m_instrument) { + m_instrument->setControllerValue(type, value); + } + + } else if (lcName == "plugin" || lcName == "synth") { + + PluginContainer *container = 0; + + if (m_section == InInstrument) { +// std::cerr << "Found plugin in instrument" << std::endl; + container = m_instrument; + m_pluginInBuss = false; + } else if (m_section == InBuss) { +// std::cerr << "Found plugin in buss" << std::endl; + container = m_buss; + m_pluginInBuss = true; + } else { + m_errorString = "Found Plugin outside Instrument or Buss"; + return false; + } + + // Despite being InInstrument or InBuss we might not actually + // have a valid one. + // + if (container) { + +// std::cerr << "Have container" << std::endl; + + emit setOperationName(i18n("Loading plugins...")); + ProgressDialog::processEvents(); + + // Get the details + int position; + if (lcName == "synth") { + position = Instrument::SYNTH_PLUGIN_POSITION; + } else { + position = atts.value("position").toInt(); + } + + bool bypassed = false; + QString bpStr = atts.value("bypassed"); + if (bpStr.lower() == "true") + bypassed = true; + + std::string program = ""; + QString progStr = atts.value("program"); + if (progStr) { + program = qstrtostr(progStr); + } + + // Plugins are identified by a structured identifier + // string, but we will accept a LADSPA UniqueId if there's + // no identifier, for backward compatibility + + QString identifier = atts.value("identifier"); + + AudioPlugin *plugin = 0; + AudioPluginManager *apm = getAudioPluginManager(); + + if (!identifier) { + if (atts.value("id")) { + unsigned long id = atts.value("id").toULong(); + if (apm) + plugin = apm->getPluginByUniqueId(id); + } + } else { + if (apm) + plugin = apm->getPluginByIdentifier(identifier); + } + +// std::cerr << "Plugin identifier " << identifier << " -> plugin " << plugin << std::endl; + + // If we find the plugin all is well and good but if + // we don't we just skip it. + // + if (plugin) { + m_plugin = container->getPlugin(position); + if (!m_plugin) { + RG_DEBUG << "WARNING: RoseXmlHandler: instrument/buss " + << container->getId() << " has no plugin position " + << position << endl; + } else { + m_plugin->setAssigned(true); + m_plugin->setBypass(bypassed); + m_plugin->setIdentifier(plugin->getIdentifier().data()); +// std::cerr << "set identifier to plugin at position " << position << std::endl; + if (program != "") { + m_plugin->setProgram(program); + } + } + } else { + // we shouldn't be halting import of the RG file just because + // we can't match a plugin + // + if (identifier) { + RG_DEBUG << "WARNING: RoseXmlHandler: plugin " << identifier << " not found" << endl; + m_pluginsNotFound.insert(identifier); + } else if (atts.value("id")) { + RG_DEBUG << "WARNING: RoseXmlHandler: plugin uid " << atts.value("id") << " not found" << endl; + } else { + m_errorString = "No plugin identifier or uid specified"; + return false; + } + } + } else { // no instrument + + if (lcName == "synth") { + QString identifier = atts.value("identifier"); + if (identifier) { + RG_DEBUG << "WARNING: RoseXmlHandler: no instrument for plugin " << identifier << endl; + m_pluginsNotFound.insert(identifier); + } + } + } + + m_section = InPlugin; + + } else if (lcName == "port") { + + if (m_section != InPlugin) { + m_errorString = "Found Port outside Plugin"; + return false; + } + unsigned long portId = atts.value("id").toULong(); + double value = qstrtodouble(atts.value("value")); + + QString changed = atts.value("changed"); + bool changedSinceProgram = (changed == "true"); + + if (m_plugin) { + m_plugin->addPort(portId, value); + if (changedSinceProgram) { + PluginPortInstance *ppi = m_plugin->getPort(portId); + if (ppi) + ppi->changedSinceProgramChange = true; + } + } + + } else if (lcName == "configure") { + + if (m_section != InPlugin) { + m_errorString = "Found Configure outside Plugin"; + return false; + } + + QString key = atts.value("key"); + QString value = atts.value("value"); + + if (m_plugin) { + m_plugin->setConfigurationValue(qstrtostr(key), qstrtostr(value)); + } + + } else if (lcName == "metronome") { + + if (m_section != InStudio) { + m_errorString = "Found Metronome outside Studio"; + return false; + } + + // Only create if we have a device + // + if (m_device && m_device->getType() == Device::Midi) { + InstrumentId instrument = + atts.value("instrument").toInt(); + + MidiMetronome metronome(instrument); + + if (atts.value("barpitch")) + metronome.setBarPitch(atts.value("barpitch").toInt()); + if (atts.value("beatpitch")) + metronome.setBeatPitch(atts.value("beatpitch").toInt()); + if (atts.value("subbeatpitch")) + metronome.setSubBeatPitch(atts.value("subbeatpitch").toInt()); + if (atts.value("depth")) + metronome.setDepth(atts.value("depth").toInt()); + if (atts.value("barvelocity")) + metronome.setBarVelocity(atts.value("barvelocity").toInt()); + if (atts.value("beatvelocity")) + metronome.setBeatVelocity(atts.value("beatvelocity").toInt()); + if (atts.value("subbeatvelocity")) + metronome.setSubBeatVelocity(atts.value("subbeatvelocity").toInt()); + + dynamic_cast(m_device)-> + setMetronome(metronome); + } + + } else if (lcName == "instrument") { + + if (m_section != InStudio) { + m_errorString = "Found Instrument outside Studio"; + return false; + } + + m_section = InInstrument; + + InstrumentId id = atts.value("id").toInt(); + std::string stringType = qstrtostr(atts.value("type")); + Instrument::InstrumentType type; + + if (stringType == "midi") + type = Instrument::Midi; + else if (stringType == "audio") + type = Instrument::Audio; + else if (stringType == "softsynth") + type = Instrument::SoftSynth; + else { + m_errorString = "Found unknown Instrument type"; + return false; + } + + // Try and match an Instrument in the file with one in + // our studio + // + Instrument *instrument = getStudio().getInstrumentById(id); + + // If we've got an instrument and the types match then + // we use it from now on. + // + if (instrument && instrument->getType() == type) { + m_instrument = instrument; + + // We can also get the channel from this tag + // + MidiByte channel = + (MidiByte)atts.value("channel").toInt(); + m_instrument->setMidiChannel(channel); + } + + } else if (lcName == "buss") { + + if (m_section != InStudio) { + m_errorString = "Found Buss outside Studio"; + return false; + } + + m_section = InBuss; + + BussId id = atts.value("id").toInt(); + Buss *buss = getStudio().getBussById(id); + + // If we've got a buss then we use it from now on. + // + if (buss) { + m_buss = buss; + } else { + m_buss = new Buss(id); + getStudio().addBuss(m_buss); + } + + } else if (lcName == "audiofiles") { + + if (m_section != NoSection) { + m_errorString = "Found AudioFiles inside another section"; + return false; + } + + m_section = InAudioFiles; + + int rate = atts.value("expectedRate").toInt(); + if (rate) { + getAudioFileManager().setExpectedSampleRate(rate); + } + + } else if (lcName == "configuration") { + + setSubHandler(new ConfigurationXmlSubHandler + (lcName, &m_doc->getConfiguration())); + + } else if (lcName == "metadata") { + + if (m_section != InComposition) { + m_errorString = "Found Metadata outside Composition"; + return false; + } + + setSubHandler(new ConfigurationXmlSubHandler + (lcName, &getComposition().getMetadata())); + + } else if (lcName == "recordlevel") { + + if (m_section != InInstrument) { + m_errorString = "Found recordLevel outside Instrument"; + return false; + } + + double value = qstrtodouble(atts.value("value")); + + // if the value retrieved is greater than (say) 15 then we + // must have an old-style 0-127 value instead of a shiny new + // dB value, so convert it + if (value > 15.0) { + value = AudioLevel::multiplier_to_dB(value / 100); + } + + if (m_instrument) + m_instrument->setRecordLevel(value); + + } else if (lcName == "audioinput") { + + if (m_section != InInstrument) { + m_errorString = "Found audioInput outside Instrument"; + return false; + } + + int value = atts.value("value").toInt(); + int channel = atts.value("channel").toInt(); + + QString type = atts.value("type"); + if (type) { + if (type.lower() == "buss") { + if (m_instrument) + m_instrument->setAudioInputToBuss(value, channel); + } else if (type.lower() == "record") { + if (m_instrument) + m_instrument->setAudioInputToRecord(value, channel); + } + } + + } else if (lcName == "audiooutput") { + + if (m_section != InInstrument) { + m_errorString = "Found audioOutput outside Instrument"; + return false; + } + + int value = atts.value("value").toInt(); + if (m_instrument) + m_instrument->setAudioOutput(value); + + } else if (lcName == "appearance") { + + m_section = InAppearance; + + } else if (lcName == "colourmap") { + + if (m_section == InAppearance) { + QString mapName = atts.value("name"); + m_inColourMap = true; + if (mapName == "segmentmap") { + m_colourMap = &m_doc->getComposition().getSegmentColourMap(); + } else + if (mapName == "generalmap") { + m_colourMap = &m_doc->getComposition().getGeneralColourMap(); + } else { // This will change later once we get more of the Appearance code sorted out + RG_DEBUG << "RoseXmlHandler::startElement : Found colourmap with unknown name\n"; + } + } else { + m_errorString = "Found colourmap outside Appearance"; + return false; + } + + } else if (lcName == "colourpair") { + + if (m_inColourMap && m_colourMap) { + unsigned int id = atts.value("id").toInt(); + QString name = atts.value("name"); + unsigned int red = atts.value("red").toInt(); + unsigned int blue = atts.value("blue").toInt(); + unsigned int green = atts.value("green").toInt(); + Colour colour(red, green, blue); + m_colourMap->addItem(colour, qstrtostr(name), id); + } else { + m_errorString = "Found colourpair outside ColourMap"; + return false; + } + + } else if (lcName == "markers") { + + if (!m_inComposition) { + m_errorString = "Found Markers outside Composition"; + return false; + } + + // clear down any markers + getComposition().clearMarkers(); + + } else if (lcName == "marker") { + if (!m_inComposition) { + m_errorString = "Found Marker outside Composition"; + return false; + } + int time = atts.value("time").toInt(); + QString name = atts.value("name"); + QString descr = atts.value("description"); + + Marker *marker = + new Marker(time, + qstrtostr(name), + qstrtostr(descr)); + + getComposition().addMarker(marker); + } else { + RG_DEBUG << "RoseXmlHandler::startElement : Don't know how to parse this : " << qName << endl; + } + + return true; +} + +bool +RoseXmlHandler::endElement(const QString& namespaceURI, + const QString& localName, + const QString& qName) +{ + if (getSubHandler()) { + bool finished; + bool res = getSubHandler()->endElement(namespaceURI, localName, qName.lower(), finished); + if (finished) + setSubHandler(0); + return res; + } + + // Set percentage done + // + if ((m_totalElements > m_elementsSoFar) && + (++m_elementsSoFar % 300 == 0)) { + + emit setProgress(int(double(m_elementsSoFar) / double(m_totalElements) * 100.0)); + ProgressDialog::processEvents(); + } + + QString lcName = qName.lower(); + + if (lcName == "rosegarden-data") { + + getComposition().updateTriggerSegmentReferences(); + + } else if (lcName == "event") { + + if (m_currentSegment && m_currentEvent) { + m_currentSegment->insert(m_currentEvent); + m_currentEvent = 0; + } else if (!m_currentSegment && m_currentEvent) { + m_errorString = "Got event outside of a Segment"; + return false; + } + + } else if (lcName == "chord") { + + m_currentTime += m_chordDuration; + m_inChord = false; + m_chordDuration = 0; + + } else if (lcName == "group") { + + m_inGroup = false; + + } else if (lcName == "segment") { + + if (m_currentSegment && m_segmentEndMarkerTime) { + m_currentSegment->setEndMarkerTime(*m_segmentEndMarkerTime); + delete m_segmentEndMarkerTime; + m_segmentEndMarkerTime = 0; + } + + m_currentSegment = 0; + m_section = NoSection; + + } else if (lcName == "bar-segment" || lcName == "tempo-segment") { + + m_currentSegment = 0; + + } else if (lcName == "composition") { + m_inComposition = false; + m_section = NoSection; + + } else if (lcName == "studio") { + + m_section = NoSection; + + } else if (lcName == "buss") { + + m_section = InStudio; + m_buss = 0; + + } else if (lcName == "instrument") { + + m_section = InStudio; + m_instrument = 0; + + } else if (lcName == "plugin") { + + if (m_pluginInBuss) { + m_section = InBuss; + } else { + m_section = InInstrument; + } + m_plugin = 0; + m_pluginId = 0; + + } else if (lcName == "device") { + + m_device = 0; + + } else if (lcName == "keymapping") { + + if (m_section == InStudio) { + if (m_keyMapping) { + if (!m_keyNameMap.empty()) { + MidiDevice *md = dynamic_cast + (m_device); + if (md) { + m_keyMapping->setMap(m_keyNameMap); + md->addKeyMapping(*m_keyMapping); + } + } + m_keyMapping = 0; + } + } + + } else if (lcName == "audiofiles") { + + m_section = NoSection; + + } else if (lcName == "appearance") { + + m_section = NoSection; + + } else if (lcName == "colourmap") { + m_inColourMap = false; + m_colourMap = 0; + } + + return true; +} + +bool +RoseXmlHandler::characters(const QString& s) +{ + if (m_subHandler) + return m_subHandler->characters(s); + + return true; +} + +QString +RoseXmlHandler::errorString() +{ + return m_errorString; +} + +bool +RoseXmlHandler::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 +RoseXmlHandler::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 ); +} + +bool +RoseXmlHandler::endDocument() +{ + if (m_foundTempo == false) { + getComposition().setCompositionDefaultTempo + (Composition::getTempoForQpm(120.0)); + } + + return true; +} + +void +RoseXmlHandler::setSubHandler(XmlSubHandler* sh) +{ + delete m_subHandler; + m_subHandler = sh; +} + +void +RoseXmlHandler::addMIDIDevice(QString name, bool createAtSequencer) +{ + unsigned int deviceId = 0; + + if (createAtSequencer) { + + QByteArray data; + QByteArray replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + + arg << (int)Device::Midi; + arg << (unsigned int)MidiDevice::Play; + + if (!rgapp->sequencerCall("addDevice(int, unsigned int)", replyType, replyData, data)) { + SEQMAN_DEBUG << "RoseXmlHandler::addMIDIDevice - " + << "can't call sequencer addDevice" << endl; + return ; + } + + if (replyType == "unsigned int") { + QDataStream reply(replyData, IO_ReadOnly); + reply >> deviceId; + } else { + SEQMAN_DEBUG << "RoseXmlHandler::addMIDIDevice - " + << "got unknown returntype from addDevice()" << endl; + return ; + } + + if (deviceId == Device::NO_DEVICE) { + SEQMAN_DEBUG << "RoseXmlHandler::addMIDIDevice - " + << "sequencer addDevice failed" << endl; + return ; + } + + SEQMAN_DEBUG << "RoseXmlHandler::addMIDIDevice - " + << " added device " << deviceId << endl; + + } else { + // Generate a new device id at the base Studio side only. + // This may not correspond to any given device id at the + // sequencer side. We should _never_ do this in a document + // that's actually intended to be retained for use, only + // in temporary documents for device import etc. + int tempId = -1; + for (DeviceListIterator i = getStudio().getDevices()->begin(); + i != getStudio().getDevices()->end(); ++i) { + if (int((*i)->getId()) > tempId) + tempId = int((*i)->getId()); + } + deviceId = tempId + 1; + } + + // add the device, so we can name it and set our pointer to it -- + // instruments will be sync'd later in the natural course of things + getStudio().addDevice(qstrtostr(name), deviceId, Device::Midi); + m_device = getStudio().getDevice(deviceId); + m_deviceRunningId = deviceId; +} + +void +RoseXmlHandler::skipToNextPlayDevice() +{ + SEQMAN_DEBUG << "RoseXmlHandler::skipToNextPlayDevice; m_deviceRunningId is " << m_deviceRunningId << endl; + + for (DeviceList::iterator i = getStudio().getDevices()->begin(); + i != getStudio().getDevices()->end(); ++i) { + + MidiDevice *md = + dynamic_cast(*i); + + if (md && md->getDirection() == MidiDevice::Play) { + if (m_deviceRunningId == Device::NO_DEVICE || + md->getId() > m_deviceRunningId) { + + SEQMAN_DEBUG << "RoseXmlHandler::skipToNextPlayDevice: found next device: id " << md->getId() << endl; + + m_device = md; + m_deviceRunningId = md->getId(); + return ; + } + } + } + + SEQMAN_DEBUG << "RoseXmlHandler::skipToNextPlayDevice: fresh out of devices" << endl; + + m_device = 0; +} + +void +RoseXmlHandler::setMIDIDeviceConnection(QString connection) +{ + SEQMAN_DEBUG << "RoseXmlHandler::setMIDIDeviceConnection(" << connection << ")" << endl; + + MidiDevice *md = dynamic_cast(m_device); + if (!md) + return ; + + QByteArray data; + QDataStream arg(data, IO_WriteOnly); + + arg << (unsigned int)md->getId(); + arg << connection; + + rgapp->sequencerSend("setPlausibleConnection(unsigned int, QString)", + data); + // connection should be sync'd later in the natural course of things +} + +void +RoseXmlHandler::setMIDIDeviceName(QString name) +{ + SEQMAN_DEBUG << "RoseXmlHandler::setMIDIDeviceName(" << name << ")" << endl; + + MidiDevice *md = dynamic_cast(m_device); + if (!md) + return ; + + QByteArray data; + QDataStream arg(data, IO_WriteOnly); + + arg << (unsigned int)md->getId(); + arg << name; + + std::cerr << "Renaming device " << md->getId() << " to " << name << std::endl; + + rgapp->sequencerSend("renameDevice(unsigned int, QString)", + data); +} + +} diff --git a/src/document/RoseXmlHandler.h b/src/document/RoseXmlHandler.h new file mode 100644 index 0000000..ab06e3a --- /dev/null +++ b/src/document/RoseXmlHandler.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ROSEXMLHANDLER_H_ +#define _RG_ROSEXMLHANDLER_H_ + +#include "base/Device.h" +#include "base/MidiProgram.h" +#include "gui/general/ProgressReporter.h" +#include +#include +#include +#include +#include "base/Event.h" +#include + + +class QXmlParseException; +class QXmlAttributes; + + +namespace Rosegarden +{ + +class XmlStorableEvent; +class XmlSubHandler; +class Studio; +class Segment; +class RosegardenGUIDoc; +class Instrument; +class Device; +class Composition; +class ColourMap; +class Buss; +class AudioPluginManager; +class AudioPluginInstance; +class AudioFileManager; + + +/** + * Handler for the Rosegarden XML format + */ +class RoseXmlHandler : public ProgressReporter, public QXmlDefaultHandler +{ +public: + + typedef enum + { + NoSection, + InComposition, + InSegment, + InStudio, + InInstrument, + InBuss, + InAudioFiles, + InPlugin, + InAppearance + } RosegardenFileSection; + + /** + * Construct a new RoseXmlHandler which will put the data extracted + * from the XML file into the specified composition + */ + RoseXmlHandler(RosegardenGUIDoc *doc, + unsigned int elementCount, + bool createNewDevicesWhenNeeded); + + virtual ~RoseXmlHandler(); + + /// 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 (); // [rwb] - for tempo element catch + + bool isDeprecated() { return m_deprecation; } + + bool isCancelled() { return m_cancelled; } + + /// Return the error string set during the parsing (if any) + QString errorString(); + + bool hasActiveAudio() const { return m_hasActiveAudio; } + std::set &pluginsNotFound() { return m_pluginsNotFound; } + + bool error(const QXmlParseException& exception); + bool fatalError(const QXmlParseException& exception); + +protected: + + // just for convenience -- just call to the document + // + Composition& getComposition(); + Studio& getStudio(); + AudioFileManager& getAudioFileManager(); + AudioPluginManager* getAudioPluginManager(); + + void setSubHandler(XmlSubHandler* sh); + XmlSubHandler* getSubHandler() { return m_subHandler; } + + void addMIDIDevice(QString name, bool createAtSequencer); + void setMIDIDeviceConnection(QString connection); + void setMIDIDeviceName(QString name); + void skipToNextPlayDevice(); + + //--------------- Data members --------------------------------- + + RosegardenGUIDoc *m_doc; + Segment *m_currentSegment; + XmlStorableEvent *m_currentEvent; + + timeT m_currentTime; + timeT m_chordDuration; + timeT *m_segmentEndMarkerTime; + + bool m_inChord; + bool m_inGroup; + bool m_inComposition; + bool m_inColourMap; + std::string m_groupType; + int m_groupId; + int m_groupTupletBase; + int m_groupTupledCount; + int m_groupUntupledCount; + std::map m_groupIdMap; + + bool m_foundTempo; + + QString m_errorString; + std::set m_pluginsNotFound; + + RosegardenFileSection m_section; + Device *m_device; + DeviceId m_deviceRunningId; + bool m_percussion; + MidiByte m_msb; + MidiByte m_lsb; + Instrument *m_instrument; + Buss *m_buss; + AudioPluginInstance *m_plugin; + bool m_pluginInBuss; + ColourMap *m_colourMap; + MidiKeyMapping *m_keyMapping; + MidiKeyMapping::KeyNameMap m_keyNameMap; + unsigned int m_pluginId; + unsigned int m_totalElements; + unsigned int m_elementsSoFar; + + XmlSubHandler *m_subHandler; + bool m_deprecation; + bool m_createDevices; + bool m_haveControls; + bool m_cancelled; + bool m_skipAllAudio; + bool m_hasActiveAudio; +}; + + +} + +#endif diff --git a/src/document/RosegardenGUIDoc.cpp b/src/document/RosegardenGUIDoc.cpp new file mode 100644 index 0000000..f89a83f --- /dev/null +++ b/src/document/RosegardenGUIDoc.cpp @@ -0,0 +1,3117 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RosegardenGUIDoc.h" +#include + +#include +#include "sound/Midi.h" +#include "gui/editors/segment/TrackEditor.h" +#include "gui/editors/segment/TrackButtons.h" +#include +#include +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "gui/general/ClefIndex.h" +#include "document/ConfigGroups.h" +#include "base/AudioDevice.h" +#include "base/AudioPluginInstance.h" +#include "base/BaseProperties.h" +#include "base/Clipboard.h" +#include "base/Composition.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/MidiProgram.h" +#include "base/MidiTypes.h" +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/SoftSynthDevice.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "base/XmlExportable.h" +#include "commands/edit/EventQuantizeCommand.h" +#include "commands/notation/NormalizeRestsCommand.h" +#include "commands/segment/AddTracksCommand.h" +#include "commands/segment/SegmentInsertCommand.h" +#include "commands/segment/SegmentRecordCommand.h" +#include "commands/segment/ChangeCompositionLengthCommand.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/application/RosegardenGUIApp.h" +#include "gui/application/RosegardenGUIView.h" +#include "gui/dialogs/UnusedAudioSelectionDialog.h" +#include "gui/editors/segment/segmentcanvas/AudioPreviewThread.h" +#include "gui/editors/segment/TrackLabel.h" +#include "gui/general/EditViewBase.h" +#include "gui/general/GUIPalette.h" +#include "gui/kdeext/KStartupLogo.h" +#include "gui/seqmanager/SequenceManager.h" +#include "gui/studio/AudioPluginManager.h" +#include "gui/studio/StudioControl.h" +#include "gui/widgets/CurrentProgressDialog.h" +#include "gui/widgets/ProgressDialog.h" +#include "MultiViewCommandHistory.h" +#include "RoseXmlHandler.h" +#include "sound/AudioFile.h" +#include "sound/AudioFileManager.h" +#include "sound/MappedCommon.h" +#include "sound/MappedComposition.h" +#include "sound/MappedDevice.h" +#include "sound/MappedInstrument.h" +#include "sound/MappedEvent.h" +#include "sound/MappedRealTime.h" +#include "sound/MappedStudio.h" +#include "sound/PluginIdentifier.h" +#include "sound/SoundDriver.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gui/widgets/ProgressBar.h" + + +namespace Rosegarden +{ + +using namespace BaseProperties; + +RosegardenGUIDoc::RosegardenGUIDoc(QWidget *parent, + AudioPluginManager *pluginManager, + bool skipAutoload, + const char *name) + : QObject(parent, name), + m_modified(false), + m_autoSaved(false), + m_audioPreviewThread(&m_audioFileManager), + m_commandHistory(new MultiViewCommandHistory()), + m_pluginManager(pluginManager), + m_audioRecordLatency(0, 0), + m_autoSavePeriod(0), + m_quickMarkerTime(-1), + m_beingDestroyed(false) +{ + syncDevices(); + + m_viewList.setAutoDelete(false); + m_editViewList.setAutoDelete(false); + + connect(m_commandHistory, SIGNAL(commandExecuted(KCommand *)), + this, SLOT(slotDocumentModified())); + + connect(m_commandHistory, SIGNAL(documentRestored()), + this, SLOT(slotDocumentRestored())); + + // autoload a new document + if (!skipAutoload) + performAutoload(); + + // now set it up as a "new document" + newDocument(); +} + +RosegardenGUIDoc::~RosegardenGUIDoc() +{ + RG_DEBUG << "~RosegardenGUIDoc()\n"; + m_beingDestroyed = true; + + m_audioPreviewThread.finish(); + m_audioPreviewThread.wait(); + + deleteEditViews(); + + // ControlRulerCanvasRepository::clear(); + + delete m_commandHistory; // must be deleted before the Composition is +} + +unsigned int +RosegardenGUIDoc::getAutoSavePeriod() const +{ + KConfig* config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + return config->readUnsignedNumEntry("autosaveinterval", 60); +} + +void RosegardenGUIDoc::attachView(RosegardenGUIView *view) +{ + m_viewList.append(view); +} + +void RosegardenGUIDoc::detachView(RosegardenGUIView *view) +{ + m_viewList.remove(view); +} + +void RosegardenGUIDoc::attachEditView(EditViewBase *view) +{ + m_editViewList.append(view); +} + +void RosegardenGUIDoc::detachEditView(EditViewBase *view) +{ + // auto-deletion is disabled, as + // the editview detaches itself when being deleted + m_editViewList.remove(view); +} + +void RosegardenGUIDoc::deleteEditViews() +{ + // enabled auto-deletion : edit views will be deleted + m_editViewList.setAutoDelete(true); + m_editViewList.clear(); +} + +void RosegardenGUIDoc::setAbsFilePath(const QString &filename) +{ + m_absFilePath = filename; +} + +void RosegardenGUIDoc::setTitle(const QString &_t) +{ + m_title = _t; +} + +const QString &RosegardenGUIDoc::getAbsFilePath() const +{ + return m_absFilePath; +} + +const QString& RosegardenGUIDoc::getTitle() const +{ + return m_title; +} + +void RosegardenGUIDoc::slotUpdateAllViews(RosegardenGUIView *sender) +{ + RosegardenGUIView *w; + + for (w = m_viewList.first(); w != 0; w = m_viewList.next()) { + if (w != sender) + w->repaint(); + } +} + +void RosegardenGUIDoc::setModified(bool m) +{ + m_modified = m; + RG_DEBUG << "RosegardenGUIDoc[" << this << "]::setModified(" << m << ")\n"; +} + +void RosegardenGUIDoc::clearModifiedStatus() +{ + setModified(false); + setAutoSaved(true); + emit documentModified(false); +} + +void RosegardenGUIDoc::slotDocumentModified() +{ + RG_DEBUG << "RosegardenGUIDoc::slotDocumentModified()" << endl; + setModified(true); + setAutoSaved(false); + emit documentModified(true); +} + +void RosegardenGUIDoc::slotDocumentRestored() +{ + RG_DEBUG << "RosegardenGUIDoc::slotDocumentRestored()\n"; + setModified(false); +} + +void +RosegardenGUIDoc::setQuickMarker() +{ + RG_DEBUG << "RosegardenGUIDoc::setQuickMarker" << endl; + + m_quickMarkerTime = getComposition().getPosition(); +} + +void +RosegardenGUIDoc::jumpToQuickMarker() +{ + RG_DEBUG << "RosegardenGUIDoc::jumpToQuickMarker" << endl; + + if (m_quickMarkerTime >= 0) + slotSetPointerPosition(m_quickMarkerTime); +} + +QString RosegardenGUIDoc::getAutoSaveFileName() +{ + QString filename = getAbsFilePath(); + if (filename.isEmpty()) + filename = QDir::currentDirPath() + "/" + getTitle(); + + QString autoSaveFileName = kapp->tempSaveName(filename); + + return autoSaveFileName; +} + +void RosegardenGUIDoc::slotAutoSave() +{ + // RG_DEBUG << "RosegardenGUIDoc::slotAutoSave()\n" << endl; + + if (isAutoSaved() || !isModified()) + return ; + + QString autoSaveFileName = getAutoSaveFileName(); + + RG_DEBUG << "RosegardenGUIDoc::slotAutoSave() - doc modified - saving '" + << getAbsFilePath() << "' as " + << autoSaveFileName << endl; + + QString errMsg; + + saveDocument(autoSaveFileName, errMsg, true); + +} + +bool RosegardenGUIDoc::isRegularDotRGFile() +{ + return getAbsFilePath().right(3).lower() == ".rg"; +} + +bool RosegardenGUIDoc::saveIfModified() +{ + RG_DEBUG << "RosegardenGUIDoc::saveIfModified()" << endl; + bool completed = true; + + if (!isModified()) + return completed; + + + RosegardenGUIApp *win = (RosegardenGUIApp *)parent(); + + int wantSave = KMessageBox::warningYesNoCancel + (win, + i18n("The current file has been modified.\n" + "Do you want to save it?"), + i18n("Warning")); + + RG_DEBUG << "wantSave = " << wantSave << endl; + + switch (wantSave) { + + case KMessageBox::Yes: + + if (!isRegularDotRGFile()) { + + RG_DEBUG << "RosegardenGUIDoc::saveIfModified() : new or imported file\n"; + completed = win->slotFileSaveAs(); + + } else { + + RG_DEBUG << "RosegardenGUIDoc::saveIfModified() : regular file\n"; + QString errMsg; + completed = saveDocument(getAbsFilePath(), errMsg); + + if (!completed) { + if (errMsg) { + KMessageBox::error(0, i18n(QString("Could not save document at %1\n(%2)") + .arg(getAbsFilePath()).arg(errMsg))); + } else { + KMessageBox::error(0, i18n(QString("Could not save document at %1") + .arg(getAbsFilePath()))); + } + } + } + + break; + + case KMessageBox::No: + // delete the autosave file so it won't annoy + // the user when reloading the file. + QFile::remove + (getAutoSaveFileName()); + completed = true; + break; + + case KMessageBox::Cancel: + completed = false; + break; + + default: + completed = false; + break; + } + + if (completed) { + completed = deleteOrphanedAudioFiles(wantSave == KMessageBox::No); + if (completed) { + m_audioFileManager.resetRecentlyCreatedFiles(); + } + } + + if (completed) + setModified(false); + return completed; +} + +bool +RosegardenGUIDoc::deleteOrphanedAudioFiles(bool documentWillNotBeSaved) +{ + std::vector recordedOrphans; + std::vector derivedOrphans; + + if (documentWillNotBeSaved) { + + // All audio files recorded or derived in this session are + // about to become orphans + + for (std::vector::const_iterator i = + m_audioFileManager.begin(); + i != m_audioFileManager.end(); ++i) { + + if (m_audioFileManager.wasAudioFileRecentlyRecorded((*i)->getId())) { + recordedOrphans.push_back(strtoqstr((*i)->getFilename())); + } + + if (m_audioFileManager.wasAudioFileRecentlyDerived((*i)->getId())) { + derivedOrphans.push_back(strtoqstr((*i)->getFilename())); + } + } + } + + // Whether we save or not, explicitly orphaned (i.e. recorded in + // this session and then unloaded) recorded files are orphans. + // Make sure they are actually unknown to the audio file manager + // (i.e. they haven't been loaded more than once, or reloaded + // after orphaning). + + for (std::vector::iterator i = m_orphanedRecordedAudioFiles.begin(); + i != m_orphanedRecordedAudioFiles.end(); ++i) { + + bool stillHave = false; + + for (std::vector::const_iterator j = + m_audioFileManager.begin(); + j != m_audioFileManager.end(); ++j) { + if (strtoqstr((*j)->getFilename()) == *i) { + stillHave = true; + break; + } + } + + if (!stillHave) recordedOrphans.push_back(*i); + } + + // Derived orphans get deleted whatever happens + //!!! Should we orphan any file derived during this session that + //is not currently used in a segment? Probably: we have no way to + //reuse them + + for (std::vector::iterator i = m_orphanedDerivedAudioFiles.begin(); + i != m_orphanedDerivedAudioFiles.end(); ++i) { + + bool stillHave = false; + + for (std::vector::const_iterator j = + m_audioFileManager.begin(); + j != m_audioFileManager.end(); ++j) { + if (strtoqstr((*j)->getFilename()) == *i) { + stillHave = true; + break; + } + } + + if (!stillHave) derivedOrphans.push_back(*i); + } + + for (size_t i = 0; i < derivedOrphans.size(); ++i) { + QFile file(derivedOrphans[i]); + if (!file.remove()) { + std::cerr << "WARNING: Failed to remove orphaned derived audio file \"" << derivedOrphans[i] << std::endl; + } + QFile peakFile(QString("%1.pk").arg(derivedOrphans[i])); + peakFile.remove(); + } + + m_orphanedDerivedAudioFiles.clear(); + + if (recordedOrphans.empty()) + return true; + + if (documentWillNotBeSaved) { + + int reply = KMessageBox::warningYesNoCancel + (0, + i18n("Delete the 1 audio file recorded during the unsaved session?", + "Delete the %n audio files recorded during the unsaved session?", + recordedOrphans.size())); + + switch (reply) { + + case KMessageBox::Yes: + break; + + case KMessageBox::No: + return true; + + default: + case KMessageBox::Cancel: + return false; + } + + } else { + + UnusedAudioSelectionDialog *dialog = + new UnusedAudioSelectionDialog + (0, + i18n("The following audio files were recorded during this session but have been unloaded\nfrom the audio file manager, and so are no longer in use in the document you are saving.\n\nYou may want to clean up these files to save disk space.\n\nPlease select any you wish to delete permanently from the hard disk.\n"), + recordedOrphans); + + if (dialog->exec() != QDialog::Accepted) { + delete dialog; + return true; + } + + recordedOrphans = dialog->getSelectedAudioFileNames(); + delete dialog; + } + + if (recordedOrphans.empty()) + return true; + + QString question = + i18n("About to delete 1 audio file permanently from the hard disk.
There will be no way to recover this file.
Are you sure?
\n", "About to delete %n audio files permanently from the hard disk.
There will be no way to recover these files.
Are you sure?
", recordedOrphans.size()); + + int reply = KMessageBox::warningContinueCancel(0, question); + + if (reply == KMessageBox::Continue) { + for (size_t i = 0; i < recordedOrphans.size(); ++i) { + QFile file(recordedOrphans[i]); + if (!file.remove()) { + KMessageBox::error(0, i18n("File %1 could not be deleted.") + .arg(recordedOrphans[i])); + } + + QFile peakFile(QString("%1.pk").arg(recordedOrphans[i])); + peakFile.remove(); + } + } + + return true; +} + +void RosegardenGUIDoc::newDocument() +{ + setModified(false); + setAbsFilePath(QString::null); + setTitle(i18n("Untitled")); + m_commandHistory->clear(); +} + +void RosegardenGUIDoc::performAutoload() +{ + QString autoloadFile = + KGlobal::dirs()->findResource("appdata", "autoload.rg"); + + QFileInfo autoloadFileInfo(autoloadFile); + + if (!autoloadFileInfo.isReadable()) { + RG_DEBUG << "RosegardenGUIDoc::performAutoload - " + << "can't find autoload file - defaulting" << endl; + return ; + } + + openDocument(autoloadFile); + +} + +bool RosegardenGUIDoc::openDocument(const QString& filename, + bool permanent, + const char* /*format*/ /*=0*/) +{ + RG_DEBUG << "RosegardenGUIDoc::openDocument(" + << filename << ")" << endl; + + if (!filename || filename.isEmpty()) + return false; + + newDocument(); + + QFileInfo fileInfo(filename); + setTitle(fileInfo.fileName()); + + // Check if file readable with fileInfo ? + if (!fileInfo.isReadable() || fileInfo.isDir()) { + KStartupLogo::hideIfStillThere(); + QString msg(i18n("Can't open file '%1'").arg(filename)); + KMessageBox::sorry(0, msg); + return false; + } + + ProgressDialog progressDlg(i18n("Reading file..."), + 100, + (QWidget*)parent()); + + connect(&progressDlg, SIGNAL(cancelClicked()), + &m_audioFileManager, SLOT(slotStopPreview())); + + progressDlg.setMinimumDuration(500); + progressDlg.setAutoReset(true); // we're re-using it for the preview generation + setAbsFilePath(fileInfo.absFilePath()); + + QString errMsg; + QString fileContents; + bool cancelled = false, okay = true; + + KFilterDev* fileCompressedDevice = static_cast(KFilterDev::deviceForFile(filename, "application/x-gzip")); + if (fileCompressedDevice == 0) { + + errMsg = i18n("Could not open Rosegarden file"); + + } else { + fileCompressedDevice->open(IO_ReadOnly); + + unsigned int elementCount = fileInfo.size() / 4; // approx. guess + // RG_DEBUG << "RosegardenGUIDoc::xmlParse() : elementCount = " << elementCount + // << " - file size : " << file->size() + // << endl; + + + // Fugly work-around in case of broken rg files + // + int c = 0; + std::vector baseBuffer; + + while (c != -1) { + c = fileCompressedDevice->getch(); + if (c != -1) + baseBuffer.push_back(c); + } + + fileCompressedDevice->close(); + + QString fileContents = QString::fromUtf8(&baseBuffer[0], + baseBuffer.size()); + + // parse xml file + okay = xmlParse(fileContents, errMsg, &progressDlg, + elementCount, permanent, cancelled); + // okay = xmlParse(fileCompressedDevice, errMsg, &progressDlg, + // elementCount, permanent, cancelled); + delete fileCompressedDevice; + + } + + if (!okay) { + KStartupLogo::hideIfStillThere(); + QString msg(i18n("Error when parsing file '%1': \"%2\"") + .arg(filename) + .arg(errMsg)); + + CurrentProgressDialog::freeze(); + KMessageBox::sorry(0, msg); + CurrentProgressDialog::thaw(); + + return false; + + } else if (cancelled) { + newDocument(); + return false; + } + + RG_DEBUG << "RosegardenGUIDoc::openDocument() end - " + << "m_composition : " << &m_composition + << " - m_composition->getNbSegments() : " + << m_composition.getNbSegments() + << " - m_composition->getDuration() : " + << m_composition.getDuration() << endl; + + if (m_composition.begin() != m_composition.end()) { + RG_DEBUG << "First segment starts at " << (*m_composition.begin())->getStartTime() << endl; + } + + // Ensure a minimum of 64 tracks + // + // unsigned int nbTracks = m_composition.getNbTracks(); + // TrackId maxTrackId = m_composition.getMaxTrackId(); + // InstrumentId instBase = MidiInstrumentBase; + + // for(unsigned int i = nbTracks; i < MinNbOfTracks; ++i) { + + // Track *track; + + // track = new Track(maxTrackId + 1, // id + // (i + instBase) % 16, // instrument + // i, // position + // "untitled", + // false); // mute + + // m_composition.addTrack(track); + // ++maxTrackId; + // } + + // We might need a progress dialog when we generate previews, + // reuse the previous one + progressDlg.setLabel(i18n("Generating audio previews...")); + + connect(&m_audioFileManager, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + try { + // generate any audio previews after loading the files + m_audioFileManager.generatePreviews(); + } catch (Exception e) { + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + KMessageBox::error(0, strtoqstr(e.getMessage())); + CurrentProgressDialog::thaw(); + } + + if (isSequencerRunning()) { + // Initialise the whole studio - faders, plugins etc. + // + initialiseStudio(); + + // Initialise the MIDI controllers (reaches through to MIDI devices + // to set them up) + // + initialiseControllers(); + } + + return true; +} + +void +RosegardenGUIDoc::mergeDocument(RosegardenGUIDoc *doc, + int options) +{ + KMacroCommand *command = new KMacroCommand(i18n("Merge")); + + timeT time0 = 0; + if (options & MERGE_AT_END) { + time0 = getComposition().getBarEndForTime(getComposition().getDuration()); + } + + int myMaxTrack = getComposition().getNbTracks(); + int yrMinTrack = 0; + int yrMaxTrack = doc->getComposition().getNbTracks(); + int yrNrTracks = yrMaxTrack - yrMinTrack + 1; + + int firstAlteredTrack = yrMinTrack; + + if (options & MERGE_IN_NEW_TRACKS) { + + //!!! worry about instruments and other studio stuff later... if at all + command->addCommand(new AddTracksCommand + (&getComposition(), + yrNrTracks, + MidiInstrumentBase, + -1)); + + firstAlteredTrack = myMaxTrack + 1; + + } else if (yrMaxTrack > myMaxTrack) { + + command->addCommand(new AddTracksCommand + (&getComposition(), + yrMaxTrack - myMaxTrack, + MidiInstrumentBase, + -1)); + } + + TrackId firstNewTrackId = getComposition().getNewTrackId(); + timeT lastSegmentEndTime = 0; + + for (Composition::iterator i = doc->getComposition().begin(), j = i; + i != doc->getComposition().end(); i = j) { + + ++j; + Segment *s = *i; + timeT segmentEndTime = s->getEndMarkerTime(); + + int yrTrack = s->getTrack(); + Track *t = doc->getComposition().getTrackById(yrTrack); + if (t) yrTrack = t->getPosition(); + + int myTrack = yrTrack; + + if (options & MERGE_IN_NEW_TRACKS) { + myTrack = yrTrack - yrMinTrack + myMaxTrack + 1; + } + + doc->getComposition().detachSegment(s); + + if (options & MERGE_AT_END) { + s->setStartTime(s->getStartTime() + time0); + segmentEndTime += time0; + } + if (segmentEndTime > lastSegmentEndTime) { + lastSegmentEndTime = segmentEndTime; + } + + Track *track = getComposition().getTrackByPosition(myTrack); + TrackId tid = 0; + if (track) tid = track->getId(); + else tid = firstNewTrackId + yrTrack - yrMinTrack; + + command->addCommand(new SegmentInsertCommand(&getComposition(), s, tid)); + } + + if (!(options & MERGE_KEEP_OLD_TIMINGS)) { + for (int i = getComposition().getTimeSignatureCount() - 1; i >= 0; --i) { + getComposition().removeTimeSignature(i); + } + for (int i = getComposition().getTempoChangeCount() - 1; i >= 0; --i) { + getComposition().removeTempoChange(i); + } + } + + if (options & MERGE_KEEP_NEW_TIMINGS) { + for (int i = 0; i < doc->getComposition().getTimeSignatureCount(); ++i) { + std::pair ts = + doc->getComposition().getTimeSignatureChange(i); + getComposition().addTimeSignature(ts.first + time0, ts.second); + } + for (int i = 0; i < doc->getComposition().getTempoChangeCount(); ++i) { + std::pair t = + doc->getComposition().getTempoChange(i); + getComposition().addTempoAtTime(t.first + time0, t.second); + } + } + + if (lastSegmentEndTime > getComposition().getEndMarker()) { + command->addCommand(new ChangeCompositionLengthCommand + (&getComposition(), + getComposition().getStartMarker(), + lastSegmentEndTime)); + } + + m_commandHistory->addCommand(command); + + emit makeTrackVisible(firstAlteredTrack + yrNrTracks/2 + 1); +} + +void RosegardenGUIDoc::clearStudio() +{ + QCString replyType; + QByteArray replyData; + rgapp->sequencerCall("clearStudio()", replyType, replyData); + RG_DEBUG << "cleared studio\n"; +} + +void RosegardenGUIDoc::initialiseStudio() +{ + Profiler profiler("initialiseStudio", true); + + RG_DEBUG << "RosegardenGUIDoc::initialiseStudio - " + << "clearing down and initialising" << endl; + + clearStudio(); + + InstrumentList list = m_studio.getAllInstruments(); + InstrumentList::iterator it = list.begin(); + int audioCount = 0; + + BussList busses = m_studio.getBusses(); + RecordInList recordIns = m_studio.getRecordIns(); + + // To reduce the number of DCOP calls at this stage, we put some + // of the float property values in a big list and commit in one + // single call at the end. We can only do this with properties + // that aren't depended on by other port, connection, or non-float + // properties during the initialisation process. + MappedObjectIdList ids; + MappedObjectPropertyList properties; + MappedObjectValueList values; + + std::vector pluginContainers; + + for (unsigned int i = 0; i < busses.size(); ++i) { + + // first one is master + MappedObjectId mappedId = + StudioControl::createStudioObject( + MappedObject::AudioBuss); + + StudioControl::setStudioObjectProperty + (mappedId, + MappedAudioBuss::BussId, + MappedObjectValue(i)); + + ids.push_back(mappedId); + properties.push_back(MappedAudioBuss::Level); + values.push_back(MappedObjectValue(busses[i]->getLevel())); + + ids.push_back(mappedId); + properties.push_back(MappedAudioBuss::Pan); + values.push_back(MappedObjectValue(busses[i]->getPan()) - 100.0); + + busses[i]->setMappedId(mappedId); + + pluginContainers.push_back(busses[i]); + } + + for (unsigned int i = 0; i < recordIns.size(); ++i) { + + MappedObjectId mappedId = + StudioControl::createStudioObject( + MappedObject::AudioInput); + + StudioControl::setStudioObjectProperty + (mappedId, + MappedAudioInput::InputNumber, + MappedObjectValue(i)); + + recordIns[i]->setMappedId(mappedId); + } + + for (; it != list.end(); it++) { + if ((*it)->getType() == Instrument::Audio || + (*it)->getType() == Instrument::SoftSynth) { + MappedObjectId mappedId = + StudioControl::createStudioObject( + MappedObject::AudioFader); + + // Set the object id against the instrument + // + (*it)->setMappedId(mappedId); + + /* + cout << "SETTING MAPPED OBJECT ID = " << mappedId + << " - on Instrument " << (*it)->getId() << endl; + */ + + + // Set the instrument id against this object + // + StudioControl::setStudioObjectProperty + (mappedId, + MappedObject::Instrument, + MappedObjectValue((*it)->getId())); + + // Set the level + // + ids.push_back(mappedId); + properties.push_back(MappedAudioFader::FaderLevel); + values.push_back(MappedObjectValue((*it)->getLevel())); + + // Set the record level + // + ids.push_back(mappedId); + properties.push_back(MappedAudioFader::FaderRecordLevel); + values.push_back(MappedObjectValue((*it)->getRecordLevel())); + + // Set the number of channels + // + ids.push_back(mappedId); + properties.push_back(MappedAudioFader::Channels); + values.push_back(MappedObjectValue((*it)->getAudioChannels())); + + // Set the pan - 0 based + // + ids.push_back(mappedId); + properties.push_back(MappedAudioFader::Pan); + values.push_back(MappedObjectValue(float((*it)->getPan())) - 100.0); + + // Set up connections: first clear any existing ones (shouldn't + // be necessary, but) + // + StudioControl::disconnectStudioObject(mappedId); + + // then handle the output connection + // + BussId outputBuss = (*it)->getAudioOutput(); + if (outputBuss < busses.size()) { + MappedObjectId bmi = busses[outputBuss]->getMappedId(); + + if (bmi > 0) { + StudioControl::connectStudioObjects(mappedId, bmi); + } + } + + // then the input + // + bool isBuss; + int channel; + int input = (*it)->getAudioInput(isBuss, channel); + MappedObjectId rmi = 0; + + if (isBuss) { + if (input < int(busses.size())) { + rmi = busses[input]->getMappedId(); + } + } else { + if (input < int(recordIns.size())) { + rmi = recordIns[input]->getMappedId(); + } + } + + ids.push_back(mappedId); + properties.push_back(MappedAudioFader::InputChannel); + values.push_back(MappedObjectValue(channel)); + + if (rmi > 0) { + StudioControl::connectStudioObjects(rmi, mappedId); + } + + pluginContainers.push_back(*it); + + audioCount++; + } + } + + for (std::vector::iterator pci = + pluginContainers.begin(); pci != pluginContainers.end(); ++pci) { + + // Initialise all the plugins for this Instrument or Buss + + for (PluginInstanceIterator pli = (*pci)->beginPlugins(); + pli != (*pci)->endPlugins(); ++pli) { + + AudioPluginInstance *plugin = *pli; + + if (plugin->isAssigned()) { + // Create the plugin slot at the sequencer Studio + // + MappedObjectId pluginMappedId = + StudioControl::createStudioObject( + MappedObject::PluginSlot); + + // Create the back linkage from the instance to the + // studio id + // + plugin->setMappedId(pluginMappedId); + + //RG_DEBUG << "CREATING PLUGIN ID = " + //<< pluginMappedId << endl; + + // Set the position + StudioControl::setStudioObjectProperty + (pluginMappedId, + MappedObject::Position, + MappedObjectValue(plugin->getPosition())); + + // Set the id of this instrument or buss on the plugin + // + StudioControl::setStudioObjectProperty + (pluginMappedId, + MappedObject::Instrument, + (*pci)->getId()); + + // Set the plugin type id - this will set it up ready + // for the rest of the settings. String value, so can't + // go in the main property list. + // + StudioControl::setStudioObjectProperty + (pluginMappedId, + MappedPluginSlot::Identifier, + plugin->getIdentifier().c_str()); + + plugin->setConfigurationValue + (qstrtostr(PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY), + getAudioFileManager().getAudioPath()); + + // Set opaque string configuration data (e.g. for DSSI plugin) + // + MappedObjectPropertyList config; + for (AudioPluginInstance::ConfigMap::const_iterator + i = plugin->getConfiguration().begin(); + i != plugin->getConfiguration().end(); ++i) { + config.push_back(strtoqstr(i->first)); + config.push_back(strtoqstr(i->second)); + } + + StudioControl::setStudioObjectPropertyList + (pluginMappedId, + MappedPluginSlot::Configuration, + config); + + // Set the bypass + // + ids.push_back(pluginMappedId); + properties.push_back(MappedPluginSlot::Bypassed); + values.push_back(MappedObjectValue(plugin->isBypassed())); + + // Set all the port values + // + PortInstanceIterator portIt; + + for (portIt = plugin->begin(); + portIt != plugin->end(); ++portIt) { + StudioControl::setStudioPluginPort + (pluginMappedId, + (*portIt)->number, + (*portIt)->value); + } + + // Set the program + // + if (plugin->getProgram() != "") { + StudioControl::setStudioObjectProperty + (pluginMappedId, + MappedPluginSlot::Program, + strtoqstr(plugin->getProgram())); + } + + // Set the post-program port values + // + for (portIt = plugin->begin(); + portIt != plugin->end(); ++portIt) { + if ((*portIt)->changedSinceProgramChange) { + StudioControl::setStudioPluginPort + (pluginMappedId, + (*portIt)->number, + (*portIt)->value); + } + } + } + } + } + + // Now commit all the remaining changes + StudioControl::setStudioObjectProperties(ids, properties, values); + + KConfig* config = kapp->config(); + config->setGroup(SequencerOptionsConfigGroup); + + bool faderOuts = config->readBoolEntry("audiofaderouts", false); + bool submasterOuts = config->readBoolEntry("audiosubmasterouts", false); + unsigned int audioFileFormat = config->readUnsignedNumEntry("audiorecordfileformat", 1); + + MidiByte ports = 0; + if (faderOuts) { + ports |= MappedEvent::FaderOuts; + } + if (submasterOuts) { + ports |= MappedEvent::SubmasterOuts; + } + MappedEvent mEports + (MidiInstrumentBase, + MappedEvent::SystemAudioPorts, + ports); + + StudioControl::sendMappedEvent(mEports); + + MappedEvent mEff + (MidiInstrumentBase, + MappedEvent::SystemAudioFileFormat, + audioFileFormat); + StudioControl::sendMappedEvent(mEff); +} + +int RosegardenGUIDoc::FILE_FORMAT_VERSION_MAJOR = 1; + +int RosegardenGUIDoc::FILE_FORMAT_VERSION_MINOR = 4; + +int RosegardenGUIDoc::FILE_FORMAT_VERSION_POINT = 0; + + + +bool RosegardenGUIDoc::saveDocument(const QString& filename, + QString& errMsg, + bool autosave) +{ + if (!QFileInfo(filename).exists()) { // safe to write directly + return saveDocumentActual(filename, errMsg, autosave); + } + + KTempFile temp(filename + ".", "", 0644); // will be umask'd + + int status = temp.status(); + if (status != 0) { + errMsg = i18n(QString("Could not create temporary file in directory of '%1': %2").arg(filename).arg(strerror(status))); + return false; + } + + QString tempFileName = temp.name(); + + RG_DEBUG << "Temporary file name is: \"" << tempFileName << "\"" << endl; + + // KTempFile creates a temporary file that is already open: close it + if (!temp.close()) { + status = temp.status(); + errMsg = i18n(QString("Failure in temporary file handling for file '%1': %2") + .arg(tempFileName).arg(strerror(status))); + return false; + } + + bool success = saveDocumentActual(tempFileName, errMsg, autosave); + + if (!success) { + // errMsg should be already set + return false; + } + + QDir dir(QFileInfo(tempFileName).dir()); + if (!dir.rename(tempFileName, filename)) { + errMsg = i18n(QString("Failed to rename temporary output file '%1' to desired output file '%2'").arg(tempFileName).arg(filename)); + return false; + } + + return true; +} + + +bool RosegardenGUIDoc::saveDocumentActual(const QString& filename, + QString& errMsg, + bool autosave) +{ + Profiler profiler("RosegardenGUIDoc::saveDocumentActual"); + RG_DEBUG << "RosegardenGUIDoc::saveDocumentActual(" << filename << ")\n"; + + KFilterDev* fileCompressedDevice = static_cast(KFilterDev::deviceForFile(filename, "application/x-gzip")); + fileCompressedDevice->setOrigFileName("audio/x-rosegarden"); + bool rc = fileCompressedDevice->open(IO_WriteOnly); + + if (!rc) { + // do some error report + errMsg = i18n(QString("Could not open file '%1' for writing").arg(filename)); + delete fileCompressedDevice; + return false; // couldn't open file + } + + + QTextStream outStream(fileCompressedDevice); + outStream.setEncoding(QTextStream::UnicodeUTF8); + + // output XML header + // + outStream << "\n" + << "\n" + << "\n"; + + ProgressDialog *progressDlg = 0; + KProgress *progress = 0; + + if (!autosave) { + + progressDlg = new ProgressDialog(i18n("Saving file..."), + 100, + (QWidget*)parent()); + progress = progressDlg->progressBar(); + + progressDlg->setMinimumDuration(500); + progressDlg->setAutoReset(true); + + } else { + + progress = ((RosegardenGUIApp *)parent())->getProgressBar(); + } + + // Send out Composition (this includes Tracks, Instruments, Tempo + // and Time Signature changes and any other sub-objects) + // + outStream << strtoqstr(getComposition().toXmlString()) + << endl << endl; + + outStream << strtoqstr(getAudioFileManager().toXmlString()) + << endl << endl; + + outStream << strtoqstr(getConfiguration().toXmlString()) + << endl << endl; + + long totalEvents = 0; + for (Composition::iterator segitr = m_composition.begin(); + segitr != m_composition.end(); ++segitr) { + totalEvents += (*segitr)->size(); + } + + for (Composition::triggersegmentcontaineriterator ci = + m_composition.getTriggerSegments().begin(); + ci != m_composition.getTriggerSegments().end(); ++ci) { + totalEvents += (*ci)->getSegment()->size(); + } + + // output all elements + // + // Iterate on segments + long eventCount = 0; + + for (Composition::iterator segitr = m_composition.begin(); + segitr != m_composition.end(); ++segitr) { + + Segment *segment = *segitr; + + saveSegment(outStream, segment, progress, totalEvents, eventCount); + + } + + // Put a break in the file + // + outStream << endl << endl; + + for (Composition::triggersegmentcontaineriterator ci = + m_composition.getTriggerSegments().begin(); + ci != m_composition.getTriggerSegments().end(); ++ci) { + + QString triggerAtts = QString + ("triggerid=\"%1\" triggerbasepitch=\"%2\" triggerbasevelocity=\"%3\" triggerretune=\"%4\" triggeradjusttimes=\"%5\" ") + .arg((*ci)->getId()) + .arg((*ci)->getBasePitch()) + .arg((*ci)->getBaseVelocity()) + .arg((*ci)->getDefaultRetune()) + .arg(strtoqstr((*ci)->getDefaultTimeAdjust())); + + Segment *segment = (*ci)->getSegment(); + saveSegment(outStream, segment, progress, totalEvents, eventCount, triggerAtts); + } + + // Put a break in the file + // + outStream << endl << endl; + + // Send out the studio - a self contained command + // + outStream << strtoqstr(m_studio.toXmlString()) << endl << endl; + + + // Send out the appearance data + outStream << "" << endl; + outStream << strtoqstr(getComposition().getSegmentColourMap().toXmlString("segmentmap")); + outStream << strtoqstr(getComposition().getGeneralColourMap().toXmlString("generalmap")); + outStream << "" << endl << endl << endl; + + // close the top-level XML tag + // + outStream << "\n"; + + // check that all went ok + // + if (fileCompressedDevice->status() != IO_Ok) { + errMsg = i18n(QString("Error while writing on '%1'").arg(filename)); + delete fileCompressedDevice; + return false; + } + + fileCompressedDevice->close(); + + delete fileCompressedDevice; // DO NOT USE outStream AFTER THIS POINT + + RG_DEBUG << endl << "RosegardenGUIDoc::saveDocument() finished\n"; + + if (!autosave) { + emit documentModified(false); + setModified(false); + m_commandHistory->documentSaved(); + delete progressDlg; + } else { + progress->setProgress(0); + } + + setAutoSaved(true); + + return true; +} + +bool RosegardenGUIDoc::exportStudio(const QString& filename, + std::vector devices) +{ + Profiler profiler("RosegardenGUIDoc::exportStudio"); + RG_DEBUG << "RosegardenGUIDoc::exportStudio(" + << filename << ")\n"; + + KFilterDev* fileCompressedDevice = static_cast(KFilterDev::deviceForFile(filename, "application/x-gzip")); + fileCompressedDevice->setOrigFileName("audio/x-rosegarden-device"); + fileCompressedDevice->open(IO_WriteOnly); + QTextStream outStream(fileCompressedDevice); + outStream.setEncoding(QTextStream::UnicodeUTF8); + + // output XML header + // + outStream << "\n" + << "\n" + << "\n"; + + // Send out the studio - a self contained command + // + outStream << strtoqstr(m_studio.toXmlString(devices)) << endl << endl; + + // close the top-level XML tag + // + outStream << "\n"; + + delete fileCompressedDevice; + + RG_DEBUG << endl << "RosegardenGUIDoc::exportStudio() finished\n"; + return true; +} + +void RosegardenGUIDoc::saveSegment(QTextStream& outStream, Segment *segment, + KProgress* progress, long totalEvents, long &count, + QString extraAttributes) +{ + QString time; + + outStream << QString("getTrack()) + .arg(segment->getStartTime()); + + if (extraAttributes) + outStream << extraAttributes << " "; + + outStream << "label=\"" << + strtoqstr(XmlExportable::encode(segment->getLabel())); + + if (segment->isRepeating()) { + outStream << "\" repeat=\"true"; + } + + if (segment->getTranspose() != 0) { + outStream << "\" transpose=\"" << segment->getTranspose(); + } + + if (segment->getDelay() != 0) { + outStream << "\" delay=\"" << segment->getDelay(); + } + + if (segment->getRealTimeDelay() != RealTime::zeroTime) { + outStream << "\" rtdelaysec=\"" << segment->getRealTimeDelay().sec + << "\" rtdelaynsec=\"" << segment->getRealTimeDelay().nsec; + } + + if (segment->getColourIndex() != 0) { + outStream << "\" colourindex=\"" << segment->getColourIndex(); + } + + if (segment->getSnapGridSize() != -1) { + outStream << "\" snapgridsize=\"" << segment->getSnapGridSize(); + } + + if (segment->getViewFeatures() != 0) { + outStream << "\" viewfeatures=\"" << segment->getViewFeatures(); + } + + const timeT *endMarker = segment->getRawEndMarkerTime(); + if (endMarker) { + outStream << "\" endmarker=\"" << *endMarker; + } + + if (segment->getType() == Segment::Audio) { + + outStream << "\" type=\"audio\" " + << "file=\"" + << segment->getAudioFileId(); + + if (segment->getStretchRatio() != 1.f && + segment->getStretchRatio() != 0.f) { + + outStream << "\" unstretched=\"" + << segment->getUnstretchedFileId() + << "\" stretch=\"" + << segment->getStretchRatio(); + } + + outStream << "\">\n"; + + // convert out - should do this as XmlExportable really + // once all this code is centralised + // + time.sprintf("%d.%06d", segment->getAudioStartTime().sec, + segment->getAudioStartTime().usec()); + + outStream << " \n"; + + time.sprintf("%d.%06d", segment->getAudioEndTime().sec, + segment->getAudioEndTime().usec()); + + outStream << " \n"; + + if (segment->isAutoFading()) { + time.sprintf("%d.%06d", segment->getFadeInTime().sec, + segment->getFadeInTime().usec()); + + outStream << " \n"; + + time.sprintf("%d.%06d", segment->getFadeOutTime().sec, + segment->getFadeOutTime().usec()); + + outStream << " \n"; + } + + } else // Internal type + { + outStream << "\">\n"; + + bool inChord = false; + timeT chordStart = 0, chordDuration = 0; + timeT expectedTime = segment->getStartTime(); + + for (Segment::iterator i = segment->begin(); + i != segment->end(); ++i) { + + timeT absTime = (*i)->getAbsoluteTime(); + + Segment::iterator nextEl = i; + ++nextEl; + + if (nextEl != segment->end() && + (*nextEl)->getAbsoluteTime() == absTime && + (*i)->getDuration() != 0 && + !inChord) { + outStream << "" << endl; + inChord = true; + chordStart = absTime; + chordDuration = 0; + } + + if (inChord && (*i)->getDuration() > 0) + if (chordDuration == 0 || (*i)->getDuration() < chordDuration) + chordDuration = (*i)->getDuration(); + + outStream << '\t' + << strtoqstr((*i)->toXmlString(expectedTime)) << endl; + + if (nextEl != segment->end() && + (*nextEl)->getAbsoluteTime() != absTime && + inChord) { + outStream << "\n"; + inChord = false; + expectedTime = chordStart + chordDuration; + } else if (inChord) { + expectedTime = absTime; + } else { + expectedTime = absTime + (*i)->getDuration(); + } + + if ((++count % 500 == 0) && progress) { + progress->setValue(count * 100 / totalEvents); + } + } + + if (inChord) { + outStream << "\n"; + } + + // Add EventRulers to segment - we call them controllers because of + // a historical mistake in naming them. My bad. RWB. + // + Segment::EventRulerList list = segment->getEventRulerList(); + + if (list.size()) { + outStream << "\n"; // gui elements + Segment::EventRulerListConstIterator it; + for (it = list.begin(); it != list.end(); ++it) { + outStream << " m_type); + + if ((*it)->m_type == Controller::EventType) { + outStream << "\" value =\"" << (*it)->m_controllerValue; + } + + outStream << "\"/>\n"; + } + outStream << "\n"; + } + + } + + + outStream << "\n"; //------------------------- + +} + +bool RosegardenGUIDoc::isSequencerRunning() +{ + RosegardenGUIApp* parentApp = dynamic_cast(parent()); + if (!parentApp) { + RG_DEBUG << "RosegardenGUIDoc::isSequencerRunning() : parentApp == 0\n"; + return false; + } + + return parentApp->isSequencerRunning(); +} + +bool +RosegardenGUIDoc::xmlParse(QString fileContents, QString &errMsg, + ProgressDialog *progress, + unsigned int elementCount, + bool permanent, + bool &cancelled) +{ + cancelled = false; + + RoseXmlHandler handler(this, elementCount, permanent); + + if (progress) { + connect(&handler, SIGNAL(setProgress(int)), + progress->progressBar(), SLOT(setValue(int))); + connect(&handler, SIGNAL(setOperationName(QString)), + progress, SLOT(slotSetOperationName(QString))); + connect(&handler, SIGNAL(incrementProgress(int)), + progress->progressBar(), SLOT(advance(int))); + connect(progress, SIGNAL(cancelClicked()), + &handler, SLOT(slotCancel())); + } + + QXmlInputSource source; + source.setData(fileContents); + QXmlSimpleReader reader; + reader.setContentHandler(&handler); + reader.setErrorHandler(&handler); + + START_TIMING; + bool ok = reader.parse(source); + PRINT_ELAPSED("RosegardenGUIDoc::xmlParse (reader.parse())"); + + if (!ok) { + + if (handler.isCancelled()) { + RG_DEBUG << "File load cancelled\n"; + KStartupLogo::hideIfStillThere(); + KMessageBox::information(0, i18n("File load cancelled")); + cancelled = true; + return true; + } else { + errMsg = handler.errorString(); + } + + } else { + + if (getSequenceManager() && + !(getSequenceManager()->getSoundDriverStatus() & AUDIO_OK)) { + + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + + if (handler.hasActiveAudio() || + (m_pluginManager && !handler.pluginsNotFound().empty())) { + +#ifdef HAVE_LIBJACK + KMessageBox::information + (0, i18n("

Audio and plugins not available

This composition uses audio files or plugins, but Rosegarden is currently running without audio because the JACK audio server was not available on startup.

Please exit Rosegarden, start the JACK audio server and re-start Rosegarden if you wish to load this complete composition.

WARNING: If you re-save this composition, all audio and plugin data and settings in it will be lost.

")); +#else + KMessageBox::information + (0, i18n("

Audio and plugins not available

This composition uses audio files or plugins, but you are running a version of Rosegarden that was compiled without audio support.

WARNING: If you re-save this composition from this version of Rosegarden, all audio and plugin data and settings in it will be lost.

")); +#endif + } + CurrentProgressDialog::thaw(); + + } else { + + bool shownWarning = false; + + int sr = 0; + if (getSequenceManager()) { + sr = getSequenceManager()->getSampleRate(); + } + + int er = m_audioFileManager.getExpectedSampleRate(); + + std::set rates = m_audioFileManager.getActualSampleRates(); + bool other = false; + bool mixed = (rates.size() > 1); + for (std::set::iterator i = rates.begin(); + i != rates.end(); ++i) { + if (*i != sr) { + other = true; + break; + } + } + + if (sr != 0 && + handler.hasActiveAudio() && + ((er != 0 && er != sr) || + (other && !mixed))) { + + if (er == 0) er = *rates.begin(); + + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + + KMessageBox::information(0, i18n("

Incorrect audio sample rate

This composition contains audio files that were recorded or imported with the audio server running at a different sample rate (%1 Hz) from the current JACK server sample rate (%2 Hz).

Rosegarden will play this composition at the correct speed, but any audio files in it will probably sound awful.

Please consider re-starting the JACK server at the correct rate (%3 Hz) and re-loading this composition before you do any more work with it.

").arg(er).arg(sr).arg(er)); + + CurrentProgressDialog::thaw(); + shownWarning = true; + + } else if (sr != 0 && mixed) { + + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + + KMessageBox::information(0, i18n("

Inconsistent audio sample rates

This composition contains audio files at more than one sample rate.

Rosegarden will play them at the correct speed, but any audio files that were recorded or imported at rates different from the current JACK server sample rate (%1 Hz) will probably sound awful.

Please see the audio file manager dialog for more details, and consider resampling any files that are at the wrong rate.

").arg(sr), + i18n("Inconsistent sample rates"), + "file-load-inconsistent-samplerates"); + + CurrentProgressDialog::thaw(); + shownWarning = true; + } + + if (m_pluginManager && !handler.pluginsNotFound().empty()) { + + // We only warn if a plugin manager is present, so as + // to avoid warnings when importing a studio from + // another file (which is the normal case in which we + // have no plugin manager). + + QString msg(i18n("

Plugins not found

The following audio plugins could not be loaded:

    ")); + + for (std::set::iterator i = handler.pluginsNotFound().begin(); + i != handler.pluginsNotFound().end(); ++i) { + QString ident = *i; + QString type, soName, label; + PluginIdentifier::parseIdentifier(ident, type, soName, label); + QString pluginFileName = QFileInfo(soName).fileName(); + msg += i18n("
  • %1 (from %2)
  • ").arg(label).arg(pluginFileName); + } + msg += "
"; + + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + KMessageBox::information(0, msg); + CurrentProgressDialog::thaw(); + shownWarning = true; + + } + + if (handler.isDeprecated() && !shownWarning) { + + QString msg(i18n("This file contains one or more old element types that are now deprecated.\nSupport for these elements may disappear in future versions of Rosegarden.\nWe recommend you re-save this file from this version of Rosegarden to ensure that it can still be re-loaded in future versions.")); + slotDocumentModified(); // so file can be re-saved immediately + + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + KMessageBox::information(0, msg); + CurrentProgressDialog::thaw(); + } + } + } + + return ok; +} + +void +RosegardenGUIDoc::insertRecordedMidi(const MappedComposition &mC) +{ + RG_DEBUG << "RosegardenGUIDoc::insertRecordedMidi: " << mC.size() << " events" << endl; + + // Just create a new record Segment if we don't have one already. + // Make sure we don't recreate the record segment if it's already + // freed. + // + + //Track *midiRecordTrack = 0; + + const Composition::recordtrackcontainer &tr = + getComposition().getRecordTracks(); + + bool haveMIDIRecordTrack = false; + + for (Composition::recordtrackcontainer::const_iterator i = + tr.begin(); i != tr.end(); ++i) { + TrackId tid = (*i); + Track *track = getComposition().getTrackById(tid); + if (track) { + Instrument *instrument = + m_studio.getInstrumentById(track->getInstrument()); + if (instrument->getType() == Instrument::Midi || + instrument->getType() == Instrument::SoftSynth) { + haveMIDIRecordTrack = true; + if (!m_recordMIDISegments[track->getInstrument()]) { + addRecordMIDISegment(track->getId()); + } + break; + } + } + } + + if (!haveMIDIRecordTrack) + return ; + + if (mC.size() > 0) { + MappedComposition::const_iterator i; + Event *rEvent = 0; + timeT duration, absTime; + timeT updateFrom = m_composition.getDuration(); + bool haveNotes = false; + + // process all the incoming MappedEvents + // + for (i = mC.begin(); i != mC.end(); ++i) { + if ((*i)->getRecordedDevice() == Device::CONTROL_DEVICE) { + // send to GUI + RosegardenGUIView *v; + for (v = m_viewList.first(); v != 0; v = m_viewList.next()) { + v->slotControllerDeviceEventReceived(*i); + } + continue; + } + + absTime = m_composition.getElapsedTimeForRealTime((*i)->getEventTime()); + + /* This is incorrect, unless the tempo at absTime happens to + be the same as the tempo at zero and there are no tempo + changes within the given duration after either zero or + absTime + + duration = m_composition.getElapsedTimeForRealTime((*i)->getDuration()); + */ + duration = m_composition. + getElapsedTimeForRealTime((*i)->getEventTime() + + (*i)->getDuration()) - absTime; + + rEvent = 0; + bool isNoteOn = false; + int pitch = 0; + int channel = (*i)->getRecordedChannel(); + int device = (*i)->getRecordedDevice(); + + TrackId tid = (*i)->getTrackId(); + Track *track = getComposition().getTrackById(tid); + + switch ((*i)->getType()) { + case MappedEvent::MidiNote: + + // adjust the notation by the opposite of track transpose so the + // resulting recording will play correctly, and notation will + // read correctly; tentative fix for #1597279 + pitch = (*i)->getPitch() - track->getTranspose(); + + if ((*i)->getDuration() < RealTime::zeroTime) { + + // it's a note-on; give it a default duration + // for insertion into the segment, and make a + // mental note to stick it in the note-on map + // for when we see the corresponding note-off + + duration = + Note(Note::Crotchet).getDuration(); + isNoteOn = true; + + rEvent = new Event(Note::EventType, + absTime, + duration); + + rEvent->set + (PITCH, pitch); + rEvent->set + (VELOCITY, (*i)->getVelocity()); + + } else { + + // it's a note-off + + //NoteOnMap::iterator mi = m_noteOnEvents.find((*i)->getPitch()); + PitchMap *pm = &m_noteOnEvents[device][channel]; + PitchMap::iterator mi = pm->find(pitch); + + if (mi != pm->end()) { + // modify the previously held note-on event, + // instead of assigning to rEvent + NoteOnRecSet rec_vec = mi->second; + Event *oldEv = *rec_vec[0].m_segmentIterator; + Event *newEv = new Event + (*oldEv, oldEv->getAbsoluteTime(), duration); + + newEv->set + (RECORDED_CHANNEL, channel); + NoteOnRecSet *replaced = + replaceRecordedEvent(rec_vec, newEv); + delete replaced; + pm->erase(mi); + if (updateFrom > newEv->getAbsoluteTime()) { + updateFrom = newEv->getAbsoluteTime(); + } + haveNotes = true; + delete newEv; + // at this point we could quantize the bar if we were + // tracking in a notation view + } else { + std::cerr << " WARNING: NOTE OFF received without corresponding NOTE ON" << std::endl; + } + } + + break; + + case MappedEvent::MidiPitchBend: + rEvent = PitchBend + ((*i)->getData1(), (*i)->getData2()).getAsEvent(absTime); + rEvent->set + (RECORDED_CHANNEL, channel); + break; + + case MappedEvent::MidiController: + rEvent = Controller + ((*i)->getData1(), (*i)->getData2()).getAsEvent(absTime); + rEvent->set + (RECORDED_CHANNEL, channel); + break; + + case MappedEvent::MidiProgramChange: + RG_DEBUG << "RosegardenGUIDoc::insertRecordedMidi()" + << " - got Program Change (unsupported)" + << endl; + break; + + case MappedEvent::MidiKeyPressure: + rEvent = KeyPressure + ((*i)->getData1(), (*i)->getData2()).getAsEvent(absTime); + rEvent->set + (RECORDED_CHANNEL, channel); + break; + + case MappedEvent::MidiChannelPressure: + rEvent = ChannelPressure + ((*i)->getData1()).getAsEvent(absTime); + rEvent->set + (RECORDED_CHANNEL, channel); + break; + + case MappedEvent::MidiSystemMessage: + channel = -1; + if ((*i)->getData1() == MIDI_SYSTEM_EXCLUSIVE) { + rEvent = SystemExclusive + (DataBlockRepository::getDataBlockForEvent((*i))).getAsEvent(absTime); + } + + // Ignore other SystemMessage events for the moment + // + + break; + + case MappedEvent::MidiNoteOneShot: + RG_DEBUG << "RosegardenGUIDoc::insertRecordedMidi() - " + << "GOT UNEXPECTED MappedEvent::MidiNoteOneShot" + << endl; + break; + + // Audio control signals - ignore these + case MappedEvent::Audio: + case MappedEvent::AudioCancel: + case MappedEvent::AudioLevel: + case MappedEvent::AudioStopped: + case MappedEvent::AudioGeneratePreview: + case MappedEvent::SystemUpdateInstruments: + break; + + default: + RG_DEBUG << "RosegardenGUIDoc::insertRecordedMidi() - " + << "GOT UNSUPPORTED MAPPED EVENT" + << endl; + break; + } + + // sanity check + // + if (rEvent == 0) + continue; + + // Set the recorded input port + // + rEvent->set + (RECORDED_PORT, device); + + // Set the proper start index (if we haven't before) + // + for ( RecordingSegmentMap::const_iterator it = m_recordMIDISegments.begin(); + it != m_recordMIDISegments.end(); ++it) { + Segment *recordMIDISegment = it->second; + if (recordMIDISegment->size() == 0) { + recordMIDISegment->setStartTime (m_composition.getBarStartForTime(absTime)); + recordMIDISegment->fillWithRests(absTime); + } + } + + // Now insert the new event + // + insertRecordedEvent(rEvent, device, channel, isNoteOn); + delete rEvent; + } + + if (haveNotes) { + + KConfig* config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + + int tracking = config->readUnsignedNumEntry("recordtracking", 0); + if (tracking == 1) { // notation + for ( RecordingSegmentMap::const_iterator it = m_recordMIDISegments.begin(); + it != m_recordMIDISegments.end(); ++it) { + Segment *recordMIDISegment = it->second; + + EventQuantizeCommand *command = new EventQuantizeCommand + (*recordMIDISegment, + updateFrom, + recordMIDISegment->getEndTime(), + "Notation Options", + true); + // don't add to history + command->execute(); + } + } + + // this signal is currently unused - leaving just in case + // recording segments are updated through the SegmentObserver::eventAdded() interface + // emit recordMIDISegmentUpdated(m_recordMIDISegment, updateFrom); + } + } +} + +void +RosegardenGUIDoc::updateRecordingMIDISegment() +{ + //RG_DEBUG << "RosegardenGUIDoc::updateRecordingMIDISegment" << endl; + + if (m_recordMIDISegments.size() == 0) { + // make this call once to create one + insertRecordedMidi(MappedComposition()); + if (m_recordMIDISegments.size() == 0) + return ; // not recording any MIDI + } + + //RG_DEBUG << "RosegardenGUIDoc::updateRecordingMIDISegment: have record MIDI segment" << endl; + + NoteOnMap tweakedNoteOnEvents; + for (NoteOnMap::iterator mi = m_noteOnEvents.begin(); + mi != m_noteOnEvents.end(); ++mi) + for (ChanMap::iterator cm = mi->second.begin(); + cm != mi->second.end(); ++cm) + for (PitchMap::iterator pm = cm->second.begin(); + pm != cm->second.end(); ++pm) { + + // anything in the note-on map should be tweaked so as to end + // at the recording pointer + NoteOnRecSet rec_vec = pm->second; + if (rec_vec.size() > 0) { + Event *oldEv = *rec_vec[0].m_segmentIterator; + Event *newEv = new Event( + *oldEv, oldEv->getAbsoluteTime(), + m_composition.getPosition() - oldEv->getAbsoluteTime() ); + + tweakedNoteOnEvents[mi->first][cm->first][pm->first] = + *replaceRecordedEvent(rec_vec, newEv); + delete newEv; + } + } + m_noteOnEvents = tweakedNoteOnEvents; +} + +RosegardenGUIDoc::NoteOnRecSet * + +RosegardenGUIDoc::replaceRecordedEvent(NoteOnRecSet& rec_vec, Event *fresh) +{ + NoteOnRecSet *new_vector = new NoteOnRecSet(); + for ( NoteOnRecSet::const_iterator i = rec_vec.begin(); i != rec_vec.end(); ++i) { + Segment *recordMIDISegment = i->m_segment; + recordMIDISegment->erase(i->m_segmentIterator); + NoteOnRec noteRec; + noteRec.m_segment = recordMIDISegment; + noteRec.m_segmentIterator = recordMIDISegment->insert(new Event(*fresh)); + new_vector->push_back(noteRec); + } + return new_vector; +} + +void +RosegardenGUIDoc::storeNoteOnEvent(Segment *s, Segment::iterator it, int device, int channel) +{ + NoteOnRec record; + record.m_segment = s; + record.m_segmentIterator = it; + int pitch = (*it)->get + (PITCH); + m_noteOnEvents[device][channel][pitch].push_back(record); +} + +void +RosegardenGUIDoc::insertRecordedEvent(Event *ev, int device, int channel, bool isNoteOn) +{ + Segment::iterator it; + for ( RecordingSegmentMap::const_iterator i = m_recordMIDISegments.begin(); + i != m_recordMIDISegments.end(); ++i) { + Segment *recordMIDISegment = i->second; + TrackId tid = recordMIDISegment->getTrack(); + Track *track = getComposition().getTrackById(tid); + if (track) { + //Instrument *instrument = + // m_studio.getInstrumentById(track->getInstrument()); + int chan_filter = track->getMidiInputChannel(); + int dev_filter = track->getMidiInputDevice(); + if (((chan_filter < 0) || (chan_filter == channel)) && + ((dev_filter == int(Device::ALL_DEVICES)) || (dev_filter == device))) { + it = recordMIDISegment->insert(new Event(*ev)); + if (isNoteOn) { + storeNoteOnEvent(recordMIDISegment, it, device, channel); + } + RG_DEBUG << "RosegardenGUIDoc::insertRecordedEvent() - matches filter" << endl; + } else { + RG_DEBUG << "RosegardenGUIDoc::insertRecordedEvent() - unmatched event discarded" << endl; + } + } + } +} + +void +RosegardenGUIDoc::stopRecordingMidi() +{ + RG_DEBUG << "RosegardenGUIDoc::stopRecordingMidi" << endl; + + Composition &c = getComposition(); + + timeT endTime = c.getBarEnd(0); + + bool haveMeaning = false; + timeT earliestMeaning = 0; + + std::vector toErase; + + for (RecordingSegmentMap::iterator i = m_recordMIDISegments.begin(); + i != m_recordMIDISegments.end(); + ++i) { + + Segment *s = i->second; + + bool meaningless = true; + + for (Segment::iterator i = s->begin(); i != s->end(); ++i) { + + if ((*i)->isa(Clef::EventType)) continue; + + // no rests in the segment yet, so anything else is meaningful + meaningless = false; + + if (!haveMeaning || (*i)->getAbsoluteTime() < earliestMeaning) { + earliestMeaning = (*i)->getAbsoluteTime(); + } + + haveMeaning = true; + break; + } + + if (meaningless) { + if (!c.deleteSegment(s)) delete s; + toErase.push_back(i); + } else { + if (endTime < s->getEndTime()) { + endTime = s->getEndTime(); + } + } + } + + for (int i = 0; i < toErase.size(); ++i) { + m_recordMIDISegments.erase(toErase[i]); + } + + if (!haveMeaning) return; + + RG_DEBUG << "RosegardenGUIDoc::stopRecordingMidi: have something" << endl; + + // adjust the clef timings so as not to leave a clef stranded at + // the start of an otherwise empty count-in + + timeT meaningfulBarStart = c.getBarStartForTime(earliestMeaning); + + for (RecordingSegmentMap::iterator i = m_recordMIDISegments.begin(); + i != m_recordMIDISegments.end(); + ++i) { + + Segment *s = i->second; + Segment::iterator i = s->begin(); + + if (i == s->end() || !(*i)->isa(Clef::EventType)) continue; + + if ((*i)->getAbsoluteTime() < meaningfulBarStart) { + Event *e = new Event(**i, meaningfulBarStart); + s->erase(i); + s->insert(e); + } + } + + for (NoteOnMap::iterator mi = m_noteOnEvents.begin(); + mi != m_noteOnEvents.end(); ++mi) { + + for (ChanMap::iterator cm = mi->second.begin(); + cm != mi->second.end(); ++cm) { + + for (PitchMap::iterator pm = cm->second.begin(); + pm != cm->second.end(); ++pm) { + + // anything remaining in the note-on map should be + // made to end at the end of the segment + + NoteOnRecSet rec_vec = pm->second; + + if (rec_vec.size() > 0) { + Event *oldEv = *rec_vec[0].m_segmentIterator; + Event *newEv = new Event + (*oldEv, oldEv->getAbsoluteTime(), + endTime - oldEv->getAbsoluteTime()); + NoteOnRecSet *replaced = + replaceRecordedEvent(rec_vec, newEv); + delete newEv; + delete replaced; + } + } + } + } + m_noteOnEvents.clear(); + + while (!m_recordMIDISegments.empty()) { + + Segment *s = m_recordMIDISegments.begin()->second; + m_recordMIDISegments.erase(m_recordMIDISegments.begin()); + + // the record segment will have already been added to the + // composition if there was anything in it; otherwise we don't + // need to do so + + if (s->getComposition() == 0) { + delete s; + continue; + } + + // Quantize for notation only -- doesn't affect performance timings. + KMacroCommand *command = new KMacroCommand(i18n("Insert Recorded MIDI")); + + command->addCommand(new EventQuantizeCommand + (*s, + s->getStartTime(), + s->getEndTime(), + "Notation Options", + true)); + + command->addCommand(new NormalizeRestsCommand + (*s, + c.getBarStartForTime(s->getStartTime()), + c.getBarEndForTime(s->getEndTime()))); + + command->addCommand(new SegmentRecordCommand(s)); + + m_commandHistory->addCommand(command); + } + + emit stoppedMIDIRecording(); + + slotUpdateAllViews(0); +} + +void +RosegardenGUIDoc::prepareAudio() +{ + if (!isSequencerRunning()) + return ; + + QCString replyType; + QByteArray replyData; + + // Clear down the sequencer AudioFilePlayer object + // + rgapp->sequencerSend("clearAllAudioFiles()"); + + for (AudioFileManagerIterator it = m_audioFileManager.begin(); + it != m_audioFileManager.end(); it++) { + + QByteArray data; + QDataStream streamOut(data, IO_WriteOnly); + + // We have to pass the filename as a QString + // + streamOut << QString(strtoqstr((*it)->getFilename())); + streamOut << (int)(*it)->getId(); + + rgapp->sequencerCall("addAudioFile(QString, int)", replyType, replyData, data); + QDataStream streamIn(replyData, IO_ReadOnly); + int result; + streamIn >> result; + if (!result) { + RG_DEBUG << "prepareAudio() - failed to add file \"" + << (*it)->getFilename() << "\"" << endl; + } + } +} + +void +RosegardenGUIDoc::slotSetPointerPosition(timeT t) +{ + m_composition.setPosition(t); + emit pointerPositionChanged(t); +} + +void +RosegardenGUIDoc::setPlayPosition(timeT t) +{ + emit playPositionChanged(t); +} + +void +RosegardenGUIDoc::setLoop(timeT t0, timeT t1) +{ + m_composition.setLoopStart(t0); + m_composition.setLoopEnd(t1); + emit loopChanged(t0, t1); +} + +void +RosegardenGUIDoc::syncDevices() +{ + Profiler profiler("RosegardenGUIDoc::syncDevices", true); + + // Start up the sequencer + // + int timeout = 60; + + while (isSequencerRunning() && !rgapp->isSequencerRegistered() && timeout > 0) { + RG_DEBUG << "RosegardenGUIDoc::syncDevices - " + << "waiting for Sequencer to come up" << endl; + + ProgressDialog::processEvents(); + sleep(1); // 1s + --timeout; + } + + if (isSequencerRunning() && !rgapp->isSequencerRegistered() && timeout == 0) { + + // Give up, kill sequencer if possible, and report + KProcess *proc = new KProcess; + *proc << "/usr/bin/killall"; + *proc << "rosegardensequencer"; + *proc << "lt-rosegardensequencer"; + + proc->start(KProcess::Block, KProcess::All); + + if (proc->exitStatus()) { + RG_DEBUG << "couldn't kill any sequencer processes" << endl; + } + + delete proc; + RosegardenGUIApp *app = (RosegardenGUIApp*)parent(); + app->slotSequencerExited(0); + return ; + } + + if (!isSequencerRunning()) + return ; + + // Set the default timer first. We only do this first time and + // when changed in the configuration dialog. + static bool setTimer = false; + if (!setTimer) { + kapp->config()->setGroup(SequencerOptionsConfigGroup); + QString currentTimer = getCurrentTimer(); + currentTimer = kapp->config()->readEntry("timer", currentTimer); + setCurrentTimer(currentTimer); + setTimer = true; + } + + QByteArray replyData; + QCString replyType; + + // Get number of devices the sequencer has found + // + rgapp->sequencerCall("getDevices()", replyType, replyData, RosegardenApplication::Empty, true); + + unsigned int devices = 0; + + if (replyType == "unsigned int") { + QDataStream reply(replyData, IO_ReadOnly); + reply >> devices; + } else { + RG_DEBUG << "RosegardenGUIDoc::syncDevices - " + << "got unknown returntype from getDevices()" << endl; + return ; + } + + RG_DEBUG << "RosegardenGUIDoc::syncDevices - devices = " + << devices << endl; + + for (unsigned int i = 0; i < devices; i++) { + + RG_DEBUG << "RosegardenGUIDoc::syncDevices - i = " + << i << endl; + + getMappedDevice(i); + } + + RG_DEBUG << "RosegardenGUIDoc::syncDevices - " + << "Sequencer alive - Instruments synced" << endl; + + + // Force update of view on current track selection + // + kapp->config()->setGroup(GeneralOptionsConfigGroup); + bool opt = kapp->config()->readBoolEntry("Show Track labels", true); + TrackLabel::InstrumentTrackLabels labels = TrackLabel::ShowInstrument; + if (opt) + labels = TrackLabel::ShowTrack; + + RosegardenGUIView *w; + for (w = m_viewList.first(); w != 0; w = m_viewList.next()) { + w->slotSelectTrackSegments(m_composition.getSelectedTrack()); + w->getTrackEditor()->getTrackButtons()->changeTrackInstrumentLabels(labels); + } + + emit devicesResyncd(); +} + +void +RosegardenGUIDoc::getMappedDevice(DeviceId id) +{ + QByteArray data; + QByteArray replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + + arg << (unsigned int)id; + + rgapp->sequencerCall("getMappedDevice(unsigned int)", + replyType, replyData, data); + + MappedDevice *mD = new MappedDevice(); + QDataStream reply(replyData, IO_ReadOnly); + + if (replyType == "MappedDevice") + // unfurl + reply >> mD; + else + return ; + + // See if we've got this device already + // + Device *device = m_studio.getDevice(id); + + if (mD->getId() == Device::NO_DEVICE) { + if (device) + m_studio.removeDevice(id); + delete mD; + return ; + } + + if (mD->size() == 0) { + // no instruments is OK for a record device + if (mD->getType() != Device::Midi || + mD->getDirection() != MidiDevice::Record) { + + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice() - " + << "no instruments found" << endl; + if (device) + m_studio.removeDevice(id); + delete mD; + return ; + } + } + + bool hadDeviceAlready = (device != 0); + + if (!hadDeviceAlready) { + if (mD->getType() == Device::Midi) { + device = + new MidiDevice + (id, + mD->getName(), + mD->getDirection()); + + dynamic_cast(device) + ->setRecording(mD->isRecording()); + + m_studio.addDevice(device); + + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - " + << "adding MIDI Device \"" + << device->getName() << "\" id = " << id + << " direction = " << mD->getDirection() + << " recording = " << mD->isRecording() + << endl; + } else if (mD->getType() == Device::SoftSynth) { + device = new SoftSynthDevice(id, mD->getName()); + m_studio.addDevice(device); + + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - " + << "adding soft synth Device \"" + << device->getName() << "\" id = " << id << endl; + } else if (mD->getType() == Device::Audio) { + device = new AudioDevice(id, mD->getName()); + m_studio.addDevice(device); + + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - " + << "adding audio Device \"" + << device->getName() << "\" id = " << id << endl; + } else { + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - " + << "unknown device - \"" << mD->getName() + << "\" (type = " + << mD->getType() << ")\n"; + return ; + } + } + + if (hadDeviceAlready) { + // direction might have changed + if (mD->getType() == Device::Midi) { + MidiDevice *midid = + dynamic_cast(device); + if (midid) { + midid->setDirection(mD->getDirection()); + midid->setRecording(mD->isRecording()); + } + } + } + + std::string connection(mD->getConnection()); + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice - got \"" << connection + << "\", direction " << mD->getDirection() + << " recording " << mD->isRecording() + << endl; + device->setConnection(connection); + + Instrument *instrument; + MappedDeviceIterator it; + + InstrumentList existingInstrs(device->getAllInstruments()); + + for (it = mD->begin(); it != mD->end(); it++) { + InstrumentId instrumentId = (*it)->getId(); + + bool haveInstrument = false; + for (InstrumentList::iterator iit = existingInstrs.begin(); + iit != existingInstrs.end(); ++iit) { + + if ((*iit)->getId() == instrumentId) { + haveInstrument = true; + break; + } + } + + if (!haveInstrument) { + RG_DEBUG << "RosegardenGUIDoc::getMappedDevice: new instr " << (*it)->getId() << endl; + instrument = new Instrument((*it)->getId(), + (*it)->getType(), + (*it)->getName(), + (*it)->getChannel(), + device); + device->addInstrument(instrument); + } + } + + delete mD; +} + +void +RosegardenGUIDoc::addRecordMIDISegment(TrackId tid) +{ + RG_DEBUG << "RosegardenGUIDoc::addRecordMIDISegment(" << tid << ")" << endl; +// std::cerr << kdBacktrace() << std::endl; + + Segment *recordMIDISegment; + + recordMIDISegment = new Segment(); + recordMIDISegment->setTrack(tid); + recordMIDISegment->setStartTime(m_recordStartTime); + + // Set an appropriate segment label + // + std::string label = ""; + + Track *track = m_composition.getTrackById(tid); + if (track) { + if (track->getPresetLabel() != "") { + label = track->getPresetLabel(); + } else if (track->getLabel() == "") { + Instrument *instr = + m_studio.getInstrumentById(track->getInstrument()); + if (instr) { + label = m_studio.getSegmentName(instr->getId()); + } + } else { + label = track->getLabel(); + } + label = qstrtostr(i18n("%1 (recorded)").arg(strtoqstr(label))); + } + + recordMIDISegment->setLabel(label); + + Clef clef = clefIndexToClef(track->getClef()); + recordMIDISegment->insert(clef.getAsEvent + (recordMIDISegment->getStartTime())); + + // set segment transpose, color, highest/lowest playable from track parameters + recordMIDISegment->setTranspose(track->getTranspose()); + recordMIDISegment->setColourIndex(track->getColor()); + recordMIDISegment->setHighestPlayable(track->getHighestPlayable()); + recordMIDISegment->setLowestPlayable(track->getLowestPlayable()); + + m_composition.addSegment(recordMIDISegment); + + m_recordMIDISegments[track->getInstrument()] = recordMIDISegment; + + RosegardenGUIView *w; + for (w = m_viewList.first(); w != 0; w = m_viewList.next()) { + w->getTrackEditor()->getTrackButtons()->slotUpdateTracks(); + } + + emit newMIDIRecordingSegment(recordMIDISegment); +} + +void +RosegardenGUIDoc::addRecordAudioSegment(InstrumentId iid, + AudioFileId auid) +{ + Segment *recordSegment = new Segment + (Segment::Audio); + + // Find the right track + + Track *recordTrack = 0; + + const Composition::recordtrackcontainer &tr = + getComposition().getRecordTracks(); + + for (Composition::recordtrackcontainer::const_iterator i = + tr.begin(); i != tr.end(); ++i) { + TrackId tid = (*i); + Track *track = getComposition().getTrackById(tid); + if (track) { + if (iid == track->getInstrument()) { + recordTrack = track; + break; + } + } + } + + if (!recordTrack) { + RG_DEBUG << "RosegardenGUIDoc::addRecordAudioSegment(" << iid << ", " + << auid << "): No record-armed track found for instrument!" + << endl; + return ; + } + + recordSegment->setTrack(recordTrack->getId()); + recordSegment->setStartTime(m_recordStartTime); + recordSegment->setAudioStartTime(RealTime::zeroTime); + + // Set an appropriate segment label + // + std::string label = ""; + + if (recordTrack) { + if (recordTrack->getLabel() == "") { + + Instrument *instr = + m_studio.getInstrumentById(recordTrack->getInstrument()); + + if (instr) { + label = instr->getName() + std::string(" "); + } + + } else { + label = recordTrack->getLabel() + std::string(" "); + } + + label += std::string("(recorded audio)"); + } + + recordSegment->setLabel(label); + recordSegment->setAudioFileId(auid); + + // set color for audio segment to distinguish it from a MIDI segment on an + // audio track drawn with the pencil (depends on having the current + // autoload.rg or a file derived from it to deliever predictable results, + // but the worst case here is segments drawn in the wrong color when + // adding new segments to old files, which I don't forsee as being enough + // of a problem to be worth cooking up a more robust implementation of + // this new color for new audio segments (DMM) + recordSegment->setColourIndex(GUIPalette::AudioDefaultIndex); + + RG_DEBUG << "RosegardenGUIDoc::addRecordAudioSegment: adding record segment for instrument " << iid << " on track " << recordTrack->getId() << endl; + m_recordAudioSegments[iid] = recordSegment; + + RosegardenGUIView *w; + for (w = m_viewList.first(); w != 0; w = m_viewList.next()) { + w->getTrackEditor()->getTrackButtons()->slotUpdateTracks(); + } + + emit newAudioRecordingSegment(recordSegment); +} + +void +RosegardenGUIDoc::updateRecordingAudioSegments() +{ + const Composition::recordtrackcontainer &tr = + getComposition().getRecordTracks(); + + for (Composition::recordtrackcontainer::const_iterator i = + tr.begin(); i != tr.end(); ++i) { + + TrackId tid = (*i); + Track *track = getComposition().getTrackById(tid); + + if (track) { + + InstrumentId iid = track->getInstrument(); + + if (m_recordAudioSegments[iid]) { + + Segment *recordSegment = m_recordAudioSegments[iid]; + if (!recordSegment->getComposition()) { + + // always insert straight away for audio + m_composition.addSegment(recordSegment); + } + + recordSegment->setAudioEndTime( + m_composition.getRealTimeDifference(recordSegment->getStartTime(), + m_composition.getPosition())); + + } else { + // RG_DEBUG << "RosegardenGUIDoc::updateRecordingAudioSegments: no segment for instr " + // << iid << endl; + } + } + } +} + +void +RosegardenGUIDoc::stopRecordingAudio() +{ + RG_DEBUG << "RosegardenGUIDoc::stopRecordingAudio" << endl; + + for (RecordingSegmentMap::iterator ri = m_recordAudioSegments.begin(); + ri != m_recordAudioSegments.end(); ++ri) { + + Segment *recordSegment = ri->second; + + if (!recordSegment) + continue; + + // set the audio end time + // + recordSegment->setAudioEndTime( + m_composition.getRealTimeDifference(recordSegment->getStartTime(), + m_composition.getPosition())); + + // now add the Segment + RG_DEBUG << "RosegardenGUIDoc::stopRecordingAudio - " + << "got recorded segment" << endl; + + // now move the segment back by the record latency + // + /*!!! + No. I don't like this. + + The record latency doesn't always exist -- for example, if recording + from a synth plugin there is no record latency, and we have no way + here to distinguish. + + The record latency is a total latency figure that actually includes + some play latency, and we compensate for that again on playback (see + bug #1378766). + + The timeT conversion of record latency is approximate in frames, + giving potential phase error. + + Cutting this out won't break any existing files, as the latency + compensation there is already encoded into the file. + + RealTime adjustedStartTime = + m_composition.getElapsedRealTime(recordSegment->getStartTime()) - + m_audioRecordLatency; + + timeT shiftedStartTime = + m_composition.getElapsedTimeForRealTime(adjustedStartTime); + + RG_DEBUG << "RosegardenGUIDoc::stopRecordingAudio - " + << "shifted recorded audio segment by " + << recordSegment->getStartTime() - shiftedStartTime + << " clicks (from " << recordSegment->getStartTime() + << " to " << shiftedStartTime << ")" << endl; + + recordSegment->setStartTime(shiftedStartTime); + */ + } + emit stoppedAudioRecording(); +} + +void +RosegardenGUIDoc::finalizeAudioFile(InstrumentId iid) +{ + RG_DEBUG << "RosegardenGUIDoc::finalizeAudioFile(" << iid << ")" << endl; + + Segment *recordSegment = 0; + recordSegment = m_recordAudioSegments[iid]; + + if (!recordSegment) { + RG_DEBUG << "RosegardenGUIDoc::finalizeAudioFile: Failed to find segment" << endl; + return ; + } + + AudioFile *newAudioFile = m_audioFileManager.getAudioFile + (recordSegment->getAudioFileId()); + if (!newAudioFile) { + std::cerr << "WARNING: RosegardenGUIDoc::finalizeAudioFile: No audio file found for instrument " << iid << " (audio file id " << recordSegment->getAudioFileId() << ")" << std::endl; + return ; + } + + // Create a progress dialog + // + ProgressDialog *progressDlg = new ProgressDialog + (i18n("Generating audio preview..."), 100, (QWidget*)parent()); + progressDlg->setAutoClose(false); + progressDlg->setAutoReset(false); + progressDlg->show(); + + connect(progressDlg, SIGNAL(cancelClicked()), + &m_audioFileManager, SLOT(slotStopPreview())); + + connect(&m_audioFileManager, SIGNAL(setProgress(int)), + progressDlg->progressBar(), SLOT(setValue(int))); + + try { + m_audioFileManager.generatePreview(newAudioFile->getId()); + //!!! mtr just for now?: or better to do this once after the fact? + //!!! m_audioFileManager.generatePreviews(); + } catch (Exception e) { + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + KMessageBox::error(0, strtoqstr(e.getMessage())); + CurrentProgressDialog::thaw(); + } + + delete progressDlg; + + if (!recordSegment->getComposition()) { + getComposition().addSegment(recordSegment); + } + + m_commandHistory->addCommand + (new SegmentRecordCommand(recordSegment)); + + // update views + slotUpdateAllViews(0); + + // Now install the file in the sequencer + // + // We're playing fast and loose with DCOP here - we just send + // this request and carry on regardless otherwise the sequencer + // can just hang our request. We don't risk a call() and we + // don't get a return type. Ugly and hacky but it appears to + // work for me - so hey. + // + QByteArray data; + QDataStream streamOut(data, IO_WriteOnly); + streamOut << QString(strtoqstr(newAudioFile->getFilename())); + streamOut << (int)newAudioFile->getId(); + rgapp->sequencerSend("addAudioFile(QString, int)", data); + + // clear down + m_recordAudioSegments.erase(iid); + emit audioFileFinalized(recordSegment); +} + +RealTime +RosegardenGUIDoc::getAudioPlayLatency() +{ + QCString replyType; + QByteArray replyData; + + if (!rgapp->sequencerCall("getAudioPlayLatency()", replyType, replyData)) { + RG_DEBUG << "RosegardenGUIDoc::getAudioPlayLatency - " + << "Playback failed to contact Rosegarden sequencer" + << endl; + return RealTime::zeroTime; + } + + // ensure the return type is ok + QDataStream streamIn(replyData, IO_ReadOnly); + MappedRealTime result; + streamIn >> result; + + return (result.getRealTime()); +} + +RealTime +RosegardenGUIDoc::getAudioRecordLatency() +{ + QCString replyType; + QByteArray replyData; + + if (!rgapp->sequencerCall("getAudioRecordLatency()", replyType, replyData)) { + RG_DEBUG << "RosegardenGUIDoc::getAudioRecordLatency - " + << "Playback failed to contact Rosegarden sequencer" + << endl; + return RealTime::zeroTime; + } + + // ensure the return type is ok + QDataStream streamIn(replyData, IO_ReadOnly); + MappedRealTime result; + streamIn >> result; + + return (result.getRealTime()); +} + +void +RosegardenGUIDoc::updateAudioRecordLatency() +{ + m_audioRecordLatency = getAudioRecordLatency(); +} + +QStringList +RosegardenGUIDoc::getTimers() +{ + QStringList list; + + QCString replyType; + QByteArray replyData; + + if (!rgapp->sequencerCall("getTimers()", replyType, replyData)) { + RG_DEBUG << "RosegardenGUIDoc::getTimers - " + << "failed to contact Rosegarden sequencer" << endl; + return list; + } + + if (replyType != "unsigned int") { + RG_DEBUG << "RosegardenGUIDoc::getTimers - " + << "wrong reply type (" << replyType << ") from sequencer" << endl; + return list; + } + + QDataStream streamIn(replyData, IO_ReadOnly); + unsigned int count = 0; + streamIn >> count; + + for (unsigned int i = 0; i < count; ++i) { + + QByteArray data; + QDataStream streamOut(data, IO_WriteOnly); + + streamOut << i; + + if (!rgapp->sequencerCall("getTimer(unsigned int)", + replyType, replyData, data)) { + RG_DEBUG << "RosegardenGUIDoc::getTimers - " + << "failed to contact Rosegarden sequencer" << endl; + return list; + } + + if (replyType != "QString") { + RG_DEBUG << "RosegardenGUIDoc::getTimers - " + << "wrong reply type (" << replyType << ") from sequencer" << endl; + return list; + } + + QDataStream streamIn(replyData, IO_ReadOnly); + QString name; + streamIn >> name; + + list.push_back(name); + } + + return list; +} + +QString +RosegardenGUIDoc::getCurrentTimer() +{ + QCString replyType; + QByteArray replyData; + + if (!rgapp->sequencerCall("getCurrentTimer()", replyType, replyData)) { + RG_DEBUG << "RosegardenGUIDoc::getCurrentTimer - " + << "failed to contact Rosegarden sequencer" << endl; + return ""; + } + + if (replyType != "QString") { + RG_DEBUG << "RosegardenGUIDoc::getCurrentTimer - " + << "wrong reply type (" << replyType << ") from sequencer" << endl; + return ""; + } + + QDataStream streamIn(replyData, IO_ReadOnly); + QString name; + streamIn >> name; + return name; +} + +void +RosegardenGUIDoc::setCurrentTimer(QString name) +{ + QCString replyType; + QByteArray replyData; + + QByteArray data; + QDataStream streamOut(data, IO_WriteOnly); + + streamOut << name; + + if (!rgapp->sequencerCall("setCurrentTimer(QString)", + replyType, replyData, data)) { + RG_DEBUG << "RosegardenGUIDoc::setCurrentTimer - " + << "failed to contact Rosegarden sequencer" << endl; + } +} + +void +RosegardenGUIDoc::initialiseControllers() +{ + InstrumentList list = m_studio.getAllInstruments(); + MappedComposition mC; + MappedEvent *mE; + + InstrumentList::iterator it = list.begin(); + for (; it != list.end(); it++) { + if ((*it)->getType() == Instrument::Midi) { + std::vector advancedControls; + + // push all the advanced static controls + // + StaticControllers &list = (*it)->getStaticControllers(); + for (StaticControllerConstIterator cIt = list.begin(); cIt != list.end(); ++cIt) { + advancedControls.push_back(MidiControlPair(cIt->first, cIt->second)); + } + + advancedControls. + push_back( + MidiControlPair(MIDI_CONTROLLER_PAN, + (*it)->getPan())); + advancedControls. + push_back( + MidiControlPair(MIDI_CONTROLLER_VOLUME, + (*it)->getVolume())); + + + std::vector::iterator + iit = advancedControls.begin(); + for (; iit != advancedControls.end(); iit++) { + try { + mE = + new MappedEvent((*it)->getId(), + MappedEvent::MidiController, + iit->first, + iit->second); + } catch (...) { + continue; + } + + mC.insert(mE); + } + } + } + + StudioControl::sendMappedComposition(mC); +} + +void +RosegardenGUIDoc::clearAllPlugins() +{ + //RG_DEBUG << "clearAllPlugins" << endl; + + InstrumentList list = m_studio.getAllInstruments(); + MappedComposition mC; + + InstrumentList::iterator it = list.begin(); + for (; it != list.end(); it++) { + if ((*it)->getType() == Instrument::Audio) { + PluginInstanceIterator pIt = (*it)->beginPlugins(); + + for (; pIt != (*it)->endPlugins(); pIt++) { + if ((*pIt)->getMappedId() != -1) { + if (StudioControl:: + destroyStudioObject((*pIt)->getMappedId()) == false) { + RG_DEBUG << "RosegardenGUIDoc::clearAllPlugins - " + << "couldn't find plugin instance " + << (*pIt)->getMappedId() << endl; + } + } + (*pIt)->clearPorts(); + } + (*it)->emptyPlugins(); + + /* + RG_DEBUG << "RosegardenGUIDoc::clearAllPlugins - " + << "cleared " << (*it)->getName() << endl; + */ + } + } +} + +Clipboard* +RosegardenGUIDoc::getClipboard() +{ + RosegardenGUIApp *app = (RosegardenGUIApp*)parent(); + return app->getClipboard(); +} + +void RosegardenGUIDoc::slotDocColoursChanged() +{ + RG_DEBUG << "RosegardenGUIDoc::slotDocColoursChanged(): emitting docColoursChanged()" << endl; + + emit docColoursChanged(); +} + +void +RosegardenGUIDoc::addOrphanedRecordedAudioFile(QString fileName) +{ + m_orphanedRecordedAudioFiles.push_back(fileName); + slotDocumentModified(); +} + +void +RosegardenGUIDoc::addOrphanedDerivedAudioFile(QString fileName) +{ + m_orphanedDerivedAudioFiles.push_back(fileName); + slotDocumentModified(); +} + +void +RosegardenGUIDoc::notifyAudioFileRemoval(AudioFileId id) +{ + AudioFile *file = 0; + + if (m_audioFileManager.wasAudioFileRecentlyRecorded(id)) { + file = m_audioFileManager.getAudioFile(id); + if (file) addOrphanedRecordedAudioFile(file->getFilename()); + return; + } + + if (m_audioFileManager.wasAudioFileRecentlyDerived(id)) { + file = m_audioFileManager.getAudioFile(id); + if (file) addOrphanedDerivedAudioFile(file->getFilename()); + return; + } +} + +} +#include "RosegardenGUIDoc.moc" diff --git a/src/document/RosegardenGUIDoc.h b/src/document/RosegardenGUIDoc.h new file mode 100644 index 0000000..1cdc88e --- /dev/null +++ b/src/document/RosegardenGUIDoc.h @@ -0,0 +1,733 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ROSEGARDENGUIDOC_H_ +#define _RG_ROSEGARDENGUIDOC_H_ + +#include "base/Composition.h" +#include "base/Configuration.h" +#include "base/Device.h" +#include "base/MidiProgram.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/Studio.h" +#include "gui/application/RosegardenGUIApp.h" +#include "gui/editors/segment/segmentcanvas/AudioPreviewThread.h" +#include +#include "sound/AudioFileManager.h" +// #include (fixes problem for Adam Dingle) +#include +#include +#include +#include +#include "base/Event.h" + + +class QWidget; +class QTextStream; +class NoteOnRecSet; +class KProgress; + + +namespace Rosegarden +{ + +class SequenceManager; +class RosegardenGUIView; +class ProgressDialog; +class MultiViewCommandHistory; +class MappedComposition; +class Event; +class EditViewBase; +class Clipboard; +class AudioPluginManager; + + +static const int MERGE_AT_END = (1 << 0); +static const int MERGE_IN_NEW_TRACKS = (1 << 1); +static const int MERGE_KEEP_OLD_TIMINGS = (1 << 2); +static const int MERGE_KEEP_NEW_TIMINGS = (1 << 3); + + +/** + * RosegardenGUIDoc provides a document object for a document-view model. + * + * The RosegardenGUIDoc class provides a document object that can be + * used in conjunction with the classes RosegardenGUIApp and + * RosegardenGUIView to create a document-view model for standard KDE + * applications based on KApplication and KTMainWindow. Thereby, the + * document object is created by the RosegardenGUIApp instance and + * contains the document structure with the according methods for + * manipulation of the document data by RosegardenGUIView + * objects. Also, RosegardenGUIDoc contains the methods for + * serialization of the document data from and to files. + * + * RosegardenGUIDoc owns the Composition in the document. + */ + +class RosegardenGUIDoc : public QObject +{ + Q_OBJECT +public: + + /** + * Constructor for the fileclass of the application + */ + RosegardenGUIDoc(QWidget *parent, + AudioPluginManager *audioPluginManager = 0, + bool skipAutoload = false, + const char *name=0); + +private: + RosegardenGUIDoc(RosegardenGUIDoc *doc); + RosegardenGUIDoc& operator=(const RosegardenGUIDoc &doc); + +public: + static int FILE_FORMAT_VERSION_MAJOR; + static int FILE_FORMAT_VERSION_MINOR; + static int FILE_FORMAT_VERSION_POINT; + + /** + * Destructor for the fileclass of the application + */ + ~RosegardenGUIDoc(); + + /** + * adds a view to the document which represents the document + * contents. Usually this is your main view. + */ + void attachView(RosegardenGUIView *view); + + /** + * removes a view from the list of currently connected views + */ + void detachView(RosegardenGUIView *view); + + /** + * adds an Edit View (notation, matrix, event list) + */ + void attachEditView(EditViewBase*); + + /** + * removes a view from the list of currently connected edit views + */ + void detachEditView(EditViewBase*); + + /** + * delete all Edit Views + */ + void deleteEditViews(); + +protected: + /** + * sets the modified flag for the document after a modifying + * action on the view connected to the document. + * + * this is just an accessor, other components should call + * slotDocumentModified() and clearModifiedStatus() instead of + * this method, which perform all the related housework. + * + */ + void setModified(bool m=true); + +public: + /** + * returns if the document is modified or not. Use this to + * determine if your document needs saving by the user on closing. + */ + bool isModified() const { return m_modified; }; + + /** + * clears the 'modified' status of the document (sets it back to false). + * + */ + void clearModifiedStatus(); + + /** + * "save modified" - asks the user for saving if the document is + * modified + */ + bool saveIfModified(); + + /** + * get the autosave interval in seconds + */ + unsigned int getAutoSavePeriod() const; + + /** + * Load the document by filename and format and emit the + * updateViews() signal. The "permanent" argument should be true + * if this document is intended to be loaded to the GUI for real + * editing work: in this case, any necessary device-synchronisation + * with the sequencer will be carried out. If permanent is false, + * the sequencer's device list will be left alone. + */ + bool openDocument(const QString &filename, bool permanent = true, + const char *format=0); + + /** + * merge another document into this one + */ + void mergeDocument(RosegardenGUIDoc *doc, int options); + + /** + * saves the document under filename and format. + * + * errMsg will be set to a user-readable error message if save fails + */ + bool saveDocument(const QString &filename, QString& errMsg, + bool autosave = false); + + /** + * exports all or part of the studio to a file. If devices is + * empty, exports all devices. + */ + bool exportStudio(const QString &filename, + std::vector devices = + std::vector()); + + /** + * sets the path to the file connected with the document + */ + void setAbsFilePath(const QString &filename); + + /** + * returns the pathname of the current document file + */ + const QString &getAbsFilePath() const; + + /** + * sets the filename of the document + */ + void setTitle(const QString &_t); + + /** + * returns the title of the document + */ + const QString &getTitle() const; + + /** + * Returns true if the file is a regular Rosegarden ".rg" file, + * false if it's an imported file or a new file (not yet saved) + */ + bool isRegularDotRGFile(); + + void setQuickMarker(); + void jumpToQuickMarker(); + timeT getQuickMarkerTime() { return m_quickMarkerTime; } + + /** + * returns the global command history + */ + MultiViewCommandHistory *getCommandHistory() { + return m_commandHistory; + } + + /** + * returns the composition (the principal constituent of the document) + */ + Composition& getComposition() { return m_composition; } + + /** + * returns the composition (the principal constituent of the document) + */ + const Composition& getComposition() const { return m_composition; } + + /* + * return the Studio + */ + Studio& getStudio() { return m_studio;} + + const Studio& getStudio() const { return m_studio;} + + /* + * return the AudioPreviewThread + */ + AudioPreviewThread& getAudioPreviewThread() + { return m_audioPreviewThread; } + + const AudioPreviewThread& getAudioPreviewThread() const + { return m_audioPreviewThread; } + + /* + * return the AudioFileManager + */ + AudioFileManager& getAudioFileManager() + { return m_audioFileManager; } + + const AudioFileManager& getAudioFileManager() const + { return m_audioFileManager; } + + /* + * return the Configuration object + */ + DocumentConfiguration& getConfiguration() { return m_config; } + + const DocumentConfiguration& getConfiguration() const + { return m_config; } + + /** + * returns the cut/copy/paste clipboard + */ + Clipboard *getClipboard(); + + /** + * Returns whether the sequencer us running + */ + bool isSequencerRunning(); + + /** + * insert some recorded MIDI events into our recording Segment + */ + void insertRecordedMidi(const MappedComposition &mc); + + /** + * Update the recording progress -- called regularly from + * RosegardenGUIApp::slotUpdatePlaybackPosition() while recording + */ + void updateRecordingMIDISegment(); + + /** + * Update the recording progress for audio + */ + void updateRecordingAudioSegments(); + + /** + * Tidy up the recording SegmentItems and other post record jobs + */ + void stopRecordingMidi(); + void stopRecordingAudio(); + + /** + * Register audio samples at the sequencer + */ + void prepareAudio(); + + /** + * Cause the playPositionChanged signal to be emitted and any + * associated internal work in the document to happen + */ + void setPlayPosition(timeT); + + /** + * Cause the loopChanged signal to be emitted and any + * associated internal work in the document to happen + */ + void setLoop(timeT, timeT); + + /** + * Cause the document to use the given time as the origin + * when inserting any subsequent recorded data + */ + void setRecordStartTime(timeT t) { m_recordStartTime = t; } + + /* + * Sync device information with sequencer + */ + void syncDevices(); + + /* + * Get a MappedDevice from the sequencer and add the + * results to our Studio + */ + void getMappedDevice(DeviceId id); + + void addRecordMIDISegment(TrackId); + void addRecordAudioSegment(InstrumentId, AudioFileId); + + // Audio play and record latencies direct from the sequencer + // + RealTime getAudioPlayLatency(); + RealTime getAudioRecordLatency(); + void updateAudioRecordLatency(); + + // Complete the add of an audio file when a new file has finished + // being recorded at the sequencer. This method will ensure that + // the audio file is added to the AudioFileManager, that + // a preview is generated and that the sequencer also knows to add + // the new file to its own hash table. Flow of control is a bit + // awkward around new audio files as timing is crucial - the gui can't + // access the file until lead-out information has been written by the + // sequencer. + // + // Note that the sequencer doesn't know the audio file id (yet), + // only the instrument it was recorded to. (It does know the + // filename, but the instrument id is enough for us.) + // + void finalizeAudioFile(InstrumentId instrument); + + // Tell the document that an audio file has been orphaned. An + // orphaned audio file is a file that was created by recording in + // Rosegarden during the current session, but that has been + // unloaded from the audio file manager. It's therefore likely + // that no other application will be using it, and that that user + // doesn't want to keep it. We can offer to delete these files + // permanently when the document is saved. + // + void addOrphanedRecordedAudioFile(QString fileName); + void addOrphanedDerivedAudioFile(QString fileName); + + // Consider whether to orphan the given audio file which is about + // to be removed from the audio file manager. + // + void notifyAudioFileRemoval(AudioFileId id); + + /* + void setAudioRecordLatency(const RealTime &latency) + { m_audioRecordLatency = latency; } + void setAudioPlayLatency(const RealTime &latency) + { m_audioPlayLatency = latency; } + */ + + // Return the AudioPluginManager + // + AudioPluginManager* getPluginManager() + { return m_pluginManager; } + + // Clear all plugins from sequencer and from gui + // + void clearAllPlugins(); + + // Initialise the MIDI controllers after we've loaded a file + // + void initialiseControllers(); + + // Clear the studio at the sequencer + // + void clearStudio(); + + // Initialise the Studio with a new document's settings + // + void initialiseStudio(); + + // Get the sequence manager from the app + // + SequenceManager* getSequenceManager() + { return (dynamic_cast(parent())) + ->getSequenceManager(); } + + //Obsolete: multitrack MIDI recording. plcl 06/2006. + //Segment *getRecordMIDISegment() { return m_recordMIDISegment; } + + QStringList getTimers(); + QString getCurrentTimer(); + void setCurrentTimer(QString); + + /** + * return the list of the views currently connected to the document + */ + QList& getViewList() { return m_viewList; } + + bool isBeingDestroyed() { return m_beingDestroyed; } + + static const unsigned int MinNbOfTracks; // 64 + +public slots: + /** + * calls repaint() on all views connected to the document object + * and is called by the view by which the document has been + * changed. As this view normally repaints itself, it is excluded + * from the paintEvent. + */ + void slotUpdateAllViews(RosegardenGUIView *sender); + + /** + * set the 'modified' flag of the document to true, + * clears the 'autosaved' flag, emits the 'documentModified' signal. + * + * always call this when changes have occurred on the document. + */ + void slotDocumentModified(); + void slotDocumentRestored(); + + /** + * saves the document to a suitably-named backup file + */ + void slotAutoSave(); + + void slotSetPointerPosition(timeT); + void slotSetPlayPosition(timeT t) { setPlayPosition(t); } + void slotSetLoop(timeT s, timeT e) {setLoop(s,e);} + + void slotDocColoursChanged(); + +signals: + /** + * Emitted when document is modified or saved + */ + void documentModified(bool); + + /** + * Emitted during playback, to suggest that views should track along, + * as well as when pointer is moved via a click on the loop ruler. + */ + void pointerPositionChanged(timeT); + + /** + * Emitted during recording, to indicate that some new notes (it's + * only emitted for notes) have appeared in the recording segment + * and anything tracking should track. updatedFrom gives the + * start of the new region, which is presumed to extend up to the + * end of the segment. + */ + void recordMIDISegmentUpdated(Segment *recordSegment, + timeT updatedFrom); + + /** + * Emitted when a new MIDI recording segment is set + */ + void newMIDIRecordingSegment(Segment*); + + /** + * Emitted when a new audio recording segment is set + */ + void newAudioRecordingSegment(Segment*); + + void makeTrackVisible(int trackPosition); + + void stoppedAudioRecording(); + void stoppedMIDIRecording(); + void audioFileFinalized(Segment*); + + void playPositionChanged(timeT); + void loopChanged(timeT, timeT); + void docColoursChanged(); + void devicesResyncd(); + +protected: + /** + * initializes the document generally + */ + void newDocument(); + + /** + * Autoload + */ + void performAutoload(); + + /** + * Parse the Rosegarden file in \a file + * + * \a errMsg will contains the error messages + * if parsing failed. + * + * @return false if parsing failed + * @see RoseXmlHandler + */ + bool xmlParse(QString fileContents, QString &errMsg, + ProgressDialog *progress, + unsigned int elementCount, + bool permanent, + bool &cancelled); + + /** + * Set the "auto saved" status of the document + * Doc. modification sets it to false, autosaving + * sets it to true + */ + void setAutoSaved(bool s) { m_autoSaved = s; } + + /** + * Returns whether the document should be auto-saved + */ + bool isAutoSaved() const { return m_autoSaved; } + + /** + * Returns the name of the autosave file + */ + QString getAutoSaveFileName(); + + /** + * Save document to the given file. This function does the actual + * save of the file to the given filename; saveDocument() wraps + * this, saving to a temporary file and then renaming to the + * required file, so as not to lose the original if a failure + * occurs during overwriting. + */ + bool saveDocumentActual(const QString &filename, QString& errMsg, + bool autosave = false); + + /** + * Save one segment to the given text stream + */ + void saveSegment(QTextStream&, Segment*, KProgress*, + long totalNbOfEvents, long &count, + QString extraAttributes = QString::null); + + bool deleteOrphanedAudioFiles(bool documentWillNotBeSaved); + + + /** + * A struct formed by a Segment pointer and an iterator to the same + * Segment, used in NoteOn calculations when recording MIDI. + */ + struct NoteOnRec { + Segment *m_segment; + Segment::iterator m_segmentIterator; + }; + + /** + * A vector of NoteOnRec elements, necessary in multitrack MIDI + * recording for NoteOn calculations + */ + typedef std::vector NoteOnRecSet; + + /** + * Store a single NoteOnRec element in the m_noteOnEvents map + */ + void storeNoteOnEvent( Segment *s, Segment::iterator it, + int device, int channel ); + + /** + * Replace recorded Note events in one or several segments, returning the + * resulting NoteOnRecSet + */ + NoteOnRecSet* replaceRecordedEvent(NoteOnRecSet &rec_vec, Event *fresh); + + /** + * Insert a recorded event in one or several segments + */ + void insertRecordedEvent(Event *ev, int device, int channel, bool isNoteOn); + + //--------------- Data members --------------------------------- + + /** + * the list of the views currently connected to the document + */ + QList m_viewList; + + /** + * the list of the edit views currently editing a part of this document + */ + QList m_editViewList; + + /** + * the modified flag of the current document + */ + bool m_modified; + + /** + * the autosaved status of the current document + */ + bool m_autoSaved; + + /** + * the title of the current document + */ + QString m_title; + + /** + * absolute file path of the current document + */ + QString m_absFilePath; + + /** + * the composition this document is wrapping + */ + Composition m_composition; + + /** + * stores AudioFile mappings + */ + AudioFileManager m_audioFileManager; + + /** + * calculates AudioFile previews + */ + AudioPreviewThread m_audioPreviewThread; + + typedef std::map RecordingSegmentMap; + + /** + * Segments onto which we can record MIDI events + */ + //Segment *m_recordMIDISegment; + RecordingSegmentMap m_recordMIDISegments; + + /** + * Segments for recording audio (per instrument) + */ + RecordingSegmentMap m_recordAudioSegments; + + /** + * a map[Pitch] of NoteOnRecSet elements, for NoteOn calculations + */ + typedef std::map PitchMap; + + /** + * a map[Channel] of PitchMap + */ + typedef std::map ChanMap; + + /** + * a map[Port] of ChanMap + */ + typedef std::map NoteOnMap; + + /** + * During recording, we collect note-ons that haven't yet had a note-off + * in here + */ + NoteOnMap m_noteOnEvents; + + + MultiViewCommandHistory *m_commandHistory; + + /** + * the Studio + */ + Studio m_studio; + + /* + * A configuration object + * + */ + DocumentConfiguration m_config; + + // AudioPluginManager - sequencer and local plugin management + // + AudioPluginManager *m_pluginManager; + + RealTime m_audioRecordLatency; + + timeT m_recordStartTime; + + timeT m_quickMarkerTime; + + std::vector m_orphanedRecordedAudioFiles; + std::vector m_orphanedDerivedAudioFiles; + + // Autosave period for this document in seconds + // + int m_autoSavePeriod; + + // Set to true when the dtor starts + bool m_beingDestroyed; +}; + + +} + +#endif diff --git a/src/document/XmlStorableEvent.cpp b/src/document/XmlStorableEvent.cpp new file mode 100644 index 0000000..7688b2a --- /dev/null +++ b/src/document/XmlStorableEvent.cpp @@ -0,0 +1,188 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "XmlStorableEvent.h" + +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "gui/editors/notation/NotationStrings.h" +#include + + +namespace Rosegarden +{ + +XmlStorableEvent::XmlStorableEvent(const QXmlAttributes &attributes, + timeT &absoluteTime) +{ + setDuration(0); + + for (int i = 0; i < attributes.length(); ++i) { + + QString attrName(attributes.qName(i)), + attrVal(attributes.value(i)); + + if (attrName == "package") { + + RG_DEBUG << "XmlStorableEvent::XmlStorableEvent: Warning: XML still uses deprecated \"package\" attribute" << endl; + + } else if (attrName == "type") { + + setType(qstrtostr(attrVal)); + + } else if (attrName == "subordering") { + + bool isNumeric = true; + int o = attrVal.toInt(&isNumeric); + + if (!isNumeric) { + RG_DEBUG << "XmlStorableEvent::XmlStorableEvent: Bad subordering: " << attrVal << endl; + } else { + if (o != 0) + setSubOrdering(o); + } + + } else if (attrName == "duration") { + + bool isNumeric = true; + timeT d = attrVal.toInt(&isNumeric); + + if (!isNumeric) { + try { + Note n(NotationStrings::getNoteForName(attrVal)); + setDuration(n.getDuration()); + } catch (NotationStrings::MalformedNoteName m) { + RG_DEBUG << "XmlStorableEvent::XmlStorableEvent: Bad duration: " << attrVal << " (" << m.getMessage() << ")" << endl; + } + } else { + setDuration(d); + } + + } else if (attrName == "absoluteTime") { + + bool isNumeric = true; + timeT t = attrVal.toInt(&isNumeric); + + if (!isNumeric) { + RG_DEBUG << "XmlStorableEvent::XmlStorableEvent: Bad absolute time: " << attrVal << endl; + } else { + absoluteTime = t; + } + + } else if (attrName == "timeOffset") { + + bool isNumeric = true; + timeT t = attrVal.toInt(&isNumeric); + + if (!isNumeric) { + RG_DEBUG << "XmlStorableEvent::XmlStorableEvent: Bad time offset: " << attrVal << endl; + } else { + absoluteTime += t; + } + + } else { + + // set generic property + // + QString val(attrVal); + + // Check if boolean val + QString valLowerCase(val.lower()); + bool isNumeric; + int numVal; + + if (valLowerCase == "true" || valLowerCase == "false") { + + set + (qstrtostr(attrName), valLowerCase == "true"); + + } else { + + // Not a bool, check if integer val + numVal = val.toInt(&isNumeric); + if (isNumeric) { + set + (qstrtostr(attrName), numVal); + } else { + // not an int either, default to string + set + (qstrtostr(attrName), qstrtostr(attrVal)); + } + } + } + } + + setAbsoluteTime(absoluteTime); +} + +XmlStorableEvent::XmlStorableEvent(Event &e) : + Event(e) +{} + +void +XmlStorableEvent::setPropertyFromAttributes(const QXmlAttributes &attributes, + bool persistent) +{ + bool have = false; + QString name = attributes.value("name"); + if (name == "") { + RG_DEBUG << "XmlStorableEvent::setProperty: no property name found, ignoring" << endl; + return ; + } + + for (int i = 0; i < attributes.length(); ++i) { + QString attrName(attributes.qName(i)), + attrVal(attributes.value(i)); + + if (attrName == "name") { + continue; + } else if (have) { + RG_DEBUG << "XmlStorableEvent::setProperty: multiple values found, ignoring all but the first" << endl; + continue; + } else if (attrName == "bool") { + set + (qstrtostr(name), attrVal.lower() == "true", + persistent); + have = true; + } else if (attrName == "int") { + set + (qstrtostr(name), attrVal.toInt(), persistent); + have = true; + } else if (attrName == "string") { + set + (qstrtostr(name), qstrtostr(attrVal), persistent); + have = true; + } else { + RG_DEBUG << "XmlStorableEvent::setProperty: unknown attribute name \"" << name << "\", ignoring" << endl; + } + } + + if (!have) + RG_DEBUG << "XmlStorableEvent::setProperty: Warning: no property value found for property " << name << endl; +} + +} diff --git a/src/document/XmlStorableEvent.h b/src/document/XmlStorableEvent.h new file mode 100644 index 0000000..197c9cb --- /dev/null +++ b/src/document/XmlStorableEvent.h @@ -0,0 +1,75 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_XMLSTORABLEEVENT_H_ +#define _RG_XMLSTORABLEEVENT_H_ + +#include "base/Event.h" +#include + + +class QXmlAttributes; + + +namespace Rosegarden +{ + + + +/** + * An Event which can generate an XML representation of itself, + * or which can be constructed from a set of XML attributes + * + * @see RoseXmlHandler + */ +class XmlStorableEvent : public Event +{ +public: + /** + * Construct an XmlStorableEvent out of the XML attributes \a atts. + * If the attributes do not include absoluteTime, use the given + * value plus the value of any timeOffset attribute. If the + * attributes include absoluteTime or timeOffset, update the given + * absoluteTime reference accordingly. + */ + XmlStorableEvent(const QXmlAttributes& atts, + timeT &absoluteTime); + + /** + * Construct an XmlStorableEvent from the specified Event. + */ + XmlStorableEvent(Event&); + + /** + * Set a property from the XML attributes \a atts + */ + void setPropertyFromAttributes(const QXmlAttributes& atts, + bool persistent); +}; + + +} + +#endif diff --git a/src/document/XmlSubHandler.cpp b/src/document/XmlSubHandler.cpp new file mode 100644 index 0000000..eef2199 --- /dev/null +++ b/src/document/XmlSubHandler.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "XmlSubHandler.h" + +namespace Rosegarden { + +XmlSubHandler::XmlSubHandler() +{ +} + +XmlSubHandler::~XmlSubHandler() +{ +} + +} diff --git a/src/document/XmlSubHandler.h b/src/document/XmlSubHandler.h new file mode 100644 index 0000000..30ba784 --- /dev/null +++ b/src/document/XmlSubHandler.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_XMLSUBHANDLER_H_ +#define _RG_XMLSUBHANDLER_H_ + +#include +#include + +namespace Rosegarden { + +class XmlSubHandler +{ +public: + XmlSubHandler(); + virtual ~XmlSubHandler(); + + virtual bool startElement(const QString& namespaceURI, + const QString& localName, + const QString& qName, + const QXmlAttributes& atts) = 0; + + /** + * @param finished : if set to true on return, means that + * the handler should be deleted + */ + virtual bool endElement(const QString& namespaceURI, + const QString& localName, + const QString& qName, + bool& finished) = 0; + + virtual bool characters(const QString& ch) = 0; +}; + +} + +#endif /*_RG_XMLSUBHANDLER_H_*/ diff --git a/src/document/io/CsoundExporter.cpp b/src/document/io/CsoundExporter.cpp new file mode 100644 index 0000000..9b61372 --- /dev/null +++ b/src/document/io/CsoundExporter.cpp @@ -0,0 +1,154 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CsoundExporter.h" + +#include "base/Event.h" +#include "base/BaseProperties.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Track.h" +#include "gui/general/ProgressReporter.h" +#include +#include +#include "gui/application/RosegardenApplication.h" + + +namespace Rosegarden +{ + +CsoundExporter::CsoundExporter(QObject *parent, + Composition *composition, + std::string fileName) : + ProgressReporter(parent, "csoundExporter"), + m_composition(composition), + m_fileName(fileName) +{ + // nothing else +} + +CsoundExporter::~CsoundExporter() +{ + // nothing +} + +static double +convertTime(Rosegarden::timeT t) +{ + return double(t) / double(Note(Note::Crotchet).getDuration()); +} + +bool +CsoundExporter::write() +{ + std::ofstream str(m_fileName.c_str(), std::ios::out); + if (!str) { + //std::cerr << "CsoundExporter::write() - can't write file" << std::endl; + return false; + } + + str << ";; Csound score file written by Rosegarden\n\n"; + if (m_composition->getCopyrightNote() != "") { + str << ";; Copyright note:\n;; " + //!!! really need to remove newlines from copyright note + << m_composition->getCopyrightNote() << "\n"; + } + + int trackNo = 0; + for (Composition::iterator i = m_composition->begin(); + i != m_composition->end(); ++i) { + + emit setProgress(int(double(trackNo++) / double(m_composition->getNbTracks()) * 100.0)); + rgapp->refreshGUI(50); + + str << "\n;; Segment: \"" << (*i)->getLabel() << "\"\n"; + str << ";; on Track: \"" + << m_composition->getTrackById((*i)->getTrack())->getLabel() + << "\"\n"; + str << ";;\n;; Inst\tTime\tDur\tPitch\tVely\n" + << ";; ----\t----\t---\t-----\t----\n"; + + for (Segment::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { + + if ((*j)->isa(Note::EventType)) { + + long pitch = 0; + (*j)->get + (BaseProperties::PITCH, pitch); + + long velocity = 127; + (*j)->get + (BaseProperties::VELOCITY, velocity); + + str << " i" + << (*i)->getTrack() << "\t" + << convertTime((*j)->getAbsoluteTime()) << "\t" + << convertTime((*j)->getDuration()) << "\t" + << 3 + (pitch / 12) << ((pitch % 12) < 10 ? ".0" : ".") + << pitch % 12 << "\t" + << velocity << "\t\n"; + + } else { + str << ";; Event type: " << (*j)->getType() << std::endl; + } + } + } + + int tempoCount = m_composition->getTempoChangeCount(); + + if (tempoCount > 0) { + + str << "\nt "; + + for (int i = 0; i < tempoCount - 1; ++i) { + + std::pair tempoChange = + m_composition->getTempoChange(i); + + timeT myTime = tempoChange.first; + timeT nextTime = myTime; + if (i < m_composition->getTempoChangeCount() - 1) { + nextTime = m_composition->getTempoChange(i + 1).first; + } + + int tempo = int(Composition::getTempoQpm(tempoChange.second)); + + str << convertTime( myTime) << " " << tempo << " " + << convertTime(nextTime) << " " << tempo << " "; + } + + str << convertTime(m_composition->getTempoChange(tempoCount - 1).first) + << " " + << int(Composition::getTempoQpm(m_composition->getTempoChange(tempoCount - 1).second)) + << std::endl; + } + + str << "\ne" << std::endl; + str.close(); + return true; +} + +} diff --git a/src/document/io/CsoundExporter.h b/src/document/io/CsoundExporter.h new file mode 100644 index 0000000..0e8c2ac --- /dev/null +++ b/src/document/io/CsoundExporter.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CSOUNDEXPORTER_H_ +#define _RG_CSOUNDEXPORTER_H_ + +#include "gui/general/ProgressReporter.h" +#include + + +class QObject; + + +namespace Rosegarden +{ + +class Composition; + + +/** + * Csound scorefile export + */ + +class CsoundExporter : public ProgressReporter +{ +public: + CsoundExporter(QObject *parent, Composition *, std::string fileName); + ~CsoundExporter(); + + bool write(); + +protected: + Composition *m_composition; + std::string m_fileName; +}; + + + +} + +#endif diff --git a/src/document/io/HydrogenLoader.cpp b/src/document/io/HydrogenLoader.cpp new file mode 100644 index 0000000..38f85fe --- /dev/null +++ b/src/document/io/HydrogenLoader.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "HydrogenLoader.h" + +#include +#include "base/Composition.h" +#include "base/PropertyName.h" +#include "base/Segment.h" +#include "base/Studio.h" +#include "gui/general/ProgressReporter.h" +#include "HydrogenXMLHandler.h" +#include +#include +#include + + +namespace Rosegarden +{ + +HydrogenLoader::HydrogenLoader(Studio *studio, + QObject *parent, const char *name): + ProgressReporter(parent, name), + m_studio(studio) +{} + +bool +HydrogenLoader::load(const QString& fileName, Composition &comp) +{ + m_composition = ∁ + comp.clear(); + + QFile file(fileName); + if (!file.open(IO_ReadOnly)) { + return false; + } + + m_studio->unassignAllInstruments(); + + HydrogenXMLHandler handler(m_composition); + + QXmlInputSource source(file); + QXmlSimpleReader reader; + reader.setContentHandler(&handler); + reader.setErrorHandler(&handler); + + bool ok = reader.parse(source); + + return ok; +} + +} diff --git a/src/document/io/HydrogenLoader.h b/src/document/io/HydrogenLoader.h new file mode 100644 index 0000000..f0cd724 --- /dev/null +++ b/src/document/io/HydrogenLoader.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_HYDROGENLOADER_H_ +#define _RG_HYDROGENLOADER_H_ + +#include "base/PropertyName.h" +#include "gui/general/ProgressReporter.h" +#include +#include + + +class QString; +class QObject; + + +namespace Rosegarden +{ + +class Studio; +class Segment; +class Composition; + + +/** + * Hydrogen drum machine file importer - should work for 0.8.1 and above + * assuming they don't change the file spec without telling us. + * + */ + +class HydrogenLoader : public ProgressReporter +{ +public: + HydrogenLoader(Studio *, + QObject *parent = 0, const char *name = 0); + + /** + * Load and parse the Hydrogen file \a fileName, and write it into the + * given Composition (clearing the existing segment data first). + * Return true for success. + */ + bool load(const QString& fileName, Composition &); + +protected: + Composition *m_composition; + Studio *m_studio; + std::string m_fileName; + +private: + static const int MAX_DOTS = 4; + static const PropertyName SKIP_PROPERTY; +}; + +typedef std::vector > SegmentMap; +typedef std::vector >::iterator SegmentMapIterator; +typedef std::vector >::const_iterator SegmentMapConstIterator; + + +} + +#endif diff --git a/src/document/io/HydrogenXMLHandler.cpp b/src/document/io/HydrogenXMLHandler.cpp new file mode 100644 index 0000000..68e1b20 --- /dev/null +++ b/src/document/io/HydrogenXMLHandler.cpp @@ -0,0 +1,403 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "HydrogenXMLHandler.h" + +#include "base/Event.h" +#include "base/BaseProperties.h" +#include +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Composition.h" +#include "base/Instrument.h" +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Track.h" +#include + + +namespace Rosegarden +{ + +HydrogenXMLHandler::HydrogenXMLHandler(Composition *composition, + InstrumentId drumIns): + m_composition(composition), + m_drumInstrument(drumIns), + m_inNote(false), + m_inInstrument(false), + m_inPattern(false), + m_inSequence(false), + m_patternName(""), + m_patternSize(0), + m_sequenceName(""), + m_position(0), + m_velocity(0.0), + m_panL(0.0), + m_panR(0.0), + m_pitch(0.0), + m_instrument(0), + m_id(0), + m_muted(false), + m_fileName(""), + m_bpm(0), + m_volume(0.0), + m_name(""), + m_author(""), + m_notes(""), + m_songMode(false), + m_version(""), + m_currentProperty(""), + m_segment(0), + m_currentTrackNb(0), + m_segmentAdded(false), + m_currentBar(0), + m_newSegment(false) +{} + +bool +HydrogenXMLHandler::startDocument() +{ + RG_DEBUG << "HydrogenXMLHandler::startDocument" << endl; + + m_inNote = false; + m_inInstrument = false; + m_inPattern = false; + m_inSequence = false; + + // Pattern attributes + // + m_patternName = ""; + m_patternSize = 0; + + // Sequence attributes + // + m_sequenceName = ""; + + // Note attributes + // + m_position = 0; + m_velocity = 0.0; + m_panL = 0.0; + m_panR = 0.0; + m_pitch = 0.0; + m_instrument = 0; + + // Instrument attributes + // + m_id = 0; + m_muted = false; + m_instrumentVolumes.clear(); + m_fileName = ""; + + // Global attributes + // + m_bpm = 0; + m_volume = 0.0; + m_name = ""; + m_author = ""; + m_notes = ""; + m_songMode = false; + m_version = ""; + + m_currentProperty = ""; + + m_segment = 0; + m_currentTrackNb = 0; + m_segmentAdded = 0; + m_currentBar = 0; + m_newSegment = false; + + return true; +} + +bool +HydrogenXMLHandler::startElement(const QString& /*namespaceURI*/, + const QString& /*localName*/, + const QString& qName, + const QXmlAttributes& /*atts*/) +{ + QString lcName = qName.lower(); + + if (lcName == "note") { + + if (m_inInstrument) + return false; + + m_inNote = true; + + } else if (lcName == "instrument") { + + // Beware instrument attributes inside Notes + if (!m_inNote) + m_inInstrument = true; + } else if (lcName == "pattern") { + m_inPattern = true; + m_segmentAdded = false; // flag the segments being added + } else if (lcName == "sequence") { + + // Create a new segment and set some flags + // + m_segment = new Segment(); + m_newSegment = true; + m_inSequence = true; + } + + m_currentProperty = lcName; + + return true; +} + +bool +HydrogenXMLHandler::endElement(const QString& /*namespaceURI*/, + const QString& /*localName*/, + const QString& qName) +{ + QString lcName = qName.lower(); + + if (lcName == "note") { + + RG_DEBUG << "HydrogenXMLHandler::endElement - Hydrogen Note : position = " << m_position + << ", velocity = " << m_velocity + << ", panL = " << m_panL + << ", panR = " << m_panR + << ", pitch = " << m_pitch + << ", instrument = " << m_instrument + << endl; + + timeT barLength = m_composition->getBarEnd(m_currentBar) - + m_composition->getBarStart(m_currentBar); + + timeT pos = m_composition->getBarStart(m_currentBar) + + timeT( + double(m_position) / double(m_patternSize) * double(barLength)); + + // Insert a rest if we've got a new segment + // + if (m_newSegment) { + Event *restEvent = new Event(Note::EventRestType, + m_composition->getBarStart(m_currentBar), + pos - m_composition->getBarStart(m_currentBar), + Note::EventRestSubOrdering); + m_segment->insert(restEvent); + m_newSegment = false; + } + + // Create and insert this event + // + Event *noteEvent = new Event(Note::EventType, + pos, Note(Note::Semiquaver).getDuration()); + + // get drum mapping from instrument and calculate velocity + noteEvent->set + ( + BaseProperties::PITCH, 36 + m_instrument); + noteEvent->set + (BaseProperties::VELOCITY, + int(127.0 * m_velocity * m_volume * + m_instrumentVolumes[m_instrument])); + m_segment->insert(noteEvent); + + m_inNote = false; + + } else if (lcName == "instrument" && m_inInstrument) { + + RG_DEBUG << "HydrogenXMLHandler::endElement - Hydrogen Instrument : id = " << m_id + << ", muted = " << m_muted + << ", volume = " << m_instrumentVolumes[m_instrument] + << ", filename = \"" << m_fileName << "\"" + << endl; + + m_inInstrument = false; + + } else if (lcName == "pattern") { + m_inPattern = false; + + if (m_segmentAdded) { + + // Add a blank track to demarcate patterns + // + Track *track = new Track + (m_currentTrackNb, m_drumInstrument, m_currentTrackNb, + "", false); + m_currentTrackNb++; + m_composition->addTrack(track); + + m_segmentAdded = false; + + // Each pattern has it's own bar so that the imported + // song shows off each pattern a bar at a time. + // + m_currentBar++; + } + + } else if (lcName == "sequence") { + + // If we're closing out a sequencer tab and we have a m_segment then + // we should close up and add that segment. Only create if we have + // some Events in it + // + if (m_segment->size() > 0) { + + m_segment->setTrack(m_currentTrackNb); + + Track *track = new Track + (m_currentTrackNb, m_drumInstrument, m_currentTrackNb, + m_patternName, false); + m_currentTrackNb++; + + // Enforce start and end markers for this bar so that we have a + // whole bar unit segment. + // + m_segment->setEndMarkerTime(m_composition->getBarEnd(m_currentBar)); + QString label = QString("%1 - %2 %3 %4").arg(strtoqstr(m_patternName)) + .arg(strtoqstr(m_sequenceName)) + .arg(i18n(" imported from Hydrogen ")).arg(strtoqstr(m_version)); + m_segment->setLabel(qstrtostr(label)); + + m_composition->addTrack(track); + m_composition->addSegment(m_segment); + m_segment = 0; + + m_segmentAdded = true; + } + + m_inSequence = false; + + } + + return true; +} + +bool +HydrogenXMLHandler::characters(const QString& chars) +{ + QString ch = chars.stripWhiteSpace(); + if (ch == "") + return true; + + if (m_inNote) { + if (m_currentProperty == "position") { + m_position = ch.toInt(); + } else if (m_currentProperty == "velocity") { + m_velocity = qstrtodouble(ch); + } else if (m_currentProperty == "pan_L") { + m_panL = qstrtodouble(ch); + } else if (m_currentProperty == "pan_R") { + m_panR = qstrtodouble(ch); + } else if (m_currentProperty == "pitch") { + m_pitch = qstrtodouble(ch); + } else if (m_currentProperty == "instrument") { + m_instrument = ch.toInt(); + + // Standard kit conversion - hardcoded conversion for Hyrdogen's default + // drum kit. The m_instrument mapping for low values maps well onto the + // kick drum GM kit starting point (MIDI pitch = 36). + // + switch (m_instrument) { + case 11: // Cowbell + m_instrument = 20; + break; + case 12: // Ride Jazz + m_instrument = 15; + break; + case 14: // Ride Rock + m_instrument = 17; + break; + case 15: // Crash Jazz + m_instrument = 16; + break; + + default: + break; + } + + } + } else if (m_inInstrument) { + if (m_currentProperty == "id") { + m_id = ch.toInt(); + } else if (m_currentProperty == "ismuted") { + if (ch.lower() == "true") + m_muted = true; + else + m_muted = false; + } else if (m_currentProperty == "filename") { + m_fileName = qstrtostr(chars); // don't strip whitespace from the filename + } else if (m_currentProperty == "volume") { + m_instrumentVolumes.push_back(qstrtodouble(ch)); + } + + + } else if (m_inPattern) { + + // Pattern attributes + + if (m_currentProperty == "name") { + if (m_inSequence) + m_sequenceName = qstrtostr(chars); + else + m_patternName = qstrtostr(chars); + } else if (m_currentProperty == "size") { + m_patternSize = ch.toInt(); + } + + } else { + + // Global attributes + if (m_currentProperty == "version") { + m_version = qstrtostr(chars); + } else if (m_currentProperty == "bpm") { + + m_bpm = qstrtodouble(ch); + m_composition->addTempoAtTime + (0, Composition::getTempoForQpm(m_bpm)); + + } else if (m_currentProperty == "volume") { + m_volume = qstrtodouble(ch); + } else if (m_currentProperty == "name") { + m_name = qstrtostr(chars); + } else if (m_currentProperty == "author") { + m_author = qstrtostr(chars); + } else if (m_currentProperty == "notes") { + m_notes = qstrtostr(chars); + } else if (m_currentProperty == "mode") { + if (ch.lower() == "song") + m_songMode = true; + else + m_songMode = false; + } + } + + return true; +} + +bool +HydrogenXMLHandler::endDocument() +{ + RG_DEBUG << "HydrogenXMLHandler::endDocument" << endl; + return true; +} + +} diff --git a/src/document/io/HydrogenXMLHandler.h b/src/document/io/HydrogenXMLHandler.h new file mode 100644 index 0000000..0bce68b --- /dev/null +++ b/src/document/io/HydrogenXMLHandler.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_HYDROGENXMLHANDLER_H_ +#define _RG_HYDROGENXMLHANDLER_H_ + +#include "HydrogenLoader.h" +#include "base/MidiProgram.h" +#include "base/Track.h" +#include +#include +#include +#include + + +class QXmlAttributes; + + +namespace Rosegarden +{ + +class Segment; +class Composition; + + +class HydrogenXMLHandler : public QXmlDefaultHandler +{ +public: + HydrogenXMLHandler(Composition *comp, + InstrumentId drumInstrument = MidiInstrumentBase + 9); + + /** + * 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 (); + +protected: + Composition *m_composition; + InstrumentId m_drumInstrument; + + bool m_inNote; + bool m_inInstrument; + bool m_inPattern; + bool m_inSequence; + + // Pattern attributes + // + std::string m_patternName; + int m_patternSize; + + // Sequence attributes + // + std::string m_sequenceName; + + // Note attributes + // + int m_position; + double m_velocity; + double m_panL; + double m_panR; + double m_pitch; + int m_instrument; + + // Instrument attributes + // + int m_id; + bool m_muted; + std::vector m_instrumentVolumes; + std::string m_fileName; + + // Global attributes + // + double m_bpm; + double m_volume; + std::string m_name; + std::string m_author; + std::string m_notes; + bool m_songMode; // Song mode or pattern mode? + std::string m_version; + + // + QString m_currentProperty; + + Segment *m_segment; + TrackId m_currentTrackNb; + bool m_segmentAdded; + int m_currentBar; + bool m_newSegment; + + SegmentMap m_segmentMap; + +}; + + + +} + +#endif diff --git a/src/document/io/LilyPondExporter.cpp b/src/document/io/LilyPondExporter.cpp new file mode 100644 index 0000000..68731f8 --- /dev/null +++ b/src/document/io/LilyPondExporter.cpp @@ -0,0 +1,2419 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + This file is Copyright 2002 + Hans Kieserman + with heavy lifting from csoundio as it was on 13/5/2002. + + Numerous additions and bug fixes by + Michael McIntyre + + Some restructuring by Chris Cannam. + + Massive brain surgery, fixes, improvements, and additions by + Heikki Junes + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "LilyPondExporter.h" + +#include +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/BaseProperties.h" +#include "base/Composition.h" +#include "base/Configuration.h" +#include "base/Event.h" +#include "base/Exception.h" +#include "base/Instrument.h" +#include "base/NotationTypes.h" +#include "base/PropertyName.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Sets.h" +#include "base/Staff.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "base/NotationQuantizer.h" +#include "base/Marker.h" +#include "base/StaffExportTypes.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/application/RosegardenGUIView.h" +#include "gui/editors/notation/NotationProperties.h" +#include "gui/editors/notation/NotationView.h" +#include "gui/editors/guitar/Chord.h" +#include "gui/general/ProgressReporter.h" +#include "gui/widgets/CurrentProgressDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Rosegarden +{ + +using namespace BaseProperties; + +const PropertyName LilyPondExporter::SKIP_PROPERTY + = "LilyPondExportSkipThisEvent"; + +LilyPondExporter::LilyPondExporter(RosegardenGUIApp *parent, + RosegardenGUIDoc *doc, + std::string fileName) : + ProgressReporter((QObject *)parent, "lilypondExporter"), + m_doc(doc), + m_fileName(fileName), + m_lastClefFound(Clef::Treble) +{ + m_composition = &m_doc->getComposition(); + m_studio = &m_doc->getStudio(); + m_view = ((RosegardenGUIApp *)parent)->getView(); + m_notationView = NULL; + + readConfigVariables(); +} + +LilyPondExporter::LilyPondExporter(NotationView *parent, + RosegardenGUIDoc *doc, + std::string fileName) : + ProgressReporter((QObject *)parent, "lilypondExporter"), + m_doc(doc), + m_fileName(fileName), + m_lastClefFound(Clef::Treble) + +{ + m_composition = &m_doc->getComposition(); + m_studio = &m_doc->getStudio(); + m_view = NULL; + m_notationView = ((NotationView *)parent); + + readConfigVariables(); +} + +void +LilyPondExporter::readConfigVariables(void) +{ + // grab config info + KConfig *cfg = kapp->config(); + cfg->setGroup(NotationViewConfigGroup); + + m_paperSize = cfg->readUnsignedNumEntry("lilypapersize", PAPER_A4); + m_paperLandscape = cfg->readBoolEntry("lilypaperlandscape", false); + m_fontSize = cfg->readUnsignedNumEntry("lilyfontsize", FONT_20); + m_raggedBottom = cfg->readBoolEntry("lilyraggedbottom", false); + m_exportSelection = cfg->readUnsignedNumEntry("lilyexportselection", EXPORT_NONMUTED_TRACKS); + m_exportLyrics = cfg->readBoolEntry("lilyexportlyrics", true); + m_exportMidi = cfg->readBoolEntry("lilyexportmidi", false); + m_exportTempoMarks = cfg->readUnsignedNumEntry("lilyexporttempomarks", EXPORT_NONE_TEMPO_MARKS); + m_exportPointAndClick = cfg->readBoolEntry("lilyexportpointandclick", false); + m_exportBeams = cfg->readBoolEntry("lilyexportbeamings", false); + m_exportStaffMerge = cfg->readBoolEntry("lilyexportstaffmerge", false); + m_exportStaffGroup = cfg->readBoolEntry("lilyexportstaffbrackets", true); + m_lyricsHAlignment = cfg->readBoolEntry("lilylyricshalignment", LEFT_ALIGN); + + m_languageLevel = cfg->readUnsignedNumEntry("lilylanguage", LILYPOND_VERSION_2_6); + m_exportMarkerMode = cfg->readUnsignedNumEntry("lilyexportmarkermode", EXPORT_NO_MARKERS ); +} + +LilyPondExporter::~LilyPondExporter() +{ + // nothing +} + +void +LilyPondExporter::handleStartingEvents(eventstartlist &eventsToStart, + std::ofstream &str) +{ + eventstartlist::iterator m = eventsToStart.begin(); + + while (m != eventsToStart.end()) { + + try { + Indication i(**m); + + if (i.getIndicationType() == Indication::Slur) { + if ((*m)->get + (NotationProperties::SLUR_ABOVE)) + str << "^( "; + else + str << "_( "; + } else if (i.getIndicationType() == Indication::PhrasingSlur) { + if ((*m)->get + (NotationProperties::SLUR_ABOVE)) + str << "^\\( "; + else + str << "_\\( "; + } else if (i.getIndicationType() == Indication::Crescendo) { + str << "\\< "; + } else if (i.getIndicationType() == Indication::Decrescendo) { + str << "\\> "; + } + + } catch (Event::BadType) { + // Not an indication + } catch (Event::NoData e) { + std::cerr << "Bad indication: " << e.getMessage() << std::endl; + } + + eventstartlist::iterator n(m); + ++n; + eventsToStart.erase(m); + m = n; + } +} + +void +LilyPondExporter::handleEndingEvents(eventendlist &eventsInProgress, + const Segment::iterator &j, + std::ofstream &str) +{ + eventendlist::iterator k = eventsInProgress.begin(); + + while (k != eventsInProgress.end()) { + + eventendlist::iterator l(k); + ++l; + + // Handle and remove all the relevant events in progress + // This assumes all deferred events are indications + + try { + Indication i(**k); + + timeT indicationEnd = + (*k)->getNotationAbsoluteTime() + i.getIndicationDuration(); + timeT eventEnd = + (*j)->getNotationAbsoluteTime() + (*j)->getNotationDuration(); + + if (indicationEnd < eventEnd || + ((i.getIndicationType() == Indication::Slur || + i.getIndicationType() == Indication::PhrasingSlur) && + indicationEnd == eventEnd)) { + + if (i.getIndicationType() == Indication::Slur) { + str << ") "; + } else if (i.getIndicationType() == Indication::PhrasingSlur) { + str << "\\) "; + } else if (i.getIndicationType() == Indication::Crescendo || + i.getIndicationType() == Indication::Decrescendo) { + str << "\\! "; + } + + eventsInProgress.erase(k); + } + + } catch (Event::BadType) { + // not an indication + + } catch (Event::NoData e) { + std::cerr << "Bad indication: " << e.getMessage() << std::endl; + } + + k = l; + } +} + +std::string +LilyPondExporter::convertPitchToLilyNote(int pitch, Accidental accidental, + const Rosegarden::Key &key) +{ + Pitch p(pitch, accidental); + std::string lilyNote = ""; + + lilyNote += (char)tolower(p.getNoteName(key)); + // std::cout << "lilyNote: " << lilyNote << std::endl; + Accidental acc = p.getAccidental(key); + if (acc == Accidentals::DoubleFlat) + lilyNote += "eses"; + else if (acc == Accidentals::Flat) + lilyNote += "es"; + else if (acc == Accidentals::Sharp) + lilyNote += "is"; + else if (acc == Accidentals::DoubleSharp) + lilyNote += "isis"; + + return lilyNote; +} + +std::string +LilyPondExporter::composeLilyMark(std::string eventMark, bool stemUp) +{ + + std::string inStr = "", outStr = ""; + std::string prefix = (stemUp) ? "_" : "^"; + + // shoot text mark straight through unless it's sf or rf + if (Marks::isTextMark(eventMark)) { + inStr = protectIllegalChars(Marks::getTextFromMark(eventMark)); + + if (inStr == "sf") { + inStr = "\\sf"; + } else if (inStr == "rf") { + inStr = "\\rfz"; + } else { + inStr = "\\markup { \\italic " + inStr + " } "; + } + + outStr = prefix + inStr; + + } else if (Marks::isFingeringMark(eventMark)) { + + // fingering marks: use markup syntax only for non-trivial fingerings + + inStr = protectIllegalChars(Marks::getFingeringFromMark(eventMark)); + + if (inStr != "0" && inStr != "1" && inStr != "2" && inStr != "3" && inStr != "4" && inStr != "5" && inStr != "+" ) { + inStr = "\\markup { \\finger \"" + inStr + "\" } "; + } + + outStr = prefix + inStr; + + } else { + outStr = "-"; + + // use full \accent format for everything, even though some shortcuts + // exist, for the sake of consistency + if (eventMark == Marks::Accent) { + outStr += "\\accent"; + } else if (eventMark == Marks::Tenuto) { + outStr += "\\tenuto"; + } else if (eventMark == Marks::Staccato) { + outStr += "\\staccato"; + } else if (eventMark == Marks::Staccatissimo) { + outStr += "\\staccatissimo"; + } else if (eventMark == Marks::Marcato) { + outStr += "\\marcato"; + } else if (eventMark == Marks::Trill) { + outStr += "\\trill"; + } else if (eventMark == Marks::LongTrill) { + // span trill up to the next note: + // tweak the beginning of the next note using an invisible rest having zero length + outStr += "\\startTrillSpan s4*0 \\stopTrillSpan"; + } else if (eventMark == Marks::Turn) { + outStr += "\\turn"; + } else if (eventMark == Marks::Pause) { + outStr += "\\fermata"; + } else if (eventMark == Marks::UpBow) { + outStr += "\\upbow"; + } else if (eventMark == Marks::DownBow) { + outStr += "\\downbow"; + } else { + outStr = ""; + std::cerr << "LilyPondExporter::composeLilyMark() - unhandled mark: " + << eventMark << std::endl; + } + } + + return outStr; +} + +std::string +LilyPondExporter::indent(const int &column) +{ + std::string outStr = ""; + for (int c = 1; c <= column; c++) { + outStr += " "; + } + return outStr; +} + +std::string +LilyPondExporter::protectIllegalChars(std::string inStr) +{ + + QString tmpStr = strtoqstr(inStr); + + tmpStr.replace(QRegExp("&"), "\\&"); + tmpStr.replace(QRegExp("\\^"), "\\^"); + tmpStr.replace(QRegExp("%"), "\\%"); + tmpStr.replace(QRegExp("<"), "\\<"); + tmpStr.replace(QRegExp(">"), "\\>"); + tmpStr.replace(QRegExp("\\["), ""); + tmpStr.replace(QRegExp("\\]"), ""); + tmpStr.replace(QRegExp("\\{"), ""); + tmpStr.replace(QRegExp("\\}"), ""); + + // + // LilyPond uses utf8 encoding. + // + return tmpStr.utf8().data(); +} + +struct MarkerComp { + // Sort Markers by time + // Perhaps this should be made generic with a template? + bool operator()( Marker *a, Marker *b ) { + return a->getTime() < b->getTime(); + } +}; + +bool +LilyPondExporter::write() +{ + QString tmpName = strtoqstr(m_fileName); + + // dmm - modified to act upon the filename itself, rather than the whole + // path; fixes bug #855349 + + // split name into parts: + QFileInfo nfo(tmpName); + QString dirName = nfo.dirPath(); + QString baseName = nfo.fileName(); + + // sed LilyPond-choking chars out of the filename proper + bool illegalFilename = (baseName.contains(' ') || baseName.contains("\\")); + baseName.replace(QRegExp(" "), ""); + baseName.replace(QRegExp("\\\\"), ""); + baseName.replace(QRegExp("'"), ""); + baseName.replace(QRegExp("\""), ""); + + // cat back together + tmpName = dirName + '/' + baseName; + + if (illegalFilename) { + CurrentProgressDialog::freeze(); + int reply = KMessageBox::warningContinueCancel( + 0, i18n("LilyPond does not allow spaces or backslashes in filenames.\n\n" + "Would you like to use\n\n %1\n\n instead?").arg(baseName)); + if (reply != KMessageBox::Continue) + return false; + } + + std::ofstream str(qstrtostr(tmpName).c_str(), std::ios::out); + if (!str) { + std::cerr << "LilyPondExporter::write() - can't write file " << tmpName << std::endl; + return false; + } + + str << "% This LilyPond file was generated by Rosegarden " << protectIllegalChars(VERSION) << std::endl; + + switch (m_languageLevel) { + + case LILYPOND_VERSION_2_6: + str << "\\version \"2.6.0\"" << std::endl; + break; + + case LILYPOND_VERSION_2_8: + str << "\\version \"2.8.0\"" << std::endl; + break; + + case LILYPOND_VERSION_2_10: + str << "\\version \"2.10.0\"" << std::endl; + break; + + case LILYPOND_VERSION_2_12: + str << "\\version \"2.12.0\"" << std::endl; + break; + + default: + // force the default version if there was an error + std::cerr << "ERROR: Unknown language level " << m_languageLevel + << ", using \\version \"2.6.0\" instead" << std::endl; + str << "\\version \"2.6.0\"" << std::endl; + m_languageLevel = LILYPOND_VERSION_2_6; + } + + // enable "point and click" debugging via pdf to make finding the + // unfortunately inevitable errors easier + if (m_exportPointAndClick) { + str << "% point and click debugging is enabled" << std::endl; + } else { + str << "% point and click debugging is disabled" << std::endl; + str << "#(ly:set-option 'point-and-click #f)" << std::endl; + } + + // LilyPond \header block + + // set indention level to make future changes to horizontal layout less + // tedious, ++col to indent a new level, --col to de-indent + int col = 0; + + // grab user headers from metadata + Configuration metadata = m_composition->getMetadata(); + std::vector propertyNames = metadata.getPropertyNames(); + + // open \header section if there's metadata to grab, and if the user + // wishes it + if (!propertyNames.empty()) { + str << "\\header {" << std::endl; + col++; // indent+ + + bool userTagline = false; + + for (unsigned int index = 0; index < propertyNames.size(); ++index) { + std::string property = propertyNames [index]; + if (property == headerDedication || property == headerTitle || + property == headerSubtitle || property == headerSubsubtitle || + property == headerPoet || property == headerComposer || + property == headerMeter || property == headerOpus || + property == headerArranger || property == headerInstrument || + property == headerPiece || property == headerCopyright || + property == headerTagline) { + std::string header = protectIllegalChars(metadata.get(property)); + if (header != "") { + str << indent(col) << property << " = \"" << header << "\"" << std::endl; + // let users override defaults, but allow for providing + // defaults if they don't: + if (property == headerTagline) + userTagline = true; + } + } + } + + // default tagline + if (!userTagline) { + str << indent(col) << "tagline = \"" + << "Created using Rosegarden " << protectIllegalChars(VERSION) << " and LilyPond" + << "\"" << std::endl; + } + + // close \header + str << indent(--col) << "}" << std::endl; + } + + // LilyPond \paper block (optional) + if (m_raggedBottom) { + str << indent(col) << "\\paper {" << std::endl; + str << indent(++col) << "ragged-bottom=##t" << std::endl; + str << indent(--col) << "}" << std::endl; + } + + // LilyPond music data! Mapping: + // LilyPond Voice = Rosegarden Segment + // LilyPond Staff = Rosegarden Track + // (not the cleanest output but maybe the most reliable) + + // paper/font sizes + int font; + switch (m_fontSize) { + case 0 : + font = 11; + break; + case 1 : + font = 13; + break; + case 2 : + font = 16; + break; + case 3 : + font = 19; + break; + case 4 : + font = 20; + break; + case 5 : + font = 23; + break; + case 6 : + font = 26; + break; + default : + font = 20; // if config problem + } + + str << indent(col) << "#(set-global-staff-size " << font << ")" << std::endl; + + // write user-specified paper type as default paper size + std::string paper = ""; + switch (m_paperSize) { + case PAPER_A3 : + paper += "a3"; + break; + case PAPER_A4 : + paper += "a4"; + break; + case PAPER_A5 : + paper += "a5"; + break; + case PAPER_A6 : + paper += "a6"; + break; + case PAPER_LEGAL : + paper += "legal"; + break; + case PAPER_LETTER : + paper += "letter"; + break; + case PAPER_TABLOID : + paper += "tabloid"; + break; + case PAPER_NONE : + paper = ""; + break; // "do not specify" + } + if (paper != "") { + str << indent(col) << "#(set-default-paper-size \"" << paper << "\"" + << (m_paperLandscape ? " 'landscape" : "") << ")" + << std::endl; + } + + // Find out the printed length of the composition + Composition::iterator i = m_composition->begin(); + if ((*i) == NULL) { + str << indent(col) << "\\score {" << std::endl; + str << indent(++col) << "% no segments found" << std::endl; + // bind staffs with or without staff group bracket + str << indent(col) // indent + << "<<" << " s4 " << ">>" << std::endl; + str << indent(col) << "\\layout { }" << std::endl; + str << indent(--col) << "}" << std::endl; + return true; + } + timeT compositionStartTime = (*i)->getStartTime(); + timeT compositionEndTime = (*i)->getEndMarkerTime(); + for (; i != m_composition->end(); ++i) { + if (compositionStartTime > (*i)->getStartTime() && (*i)->getTrack() >= 0) { + compositionStartTime = (*i)->getStartTime(); + } + if (compositionEndTime < (*i)->getEndMarkerTime()) { + compositionEndTime = (*i)->getEndMarkerTime(); + } + } + + // define global context which is common for all staffs + str << indent(col++) << "global = { " << std::endl; + TimeSignature timeSignature = m_composition-> + getTimeSignatureAt(m_composition->getStartMarker()); + if (m_composition->getBarStart(m_composition->getBarNumber(compositionStartTime)) < compositionStartTime) { + str << indent(col) << "\\partial "; + // Arbitrary partial durations are handled by the following way: + // split the partial duration to 64th notes: instead of "4" write "64*16". (hjj) + Note partialNote = Note::getNearestNote(1, MAX_DOTS); + int partialDuration = m_composition->getBarStart(m_composition->getBarNumber(compositionStartTime) + 1) - compositionStartTime; + writeDuration(1, str); + str << "*" << ((int)(partialDuration / partialNote.getDuration())) + << std::endl; + } + int leftBar = 0; + int rightBar = leftBar; + do { + bool isNew = false; + m_composition->getTimeSignatureInBar(rightBar + 1, isNew); + + if (isNew || (m_composition->getBarStart(rightBar + 1) >= compositionEndTime)) { + // - set initial time signature; further time signature changes + // are defined within the segments, because they may be hidden + str << indent(col) << (leftBar == 0 ? "" : "% ") << "\\time " + << timeSignature.getNumerator() << "/" + << timeSignature.getDenominator() << std::endl; + // - place skips upto the end of the composition; + // this justifies the printed staffs + str << indent(col); + timeT leftTime = m_composition->getBarStart(leftBar); + timeT rightTime = m_composition->getBarStart(rightBar + 1); + if (leftTime < compositionStartTime) { + leftTime = compositionStartTime; + } + writeSkip(timeSignature, leftTime, rightTime - leftTime, false, str); + str << " %% " << (leftBar + 1) << "-" << (rightBar + 1) << std::endl; + + timeSignature = m_composition->getTimeSignatureInBar(rightBar + 1, isNew); + leftBar = rightBar + 1; + } + } while (m_composition->getBarStart(++rightBar) < compositionEndTime); + str << indent(--col) << "}" << std::endl; + + // time signatures changes are in segments, reset initial value + timeSignature = m_composition-> + getTimeSignatureAt(m_composition->getStartMarker()); + + // All the tempo changes are included in "globalTempo" context. + // This context contains only skip notes between the tempo changes. + // First tempo marking should still be include in \midi{ } block. + // If tempo marks are printed in future, they should probably be + // included in this context and the note duration in the tempo + // mark should be according to the time signature. (hjj) + int tempoCount = m_composition->getTempoChangeCount(); + + if (tempoCount > 0) { + + timeT prevTempoChangeTime = m_composition->getStartMarker(); + int tempo = int(Composition::getTempoQpm(m_composition->getTempoAtTime(prevTempoChangeTime))); + bool tempoMarksInvisible = false; + + str << indent(col++) << "globalTempo = {" << std::endl; + if (m_exportTempoMarks == EXPORT_NONE_TEMPO_MARKS && tempoMarksInvisible == false) { + str << indent(col) << "\\override Score.MetronomeMark #'transparent = ##t" << std::endl; + tempoMarksInvisible = true; + } + str << indent(col) << "\\tempo 4 = " << tempo << " "; + int prevTempo = tempo; + + for (int i = 0; i < tempoCount; ++i) { + + std::pair tempoChange = + m_composition->getTempoChange(i); + + timeT tempoChangeTime = tempoChange.first; + + tempo = int(Composition::getTempoQpm(tempoChange.second)); + + // First tempo change may be before the first segment. + // Do not apply it before the first segment appears. + if (tempoChangeTime < compositionStartTime) { + tempoChangeTime = compositionStartTime; + } else if (tempoChangeTime >= compositionEndTime) { + tempoChangeTime = compositionEndTime; + } + if (prevTempoChangeTime < compositionStartTime) { + prevTempoChangeTime = compositionStartTime; + } else if (prevTempoChangeTime >= compositionEndTime) { + prevTempoChangeTime = compositionEndTime; + } + writeSkip(m_composition->getTimeSignatureAt(tempoChangeTime), + tempoChangeTime, tempoChangeTime - prevTempoChangeTime, false, str); + // add new \tempo only if tempo was changed + if (tempo != prevTempo) { + if (m_exportTempoMarks == EXPORT_FIRST_TEMPO_MARK && tempoMarksInvisible == false) { + str << std::endl << indent(col) << "\\override Score.MetronomeMark #'transparent = ##t"; + tempoMarksInvisible = true; + } + str << std::endl << indent(col) << "\\tempo 4 = " << tempo << " "; + } + + prevTempo = tempo; + prevTempoChangeTime = tempoChangeTime; + if (prevTempoChangeTime == compositionEndTime) + break; + } + // First tempo change may be before the first segment. + // Do not apply it before the first segment appears. + if (prevTempoChangeTime < compositionStartTime) { + prevTempoChangeTime = compositionStartTime; + } + writeSkip(m_composition->getTimeSignatureAt(prevTempoChangeTime), + prevTempoChangeTime, compositionEndTime - prevTempoChangeTime, false, str); + str << std::endl; + str << indent(--col) << "}" << std::endl; + } + // Markers + // Skip until marker, make sure there's only one marker per measure + if ( m_exportMarkerMode != EXPORT_NO_MARKERS ) { + str << indent(col++) << "markers = {" << std::endl; + timeT prevMarkerTime = 0; + + // Need the markers sorted by time + Composition::markercontainer markers( m_composition->getMarkers() ); // copy + std::sort( markers.begin(), markers.end(), MarkerComp() ); + Composition::markerconstiterator i_marker = markers.begin(); + + while ( i_marker != markers.end() ) { + timeT markerTime = m_composition->getBarStartForTime((*i_marker)->getTime()); + RG_DEBUG << "Marker: " << (*i_marker)->getTime() << " previous: " << prevMarkerTime << endl; + // how to cope with time signature changes? + if ( markerTime > prevMarkerTime ) { + str << indent(col); + writeSkip(m_composition->getTimeSignatureAt(markerTime), + markerTime, markerTime - prevMarkerTime, false, str); + str << "\\mark "; + switch (m_exportMarkerMode) { + case EXPORT_DEFAULT_MARKERS: + // Use the marker name for text + str << "\\default %% " << (*i_marker)->getName() << std::endl; + break; + case EXPORT_TEXT_MARKERS: + // Raise the text above the staff as not to clash with the other stuff + str << "\\markup { \\hspace #0 \\raise #1.5 \"" << (*i_marker)->getName() << "\" }" << std::endl; + break; + default: + break; + } + prevMarkerTime = markerTime; + } + ++i_marker; + } + str << indent(--col) << "}" << std::endl; + } + + // open \score section + str << "\\score {" << std::endl; + + int lastTrackIndex = -1; + int voiceCounter = 0; + bool firstTrack = true; + int staffGroupCounter = 0; + int pianoStaffCounter = 0; + int bracket = 0; + int prevBracket = -1; + + // Write out all segments for each Track, in track order. + // This involves a hell of a lot of loops through all tracks + // and segments, but the time spent doing that should still + // be relatively small in the greater scheme. + + Track *track = 0; + + for (int trackPos = 0; + (track = m_composition->getTrackByPosition(trackPos)) != 0; ++trackPos) { + + for (Composition::iterator i = m_composition->begin(); + i != m_composition->end(); ++i) { + + if ((*i)->getTrack() != track->getId()) + continue; + + // handle the bracket(s) for the first track, and if no brackets + // present, open with a << + prevBracket = bracket; + bracket = track->getStaffBracket(); + + //!!! how will all these indentions work out? Probably not well, + // but maybe if users always provide sensible input, this will work + // out sensibly. Maybe. If not, we'll need some tracking gizmos to + // figure out the indention, or just skip the indention for these or + // something. TBA. + if (firstTrack) { + // seems to be common to every case now + str << indent(col++) << "<< % common" << std::endl; + } + + if (firstTrack && m_exportStaffGroup) { + + if (bracket == Brackets::SquareOn) { + str << indent(col++) << "\\context StaffGroup = \"" << staffGroupCounter++ + << "\" << " << std::endl; //indent+ + } else if (bracket == Brackets::CurlyOn) { + str << indent(col++) << "\\context PianoStaff = \"" << pianoStaffCounter++ + << "\" << " << std::endl; //indent+ + } else if (bracket == Brackets::CurlySquareOn) { + str << indent(col++) << "\\context StaffGroup = \"" << staffGroupCounter++ + << "\" << " << std::endl; //indent+ + str << indent(col++) << "\\context PianoStaff = \"" << pianoStaffCounter++ + << "\" << " << std::endl; //indent+ + } + + // Make chords offset colliding notes by default (only write for + // first track) + str << indent(++col) << "% force offset of colliding notes in chords:" + << std::endl; + str << indent(col) << "\\override Score.NoteColumn #\'force-hshift = #1.0" + << std::endl; + } + + emit setProgress(int(double(trackPos) / + double(m_composition->getNbTracks()) * 100.0)); + rgapp->refreshGUI(50); + + bool currentSegmentSelected = false; + if ((m_exportSelection == EXPORT_SELECTED_SEGMENTS) && + (m_view != NULL) && (m_view->haveSelection())) { + // + // Check whether the current segment is in the list of selected segments. + // + SegmentSelection selection = m_view->getSelection(); + for (SegmentSelection::iterator it = selection.begin(); it != selection.end(); it++) { + if ((*it) == (*i)) currentSegmentSelected = true; + } + } else if ((m_exportSelection == EXPORT_SELECTED_SEGMENTS) && (m_notationView != NULL)) { + currentSegmentSelected = m_notationView->hasSegment(*i); + } + + // Check whether the track is a non-midi track. + InstrumentId instrumentId = track->getInstrument(); + bool isMidiTrack = instrumentId >= MidiInstrumentBase; + + if (isMidiTrack && ( // Skip non-midi tracks. + (m_exportSelection == EXPORT_ALL_TRACKS) || + ((m_exportSelection == EXPORT_NONMUTED_TRACKS) && (!track->isMuted())) || + ((m_exportSelection == EXPORT_SELECTED_TRACK) && (m_view != NULL) && + (track->getId() == m_composition->getSelectedTrack())) || + ((m_exportSelection == EXPORT_SELECTED_TRACK) && (m_notationView != NULL) && + (track->getId() == m_notationView->getCurrentSegment()->getTrack())) || + ((m_exportSelection == EXPORT_SELECTED_SEGMENTS) && (currentSegmentSelected)))) { + if ((int) (*i)->getTrack() != lastTrackIndex) { + if (lastTrackIndex != -1) { + // close the old track (Staff context) + str << indent(--col) << ">> % Staff ends" << std::endl; //indent- + } + lastTrackIndex = (*i)->getTrack(); + + + // handle any necessary bracket closures with a rude + // hack, because bracket closures need to be handled + // right under staff closures, but at this point in the + // loop we are one track too early for closing, so we use + // the bracket setting for the previous track for closing + // purposes (I'm not quite sure why this works, but it does) + if (m_exportStaffGroup) { + if (prevBracket == Brackets::SquareOff || + prevBracket == Brackets::SquareOnOff) { + str << indent(--col) << ">> % StaffGroup " << staffGroupCounter + << std::endl; //indent- + } else if (prevBracket == Brackets::CurlyOff) { + str << indent(--col) << ">> % PianoStaff " << pianoStaffCounter + << std::endl; //indent- + } else if (prevBracket == Brackets::CurlySquareOff) { + str << indent(--col) << ">> % PianoStaff " << pianoStaffCounter + << std::endl; //indent- + str << indent(--col) << ">> % StaffGroup " << staffGroupCounter + << std::endl; //indent- + } + } + + // handle any bracket start events (unless track staff + // brackets are being ignored, as when printing single parts + // out of a bigger score one by one) + if (!firstTrack && m_exportStaffGroup) { + if (bracket == Brackets::SquareOn || + bracket == Brackets::SquareOnOff) { + str << indent(col++) << "\\context StaffGroup = \"" + << ++staffGroupCounter << "\" <<" << std::endl; + } else if (bracket == Brackets::CurlyOn) { + str << indent(col++) << "\\context PianoStaff = \"" + << ++pianoStaffCounter << "\" <<" << std::endl; + } else if (bracket == Brackets::CurlySquareOn) { + str << indent(col++) << "\\context StaffGroup = \"" + << ++staffGroupCounter << "\" <<" << std::endl; + str << indent(col++) << "\\context PianoStaff = \"" + << ++pianoStaffCounter << "\" <<" << std::endl; + } + } + + // avoid problem with tracks yielding a + // .ly file that jumbles all notes together on a + // single staff... every Staff context has to + // have a unique name, even if the + // Staff.instrument property is the same for + // multiple staffs... + // Added an option to merge staffs with the same, non-empty + // name. This option makes it possible to produce staffs + // with polyphonic, and polyrhytmic, music. Polyrhytmic + // music in a single staff is typical in piano, or + // guitar music. (hjj) + // In the case of colliding note heads, user may define + // - DISPLACED_X -- for a note/chord + // - INVISIBLE -- for a rest + std::ostringstream staffName; + staffName << protectIllegalChars(m_composition-> + getTrackById(lastTrackIndex)->getLabel()); + + if (!m_exportStaffMerge || staffName.str() == "") { + str << std::endl << indent(col) + << "\\context Staff = \"track " + << (trackPos + 1) << "\" "; + } else { + str << std::endl << indent(col) + << "\\context Staff = \"" << staffName.str() + << "\" "; + } + + str << "<< " << std::endl; + + // The octavation is omitted in the instrument name. + // HJJ: Should it be automatically added to the clef: G^8 ? + // What if two segments have different transpose in a track? + std::ostringstream staffNameWithTranspose; + staffNameWithTranspose << "\\markup { \\column { \"" << staffName.str() << " \""; + if (((*i)->getTranspose() % 12) != 0) { + staffNameWithTranspose << " \\line { "; + switch ((*i)->getTranspose() % 12) { + case 1 : staffNameWithTranspose << "\"in D\" \\smaller \\flat"; break; + case 2 : staffNameWithTranspose << "\"in D\""; break; + case 3 : staffNameWithTranspose << "\"in E\" \\smaller \\flat"; break; + case 4 : staffNameWithTranspose << "\"in E\""; break; + case 5 : staffNameWithTranspose << "\"in F\""; break; + case 6 : staffNameWithTranspose << "\"in G\" \\smaller \\flat"; break; + case 7 : staffNameWithTranspose << "\"in G\""; break; + case 8 : staffNameWithTranspose << "\"in A\" \\smaller \\flat"; break; + case 9 : staffNameWithTranspose << "\"in A\""; break; + case 10 : staffNameWithTranspose << "\"in B\" \\smaller \\flat"; break; + case 11 : staffNameWithTranspose << "\"in B\""; break; + } + staffNameWithTranspose << " }"; + } + staffNameWithTranspose << " } }"; + if (m_languageLevel < LILYPOND_VERSION_2_10) { + str << indent(++col) << "\\set Staff.instrument = " << staffNameWithTranspose.str() + << std::endl; + } else { + str << indent(++col) << "\\set Staff.instrumentName = " + << staffNameWithTranspose.str() << std::endl; + } + + if (m_exportMidi) { + // Set midi instrument for the Staff + std::ostringstream staffMidiName; + Instrument *instr = m_studio->getInstrumentById( + m_composition->getTrackById(lastTrackIndex)->getInstrument()); + staffMidiName << instr->getProgramName(); + + str << indent(col) << "\\set Staff.midiInstrument = \"" << staffMidiName.str() + << "\"" << std::endl; + } + + // multi measure rests are used by default + str << indent(col) << "\\set Score.skipBars = ##t" << std::endl; + + // turn off the stupid accidental cancelling business, + // because we don't do that ourselves, and because my 11 + // year old son pointed out to me that it "Looks really + // stupid. Why is it cancelling out four flats and then + // adding five flats back? That's brain damaged." + str << indent(col) << "\\set Staff.printKeyCancellation = ##f" << std::endl; + str << indent(col) << "\\new Voice \\global" << std::endl; + if (tempoCount > 0) { + str << indent(col) << "\\new Voice \\globalTempo" << std::endl; + } + if ( m_exportMarkerMode != EXPORT_NO_MARKERS ) { + str << indent(col) << "\\new Voice \\markers" << std::endl; + } + + } + + // Temporary storage for non-atomic events (!BOOM) + // ex. LilyPond expects signals when a decrescendo starts + // as well as when it ends + eventendlist eventsInProgress; + eventstartlist eventsToStart; + + // If the segment doesn't start at 0, add a "skip" to the start + // No worries about overlapping segments, because Voices can overlap + // voiceCounter is a hack because LilyPond does not by default make + // them unique + std::ostringstream voiceNumber; + voiceNumber << "voice " << ++voiceCounter; + + str << std::endl << indent(col++) << "\\context Voice = \"" << voiceNumber.str() + << "\" {"; // indent+ + + str << std::endl << indent(col) << "\\override Voice.TextScript #'padding = #2.0"; + str << std::endl << indent(col) << "\\override MultiMeasureRest #'expand-limit = 1" << std::endl; + + // staff notation size + int staffSize = track->getStaffSize(); + if (staffSize == StaffTypes::Small) str << indent(col) << "\\small" << std::endl; + else if (staffSize == StaffTypes::Tiny) str << indent(col) << "\\tiny" << std::endl; + + SegmentNotationHelper helper(**i); + helper.setNotationProperties(); + + int firstBar = m_composition->getBarNumber((*i)->getStartTime()); + + if (firstBar > 0) { + // Add a skip for the duration until the start of the first + // bar in the segment. If the segment doesn't start on a bar + // line, an additional skip will be written (in the form of + // a series of rests) at the start of writeBar, below. + //!!! This doesn't cope correctly yet with time signature changes + // during this skipped section. + str << std::endl << indent(col); + writeSkip(timeSignature, compositionStartTime, + m_composition->getBarStart(firstBar) - compositionStartTime, + false, str); + } + + std::string lilyText = ""; // text events + std::string prevStyle = ""; // track note styles + + Rosegarden::Key key; + + bool haveRepeating = false; + bool haveAlternates = false; + + bool nextBarIsAlt1 = false; + bool nextBarIsAlt2 = false; + bool prevBarWasAlt2 = false; + + int MultiMeasureRestCount = 0; + + bool nextBarIsDouble = false; + bool nextBarIsEnd = false; + bool nextBarIsDot = false; + + for (int barNo = m_composition->getBarNumber((*i)->getStartTime()); + barNo <= m_composition->getBarNumber((*i)->getEndMarkerTime()); + ++barNo) { + + timeT barStart = m_composition->getBarStart(barNo); + timeT barEnd = m_composition->getBarEnd(barNo); + if (barStart < compositionStartTime) { + barStart = compositionStartTime; + } + + // open \repeat section if this is the first bar in the + // repeat + if ((*i)->isRepeating() && !haveRepeating) { + + haveRepeating = true; + + //!!! calculate the number of times this segment + //repeats and make the following variable meaningful + int numRepeats = 2; + + str << std::endl << indent(col++) << "\\repeat volta " << numRepeats << " {"; + } + + // open the \alternative section if this bar is alternative ending 1 + // ending (because there was an "Alt1" flag in the + // previous bar to the left of where we are right now) + // + // Alt1 remains in effect until we run into Alt2, which + // runs to the end of the segment + if (nextBarIsAlt1 && haveRepeating) { + str << std::endl << indent(--col) << "} \% repeat close (before alternatives) "; + str << std::endl << indent(col++) << "\\alternative {"; + str << std::endl << indent(col++) << "{ \% open alternative 1 "; + nextBarIsAlt1 = false; + haveAlternates = true; + } else if (nextBarIsAlt2 && haveRepeating) { + if (!prevBarWasAlt2) { + col--; + // add an extra str to the following to shut up + // compiler warning from --ing and ++ing it in the + // same statement + str << std::endl << indent(--col) << "} \% close alternative 1 "; + str << std::endl << indent(col++) << "{ \% open alternative 2"; + col++; + } + prevBarWasAlt2 = true; + } + + // write out a bar's worth of events + writeBar(*i, barNo, barStart, barEnd, col, key, + lilyText, + prevStyle, eventsInProgress, str, + MultiMeasureRestCount, + nextBarIsAlt1, nextBarIsAlt2, nextBarIsDouble, nextBarIsEnd, nextBarIsDot); + + } + + // close \repeat + if (haveRepeating) { + + // close \alternative section if present + if (haveAlternates) { + str << std::endl << indent(--col) << " } \% close alternative 2 "; + } + + // close \repeat section in either case + str << std::endl << indent(--col) << " } \% close " + << (haveAlternates ? "alternatives" : "repeat"); + } + + // closing bar + if (((*i)->getEndMarkerTime() == compositionEndTime) && !haveRepeating) { + str << std::endl << indent(col) << "\\bar \"|.\""; + } + + // close Voice context + str << std::endl << indent(--col) << "} % Voice" << std::endl; // indent- + + // + // Write accumulated lyric events to the Lyric context, if desired. + // + // Sync the code below with LyricEditDialog::unparse() !! + // + if (m_exportLyrics) { + for (long currentVerse = 0, lastVerse = 0; + currentVerse <= lastVerse; + currentVerse++) { + bool haveLyric = false; + bool firstNote = true; + QString text = ""; + + timeT lastTime = (*i)->getStartTime(); + for (Segment::iterator j = (*i)->begin(); + (*i)->isBeforeEndMarker(j); ++j) { + + bool isNote = (*j)->isa(Note::EventType); + bool isLyric = false; + + if (!isNote) { + if ((*j)->isa(Text::EventType)) { + std::string textType; + if ((*j)->get + (Text::TextTypePropertyName, textType) && + textType == Text::Lyric) { + isLyric = true; + } + } + } + + if (!isNote && !isLyric) continue; + + timeT myTime = (*j)->getNotationAbsoluteTime(); + + if (isNote) { + if ((myTime > lastTime) || firstNote) { + if (!haveLyric) + text += " _"; + lastTime = myTime; + haveLyric = false; + firstNote = false; + } + } + + if (isLyric) { + long verse; + (*j)->get(Text::LyricVersePropertyName, verse); + + if (verse == currentVerse) { + std::string ssyllable; + (*j)->get(Text::TextPropertyName, ssyllable); + text += " "; + + QString syllable(strtoqstr(ssyllable)); + syllable.replace(QRegExp("\\s+"), ""); + text += "\"" + syllable + "\""; + haveLyric = true; + } else if (verse > lastVerse) { + lastVerse = verse; + } + } + } + + text.replace( QRegExp(" _+([^ ])") , " \\1" ); + text.replace( "\"_\"" , " " ); + + // Do not create empty context for lyrics. + // Does this save some vertical space, as was written + // in earlier comment? + QRegExp rx( "\"" ); + if ( rx.search( text ) != -1 ) { + + str << indent(col) << "\\lyricsto \"" << voiceNumber.str() << "\"" + << " \\new Lyrics \\lyricmode {" << std::endl; + if (m_lyricsHAlignment == RIGHT_ALIGN) { + str << indent(++col) << "\\override LyricText #'self-alignment-X = #RIGHT" + << std::endl; + } else if (m_lyricsHAlignment == CENTER_ALIGN) { + str << indent(++col) << "\\override LyricText #'self-alignment-X = #CENTER" + << std::endl; + } else { + str << indent(++col) << "\\override LyricText #'self-alignment-X = #LEFT" + << std::endl; + } + str << indent(col) << "\\set ignoreMelismata = ##t" << std::endl; + str << indent(col) << text.utf8() << " " << std::endl; + str << indent(col) << "\\unset ignoreMelismata" << std::endl; + str << indent(--col) << "} % Lyrics " << (currentVerse+1) << std::endl; + // close the Lyrics context + } // if ( rx.search( text.... + } // for (long currentVerse = 0.... + } // if (m_exportLyrics.... + } // if (isMidiTrack.... + firstTrack = false; + } // for (Composition::iterator i = m_composition->begin().... + } // for (int trackPos = 0.... + + // close the last track (Staff context) + if (voiceCounter > 0) { + str << indent(--col) << ">> % Staff (final) ends" << std::endl; // indent- + + // handle any necessary final bracket closures (if brackets are being + // exported) + if (m_exportStaffGroup) { + if (bracket == Brackets::SquareOff || + bracket == Brackets::SquareOnOff) { + str << indent(--col) << ">> % StaffGroup " << staffGroupCounter + << std::endl; //indent- + } else if (bracket == Brackets::CurlyOff) { + str << indent(--col) << ">> % PianoStaff (final) " << pianoStaffCounter + << std::endl; //indent- + } else if (bracket == Brackets::CurlySquareOff) { + str << indent(--col) << ">> % PianoStaff (final) " << pianoStaffCounter + << std::endl; //indent- + str << indent(--col) << ">> % StaffGroup (final) " << staffGroupCounter + << std::endl; //indent- + } + } + } else { + str << indent(--col) << "% (All staffs were muted.)" << std::endl; + } + + // close \notes section + str << std::endl << indent(--col) << ">> % notes" << std::endl << std::endl; // indent- +// str << std::endl << indent(col) << ">> % global wrapper" << std::endl; + + // write \layout block + str << indent(col) << "\\layout { }" << std::endl; + + // write initial tempo in Midi block, if user wishes (added per user request... + // makes debugging the .ly file easier because fewer "noisy" errors are + // produced during the process of rendering MIDI...) + if (m_exportMidi) { + int tempo = int(Composition::getTempoQpm(m_composition->getTempoAtTime(m_composition->getStartMarker()))); + // Incomplete? Can I get away without converting tempo relative to the time + // signature for this purpose? we'll see... + str << indent(col++) << "\\midi {" << std::endl; + str << indent(col) << "\\tempo 4 = " << tempo << std::endl; + str << indent(--col) << "} " << std::endl; + } + + // close \score section and close out the file + str << "} % score" << std::endl; + str.close(); + return true; +} + +timeT +LilyPondExporter::calculateDuration(Segment *s, + const Segment::iterator &i, + timeT barEnd, + timeT &soundingDuration, + const std::pair &tupletRatio, + bool &overlong) +{ + timeT duration = (*i)->getNotationDuration(); + timeT absTime = (*i)->getNotationAbsoluteTime(); + + RG_DEBUG << "LilyPondExporter::calculateDuration: first duration, absTime: " + << duration << ", " << absTime << endl; + + timeT durationCorrection = 0; + + if ((*i)->isa(Note::EventType) || (*i)->isa(Note::EventRestType)) { + try { + // tuplet compensation, etc + Note::Type type = (*i)->get(NOTE_TYPE); + int dots = (*i)->get(NOTE_DOTS); + durationCorrection = Note(type, dots).getDuration() - duration; + } catch (Exception e) { // no properties + } + } + + duration += durationCorrection; + + RG_DEBUG << "LilyPondExporter::calculateDuration: now duration is " + << duration << " after correction of " << durationCorrection << endl; + + soundingDuration = duration * tupletRatio.first/ tupletRatio.second; + + timeT toNext = barEnd - absTime; + if (soundingDuration > toNext) { + soundingDuration = toNext; + duration = soundingDuration * tupletRatio.second/ tupletRatio.first; + overlong = true; + } + + RG_DEBUG << "LilyPondExporter::calculateDuration: time to barEnd is " + << toNext << endl; + + // Examine the following event, and truncate our duration + // if we overlap it. + Segment::iterator nextElt = s->end(); + toNext = soundingDuration; + + if ((*i)->isa(Note::EventType)) { + + Chord chord(*s, i, m_composition->getNotationQuantizer()); + Segment::iterator nextElt = chord.getFinalElement(); + ++nextElt; + + if (s->isBeforeEndMarker(nextElt)) { + // The quantizer sometimes sticks a rest at the same time + // as this note -- don't use that one here, and mark it as + // not to be exported -- it's just a heavy-handed way of + // rendering counterpoint in RG + if ((*nextElt)->isa(Note::EventRestType) && + (*nextElt)->getNotationAbsoluteTime() == absTime) { + (*nextElt)->set(SKIP_PROPERTY, true); + ++nextElt; + } + } + + } else { + nextElt = i; + ++nextElt; + while (s->isBeforeEndMarker(nextElt)) { + if ((*nextElt)->isa(Controller::EventType) || + (*nextElt)->isa(ProgramChange::EventType) || + (*nextElt)->isa(SystemExclusive::EventType) || + (*nextElt)->isa(ChannelPressure::EventType) || + (*nextElt)->isa(KeyPressure::EventType) || + (*nextElt)->isa(PitchBend::EventType)) + ++nextElt; + else + break; + } + } + + if (s->isBeforeEndMarker(nextElt)) { + RG_DEBUG << "LilyPondExporter::calculateDuration: inside conditional " << endl; + toNext = (*nextElt)->getNotationAbsoluteTime() - absTime; + // if the note was lengthened, assume it was lengthened to the left + // when truncating to the beginning of the next note + if (durationCorrection > 0) { + toNext += durationCorrection; + } + if (soundingDuration > toNext) { + soundingDuration = toNext; + duration = soundingDuration * tupletRatio.second/ tupletRatio.first; + } + } + + RG_DEBUG << "LilyPondExporter::calculateDuration: second toNext is " + << toNext << endl; + + RG_DEBUG << "LilyPondExporter::calculateDuration: final duration, soundingDuration: " << duration << ", " << soundingDuration << endl; + + return duration; +} + +void +LilyPondExporter::writeBar(Segment *s, + int barNo, int barStart, int barEnd, int col, + Rosegarden::Key &key, + std::string &lilyText, + std::string &prevStyle, + eventendlist &eventsInProgress, + std::ofstream &str, + int &MultiMeasureRestCount, + bool &nextBarIsAlt1, bool &nextBarIsAlt2, + bool &nextBarIsDouble, bool &nextBarIsEnd, bool &nextBarIsDot) +{ + int lastStem = 0; // 0 => unset, -1 => down, 1 => up + int isGrace = 0; + + Segment::iterator i = s->findTime(barStart); + if (!s->isBeforeEndMarker(i)) + return ; + + if (MultiMeasureRestCount == 0) { + str << std::endl; + + if ((barNo + 1) % 5 == 0) { + str << "%% " << barNo + 1 << std::endl << indent(col); + } else { + str << indent(col); + } + } + + bool isNew = false; + TimeSignature timeSignature = m_composition->getTimeSignatureInBar(barNo, isNew); + if (isNew) { + if (timeSignature.isHidden()) { + str << "\\once \\override Staff.TimeSignature #'break-visibility = #(vector #f #f #f) "; + } + str << "\\time " + << timeSignature.getNumerator() << "/" + << timeSignature.getDenominator() + << std::endl << indent(col); + } + + timeT absTime = (*i)->getNotationAbsoluteTime(); + timeT writtenDuration = 0; + std::pair barDurationRatio(timeSignature.getNumerator(),timeSignature.getDenominator()); + std::pair durationRatioSum(0,1); + static std::pair durationRatio(0,1); + + if (absTime > barStart) { + Note note(Note::getNearestNote(absTime - barStart, MAX_DOTS)); + writtenDuration += note.getDuration(); + durationRatio = writeSkip(timeSignature, 0, note.getDuration(), true, str); + durationRatioSum = fractionSum(durationRatioSum,durationRatio); + // str << qstrtostr(QString(" %{ %1/%2 %} ").arg(durationRatio.first).arg(durationRatio.second)); // DEBUG + } + + timeT prevDuration = -1; + eventstartlist eventsToStart; + + long groupId = -1; + std::string groupType = ""; + std::pair tupletRatio(1, 1); + + bool overlong = false; + bool newBeamedGroup = false; + int notesInBeamedGroup = 0; + + while (s->isBeforeEndMarker(i)) { + + if ((*i)->getNotationAbsoluteTime() >= barEnd) + break; + + // First test whether we're entering or leaving a group, + // before we consider how to write the event itself (at least + // for pre-2.0 LilyPond output) + QString startGroupBeamingsStr = ""; + QString endGroupBeamingsStr = ""; + + if ((*i)->isa(Note::EventType) || (*i)->isa(Note::EventRestType) || + (*i)->isa(Clef::EventType) || (*i)->isa(Rosegarden::Key::EventType)) { + + long newGroupId = -1; + if ((*i)->get + (BEAMED_GROUP_ID, newGroupId)) { + + if (newGroupId != groupId) { + // entering a new beamed group + + if (groupId != -1) { + // and leaving an old one + if (groupType == GROUP_TYPE_TUPLED) { + if (m_exportBeams && notesInBeamedGroup > 0) + endGroupBeamingsStr += "] "; + endGroupBeamingsStr += "} "; + } else if (groupType == GROUP_TYPE_BEAMED) { + if (m_exportBeams && notesInBeamedGroup > 0) + endGroupBeamingsStr += "] "; + } + } + + groupId = newGroupId; + groupType = ""; + (void)(*i)->get + (BEAMED_GROUP_TYPE, groupType); + + if (groupType == GROUP_TYPE_TUPLED) { + long numerator = 0; + long denominator = 0; + (*i)->get + (BEAMED_GROUP_TUPLED_COUNT, numerator); + (*i)->get + (BEAMED_GROUP_UNTUPLED_COUNT, denominator); + if (numerator == 0 || denominator == 0) { + std::cerr << "WARNING: LilyPondExporter::writeBar: " + << "tupled event without tupled/untupled counts" + << std::endl; + groupId = -1; + groupType = ""; + } else { + startGroupBeamingsStr += QString("\\times %1/%2 { ").arg(numerator).arg(denominator); + tupletRatio = std::pair(numerator, denominator); + // Require explicit beamed groups, + // fixes bug #1683205. + // HJJ: Why line below was originally present? + // newBeamedGroup = true; + notesInBeamedGroup = 0; + } + } else if (groupType == GROUP_TYPE_BEAMED) { + newBeamedGroup = true; + notesInBeamedGroup = 0; + // there can currently be only on group type, reset tuplet ratio + tupletRatio = std::pair(1,1); + } + } + + } + else { + + if (groupId != -1) { + // leaving a beamed group + if (groupType == GROUP_TYPE_TUPLED) { + if (m_exportBeams && notesInBeamedGroup > 0) + endGroupBeamingsStr += "] "; + endGroupBeamingsStr += "} "; + tupletRatio = std::pair(1, 1); + } else if (groupType == GROUP_TYPE_BEAMED) { + if (m_exportBeams && notesInBeamedGroup > 0) + endGroupBeamingsStr += "] "; + } + groupId = -1; + groupType = ""; + } + } + } + + // Test whether the next note is grace note or not. + // The start or end of beamed grouping should be put in proper places. + str << endGroupBeamingsStr.utf8(); + if ((*i)->has(IS_GRACE_NOTE) && (*i)->get(IS_GRACE_NOTE)) { + if (isGrace == 0) { + isGrace = 1; + str << "\\grace { "; + // str << "%{ grace starts %} "; // DEBUG + } + } else if (isGrace == 1) { + isGrace = 0; + // str << "%{ grace ends %} "; // DEBUG + str << "} "; + } + str << startGroupBeamingsStr.utf8(); + + timeT soundingDuration = -1; + timeT duration = calculateDuration + (s, i, barEnd, soundingDuration, tupletRatio, overlong); + + if (soundingDuration == -1) { + soundingDuration = duration * tupletRatio.first / tupletRatio.second; + } + + if ((*i)->has(SKIP_PROPERTY)) { + (*i)->unset(SKIP_PROPERTY); + ++i; + continue; + } + + bool needsSlashRest = false; + + if ((*i)->isa(Note::EventType)) { + + Chord chord(*s, i, m_composition->getNotationQuantizer()); + Event *e = *chord.getInitialNote(); + bool tiedForward = false; + bool tiedUp = false; + + // Examine the following event, and truncate our duration + // if we overlap it. + + if (e->has(DISPLACED_X)) { + double xDisplacement = 1 + ((double) e->get + (DISPLACED_X)) / 1000; + str << "\\once \\override NoteColumn #'force-hshift = #" + << xDisplacement << " "; + } + + bool hiddenNote = false; + if (e->has(INVISIBLE)) { + if (e->get + (INVISIBLE)) { + hiddenNote = true; + } + } + + if ( hiddenNote ) { + str << "\\hideNotes "; + } + + if (e->has(NotationProperties::STEM_UP)) { + if (e->get + (NotationProperties::STEM_UP)) { + if (lastStem != 1) { + str << "\\stemUp "; + lastStem = 1; + } + } + else { + if (lastStem != -1) { + str << "\\stemDown "; + lastStem = -1; + } + } + } else { + if (lastStem != 0) { + str << "\\stemNeutral "; + lastStem = 0; + } + } + + if (chord.size() > 1) + str << "< "; + + Segment::iterator stylei = s->end(); + + for (i = chord.getInitialElement(); s->isBeforeEndMarker(i); ++i) { + + if ((*i)->isa(Text::EventType)) { + if (!handleDirective(*i, lilyText, nextBarIsAlt1, nextBarIsAlt2, + nextBarIsDouble, nextBarIsEnd, nextBarIsDot)) { + + handleText(*i, lilyText); + } + + } else if ((*i)->isa(Note::EventType)) { + + if (m_languageLevel >= LILYPOND_VERSION_2_8) { + // one \tweak per each chord note + if (chord.size() > 1) + writeStyle(*i, prevStyle, col, str, true); + else + writeStyle(*i, prevStyle, col, str, false); + } else { + // only one override per chord, and that outside the <> + stylei = i; + } + writePitch(*i, key, str); + + bool noteHasCautionaryAccidental = false; + (*i)->get + (NotationProperties::USE_CAUTIONARY_ACCIDENTAL, noteHasCautionaryAccidental); + if (noteHasCautionaryAccidental) + str << "?"; + + // get TIED_FORWARD and TIE_IS_ABOVE for later + (*i)->get(TIED_FORWARD, tiedForward); + (*i)->get(TIE_IS_ABOVE, tiedUp); + + str << " "; + } else if ((*i)->isa(Indication::EventType)) { + eventsToStart.insert(*i); + eventsInProgress.insert(*i); + } + + if (i == chord.getFinalElement()) + break; + } + + if (chord.size() > 1) + str << "> "; + + if (duration != prevDuration) { + durationRatio = writeDuration(duration, str); + str << " "; + prevDuration = duration; + } + + if (m_languageLevel == LILYPOND_VERSION_2_6) { + // only one override per chord, and that outside the <> + if (stylei != s->end()) { + writeStyle(*stylei, prevStyle, col, str, false); + stylei = s->end(); + } + } + + if (lilyText != "") { + str << lilyText; + lilyText = ""; + } + writeSlashes(*i, str); + + writtenDuration += soundingDuration; + std::pair ratio = fractionProduct(durationRatio,tupletRatio); + durationRatioSum = fractionSum(durationRatioSum, ratio); + // str << qstrtostr(QString(" %{ %1/%2 * %3/%4 = %5/%6 %} ").arg(durationRatio.first).arg(durationRatio.second).arg(tupletRatio.first).arg(tupletRatio.second).arg(ratio.first).arg(ratio.second)); // DEBUG + + std::vector marks(chord.getMarksForChord()); + // problem here: stem direction unavailable (it's a view-local property) + bool stemUp = true; + e->get + (NotationProperties::STEM_UP, stemUp); + for (std::vector::iterator j = marks.begin(); j != marks.end(); ++j) { + str << composeLilyMark(*j, stemUp); + } + if (marks.size() > 0) + str << " "; + + handleEndingEvents(eventsInProgress, i, str); + handleStartingEvents(eventsToStart, str); + + if (tiedForward) + if (tiedUp) + str << "^~ "; + else + str << "_~ "; + + if ( hiddenNote ) { + str << "\\unHideNotes "; + } + + if (newBeamedGroup) { + // This is a workaround for bug #1705430: + // Beaming groups erroneous after merging notes + // There will be fewer "e4. [ ]" errors in LilyPond-compiling. + // HJJ: This should be fixed in notation engine, + // after which the workaround below should be removed. + Note note(Note::getNearestNote(duration, MAX_DOTS)); + + switch (note.getNoteType()) { + case Note::SixtyFourthNote: + case Note::ThirtySecondNote: + case Note::SixteenthNote: + case Note::EighthNote: + notesInBeamedGroup++; + break; + } + } + // // Old version before the workaround for bug #1705430: + // if (newBeamedGroup) + // notesInBeamedGroup++; + } else if ((*i)->isa(Note::EventRestType)) { + + bool hiddenRest = false; + if ((*i)->has(INVISIBLE)) { + if ((*i)->get + (INVISIBLE)) { + hiddenRest = true; + } + } + + bool offsetRest = false; + int restOffset = 0; + if ((*i)->has(DISPLACED_Y)) { + restOffset = (*i)->get(DISPLACED_Y); + offsetRest = true; + } + + if (offsetRest) { + std::cout << "REST OFFSET: " << restOffset << std::endl; + } else { + std::cout << "NO REST OFFSET" << std::endl; + } + + if (MultiMeasureRestCount == 0) { + if (hiddenRest) { + str << "s"; + } else if (duration == timeSignature.getBarDuration()) { + // Look ahead the segment in order to detect + // the number of measures in the multi measure rest. + Segment::iterator mm_i = i; + while (s->isBeforeEndMarker(++mm_i)) { + if ((*mm_i)->isa(Note::EventRestType) && + (*mm_i)->getNotationDuration() == (*i)->getNotationDuration() && + timeSignature == m_composition->getTimeSignatureAt((*mm_i)->getNotationAbsoluteTime())) { + MultiMeasureRestCount++; + } else { + break; + } + } + str << "R"; + } else { + if (offsetRest) { + // use offset height to get an approximate corresponding + // height on staff + restOffset = restOffset / 1000; + restOffset -= restOffset * 2; + + // use height on staff to get a MIDI pitch + // get clef from whatever the last clef event was + Rosegarden::Key k; + Accidental a; + Pitch helper(restOffset, m_lastClefFound, k, a); + + // port some code from writePitch() here, rather than + // rewriting writePitch() to do both jobs, which + // somebody could conceivably clean up one day if anyone + // is bored + + // use MIDI pitch to get a named note + int p = helper.getPerformancePitch(); + std::string n = convertPitchToLilyNote(p, a, k); + + // write named note + str << n; + + // generate and write octave marks + std::string m = ""; + int o = (int)(p / 12); + + // mystery hack (it was always aiming too low) + o++; + + if (o < 4) { + for (; o < 4; o++) + m += ","; + } else { + for (; o > 4; o--) + m += "\'"; + } + + str << m; + + // defer the \rest until after any duration, because it + // can't come before a duration if a duration change is + // necessary, which is all determined a bit further on + needsSlashRest = true; + + + std::cout << "using pitch letter:" + << n << m + << " for offset: " + << restOffset + << " for calculated octave: " + << o + << " in clef: " + << m_lastClefFound.getClefType() + << std::endl; + } else { + str << "r"; + } + } + + if (duration != prevDuration) { + durationRatio = writeDuration(duration, str); + if (MultiMeasureRestCount > 0) { + str << "*" << (1 + MultiMeasureRestCount); + } + prevDuration = duration; + } + + // have to add \rest to a fake rest note after any required + // duration change + if (needsSlashRest) { + str << "\\rest"; + needsSlashRest = false; + } + + if (lilyText != "") { + str << lilyText; + lilyText = ""; + } + + str << " "; + + handleEndingEvents(eventsInProgress, i, str); + handleStartingEvents(eventsToStart, str); + + if (newBeamedGroup) + notesInBeamedGroup++; + } else { + MultiMeasureRestCount--; + } + writtenDuration += soundingDuration; + std::pair ratio = fractionProduct(durationRatio,tupletRatio); + durationRatioSum = fractionSum(durationRatioSum, ratio); + // str << qstrtostr(QString(" %{ %1/%2 * %3/%4 = %5/%6 %} ").arg(durationRatio.first).arg(durationRatio.second).arg(tupletRatio.first).arg(tupletRatio.second).arg(ratio.first).arg(ratio.second)); // DEBUG + } else if ((*i)->isa(Clef::EventType)) { + + try { + // Incomplete: Set which note the clef should center on (DMM - why?) + // To allow octavation of the clef, enclose the clefname always with quotes. + str << "\\clef \""; + + Clef clef(**i); + + if (clef.getClefType() == Clef::Treble) { + str << "treble"; + } else if (clef.getClefType() == Clef::French) { + str << "french"; + } else if (clef.getClefType() == Clef::Soprano) { + str << "soprano"; + } else if (clef.getClefType() == Clef::Mezzosoprano) { + str << "mezzosoprano"; + } else if (clef.getClefType() == Clef::Alto) { + str << "alto"; + } else if (clef.getClefType() == Clef::Tenor) { + str << "tenor"; + } else if (clef.getClefType() == Clef::Baritone) { + str << "baritone"; + } else if (clef.getClefType() == Clef::Varbaritone) { + str << "varbaritone"; + } else if (clef.getClefType() == Clef::Bass) { + str << "bass"; + } else if (clef.getClefType() == Clef::Subbass) { + str << "subbass"; + } + + // save clef for later use by rests that need repositioned + m_lastClefFound = clef; + std::cout << "getting clef" + << std::endl + << "clef: " + << clef.getClefType() + << " lastClefFound: " + << m_lastClefFound.getClefType() + << std::endl; + + // Transpose the clef one or two octaves up or down, if specified. + int octaveOffset = clef.getOctaveOffset(); + if (octaveOffset > 0) { + str << "^" << 8*octaveOffset; + } else if (octaveOffset < 0) { + str << "_" << -8*octaveOffset; + } + + str << "\"" << std::endl << indent(col); + + } catch (Exception e) { + std::cerr << "Bad clef: " << e.getMessage() << std::endl; + } + + } else if ((*i)->isa(Rosegarden::Key::EventType)) { + // ignore hidden key signatures + bool hiddenKey = false; + if ((*i)->has(INVISIBLE)) { + (*i)->get (INVISIBLE, hiddenKey); + } + + if (!hiddenKey) { + try { + str << "\\key "; + key = Rosegarden::Key(**i); + + Accidental accidental = Accidentals::NoAccidental; + + str << convertPitchToLilyNote(key.getTonicPitch(), accidental, key); + + if (key.isMinor()) { + str << " \\minor"; + } else { + str << " \\major"; + } + str << std::endl << indent(col); + + } catch (Exception e) { + std::cerr << "Bad key: " << e.getMessage() << std::endl; + } + } + + } else if ((*i)->isa(Text::EventType)) { + + if (!handleDirective(*i, lilyText, nextBarIsAlt1, nextBarIsAlt2, + nextBarIsDouble, nextBarIsEnd, nextBarIsDot)) { + handleText(*i, lilyText); + } + + } else if ((*i)->isa(Guitar::Chord::EventType)) { + + try { + Guitar::Chord chord = Guitar::Chord(**i); + const Guitar::Fingering& fingering = chord.getFingering(); + + int barreStart = 0, barreEnd = 0, barreFret = 0; + + // + // Check if there is a barre. + // + if (fingering.hasBarre()) { + Guitar::Fingering::Barre barre = fingering.getBarre(); + barreStart = barre.start; + barreEnd = barre.end; + barreFret = barre.fret; + } + + if (barreStart == 0) { + str << " s4*0^\\markup \\fret-diagram #\""; + } else { + str << " s4*0^\\markup \\override #'(barre-type . straight) \\fret-diagram #\""; + } + // + // Check each string individually. + // Note: LilyPond numbers strings differently. + // + for (int stringNum = 6; stringNum >= 1; --stringNum) { + if (barreStart == stringNum) { + str << "c:" << barreStart << "-" << barreEnd << "-" << barreFret << ";"; + } + + if (fingering.getStringStatus( 6-stringNum ) == Guitar::Fingering::MUTED) { + str << stringNum << "-x;"; + } else if (fingering.getStringStatus( 6-stringNum ) == Guitar::Fingering::OPEN) { + str << stringNum << "-o;"; + } else { + int stringStatus = fingering.getStringStatus(6-stringNum); + if ((stringNum <= barreStart) && (stringNum >= barreEnd)) { + str << stringNum << "-" << barreFret << ";"; + } else { + str << stringNum << "-" << stringStatus << ";"; + } + } + } + str << "\" "; + + } catch (Exception e) { // GuitarChord ctor failed + RG_DEBUG << "Bad GuitarChord event in LilyPond export" << endl; + } + } + + // LilyPond 2.0 introduces required postfix syntax for beaming + if (m_exportBeams && newBeamedGroup && notesInBeamedGroup > 0) { + str << "[ "; + newBeamedGroup = false; + } + + if ((*i)->isa(Indication::EventType)) { + eventsToStart.insert(*i); + eventsInProgress.insert(*i); + } + + ++i; + } + + if (groupId != -1) { + if (groupType == GROUP_TYPE_TUPLED) { + if (m_exportBeams && notesInBeamedGroup > 0) + str << "] "; + str << "} "; + tupletRatio = std::pair(1, 1); + } else if (groupType == GROUP_TYPE_BEAMED) { + if (m_exportBeams && notesInBeamedGroup > 0) + str << "] "; + } + } + + if (isGrace == 1) { + isGrace = 0; + // str << "%{ grace ends %} "; // DEBUG + str << "} "; + } + + if (lastStem != 0) { + str << "\\stemNeutral "; + } + + if (overlong) { + str << std::endl << indent(col) << + qstrtostr(QString("% %1"). + arg(i18n("warning: overlong bar truncated here"))); + } + + if (fractionSmaller(durationRatioSum, barDurationRatio)) { + str << std::endl << indent(col) << + qstrtostr(QString("% %1"). + arg(i18n("warning: bar too short, padding with rests"))); + str << std::endl << indent(col) << + qstrtostr(QString("% %1/%2 < %3/%4"). + arg(durationRatioSum.first). + arg(durationRatioSum.second). + arg(barDurationRatio.first). + arg(barDurationRatio.second)) + << std::endl << indent(col); + durationRatio = writeSkip(timeSignature, writtenDuration, + (barEnd - barStart) - writtenDuration, true, str); + durationRatioSum = fractionSum(durationRatioSum,durationRatio); + } + // + // Export bar and bar checks. + // + if (nextBarIsDouble) { + str << "\\bar \"||\" "; + nextBarIsDouble = false; + } else if (nextBarIsEnd) { + str << "\\bar \"|.\" "; + nextBarIsEnd = false; + } else if (nextBarIsDot) { + str << "\\bar \":\" "; + nextBarIsDot = false; + } else if (MultiMeasureRestCount == 0) { + str << " |"; + } +} + +std::pair +LilyPondExporter::writeSkip(const TimeSignature &timeSig, + timeT offset, + timeT duration, + bool useRests, + std::ofstream &str) +{ + DurationList dlist; + timeSig.getDurationListForInterval(dlist, duration, offset); + std::pair durationRatioSum(0,1); + std::pair durationRatio(0,1); + + int t = 0, count = 0; + + for (DurationList::iterator i = dlist.begin(); ; ++i) { + + if (i == dlist.end() || (*i) != t) { + + if (count > 0) { + + if (!useRests) + str << "\\skip "; + else if (t == timeSig.getBarDuration()) + str << "R"; + else + str << "r"; + + durationRatio = writeDuration(t, str); + + if (count > 1) { + str << "*" << count; + durationRatio = fractionProduct(durationRatio,count); + } + str << " "; + + durationRatioSum = fractionSum(durationRatioSum,durationRatio); + } + + if (i != dlist.end()) { + t = *i; + count = 1; + } + + } else { + ++count; + } + + if (i == dlist.end()) + break; + } + return durationRatioSum; +} + +bool +LilyPondExporter::handleDirective(const Event *textEvent, + std::string &lilyText, + bool &nextBarIsAlt1, bool &nextBarIsAlt2, + bool &nextBarIsDouble, bool &nextBarIsEnd, bool &nextBarIsDot) +{ + Text text(*textEvent); + + if (text.getTextType() == Text::LilyPondDirective) { + std::string directive = text.getText(); + if (directive == Text::Segno) { + lilyText += "^\\markup { \\musicglyph #\"scripts.segno\" } "; + } else if (directive == Text::Coda) { + lilyText += "^\\markup { \\musicglyph #\"scripts.coda\" } "; + } else if (directive == Text::Alternate1) { + nextBarIsAlt1 = true; + } else if (directive == Text::Alternate2) { + nextBarIsAlt1 = false; + nextBarIsAlt2 = true; + } else if (directive == Text::BarDouble) { + nextBarIsDouble = true; + } else if (directive == Text::BarEnd) { + nextBarIsEnd = true; + } else if (directive == Text::BarDot) { + nextBarIsDot = true; + } else { + // pass along less special directives for handling as plain text, + // so they can be attached to chords and whatlike without + // redundancy + return false; + } + return true; + } else { + return false; + } +} + +void +LilyPondExporter::handleText(const Event *textEvent, + std::string &lilyText) +{ + try { + + Text text(*textEvent); + std::string s = text.getText(); + + // only protect illegal chars if this is Text, rather than + // LilyPondDirective + if ((*textEvent).isa(Text::EventType)) + s = protectIllegalChars(s); + + if (text.getTextType() == Text::Tempo) { + + // print above staff, bold, large + lilyText += "^\\markup { \\bold \\large \"" + s + "\" } "; + + } else if (text.getTextType() == Text::LocalTempo || + text.getTextType() == Text::Chord) { + + // print above staff, bold, small + lilyText += "^\\markup { \\bold \"" + s + "\" } "; + + } else if (text.getTextType() == Text::Dynamic) { + + // supported dynamics first + if (s == "ppppp" || s == "pppp" || s == "ppp" || + s == "pp" || s == "p" || s == "mp" || + s == "mf" || s == "f" || s == "ff" || + s == "fff" || s == "ffff" || s == "rfz" || + s == "sf") { + + lilyText += "-\\" + s + " "; + + } else { + // export as a plain markup: + // print below staff, bold italics, small + lilyText += "_\\markup { \\bold \\italic \"" + s + "\" } "; + } + + } else if (text.getTextType() == Text::Direction) { + + // print above staff, large + lilyText += "^\\markup { \\large \"" + s + "\" } "; + + } else if (text.getTextType() == Text::LocalDirection) { + + // print below staff, bold italics, small + lilyText += "_\\markup { \\bold \\italic \"" + s + "\" } "; + + // LilyPond directives that don't require special handling across + // barlines are handled here along with ordinary text types. These + // can be injected wherever they happen to occur, and should get + // attached to the right bits in due course without extra effort. + // + } else if (text.getText() == Text::Gliss) { + lilyText += "\\glissando "; + } else if (text.getText() == Text::Arpeggio) { + lilyText += "\\arpeggio "; + } else if (text.getText() == Text::Tiny) { + lilyText += "\\tiny "; + } else if (text.getText() == Text::Small) { + lilyText += "\\small "; + } else if (text.getText() == Text::NormalSize) { + lilyText += "\\normalsize "; + } else { + textEvent->get + (Text::TextTypePropertyName, s); + std::cerr << "LilyPondExporter::write() - unhandled text type: " + << s << std::endl; + } + } catch (Exception e) { + std::cerr << "Bad text: " << e.getMessage() << std::endl; + } +} + +void +LilyPondExporter::writePitch(const Event *note, + const Rosegarden::Key &key, + std::ofstream &str) +{ + // Note pitch (need name as well as octave) + // It is also possible to have "relative" pitches, + // but for simplicity we always use absolute pitch + // 60 is middle C, one unit is a half-step + + long pitch = 60; + note->get + (PITCH, pitch); + + Accidental accidental = Accidentals::NoAccidental; + note->get + (ACCIDENTAL, accidental); + + // format of LilyPond note is: + // name + octave + (duration) + text markup + + // calculate note name and write note + std::string lilyNote; + + lilyNote = convertPitchToLilyNote(pitch, accidental, key); + + str << lilyNote; + + // generate and write octave marks + std::string octaveMarks = ""; + int octave = (int)(pitch / 12); + + // tweak the octave break for B# / Cb + if ((lilyNote == "bisis") || (lilyNote == "bis")) { + octave--; + } else if ((lilyNote == "ceses") || (lilyNote == "ces")) { + octave++; + } + + if (octave < 4) { + for (; octave < 4; octave++) + octaveMarks += ","; + } else { + for (; octave > 4; octave--) + octaveMarks += "\'"; + } + + str << octaveMarks; +} + +void +LilyPondExporter::writeStyle(const Event *note, std::string &prevStyle, + int col, std::ofstream &str, bool isInChord) +{ + // some hard-coded styles in order to provide rudimentary style export support + // note that this is technically bad practice, as style names are not supposed + // to be fixed but deduced from the style files actually present on the system + const std::string styleMensural = "Mensural"; + const std::string styleTriangle = "Triangle"; + const std::string styleCross = "Cross"; + const std::string styleClassical = "Classical"; + + // handle various note styles before opening any chord + // brackets + std::string style = ""; + note->get + (NotationProperties::NOTE_STYLE, style); + + if (style != prevStyle) { + + if (style == styleClassical && prevStyle == "") + return ; + + if (!isInChord) + prevStyle = style; + + if (style == styleMensural) { + style = "mensural"; + } else if (style == styleTriangle) { + style = "triangle"; + } else if (style == styleCross) { + style = "cross"; + } else { + style = "default"; // failsafe default or explicit + } + + if (!isInChord) { + str << std::endl << indent(col) << "\\override Voice.NoteHead #'style = #'" << style << std::endl << indent(col); + } else { + str << "\\tweak #'style #'" << style << " "; + } + } +} + +std::pair +LilyPondExporter::writeDuration(timeT duration, + std::ofstream &str) +{ + Note note(Note::getNearestNote(duration, MAX_DOTS)); + std::pair durationRatio(0,1); + + switch (note.getNoteType()) { + + case Note::SixtyFourthNote: + str << "64"; durationRatio = std::pair(1,64); + break; + + case Note::ThirtySecondNote: + str << "32"; durationRatio = std::pair(1,32); + break; + + case Note::SixteenthNote: + str << "16"; durationRatio = std::pair(1,16); + break; + + case Note::EighthNote: + str << "8"; durationRatio = std::pair(1,8); + break; + + case Note::QuarterNote: + str << "4"; durationRatio = std::pair(1,4); + break; + + case Note::HalfNote: + str << "2"; durationRatio = std::pair(1,2); + break; + + case Note::WholeNote: + str << "1"; durationRatio = std::pair(1,1); + break; + + case Note::DoubleWholeNote: + str << "\\breve"; durationRatio = std::pair(2,1); + break; + } + + for (int numDots = 0; numDots < note.getDots(); numDots++) { + str << "."; + } + durationRatio = fractionProduct(durationRatio, + std::pair((1<<(note.getDots()+1))-1,1<get + (NotationProperties::SLASHES, slashes); + if (slashes > 0) { + str << ":"; + int length = 4; + for (int c = 1; c <= slashes; c++) { + length *= 2; + } + str << length; + } +} + +} diff --git a/src/document/io/LilyPondExporter.h b/src/document/io/LilyPondExporter.h new file mode 100644 index 0000000..ffb831d --- /dev/null +++ b/src/document/io/LilyPondExporter.h @@ -0,0 +1,262 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + This file is Copyright 2002 + Hans Kieserman + with heavy lifting from csoundio as it was on 13/5/2002. + + Numerous additions and bug fixes by + Michael McIntyre + + Some restructuring by Chris Cannam. + + Brain surgery to support LilyPond 2.x export by Heikki Junes. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_LILYPONDEXPORTER_H_ +#define _RG_LILYPONDEXPORTER_H_ + +#include "base/Event.h" +#include "base/PropertyName.h" +#include "base/Segment.h" +#include "gui/general/ProgressReporter.h" +#include +#include +#include +#include + + +class QObject; + + +namespace Rosegarden +{ + +class TimeSignature; +class Studio; +class RosegardenGUIApp; +class RosegardenGUIView; +class RosegardenGUIDoc; +class NotationView; +class Key; +class Composition; + +const std::string headerDedication = "dedication"; +const std::string headerTitle = "title"; +const std::string headerSubtitle = "subtitle"; +const std::string headerSubsubtitle = "subsubtitle"; +const std::string headerPoet = "poet"; +const std::string headerComposer = "composer"; +const std::string headerMeter = "meter"; +const std::string headerOpus = "opus"; +const std::string headerArranger = "arranger"; +const std::string headerInstrument = "instrument"; +const std::string headerPiece = "piece"; +const std::string headerCopyright = "copyright"; +const std::string headerTagline = "tagline"; + +/** + * LilyPond scorefile export + */ + +class LilyPondExporter : public ProgressReporter +{ +public: + typedef std::multiset eventstartlist; + typedef std::multiset eventendlist; + +public: + LilyPondExporter(RosegardenGUIApp *parent, RosegardenGUIDoc *, std::string fileName); + LilyPondExporter(NotationView *parent, RosegardenGUIDoc *, std::string fileName); + ~LilyPondExporter(); + + bool write(); + +protected: + RosegardenGUIView *m_view; + NotationView *m_notationView; + RosegardenGUIDoc *m_doc; + Composition *m_composition; + Studio *m_studio; + std::string m_fileName; + Clef m_lastClefFound; + + void readConfigVariables(void); + void writeBar(Segment *, int barNo, int barStart, int barEnd, int col, + Rosegarden::Key &key, std::string &lilyText, + std::string &prevStyle, eventendlist &eventsInProgress, + std::ofstream &str, int &MultiMeasureRestCount, + bool &nextBarIsAlt1, bool &nextBarIsAlt2, + bool &nextBarIsDouble, bool &nextBarIsEnd, bool &nextBarIsDot); + + timeT calculateDuration(Segment *s, + const Segment::iterator &i, + timeT barEnd, + timeT &soundingDuration, + const std::pair &tupletRatio, + bool &overlong); + + void handleStartingEvents(eventstartlist &eventsToStart, std::ofstream &str); + void handleEndingEvents(eventendlist &eventsInProgress, + const Segment::iterator &j, std::ofstream &str); + + // convert note pitch into LilyPond format note string + std::string convertPitchToLilyNote(int pitch, + Accidental accidental, + const Rosegarden::Key &key); + + // compose an appropriate LilyPond representation for various Marks + std::string composeLilyMark(std::string eventMark, bool stemUp); + + // find/protect illegal characters in user-supplied strings + std::string protectIllegalChars(std::string inStr); + + // return a string full of column tabs + std::string indent(const int &column); + + std::pair writeSkip(const TimeSignature &timeSig, + timeT offset, + timeT duration, + bool useRests, + std::ofstream &); + + /* + * Handle LilyPond directive. Returns true if the event was a directive, + * so subsequent code does not bother to process the event twice + */ + bool handleDirective(const Event *textEvent, + std::string &lilyText, + bool &nextBarIsAlt1, bool &nextBarIsAlt2, + bool &nextBarIsDouble, bool &nextBarIsEnd, bool &nextBarIsDot); + + void handleText(const Event *, std::string &lilyText); + void writePitch(const Event *note, const Rosegarden::Key &key, std::ofstream &); + void writeStyle(const Event *note, std::string &prevStyle, int col, std::ofstream &, bool isInChord); + std::pair writeDuration(timeT duration, std::ofstream &); + void writeSlashes(const Event *note, std::ofstream &); + +private: + static const int MAX_DOTS = 4; + static const PropertyName SKIP_PROPERTY; + + unsigned int m_paperSize; + static const unsigned int PAPER_A3 = 0; + static const unsigned int PAPER_A4 = 1; + static const unsigned int PAPER_A5 = 2; + static const unsigned int PAPER_A6 = 3; + static const unsigned int PAPER_LEGAL = 4; + static const unsigned int PAPER_LETTER = 5; + static const unsigned int PAPER_TABLOID = 6; + static const unsigned int PAPER_NONE = 7; + + bool m_paperLandscape; + unsigned int m_fontSize; + static const unsigned int FONT_11 = 0; + static const unsigned int FONT_13 = 1; + static const unsigned int FONT_16 = 2; + static const unsigned int FONT_19 = 3; + static const unsigned int FONT_20 = 4; + static const unsigned int FONT_23 = 5; + static const unsigned int FONT_26 = 6; + + bool m_exportLyrics; + bool m_exportMidi; + + unsigned int m_lyricsHAlignment; + static const unsigned int LEFT_ALIGN = 0; + static const unsigned int CENTER_ALIGN = 1; + static const unsigned int RIGHT_ALIGN = 2; + + unsigned int m_exportTempoMarks; + static const unsigned int EXPORT_NONE_TEMPO_MARKS = 0; + static const unsigned int EXPORT_FIRST_TEMPO_MARK = 1; + static const unsigned int EXPORT_ALL_TEMPO_MARKS = 2; + + unsigned int m_exportSelection; + static const unsigned int EXPORT_ALL_TRACKS = 0; + static const unsigned int EXPORT_NONMUTED_TRACKS = 1; + static const unsigned int EXPORT_SELECTED_TRACK = 2; + static const unsigned int EXPORT_SELECTED_SEGMENTS = 3; + + bool m_exportPointAndClick; + bool m_exportBeams; + bool m_exportStaffGroup; + bool m_exportStaffMerge; + bool m_raggedBottom; + + unsigned int m_exportMarkerMode; + + static const unsigned int EXPORT_NO_MARKERS = 0; + static const unsigned int EXPORT_DEFAULT_MARKERS = 1; + static const unsigned int EXPORT_TEXT_MARKERS = 2; + + int m_languageLevel; + static const int LILYPOND_VERSION_2_6 = 0; + static const int LILYPOND_VERSION_2_8 = 1; + static const int LILYPOND_VERSION_2_10 = 2; + static const int LILYPOND_VERSION_2_12 = 3; + + std::pair fractionSum(std::pair x,std::pair y) { + std::pair z( + x.first * y.second + x.second * y.first, + x.second * y.second); + return fractionSimplify(z); + } + std::pair fractionProduct(std::pair x,std::pair y) { + std::pair z( + x.first * y.first, + x.second * y.second); + return fractionSimplify(z); + } + std::pair fractionProduct(std::pair x,int y) { + std::pair z( + x.first * y, + x.second); + return fractionSimplify(z); + } + bool fractionSmaller(std::pair x,std::pair y) { + return (x.first * y.second < x.second * y.first); + } + std::pair fractionSimplify(std::pair x) { + return std::pair(x.first/gcd(x.first,x.second), + x.second/gcd(x.first,x.second)); + } + int gcd(int a, int b) { + // Euclid's algorithm to find the greatest common divisor + while ( 1 ) { + int r = a % b; + if ( r == 0 ) + return (b == 0 ? 1 : b); + a = b; + b = r; + } + } +}; + + + +} + +#endif + diff --git a/src/document/io/MupExporter.cpp b/src/document/io/MupExporter.cpp new file mode 100644 index 0000000..067c909 --- /dev/null +++ b/src/document/io/MupExporter.cpp @@ -0,0 +1,453 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MupExporter.h" + +#include "misc/Debug.h" +#include "base/BaseProperties.h" +#include "base/Composition.h" +#include "base/Event.h" +#include "base/Exception.h" +#include "base/NotationQuantizer.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Sets.h" +#include "base/Track.h" +#include "gui/general/ProgressReporter.h" +#include + +using std::string; + +namespace Rosegarden +{ +using namespace BaseProperties; + +MupExporter::MupExporter(QObject *parent, + Composition *composition, + string fileName) : + ProgressReporter(parent, "mupExporter"), + m_composition(composition), + m_fileName(fileName) +{ + // nothing else +} + +MupExporter::~MupExporter() +{ + // nothing +} + +bool +MupExporter::write() +{ + Composition *c = m_composition; + + std::ofstream str(m_fileName.c_str(), std::ios::out); + if (!str) { + std::cerr << "MupExporter::write() - can't write file " << m_fileName + << std::endl; + return false; + } + + str << "score\n"; + str << "\tstaffs=" << c->getNbTracks() << "\n"; + + int ts = c->getTimeSignatureCount(); + std::pair tspair; + if (ts > 0) + tspair = c->getTimeSignatureChange(0); + str << "\ttime=" + << tspair.second.getNumerator() << "/" + << tspair.second.getDenominator() << "\n"; + + for (int barNo = -1; barNo < c->getNbBars(); ++barNo) { + + for (TrackId trackNo = c->getMinTrackId(); + trackNo <= c->getMaxTrackId(); ++trackNo) { + + if (barNo < 0) { + writeClefAndKey(str, trackNo); + continue; + } + + if (barNo == 0 && trackNo == 0) { + str << "\nmusic\n"; + } + + str << "\t" << trackNo + 1 << ":"; + + Segment *s = 0; + timeT barStart = c->getBarStart(barNo); + timeT barEnd = c->getBarEnd(barNo); + + for (Composition::iterator ci = c->begin(); ci != c->end(); ++ci) { + if ((*ci)->getTrack() == trackNo && + (*ci)->getStartTime() < barEnd && + (*ci)->getEndMarkerTime() > barStart) { + s = *ci; + break; + } + } + + TimeSignature timeSig(c->getTimeSignatureAt(barStart)); + + if (!s) { + // write empty bar + writeInventedRests(str, timeSig, 0, barEnd - barStart); + continue; + } + + if (s->getStartTime() > barStart) { + writeInventedRests(str, timeSig, + 0, s->getStartTime() - barStart); + } + + // Mup insists that every bar has the correct duration, and won't + // recover if one goes wrong. Keep careful tabs on this: it means + // that for example we have to round chord durations down where + // the next chord starts too soon + //!!! we _really_ can't cope with time sig changes yet! + + timeT writtenDuration = writeBar(str, c, s, barStart, barEnd, + timeSig, trackNo); + + if (writtenDuration < timeSig.getBarDuration()) { + RG_DEBUG << "writtenDuration: " << writtenDuration + << ", bar duration " << timeSig.getBarDuration() + << endl; + writeInventedRests(str, timeSig, writtenDuration, + timeSig.getBarDuration() - writtenDuration); + + } else if (writtenDuration > timeSig.getBarDuration()) { + std::cerr << "WARNING: overfull bar in Mup export: duration " << writtenDuration + << " into bar of duration " << timeSig.getBarDuration() + << std::endl; + //!!! warn user + } + + str << "\n"; + } + + if (barNo >= 0) + str << "bar" << std::endl; + } + + str << "\n" << std::endl; + str.close(); + return true; +} + +timeT +MupExporter::writeBar(std::ofstream &str, + Composition *c, + Segment *s, + timeT barStart, timeT barEnd, + TimeSignature &timeSig, + TrackId trackNo) +{ + timeT writtenDuration = 0; + SegmentNotationHelper helper(*s); + helper.setNotationProperties(); + + long currentGroupId = -1; + string currentGroupType = ""; + long currentTupletCount = 3; + bool first = true; + bool openBeamWaiting = false; + + for (Segment::iterator si = s->findTime(barStart); + s->isBeforeEndMarker(si) && + (*si)->getNotationAbsoluteTime() < barEnd; ++si) { + + if ((*si)->isa(Note::EventType)) { + + Chord chord(*s, si, c->getNotationQuantizer()); + Event *e = *chord.getInitialNote(); + + timeT absTime = e->getNotationAbsoluteTime(); + timeT duration = e->getNotationDuration(); + try { + // tuplet compensation, etc + Note::Type type = e->get(NOTE_TYPE); + int dots = e->get + (NOTE_DOTS); + duration = Note(type, dots).getDuration(); + } catch (Exception e) { // no properties + std::cerr << "WARNING: MupExporter::writeBar: incomplete note properties: " << e.getMessage() << std::endl; + } + + timeT toNext = duration; + Segment::iterator nextElt = chord.getFinalElement(); + if (s->isBeforeEndMarker(++nextElt)) { + toNext = (*nextElt)->getNotationAbsoluteTime() - absTime; + if (toNext < duration) + duration = toNext; + } + + bool enteringGroup = false; + + if (e->has(BEAMED_GROUP_ID) && e->has(BEAMED_GROUP_TYPE)) { + + long id = e->get + (BEAMED_GROUP_ID); + string type = e->get + (BEAMED_GROUP_TYPE); + + if (id != currentGroupId) { + + // leave previous group first + if (currentGroupId >= 0) { + if (!openBeamWaiting) + str << " ebm"; + openBeamWaiting = false; + + if (currentGroupType == GROUP_TYPE_TUPLED) { + str << "; }" << currentTupletCount; + } + } + + currentGroupId = id; + currentGroupType = type; + enteringGroup = true; + } + } else { + + if (currentGroupId >= 0) { + if (!openBeamWaiting) + str << " ebm"; + openBeamWaiting = false; + + if (currentGroupType == GROUP_TYPE_TUPLED) { + str << "; }" << currentTupletCount; + } + + currentGroupId = -1; + currentGroupType = ""; + } + } + + if (openBeamWaiting) + str << " bm"; + if (!first) + str << ";"; + str << " "; + + if (currentGroupType == GROUP_TYPE_TUPLED) { + e->get + (BEAMED_GROUP_UNTUPLED_COUNT, currentTupletCount); + if (enteringGroup) + str << "{ "; + //!!! duration = helper.getCompensatedNotationDuration(e); + + } + + writeDuration(str, duration); + + if (toNext > duration && currentGroupType != GROUP_TYPE_TUPLED) { + writeInventedRests + (str, timeSig, + absTime + duration - barStart, toNext - duration); + } + + writtenDuration += toNext; + + for (Chord::iterator chi = chord.begin(); + chi != chord.end(); ++chi) { + writePitch(str, trackNo, **chi); + } + + openBeamWaiting = false; + if (currentGroupType == GROUP_TYPE_BEAMED || + currentGroupType == GROUP_TYPE_TUPLED) { + if (enteringGroup) + openBeamWaiting = true; + } + + si = chord.getFinalElement(); + + first = false; + + } else if ((*si)->isa(Note::EventRestType)) { + + if (currentGroupId >= 0) { + + if (!openBeamWaiting) + str << " ebm"; + openBeamWaiting = false; + + if (currentGroupType == GROUP_TYPE_TUPLED) { + str << "; }" << currentTupletCount; + } + + currentGroupId = -1; + currentGroupType = ""; + } + + if (openBeamWaiting) + str << " bm"; + if (!first) + str << ";"; + str << " "; + + writeDuration(str, (*si)->getNotationDuration()); + writtenDuration += (*si)->getNotationDuration(); + str << "r"; + + first = false; + openBeamWaiting = false; + + } // ignore all other sorts of events for now + } + + if (currentGroupId >= 0) { + if (!openBeamWaiting) + str << " ebm"; + openBeamWaiting = false; + + if (currentGroupType == GROUP_TYPE_TUPLED) { + str << "; }" << currentTupletCount; + } + } + + if (openBeamWaiting) + str << " bm"; + if (!first) + str << ";"; + + return writtenDuration; +} + +void +MupExporter::writeClefAndKey(std::ofstream &str, TrackId trackNo) +{ + Composition *c = m_composition; + + for (Composition::iterator i = c->begin(); i != c->end(); ++i) { + if ((*i)->getTrack() == trackNo) { + + Clef clef((*i)->getClefAtTime((*i)->getStartTime())); + Rosegarden::Key key((*i)->getKeyAtTime((*i)->getStartTime())); + + + str << "staff " << trackNo + 1 << "\n"; + + if (clef.getClefType() == Clef::Treble) { + str << "\tclef=treble\n"; + } else if (clef.getClefType() == Clef::Alto) { + str << "\tclef=alto\n"; + } else if (clef.getClefType() == Clef::Tenor) { + str << "\tclef=tenor\n"; + } else if (clef.getClefType() == Clef::Bass) { + str << "\tclef=bass\n"; + } + + str << "\tkey=" << key.getAccidentalCount() + << (key.isSharp() ? "#" : "&") + << (key.isMinor() ? "minor" : "major") << std::endl; + + m_clefKeyMap[trackNo] = ClefKeyPair(clef, key); + + return ; + } + } +} + +void +MupExporter::writeInventedRests(std::ofstream &str, + TimeSignature &timeSig, + timeT offset, + timeT duration) +{ + str << " "; + DurationList dlist; + timeSig.getDurationListForInterval(dlist, duration, offset); + for (DurationList::iterator i = dlist.begin(); + i != dlist.end(); ++i) { + writeDuration(str, *i); + str << "r;"; + } +} + +void +MupExporter::writePitch(std::ofstream &str, TrackId trackNo, + Event *event) +{ + long pitch = 0; + if (!event->get + (PITCH, pitch)) { + str << "c"; // have to write something, or it won't parse + return ; + } + + Accidental accidental = Accidentals::NoAccidental; + (void)event->get + (ACCIDENTAL, accidental); + + // mup octave: treble clef is in octave 4? + + ClefKeyPair ck; + ClefKeyMap::iterator ckmi = m_clefKeyMap.find(trackNo); + if (ckmi != m_clefKeyMap.end()) + ck = ckmi->second; + + Pitch p(pitch, accidental); + Accidental acc(p.getDisplayAccidental(ck.second)); + char note(p.getNoteName(ck.second)); + int octave(p.getOctave()); + + // just to avoid assuming that the note names returned by Pitch are in + // the same set as those expected by Mup -- in practice they are the same + // letters but this changes the case + str << "cdefgab"[Pitch::getIndexForNote(note)]; + + if (acc == Accidentals::DoubleFlat) + str << "&&"; + else if (acc == Accidentals::Flat) + str << "&"; + else if (acc == Accidentals::Sharp) + str << "#"; + else if (acc == Accidentals::DoubleSharp) + str << "##"; + else if (acc == Accidentals::Natural) + str << "n"; + + str << octave + 1; +} + +void +MupExporter::writeDuration(std::ofstream &str, timeT duration) +{ + Note note(Note::getNearestNote(duration, 2)); + int n = Note::Semibreve - note.getNoteType(); + if (n < 0) + str << "1/" << (1 << ( -n)); + else + str << (1 << n); + for (int d = 0; d < note.getDots(); ++d) + str << "."; +} + +} diff --git a/src/document/io/MupExporter.h b/src/document/io/MupExporter.h new file mode 100644 index 0000000..3740252 --- /dev/null +++ b/src/document/io/MupExporter.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MUPEXPORTER_H_ +#define _RG_MUPEXPORTER_H_ + +#include "base/Track.h" +#include "gui/general/ProgressReporter.h" +#include +#include +#include +#include "base/Event.h" +#include "base/NotationTypes.h" +#include + + +class QObject; + + +namespace Rosegarden +{ + +class TimeSignature; +class Segment; +class Event; +class Composition; + + +/** + * Mup file export + */ + +class MupExporter : public ProgressReporter +{ +public: + MupExporter(QObject *parent, Composition *, std::string fileName); + ~MupExporter(); + + bool write(); + +protected: + timeT writeBar(std::ofstream &, + Composition *, + Segment *, + timeT, timeT, + TimeSignature &, + TrackId); + void writeClefAndKey(std::ofstream &, TrackId trackNo); + void writeInventedRests(std::ofstream &, + TimeSignature &timeSig, + timeT offset, + timeT duration); + void writePitch(std::ofstream &, TrackId, Event *event); + void writeDuration(std::ofstream &, timeT duration); + + typedef std::pair ClefKeyPair; + typedef std::map ClefKeyMap; + ClefKeyMap m_clefKeyMap; + + Composition *m_composition; + std::string m_fileName; +}; + + +} + +#endif diff --git a/src/document/io/MusicXmlExporter.cpp b/src/document/io/MusicXmlExporter.cpp new file mode 100644 index 0000000..e1384c6 --- /dev/null +++ b/src/document/io/MusicXmlExporter.cpp @@ -0,0 +1,555 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2002 + Hans Kieserman + with heavy lifting from csoundio as it was on 13/5/2002. + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MusicXmlExporter.h" + +#include "base/BaseProperties.h" +#include "base/Composition.h" +#include "base/CompositionTimeSliceAdapter.h" +#include "base/Event.h" +#include "base/Instrument.h" +#include "base/NotationTypes.h" +#include "base/XmlExportable.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/general/ProgressReporter.h" +#include + +namespace Rosegarden +{ + +using namespace BaseProperties; + +MusicXmlExporter::MusicXmlExporter(QObject *parent, + RosegardenGUIDoc *doc, + std::string fileName) : + ProgressReporter(parent, "musicXmlExporter"), + m_doc(doc), + m_fileName(fileName) +{ + // nothing else +} + +MusicXmlExporter::~MusicXmlExporter() +{ + // nothing +} + +void +MusicXmlExporter::writeNote(Event *e, timeT lastNoteTime, + AccidentalTable &accTable, + const Clef &clef, + const Rosegarden::Key &key, + std::ofstream &str) +{ + str << "\t\t\t" << std::endl; + + Pitch pitch(64); + Accidental acc; + Accidental displayAcc; + bool cautionary; + Accidental processedDisplayAcc; + + if (e->isa(Note::EventRestType)) { + str << "\t\t\t\t" << std::endl; + + } else { + + // Order of MusicXML elements within a note: + // chord + // pitch + // duration + // tie + // instrument + // voice + // type + // dot(s) + // accidental + // time modification + // stem + // notehead + // staff + // beam + // notations + // lyric + + if (e->getNotationAbsoluteTime() == lastNoteTime) { + str << "\t\t\t\t" << std::endl; + } else { + accTable.update(); + } + + str << "\t\t\t\t" << std::endl; + + long p = 0; + e->get(PITCH, p); + pitch = p; + + str << "\t\t\t\t\t" << pitch.getNoteName(key) << "" << std::endl; + + acc = pitch.getAccidental(key.isSharp()); + displayAcc = pitch.getDisplayAccidental(key); + + cautionary = false; + processedDisplayAcc = + accTable.processDisplayAccidental + (displayAcc, pitch.getHeightOnStaff(clef, key), cautionary); + + // don't handle cautionary accidentals here: + if (cautionary) + processedDisplayAcc = Accidentals::NoAccidental; + + if (acc == Accidentals::DoubleFlat) { + str << "\t\t\t\t\t-2" << std::endl; + } else if (acc == Accidentals::Flat) { + str << "\t\t\t\t\t-1" << std::endl; + } else if (acc == Accidentals::Sharp) { + str << "\t\t\t\t\t1" << std::endl; + } else if (acc == Accidentals::DoubleSharp) { + str << "\t\t\t\t\t2" << std::endl; + } + + int octave = pitch.getOctave( -1); + str << "\t\t\t\t\t" << octave << "" << std::endl; + + str << "\t\t\t\t" << std::endl; + } + + // Since there's no way to provide the performance absolute time + // for a note, there's also no point in providing the performance + // duration, even though it might in principle be of interest + str << "\t\t\t\t" << e->getNotationDuration() << "" << std::endl; + + if (!e->isa(Note::EventRestType)) { + + if (e->has(TIED_BACKWARD) && + e->get + (TIED_BACKWARD)) { + str << "\t\t\t\t" << std::endl; + } + if (e->has(TIED_FORWARD) && + e->get + (TIED_FORWARD)) { + str << "\t\t\t\t" << std::endl; + } + + // Incomplete: will RG ever use this? + str << "\t\t\t\t" << "1" << "" << std::endl; + } + + Note note = Note::getNearestNote(e->getNotationDuration()); + + static const char *noteNames[] = { + "64th", "32nd", "16th", "eighth", "quarter", "half", "whole", "breve" + }; + + int noteType = note.getNoteType(); + if (noteType < 0 || noteType >= int(sizeof(noteNames) / sizeof(noteNames[0]))) { + std::cerr << "WARNING: MusicXmlExporter::writeNote: bad note type " + << noteType << std::endl; + noteType = 4; + } + + str << "\t\t\t\t" << noteNames[noteType] << "" << std::endl; + for (int i = 0; i < note.getDots(); ++i) { + str << "\t\t\t\t" << std::endl; + } + + if (!e->isa(Note::EventRestType)) { + + if (processedDisplayAcc == Accidentals::DoubleFlat) { + str << "\t\t\t\tflat-flat" << std::endl; + } else if (processedDisplayAcc == Accidentals::Flat) { + str << "\t\t\t\tflat" << std::endl; + } else if (processedDisplayAcc == Accidentals::Natural) { + str << "\t\t\t\tnatural" << std::endl; + } else if (processedDisplayAcc == Accidentals::Sharp) { + str << "\t\t\t\tsharp" << std::endl; + } else if (processedDisplayAcc == Accidentals::DoubleSharp) { + str << "\t\t\t\tdouble-sharp" << std::endl; + } + + bool haveNotations = false; + if (e->has(TIED_BACKWARD) && + e->get + (TIED_BACKWARD)) { + if (!haveNotations) { + str << "\t\t\t\t" << std::endl; + haveNotations = true; + } + str << "\t\t\t\t\t" << std::endl; + } + if (e->has(TIED_FORWARD) && + e->get + (TIED_FORWARD)) { + if (!haveNotations) { + str << "\t\t\t\t" << std::endl; + haveNotations = true; + } + str << "\t\t\t\t\t" << std::endl; + } + if (haveNotations) { + str << "\t\t\t\t" << std::endl; + } + } + + // could also do down if you wanted + str << "\t\t\t" << std::endl; +} + +void +MusicXmlExporter::writeKey(Rosegarden::Key whichKey, std::ofstream &str) +{ + str << "\t\t\t\t" << std::endl; + str << "\t\t\t\t" + << (whichKey.isSharp() ? "" : "-") + << (whichKey.getAccidentalCount()) << "" << std::endl; + str << "\t\t\t\t"; + if (whichKey.isMinor()) { + str << "minor"; + } else { + str << "major"; + } + str << "" << std::endl; + str << "\t\t\t\t" << std::endl; +} + +void +MusicXmlExporter::writeTime(TimeSignature timeSignature, std::ofstream &str) +{ + str << "\t\t\t\t" << std::endl; +} + +void +MusicXmlExporter::writeClef(Clef whichClef, std::ofstream &str) +{ + str << "\t\t\t\t" << std::endl; + if (whichClef == Clef::Treble) { + str << "\t\t\t\tG" << std::endl; + str << "\t\t\t\t2" << std::endl; + } else if (whichClef == Clef::Alto) { + str << "\t\t\t\tC" << std::endl; + str << "\t\t\t\t3" << std::endl; + } else if (whichClef == Clef::Tenor) { + str << "\t\t\t\tC" << std::endl; + str << "\t\t\t\t4" << std::endl; + } else if (whichClef == Clef::Bass) { + str << "\t\t\t\tF" << std::endl; + str << "\t\t\t\t4" << std::endl; + } + str << "\t\t\t\t" << std::endl; +} + +std::string +MusicXmlExporter::numToId(int num) +{ + int base = num % 52; + char c; + if (base < 26) c = 'A' + char(base); + else c = 'a' + char(base - 26); + std::string s; + s += c; + while (num / 52 > 0) { + s += c; + num /= 52; + } + return s; +} + +bool +MusicXmlExporter::write() +{ + Composition *composition = &m_doc->getComposition(); + + std::ofstream str(m_fileName.c_str(), std::ios::out); + if (!str) { + std::cerr << "MusicXmlExporter::write() - can't write file " << m_fileName << std::endl; + return false; + } + + // XML header information + str << "" << std::endl; + str << "" << std::endl; + // MusicXml header information + str << "" << std::endl; + str << "\t " << XmlExportable::encode(m_fileName) + << " " << std::endl; + // Movement, etc. info goes here + str << "\t " << std::endl; + if (composition->getCopyrightNote() != "") { + str << "\t\t" + << XmlExportable::encode(composition->getCopyrightNote()) + << "" << std::endl; + } + str << "\t\t" << std::endl; + // Incomplete: Insert date! + // str << "\t\t\t" << << "" << std::endl; + str << "\t\t\tRosegarden v" VERSION "" << std::endl; + str << "\t\t" << std::endl; + str << "\t " << std::endl; + + // MIDI information + str << "\t" << std::endl; + Composition::trackcontainer& tracks = composition->getTracks(); + + int trackNo = 0; + timeT lastNoteTime = -1; + + for (Composition::trackiterator i = tracks.begin(); + i != tracks.end(); ++i) { + // Incomplete: What about all the other Midi stuff? + // Incomplete: (Future) GUI to set labels if they're not already + Instrument * trackInstrument = (&m_doc->getStudio())->getInstrumentById((*i).second->getInstrument()); + str << "\t\t" << std::endl; + str << "\t\t\t" << XmlExportable::encode((*i).second->getLabel()) << "" << std::endl; + if (trackInstrument) { +/* + Removing this stuff for now. It doesn't work, because the ids are + are expected to be non-numeric names that refer to elements + elsewhere that define the actual instruments. I think. + + str << "\t\t\tgetName() << "\">" << std::endl; + str << "\t\t\t\t" << trackInstrument->getType() << "" << std::endl; + str << "\t\t\t" << std::endl; + str << "\t\t\tgetName() << "\">" << std::endl; + str << "\t\t\t\t" << ((unsigned int)trackInstrument->getMidiChannel() + 1) << "" << std::endl; + if (trackInstrument->sendsProgramChange()) { + str << "\t\t\t\t" << ((unsigned int)trackInstrument->getProgramChange() + 1) << "" << std::endl; + } + str << "\t\t\t" << std::endl; +*/ + } + str << "\t\t" << std::endl; + + emit setProgress(int(double(trackNo++) / double(tracks.size()) * 20.0)); + rgapp->refreshGUI(50); + + } // end track iterator + str << "\t" << std::endl; + + // Notes! + // Write out all segments for each Track + trackNo = 0; + + for (Composition::trackiterator j = tracks.begin(); + j != tracks.end(); ++j) { + + bool startedPart = false; + + // Code courtesy docs/code/iterators.txt + CompositionTimeSliceAdapter::TrackSet trackSet; + + // Incomplete: get the track info for each track (i.e. this should + // be in an iterator loop) into the track set + trackSet.insert((*j).first); + CompositionTimeSliceAdapter adapter(composition, trackSet); + + int oldMeasureNumber = -1; + bool startedAttributes = false; + Rosegarden::Key key; + Clef clef; + AccidentalTable accTable(key, clef); + TimeSignature prevTimeSignature; + + bool timeSigPending = false; + bool keyPending = false; + bool clefPending = false; + + for (CompositionTimeSliceAdapter::iterator k = adapter.begin(); + k != adapter.end(); ++k) { + + Event *event = *k; + timeT absoluteTime = event->getNotationAbsoluteTime(); + + if (!startedPart) { + str << "\t" << std::endl; + startedPart = true; + } + + // Open a new measure if necessary + // Incomplete: How does MusicXML handle non-contiguous measures? + + int measureNumber = composition->getBarNumber(absoluteTime); + + TimeSignature timeSignature = composition->getTimeSignatureAt(absoluteTime); + + if (measureNumber != oldMeasureNumber) { + + if (startedAttributes) { + + // rather bizarrely, MusicXML appears to require + // key, time, clef in that order + + if (keyPending) { + writeKey(key, str); + keyPending = false; + } + if (timeSigPending) { + writeTime(prevTimeSignature, str); + timeSigPending = false; + } + if (clefPending) { + writeClef(clef, str); + clefPending = false; + } + + str << "\t\t\t" << std::endl; + startedAttributes = false; + } + + while (measureNumber > oldMeasureNumber) { + + bool first = (oldMeasureNumber < 0); + + if (!first) { + if (startedAttributes) { + str << "\t\t\t" << std::endl; + } + str << "\t\t\n" << std::endl; + } + + ++oldMeasureNumber; + + str << "\t\t" << std::endl; + + if (first) { + str << "\t\t\t" << std::endl; + // Divisions is divisions of crotchet (quarter-note) on which all + // note-lengths are based + str << "\t\t\t\t" << Note(Note::Crotchet).getDuration() << "" << std::endl; + startedAttributes = true; + timeSigPending = true; + } + } + + accTable = AccidentalTable(key, clef); + } + + oldMeasureNumber = measureNumber; + + if (timeSignature != prevTimeSignature) { + prevTimeSignature = timeSignature; + timeSigPending = true; + if (!startedAttributes) { + str << "\t\t\t" << std::endl; + startedAttributes = true; + } + } + + // process event + if (event->isa(Rosegarden::Key::EventType)) { + + if (!startedAttributes) { + str << "\t\t\t" << std::endl; + startedAttributes = true; + } + key = Rosegarden::Key(*event); + keyPending = true; + accTable = AccidentalTable(key, clef); + + } else if (event->isa(Clef::EventType)) { + + if (!startedAttributes) { + str << "\t\t\t" << std::endl; + startedAttributes = true; + } + clef = Clef(*event); + clefPending = true; + accTable = AccidentalTable(key, clef); + + } else if (event->isa(Note::EventRestType) || + event->isa(Note::EventType)) { + + if (startedAttributes) { + + if (keyPending) { + writeKey(key, str); + keyPending = false; + } + if (timeSigPending) { + writeTime(prevTimeSignature, str); + timeSigPending = false; + } + if (clefPending) { + writeClef(clef, str); + clefPending = false; + } + + str << "\t\t\t" << std::endl; + startedAttributes = false; + } + + writeNote(event, lastNoteTime, accTable, clef, key, str); + + if (event->isa(Note::EventType)) { + lastNoteTime = event->getNotationAbsoluteTime(); + } else if (event->isa(Note::EventRestType)) { + lastNoteTime = -1; + } + } + } + + if (startedPart) { + if (startedAttributes) { + + if (keyPending) { + writeKey(key, str); + keyPending = false; + } + if (timeSigPending) { + writeTime(prevTimeSignature, str); + timeSigPending = false; + } + if (clefPending) { + writeClef(clef, str); + clefPending = false; + } + + str << "\t\t\t" << std::endl; + startedAttributes = false; + } + + str << "\t\t" << std::endl; + str << "\t" << std::endl; + } + + emit setProgress(20 + + int(double(trackNo++) / double(tracks.size()) * 80.0)); + rgapp->refreshGUI(50); + } + + str << "" << std::endl; + str.close(); + return true; +} + +} diff --git a/src/document/io/MusicXmlExporter.h b/src/document/io/MusicXmlExporter.h new file mode 100644 index 0000000..f730de8 --- /dev/null +++ b/src/document/io/MusicXmlExporter.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 , + Chris Cannam , + Richard Bown + + This file is Copyright 2002 + Hans Kieserman + with heavy lifting from csoundio as it was on 13/5/2002. + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MUSICXMLEXPORTER_H_ +#define _RG_MUSICXMLEXPORTER_H_ + +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "gui/general/ProgressReporter.h" +#include +#include +#include + + +class QObject; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class Key; +class Clef; +class AccidentalTable; + + +/** + * MusicXml scorefile export + */ + +class MusicXmlExporter : public ProgressReporter +{ +public: + typedef std::multiset eventstartlist; + typedef std::multiset eventendlist; +public: + MusicXmlExporter(QObject *parent, RosegardenGUIDoc *, std::string fileName); + ~MusicXmlExporter(); + + bool write(); + +protected: + RosegardenGUIDoc *m_doc; + std::string m_fileName; + void writeClef(Rosegarden::Clef, std::ofstream &str); + void writeKey(Rosegarden::Key, std::ofstream &str); + void writeTime(TimeSignature timeSignature, std::ofstream &str); + void writeNote(Event *e, timeT lastNoteTime, + AccidentalTable &table, + const Clef &clef, + const Rosegarden::Key &key, + std::ofstream &str); + + std::string numToId(int); +}; + + + +} + +#endif diff --git a/src/document/io/RG21Loader.cpp b/src/document/io/RG21Loader.cpp new file mode 100644 index 0000000..84f3d03 --- /dev/null +++ b/src/document/io/RG21Loader.cpp @@ -0,0 +1,797 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RG21Loader.h" + +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Composition.h" +#include "base/Event.h" +#include "base/BaseProperties.h" +#include "base/Instrument.h" +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "gui/editors/notation/NotationStrings.h" +#include "gui/general/ProgressReporter.h" +#include +#include +#include +#include +#include +#include +#include + +using std::vector; +using std::string; + + +namespace Rosegarden +{ + +using namespace BaseProperties; +using namespace Accidentals; +using namespace Marks; + +RG21Loader::RG21Loader(Studio *studio, + QObject *parent, const char* name) + : ProgressReporter(parent, name), + m_stream(0), + m_studio(studio), + m_composition(0), + m_currentSegment(0), + m_currentSegmentTime(0), + m_currentSegmentNb(0), + m_currentClef(Clef::Treble), + m_currentInstrumentId(MidiInstrumentBase), + m_inGroup(false), + m_tieStatus(0), + m_nbStaves(0) +{} + +RG21Loader::~RG21Loader() +{} + +bool RG21Loader::parseClef() +{ + if (m_tokens.count() != 3 || !m_currentSegment) + return false; + + std::string clefName = qstrtostr(m_tokens[2].lower()); + + m_currentClef = Clef(clefName); + Event *clefEvent = m_currentClef.getAsEvent(m_currentSegmentTime); + m_currentSegment->insert(clefEvent); + + return true; +} + +bool RG21Loader::parseKey() +{ + if (m_tokens.count() < 3 || !m_currentSegment) + return false; + + QString keyBase = m_tokens[2]; + if (keyBase.length() > 1) { + // Deal correctly with e.g. Bb major + keyBase = + keyBase.left(1).upper() + + keyBase.right(keyBase.length() - 1).lower(); + } else { + keyBase = keyBase.upper(); + } + + QString keyName = QString("%1 %2or") + .arg(keyBase) + .arg(m_tokens[3].lower()); + + m_currentKey = Rosegarden::Key(qstrtostr(keyName)); + Event *keyEvent = m_currentKey.getAsEvent(m_currentSegmentTime); + m_currentSegment->insert(keyEvent); + + return true; +} + +bool RG21Loader::parseMetronome() +{ + if (m_tokens.count() < 2) + return false; + if (!m_composition) + return false; + + QStringList::Iterator i = m_tokens.begin(); + timeT duration = convertRG21Duration(i); + + bool isNumeric = false; + int count = (*i).toInt(&isNumeric); + if (!count || !isNumeric) + return false; + + // we need to take into account the fact that "duration" might not + // be a crotchet + + double qpm = (count * duration) / Note(Note::Crotchet).getDuration(); + m_composition->addTempoAtTime(m_currentSegmentTime, + m_composition->getTempoForQpm(qpm)); + return true; +} + +bool RG21Loader::parseChordItem() +{ + if (m_tokens.count() < 4) + return false; + + QStringList::Iterator i = m_tokens.begin(); + timeT duration = convertRG21Duration(i); + + // get chord mod flags and nb of notes. chord mod is hex + int chordMods = (*i).toInt(0, 16); + ++i; + /*int nbNotes = (*i).toInt();*/ + ++i; + + vector marks = convertRG21ChordMods(chordMods); + + // now get notes + for (;i != m_tokens.end(); ++i) { + + long pitch = (*i).toInt(); + ++i; + + // The noteMods field is nominally a hex integer. As it + // happens its value can never exceed 7, but I guess we + // should do the right thing anyway + int noteMods = (*i).toInt(0, 16); + pitch = convertRG21Pitch(pitch, noteMods); + + Event *noteEvent = new Event(Note::EventType, + m_currentSegmentTime, duration); + noteEvent->set + (PITCH, pitch); + + if (m_tieStatus == 1) { + noteEvent->set + (TIED_FORWARD, true); + } else if (m_tieStatus == 2) { + noteEvent->set + (TIED_BACKWARD, true); + } + + if (marks.size() > 0) { + noteEvent->set + (MARK_COUNT, marks.size()); + for (unsigned int j = 0; j < marks.size(); ++j) { + noteEvent->set + (getMarkPropertyName(j), marks[j]); + } + } + + // RG_DEBUG << "RG21Loader::parseChordItem() : insert note pitch " << pitch + // << " at time " << m_currentSegmentTime << endl; + + setGroupProperties(noteEvent); + + m_currentSegment->insert(noteEvent); + } + + m_currentSegmentTime += duration; + if (m_tieStatus == 2) + m_tieStatus = 0; + else if (m_tieStatus == 1) + m_tieStatus = 2; + + return true; +} + +bool RG21Loader::parseRest() +{ + if (m_tokens.count() < 2) + return false; + + QStringList::Iterator i = m_tokens.begin(); + timeT duration = convertRG21Duration(i); + + Event *restEvent = new Event(Note::EventRestType, + m_currentSegmentTime, duration, + Note::EventRestSubOrdering); + + setGroupProperties(restEvent); + + m_currentSegment->insert(restEvent); + m_currentSegmentTime += duration; + + return true; +} + +bool RG21Loader::parseText() +{ + if (!m_currentSegment) + return false; + + std::string s; + for (unsigned int i = 1; i < m_tokens.count(); ++i) { + if (i > 1) + s += " "; + s += qstrtostr(m_tokens[i]); + } + + if (!readNextLine() || + m_tokens.count() != 2 || m_tokens[0].lower() != "position") { + return false; + } + + int rg21posn = m_tokens[1].toInt(); + std::string type = Text::UnspecifiedType; + + switch (rg21posn) { + + case TextAboveStave: + type = Text::LocalTempo; + break; + + case TextAboveStaveLarge: + type = Text::Tempo; + break; + + case TextAboveBarLine: + type = Text::Direction; + break; + + case TextBelowStave: + type = Text::Lyric; // perhaps + break; + + case TextBelowStaveItalic: + type = Text::LocalDirection; + break; + + case TextChordName: + type = Text::ChordName; + break; + + case TextDynamic: + type = Text::Dynamic; + break; + } + + Text text(s, type); + Event *textEvent = text.getAsEvent(m_currentSegmentTime); + m_currentSegment->insert(textEvent); + + return true; +} + +void RG21Loader::setGroupProperties(Event *e) +{ + if (m_inGroup) { + + e->set + (BEAMED_GROUP_ID, m_groupId); + e->set + (BEAMED_GROUP_TYPE, m_groupType); + + m_groupUntupledLength += e->getDuration(); + } +} + +bool RG21Loader::parseGroupStart() +{ + m_groupType = qstrtostr(m_tokens[0].lower()); + m_inGroup = true; + m_groupId = m_currentSegment->getNextId(); + m_groupStartTime = m_currentSegmentTime; + + if (m_groupType == GROUP_TYPE_BEAMED) { + + // no more to do + + } else if (m_groupType == GROUP_TYPE_TUPLED) { + + // RG2.1 records two figures A and B, of which A is a time + // value indicating the total duration of the group _after_ + // tupling (which we would call the tupled length), and B is + // the count that appears above the group (which we call the + // untupled count). We need to know C, the total duration of + // the group _before_ tupling; then we can calculate the + // tuplet base (C / B) and tupled count (A * B / C). + + m_groupTupledLength = m_tokens[1].toUInt() * + Note(Note::Hemidemisemiquaver).getDuration(); + + m_groupUntupledCount = m_tokens[2].toUInt(); + m_groupUntupledLength = 0; + + } else { + + RG_DEBUG + << "RG21Loader::parseGroupStart: WARNING: Unknown group type " + << m_groupType << ", ignoring" << endl; + m_inGroup = false; + } + + return true; +} + +bool RG21Loader::parseIndicationStart() +{ + if (m_tokens.count() < 4) + return false; + + unsigned int indicationId = m_tokens[2].toUInt(); + std::string indicationType = qstrtostr(m_tokens[3].lower()); + + // RG_DEBUG << "Indication start: type is \"" << indicationType << "\"" << endl; + + if (indicationType == "tie") { + + if (m_tieStatus != 0) { + RG_DEBUG + << "RG21Loader:: parseIndicationStart: WARNING: Found tie within " + << "tie, ignoring" << endl; + return true; + } + // m_tieStatus = 1; + + Segment::iterator i = m_currentSegment->end(); + if (i != m_currentSegment->begin()) { + --i; + timeT t = (*i)->getAbsoluteTime(); + while ((*i)->getAbsoluteTime() == t) { + (*i)->set + (TIED_FORWARD, true); + if (i == m_currentSegment->begin()) + break; + --i; + } + } + m_tieStatus = 2; + + RG_DEBUG << "rg21io: Indication start: it's a tie" << endl; + + } else { + + // Jeez. Whose great idea was it to place marks _after_ the + // events they're marking in the RG2.1 file format? + + timeT indicationTime = m_currentSegmentTime; + Segment::iterator i = m_currentSegment->end(); + if (i != m_currentSegment->begin()) { + --i; + indicationTime = (*i)->getAbsoluteTime(); + } + + Indication indication(indicationType, 0); + Event *e = indication.getAsEvent(indicationTime); + e->setMaybe("indicationId", indicationId); + setGroupProperties(e); + m_indicationsExtant[indicationId] = e; + + // place the indication in the segment now; don't wait for the + // close-indication, because some things may need to know about it + // before then (e.g. close-group) + + m_currentSegment->insert(e); + + RG_DEBUG << "rg21io: Indication start: it's a real indication; id is " << indicationId << ", event is:" << endl; + e->dump(std::cerr); + + } + + // other indications not handled yet + return true; +} + +void RG21Loader::closeIndication() +{ + if (m_tokens.count() < 3) + return ; + + unsigned int indicationId = m_tokens[2].toUInt(); + EventIdMap::iterator i = m_indicationsExtant.find(indicationId); + + RG_DEBUG << "rg21io: Indication close: indication id is " << indicationId << endl; + + // this is normal (for ties): + if (i == m_indicationsExtant.end()) + return ; + + Event *indicationEvent = i->second; + m_indicationsExtant.erase(i); + + indicationEvent->set + + //!!! (Indication::IndicationDurationPropertyName, + ("indicationduration", + m_currentSegmentTime - indicationEvent->getAbsoluteTime()); +} + +void RG21Loader::closeGroup() +{ + if (m_groupType == GROUP_TYPE_TUPLED) { + + Segment::iterator i = m_currentSegment->end(); + vector toInsert; + vector toErase; + + if (i != m_currentSegment->begin()) { + + --i; + long groupId; + timeT prev = m_groupStartTime + m_groupTupledLength; + + while ((*i)->get + (BEAMED_GROUP_ID, groupId) && + groupId == m_groupId) { + + timeT absoluteTime = (*i)->getAbsoluteTime(); + timeT offset = absoluteTime - m_groupStartTime; + timeT intended = + (offset * m_groupTupledLength) / m_groupUntupledLength; + + RG_DEBUG + << "RG21Loader::closeGroup:" + << " m_groupStartTime = " << m_groupStartTime + << ", m_groupTupledLength = " << m_groupTupledLength + << ", m_groupUntupledCount = " << m_groupUntupledCount + << ", m_groupUntupledLength = " << m_groupUntupledLength + << ", absoluteTime = " << (*i)->getAbsoluteTime() + << ", offset = " << offset + << ", intended = " << intended + << ", new absolute time = " + << (absoluteTime + intended - offset) + << ", new duration = " + << (prev - absoluteTime) + << endl; + + absoluteTime = absoluteTime + intended - offset; + Event *e(new Event(**i, absoluteTime, prev - absoluteTime)); + prev = absoluteTime; + + // See comment in parseGroupStart + e->set + (BEAMED_GROUP_TUPLET_BASE, + m_groupUntupledLength / m_groupUntupledCount); + e->set + (BEAMED_GROUP_TUPLED_COUNT, + m_groupTupledLength * m_groupUntupledCount / + m_groupUntupledLength); + e->set + (BEAMED_GROUP_UNTUPLED_COUNT, m_groupUntupledCount); + + // To change the time of an event, we need to erase & + // re-insert it. But erasure will delete the event, and + // if it's an indication event that will invalidate our + // indicationsExtant entry. Hence this unpleasantness: + + if ((*i)->isa(Indication::EventType)) { + long indicationId = 0; + if ((*i)->get + ("indicationId", indicationId)) { + EventIdMap::iterator ei = + m_indicationsExtant.find(indicationId); + if (ei != m_indicationsExtant.end()) { + m_indicationsExtant.erase(ei); + m_indicationsExtant[indicationId] = e; + } + } + } + + toInsert.push_back(e); + toErase.push_back(i); + + if (i == m_currentSegment->begin()) + break; + --i; + } + } + + for (unsigned int i = 0; i < toInsert.size(); ++i) { + m_currentSegment->insert(toInsert[i]); + } + for (unsigned int i = 0; i < toErase.size(); ++i) { + m_currentSegment->erase(toErase[i]); + } + + m_currentSegmentTime = m_groupStartTime + m_groupTupledLength; + } + + m_inGroup = false; +} + +bool RG21Loader::parseBarType() +{ + if (m_tokens.count() < 5) + return false; + if (!m_composition) + return false; + + int staffNo = m_tokens[1].toInt(); + if (staffNo > 0) { + RG_DEBUG + << "RG21Loader::parseBarType: We don't support different time\n" + << "signatures on different staffs; disregarding time signature for staff " << staffNo << endl; + return true; + } + + // barNo is a hex integer + int barNo = m_tokens[2].toInt(0, 16); + + int numerator = m_tokens[4].toInt(); + int denominator = m_tokens[5].toInt(); + + timeT sigTime = m_composition->getBarRange(barNo).first; + TimeSignature timeSig(numerator, denominator); + m_composition->addTimeSignature(sigTime, timeSig); + + return true; +} + +bool RG21Loader::parseStaveType() +{ + //!!! tags & connected are not yet implemented + + if (m_tokens.count() < 9) + return false; + if (!m_composition) + return false; + + bool isNumeric = false; + + int staffNo = m_tokens[1].toInt(&isNumeric); + if (!isNumeric) + return false; + + int programNo = m_tokens[8].toInt(); + + if (staffNo >= (int)m_composition->getMinTrackId() && + staffNo <= (int)m_composition->getMaxTrackId()) { + + Track *track = m_composition->getTrackById(staffNo); + + if (track) { + Instrument *instr = + m_studio->assignMidiProgramToInstrument(programNo, false); + if (instr) + track->setInstrument(instr->getId()); + } + } + + return true; +} + +timeT RG21Loader::convertRG21Duration(QStringList::Iterator& i) +{ + QString durationString = (*i).lower(); + ++i; + + if (durationString == "dotted") { + durationString += ' '; + durationString += (*i).lower(); + ++i; + } + + try { + + Note n(NotationStrings::getNoteForName(durationString)); + return n.getDuration(); + + } catch (NotationStrings::MalformedNoteName m) { + + RG_DEBUG << "RG21Loader::convertRG21Duration: Bad duration: " + << durationString << endl; + return 0; + } + +} + +void RG21Loader::closeSegment() +{ + if (m_currentSegment) { + + TrackId trackId = m_currentSegmentNb - 1; + + m_currentSegment->setTrack(trackId); + + Track *track = new Track + (trackId, m_currentInstrumentId, trackId, + qstrtostr(m_currentStaffName), false); + m_currentInstrumentId = (++m_currentInstrumentId) % 16; + + m_composition->addTrack(track); + m_composition->addSegment(m_currentSegment); + m_currentSegment = 0; + m_currentSegmentTime = 0; + m_currentClef = Clef(Clef::Treble); + + } else { + // ?? + } +} + +long RG21Loader::convertRG21Pitch(long pitch, int noteModifier) +{ + Accidental accidental = + (noteModifier & ModSharp) ? Sharp : + (noteModifier & ModFlat) ? Flat : + (noteModifier & ModNatural) ? Natural : NoAccidental; + + long rtn = Pitch::getPerformancePitchFromRG21Pitch + (pitch, accidental, m_currentClef, m_currentKey); + + return rtn; +} + +bool RG21Loader::readNextLine() +{ + bool inComment = false; + + do { + inComment = false; + + m_currentLine = m_stream->readLine(); + + if (m_stream->eof()) + return false; + + m_currentLine = m_currentLine.simplifyWhiteSpace(); + + if (m_currentLine[0] == '#' || + m_currentLine.length() == 0) { + inComment = true; + continue; // skip comments + } + + m_tokens = QStringList::split(' ', m_currentLine); + + } while (inComment); + + return true; +} + +bool RG21Loader::load(const QString &fileName, Composition &comp) +{ + m_composition = ∁ + comp.clear(); + + QFile file(fileName); + if (file.open(IO_ReadOnly)) { + m_stream = new QTextStream(&file); + } else { + return false; + } + + m_studio->unassignAllInstruments(); + + while (!m_stream->eof()) { + + if (!readNextLine()) + break; + + QString firstToken = m_tokens.first(); + + if (firstToken == "Staves" || firstToken == "Staffs") { // nb staves + + m_nbStaves = m_tokens[1].toUInt(); + + } else if (firstToken == "Name") { // Staff name + + m_currentStaffName = m_tokens[1]; // we don't do anything with it yet + m_currentSegment = new Segment; + ++m_currentSegmentNb; + + } else if (firstToken == "Clef") { + + parseClef(); + + } else if (firstToken == "Key") { + + parseKey(); + + } else if (firstToken == "Metronome") { + + if (!readNextLine()) + break; + parseMetronome(); + + } else if (firstToken == ":") { // chord + + m_tokens.remove(m_tokens.begin()); // get rid of 1st token ':' + parseChordItem(); + + } else if (firstToken == "Rest") { // rest + + if (!readNextLine()) + break; + + parseRest(); + + } else if (firstToken == "Text") { + + if (!readNextLine()) + break; + + parseText(); + + } else if (firstToken == "Group") { + + if (!readNextLine()) + break; + + parseGroupStart(); + + } else if (firstToken == "Mark") { + + if (m_tokens[1] == "start") + parseIndicationStart(); + else if (m_tokens[1] == "end") + closeIndication(); + + } else if (firstToken == "Bar") { + + parseBarType(); + + } else if (firstToken == "Stave") { + + parseStaveType(); + + } else if (firstToken == "End") { + + if (m_inGroup) + closeGroup(); + else + closeSegment(); + + } else { + + RG_DEBUG << "RG21Loader::parse: Unsupported element type \"" << firstToken << "\", ignoring" << endl; + } + } + + delete m_stream; + m_stream = 0; + + return true; +} + +vector RG21Loader::convertRG21ChordMods(int chordMods) +{ + vector marks; + + // bit laborious! + if (chordMods & ModDot) marks.push_back(Staccato); + if (chordMods & ModLegato) marks.push_back(Tenuto); + if (chordMods & ModAccent) marks.push_back(Accent); + if (chordMods & ModSfz) marks.push_back(Sforzando); + if (chordMods & ModRfz) marks.push_back(Rinforzando); + if (chordMods & ModTrill) marks.push_back(Trill); + if (chordMods & ModTurn) marks.push_back(Turn); + if (chordMods & ModPause) marks.push_back(Pause); + + return marks; +} + +} diff --git a/src/document/io/RG21Loader.h b/src/document/io/RG21Loader.h new file mode 100644 index 0000000..1e944af --- /dev/null +++ b/src/document/io/RG21Loader.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_RG21LOADER_H_ +#define _RG_RG21LOADER_H_ + +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "gui/general/ProgressReporter.h" +#include +#include +#include +#include +#include +#include "base/Event.h" + + +class QTextStream; +class QObject; +class Iterator; + + +namespace Rosegarden +{ + +class Studio; +class Segment; +class Event; +class Composition; + + +/** + * Rosegarden 2.1 file import + */ +class RG21Loader : public ProgressReporter +{ +public: + RG21Loader(Studio *, + QObject *parent = 0, const char *name = 0); + ~RG21Loader(); + + /** + * Load and parse the RG2.1 file \a fileName, and write it into the + * given Composition (clearing the existing segment data first). + * Return true for success. + */ + bool load(const QString& fileName, Composition &); + +protected: + + // RG21 note mods + enum { ModSharp = (1<<0), + ModFlat = (1<<1), + ModNatural = (1<<2) + }; + + // RG21 chord mods + enum { ModDot = (1<<0), + ModLegato = (1<<1), + ModAccent = (1<<2), + ModSfz = (1<<3), + ModRfz = (1<<4), + ModTrill = (1<<5), + ModTurn = (1<<6), + ModPause = (1<<7) + }; + + // RG21 text positions + enum { TextAboveStave = 0, + TextAboveStaveLarge, + TextAboveBarLine, + TextBelowStave, + TextBelowStaveItalic, + TextChordName, + TextDynamic + }; + + bool parseClef(); + bool parseKey(); + bool parseMetronome(); + bool parseChordItem(); + bool parseRest(); + bool parseText(); + bool parseGroupStart(); + bool parseIndicationStart(); + bool parseBarType(); + bool parseStaveType(); + + void closeGroup(); + void closeIndication(); + void closeSegment(); + + void setGroupProperties(Event *); + + long convertRG21Pitch(long rg21pitch, int noteModifier); + timeT convertRG21Duration(QStringList::Iterator&); + std::vector convertRG21ChordMods(int chordMod); + + bool readNextLine(); + + //--------------- Data members --------------------------------- + + QTextStream *m_stream; + + Studio *m_studio; + Composition* m_composition; + Segment* m_currentSegment; + unsigned int m_currentSegmentTime; + unsigned int m_currentSegmentNb; + Clef m_currentClef; + Rosegarden::Key m_currentKey; + InstrumentId m_currentInstrumentId; + + typedef std::map EventIdMap; + EventIdMap m_indicationsExtant; + + bool m_inGroup; + long m_groupId; + std::string m_groupType; + timeT m_groupStartTime; + int m_groupTupledLength; + int m_groupTupledCount; + int m_groupUntupledLength; + int m_groupUntupledCount; + + int m_tieStatus; // 0 -> none, 1 -> tie started, 2 -> seen one note + + QString m_currentLine; + QString m_currentStaffName; + + QStringList m_tokens; + + unsigned int m_nbStaves; +}; + + + +} + +#endif diff --git a/src/gui/application/LircClient.cpp b/src/gui/application/LircClient.cpp new file mode 100644 index 0000000..ae731d7 --- /dev/null +++ b/src/gui/application/LircClient.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 , + Chris Cannam , + Richard Bown + + This file is Copyright 2005 + Toni Arnold + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "LircClient.h" + +#ifdef HAVE_LIRC + +#include "misc/Debug.h" +#include "base/Exception.h" +#include +#include +#include +#include + +namespace Rosegarden +{ + +LircClient::LircClient(void) + : QObject() +{ + int socketFlags; + + // socket setup with nonblock + m_socket = lirc_init("rosegarden", 1); + if (m_socket == -1) { + throw Exception("Failed to connect to LIRC"); + } + + if (lirc_readconfig(NULL, &m_config, NULL) == -1) { + throw Exception("Failed reading LIRC config file"); + } + + fcntl(m_socket, F_GETOWN, getpid()); + socketFlags = fcntl(m_socket, F_GETFL, 0); + if (socketFlags != -1) { + fcntl(socketFlags, F_SETFL, socketFlags | O_NONBLOCK); + } + + m_socketNotifier = new QSocketNotifier(m_socket, QSocketNotifier::Read, 0); + connect(m_socketNotifier, SIGNAL(activated(int)), this, SLOT(readButton()) ); + + RG_DEBUG << "LircClient::LircClient: connected to socket: " << m_socket << endl; +} + +LircClient::~LircClient() +{ + lirc_freeconfig(m_config); + delete m_socketNotifier; + lirc_deinit(); + + RG_DEBUG << "LircClient::~LircClient: cleaned up" << endl; +} + +void LircClient::readButton() +{ + char *code; + int ret; + + RG_DEBUG << "LircClient::readButton" << endl; + + if (lirc_nextcode(&code) == 0 && code != NULL) { // no error && a string is available + // handle any command attached to that button + while ( (ret = lirc_code2char(m_config, code, &m_command)) == 0 && m_command != NULL ) + { + emit buttonPressed(m_command); + } + free(code); + } +} + +} + +#include "LircClient.moc" + +#endif diff --git a/src/gui/application/LircClient.h b/src/gui/application/LircClient.h new file mode 100644 index 0000000..b80d3d7 --- /dev/null +++ b/src/gui/application/LircClient.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 , + Chris Cannam , + Richard Bown + + This file is Copyright 2005 + Toni Arnold + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_LIRCCLIENT_H_ +#define _RG_LIRCCLIENT_H_ + +#ifdef HAVE_LIRC + +#include +#include + +class QSocketNotifier; + + +namespace Rosegarden +{ + + + +class LircClient : public QObject +{ + Q_OBJECT +public: + LircClient(void); + ~LircClient(); + +public slots: + void readButton(); + +signals: + void buttonPressed(char *); + +private: + int m_socket; + QSocketNotifier *m_socketNotifier; + struct lirc_config *m_config; + char *m_command; +}; + + + +} + +#endif + +#endif diff --git a/src/gui/application/LircCommander.cpp b/src/gui/application/LircCommander.cpp new file mode 100644 index 0000000..53562ca --- /dev/null +++ b/src/gui/application/LircCommander.cpp @@ -0,0 +1,170 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2005 + Toni Arnold + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "LircCommander.h" +#include "LircClient.h" + +#ifdef HAVE_LIRC + +#include "misc/Debug.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/editors/segment/TrackButtons.h" +#include "RosegardenGUIApp.h" +#include "RosegardenGUIView.h" + +#include + + +namespace Rosegarden +{ + +LircCommander::LircCommander(LircClient *lirc, RosegardenGUIApp *rgGUIApp) + : QObject() +{ + m_lirc = lirc; + m_rgGUIApp = rgGUIApp; + connect(m_lirc, SIGNAL(buttonPressed(char *)), + this, SLOT(slotExecute(char *)) ); + + connect(this, SIGNAL(play()), + m_rgGUIApp, SLOT(slotPlay()) ); + connect(this, SIGNAL(stop()), + m_rgGUIApp, SLOT(slotStop()) ); + connect(this, SIGNAL(record()), + m_rgGUIApp, SLOT(slotRecord()) ); + connect(this, SIGNAL(rewind()), + m_rgGUIApp, SLOT(slotRewind()) ); + connect(this, SIGNAL(rewindToBeginning()), + m_rgGUIApp, SLOT(slotRewindToBeginning()) ); + connect(this, SIGNAL(fastForward()), + m_rgGUIApp, SLOT(slotFastforward()) ); + connect(this, SIGNAL(fastForwardToEnd()), + m_rgGUIApp, SLOT(slotFastForwardToEnd()) ); + connect(this, SIGNAL(toggleRecord()), + m_rgGUIApp, SLOT(slotToggleRecord()) ); + connect(this, SIGNAL(trackDown()), + m_rgGUIApp, SLOT(slotTrackDown()) ); + connect(this, SIGNAL(trackUp()), + m_rgGUIApp, SLOT(slotTrackUp()) ); + connect(this, SIGNAL(trackMute()), + m_rgGUIApp, SLOT(slotToggleMutedCurrentTrack()) ); + connect(this, SIGNAL(trackRecord()), + m_rgGUIApp, SLOT(slotToggleRecordCurrentTrack()) ); +} + +LircCommander::command LircCommander::commands[] = + { + { "FORWARD", cmd_fastForward }, + { "FORWARDTOEND", cmd_fastForwardToEnd }, + { "PLAY", cmd_play }, + { "PUNCHINRECORD", cmd_toggleRecord }, + { "RECORD", cmd_record }, + { "REWIND", cmd_rewind }, + { "REWINDTOBEGINNING", cmd_rewindToBeginning }, + { "STOP", cmd_stop }, + { "TRACK+", cmd_trackUp }, + { "TRACK-", cmd_trackDown }, + { "TRACK-MUTE", cmd_trackMute }, + { "TRACK-RECORD", cmd_trackRecord }, + }; + + +int LircCommander::compareCommandName(const void *c1, const void *c2) +{ + return (strcmp(((struct command *)c1)->name, ((struct command *)c2)->name)); +} + +void LircCommander::slotExecute(char *command) +{ + struct command tmp, *res; + + RG_DEBUG << "LircCommander::slotExecute: invoking command: " << command << endl; + + // find the function for the name + tmp.name = command; + res = (struct command *)bsearch(&tmp, commands, + sizeof(commands) / sizeof(struct command), + sizeof(struct command), + compareCommandName); + if (res != NULL) + { + switch (res->code) + { + case cmd_play: + emit play(); + break; + case cmd_stop: + emit stop(); + break; + case cmd_record: + emit record(); + break; + case cmd_rewind: + emit rewind(); + break; + case cmd_rewindToBeginning: + emit rewindToBeginning(); + break; + case cmd_fastForward: + emit fastForward(); + break; + case cmd_fastForwardToEnd: + emit fastForwardToEnd(); + break; + case cmd_toggleRecord: + emit toggleRecord(); + break; + case cmd_trackDown: + emit trackDown(); + break; + case cmd_trackUp: + emit trackUp(); + break; + case cmd_trackMute: + emit trackMute(); + break; + case cmd_trackRecord: + emit trackRecord(); + break; + default: + RG_DEBUG << "LircCommander::slotExecute: unhandled command " << command << endl; + return; + } + RG_DEBUG << "LircCommander::slotExecute: handled command: " << command << endl; + } + else + { + RG_DEBUG << "LircCommander::slotExecute: invoking command: " << command + << " failed (command not defined in LircCommander::commands[])" << endl; + }; +} + +} + +#include "LircCommander.moc" + +#endif diff --git a/src/gui/application/LircCommander.h b/src/gui/application/LircCommander.h new file mode 100644 index 0000000..84a857e --- /dev/null +++ b/src/gui/application/LircCommander.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 , + Chris Cannam , + Richard Bown + + This file is Copyright 2005 + Toni Arnold + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_LIRCCOMMANDER_H_ +#define _RG_LIRCCOMMANDER_H_ + +#ifdef HAVE_LIRC + +#include +#include "base/Track.h" + + +namespace Rosegarden +{ + +class RosegardenGUIApp; +class RosegardenGUIDoc; +class TrackButtons; +class LircClient; + + +class LircCommander : public QObject +{ + Q_OBJECT +public: + LircCommander(LircClient *lirc, RosegardenGUIApp *rgGUIApp); + +signals: + //for RosegardenGUIApp + void play(); + void stop(); + void record(); + void rewind(); + void rewindToBeginning(); + void fastForward(); + void fastForwardToEnd(); + void toggleRecord(); + void trackDown(); + void trackUp(); + void trackMute(); + void trackRecord(); + +private slots: + void slotExecute(char *); + //void slotDocumentChanged(RosegardenGUIDoc *); + +private: + LircClient *m_lirc; + RosegardenGUIApp *m_rgGUIApp; + //TrackButtons *m_trackButtons; + + // commands invoked by lirc + enum commandCode { + cmd_play, + cmd_stop, + cmd_record, + cmd_rewind, + cmd_rewindToBeginning, + cmd_fastForward, + cmd_fastForwardToEnd, + cmd_toggleRecord, + cmd_trackDown, + cmd_trackUp, + cmd_trackMute, + cmd_trackRecord + }; + + + // the command -> method mapping table + static struct command + { + char *name; /* command name */ + commandCode code; /* function to process it */ + } + commands[]; + + // utilities + static int compareCommandName(const void *c1, const void *c2); + +}; + + + +} + +#endif + +#endif diff --git a/src/gui/application/RosegardenApplication.cpp b/src/gui/application/RosegardenApplication.cpp new file mode 100644 index 0000000..6e85aab --- /dev/null +++ b/src/gui/application/RosegardenApplication.cpp @@ -0,0 +1,145 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RosegardenApplication.h" + +#include "misc/Debug.h" +#include "document/ConfigGroups.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/application/RosegardenDCOP.h" +#include "gui/kdeext/KTmpStatusMsg.h" +#include "RosegardenGUIApp.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +int RosegardenApplication::newInstance() +{ + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + if (RosegardenGUIApp::self() && args->count() && + RosegardenGUIApp::self()->getDocument() && + RosegardenGUIApp::self()->getDocument()->saveIfModified()) { + // Check for modifications and save if necessary - if cancelled + // then don't load the new file. + // + RosegardenGUIApp::self()->openFile(args->arg(0)); + } + + return KUniqueApplication::newInstance(); +} + +bool RosegardenApplication::isSequencerRegistered() +{ + if (noSequencerMode()) + return false; + return dcopClient()->isApplicationRegistered(ROSEGARDEN_SEQUENCER_APP_NAME); +} + +bool RosegardenApplication::sequencerSend(QCString dcopCall, QByteArray params) +{ + if (noSequencerMode()) + return false; + + return dcopClient()->send(ROSEGARDEN_SEQUENCER_APP_NAME, + ROSEGARDEN_SEQUENCER_IFACE_NAME, + dcopCall, params); +} + +bool RosegardenApplication::sequencerCall(QCString dcopCall, QCString& replyType, QByteArray& replyData, + QByteArray params, bool useEventLoop) +{ + if (noSequencerMode()) + return false; + return dcopClient()->call(ROSEGARDEN_SEQUENCER_APP_NAME, + ROSEGARDEN_SEQUENCER_IFACE_NAME, + dcopCall, params, replyType, replyData, useEventLoop); +} + +void RosegardenApplication::sfxLoadExited(KProcess *proc) +{ + if (!proc->normalExit()) { + QString configGroup = config()->group(); + config()->setGroup(SequencerOptionsConfigGroup); + QString soundFontPath = config()->readEntry("soundfontpath", ""); + config()->setGroup(configGroup); + + KMessageBox::error(mainWidget(), + i18n("Failed to load soundfont %1").arg(soundFontPath)); + } else { + RG_DEBUG << "RosegardenApplication::sfxLoadExited() : sfxload exited normally\n"; + } + +} + +void RosegardenApplication::slotSetStatusMessage(QString msg) +{ + KMainWindow* mainWindow = dynamic_cast(mainWidget()); + if (mainWindow) { + if (msg.isEmpty()) + msg = KTmpStatusMsg::getDefaultMsg(); + mainWindow->statusBar()->changeItem(QString(" %1").arg(msg), KTmpStatusMsg::getDefaultId()); + } + +} + +void +RosegardenApplication::refreshGUI(int maxTime) +{ + eventLoop()->processEvents(QEventLoop::ExcludeUserInput | + QEventLoop::ExcludeSocketNotifiers, + maxTime); +} + +void RosegardenApplication::saveState(QSessionManager& sm) +{ + emit aboutToSaveState(); + KUniqueApplication::saveState(sm); +} + +RosegardenApplication* RosegardenApplication::rgApp() +{ + return dynamic_cast(kApplication()); +} + +QByteArray RosegardenApplication::Empty; + +} + +#include "RosegardenApplication.moc" diff --git a/src/gui/application/RosegardenApplication.h b/src/gui/application/RosegardenApplication.h new file mode 100644 index 0000000..4541308 --- /dev/null +++ b/src/gui/application/RosegardenApplication.h @@ -0,0 +1,97 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ROSEGARDENAPPLICATION_H_ +#define _RG_ROSEGARDENAPPLICATION_H_ + +#include +#include +#include + + +class QSessionManager; +class KProcess; + + +namespace Rosegarden +{ + + + +/** + * RosegardenApplication + * + * Handles RosegardenGUIApps perceived uniqueness for us. + * + */ +class RosegardenApplication : public KUniqueApplication +{ + Q_OBJECT +public: + RosegardenApplication(): KUniqueApplication(), m_noSequencerMode(false) {} + + /** + * Handle the attempt at creation of a new instance - + * only accept new file names which we attempt to load + * into the existing instance (if it exists) + */ + virtual int newInstance(); + + void refreshGUI(int maxTime); + + bool isSequencerRegistered(); + bool sequencerSend(QCString dcopCall, QByteArray params = Empty); + bool sequencerCall(QCString dcopCall, QCString& replyType, + QByteArray& replyData, QByteArray params = Empty, bool useEventLoop = false); + + static RosegardenApplication* rgApp(); + + static QByteArray Empty; + + void setNoSequencerMode(bool m=true) { m_noSequencerMode = m; } + bool noSequencerMode() { return m_noSequencerMode; } + + virtual void saveState(QSessionManager&); + +signals: + // connect this to RosegardenGUIApp + void aboutToSaveState(); + +public slots: + void sfxLoadExited(KProcess *proc); + void slotSetStatusMessage(QString txt); + +protected: + //--------------- Data members --------------------------------- + + bool m_noSequencerMode; +}; + +#define rgapp RosegardenApplication::rgApp() + + +} + +#endif diff --git a/src/gui/application/RosegardenDCOP.h b/src/gui/application/RosegardenDCOP.h new file mode 100644 index 0000000..2689945 --- /dev/null +++ b/src/gui/application/RosegardenDCOP.h @@ -0,0 +1,50 @@ +// -*- c-basic-offset: 4 -*- +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _ROSEGARDEN_DCOP_H_ +#define _ROSEGARDEN_DCOP_H_ + + +// The names of our applications and interfaces that we share +// +#define ROSEGARDEN_SEQUENCER_APP_NAME "rosegardensequencer" +#define ROSEGARDEN_SEQUENCER_IFACE_NAME "RosegardenSequencerIface" + +#define ROSEGARDEN_GUI_APP_NAME "rosegarden" +#define ROSEGARDEN_GUI_IFACE_NAME "RosegardenIface" + + +// Sequencer communicates its state through this enum +// +typedef enum +{ + STOPPED, + PLAYING, + RECORDING, + STOPPING, + STARTING_TO_PLAY, + STARTING_TO_RECORD, + RECORDING_ARMED, // gui only state + QUIT +} TransportStatus; + + +#endif // _ROSEGARDEN_DCOP_H_ diff --git a/src/gui/application/RosegardenGUIApp.cpp b/src/gui/application/RosegardenGUIApp.cpp new file mode 100644 index 0000000..608ad58 --- /dev/null +++ b/src/gui/application/RosegardenGUIApp.cpp @@ -0,0 +1,8073 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RosegardenGUIApp.h" +#include + +#include "gui/editors/segment/TrackEditor.h" +#include "gui/editors/segment/TrackButtons.h" +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "gui/application/RosegardenDCOP.h" +#include "base/AnalysisTypes.h" +#include "base/AudioPluginInstance.h" +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "base/CompositionTimeSliceAdapter.h" +#include "base/Configuration.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/Profiler.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Selection.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "commands/edit/CopyCommand.h" +#include "commands/edit/CutCommand.h" +#include "commands/edit/EventQuantizeCommand.h" +#include "commands/edit/PasteSegmentsCommand.h" +#include "commands/edit/TransposeCommand.h" +#include "commands/edit/AddMarkerCommand.h" +#include "commands/edit/ModifyMarkerCommand.h" +#include "commands/edit/RemoveMarkerCommand.h" +#include "commands/notation/KeyInsertionCommand.h" +#include "commands/segment/AddTempoChangeCommand.h" +#include "commands/segment/AddTimeSignatureAndNormalizeCommand.h" +#include "commands/segment/AddTimeSignatureCommand.h" +#include "commands/segment/AudioSegmentAutoSplitCommand.h" +#include "commands/segment/AudioSegmentRescaleCommand.h" +#include "commands/segment/AudioSegmentSplitCommand.h" +#include "commands/segment/ChangeCompositionLengthCommand.h" +#include "commands/segment/CreateTempoMapFromSegmentCommand.h" +#include "commands/segment/CutRangeCommand.h" +#include "commands/segment/DeleteRangeCommand.h" +#include "commands/segment/InsertRangeCommand.h" +#include "commands/segment/ModifyDefaultTempoCommand.h" +#include "commands/segment/MoveTracksCommand.h" +#include "commands/segment/PasteRangeCommand.h" +#include "commands/segment/RemoveTempoChangeCommand.h" +#include "commands/segment/SegmentAutoSplitCommand.h" +#include "commands/segment/SegmentChangeTransposeCommand.h" +#include "commands/segment/SegmentJoinCommand.h" +#include "commands/segment/SegmentLabelCommand.h" +#include "commands/segment/SegmentReconfigureCommand.h" +#include "commands/segment/SegmentRescaleCommand.h" +#include "commands/segment/SegmentSplitByPitchCommand.h" +#include "commands/segment/SegmentSplitByRecordingSrcCommand.h" +#include "commands/segment/SegmentSplitCommand.h" +#include "commands/segment/SegmentTransposeCommand.h" +#include "commands/studio/CreateOrDeleteDeviceCommand.h" +#include "commands/studio/ModifyDeviceCommand.h" +#include "document/io/CsoundExporter.h" +#include "document/io/HydrogenLoader.h" +#include "document/io/LilyPondExporter.h" +#include "document/MultiViewCommandHistory.h" +#include "document/io/RG21Loader.h" +#include "document/io/MupExporter.h" +#include "document/io/MusicXmlExporter.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/dialogs/AddTracksDialog.h" +#include "gui/dialogs/AudioManagerDialog.h" +#include "gui/dialogs/AudioPluginDialog.h" +#include "gui/dialogs/AudioSplitDialog.h" +#include "gui/dialogs/BeatsBarsDialog.h" +#include "gui/dialogs/CompositionLengthDialog.h" +#include "gui/dialogs/ConfigureDialog.h" +#include "gui/dialogs/CountdownDialog.h" +#include "gui/dialogs/DocumentConfigureDialog.h" +#include "gui/dialogs/FileMergeDialog.h" +#include "gui/dialogs/IdentifyTextCodecDialog.h" +#include "gui/dialogs/IntervalDialog.h" +#include "gui/dialogs/LilyPondOptionsDialog.h" +#include "gui/dialogs/ManageMetronomeDialog.h" +#include "gui/dialogs/QuantizeDialog.h" +#include "gui/dialogs/RescaleDialog.h" +#include "gui/dialogs/SplitByPitchDialog.h" +#include "gui/dialogs/SplitByRecordingSrcDialog.h" +#include "gui/dialogs/TempoDialog.h" +#include "gui/dialogs/TimeDialog.h" +#include "gui/dialogs/TimeSignatureDialog.h" +#include "gui/dialogs/TransportDialog.h" +#include "gui/editors/parameters/InstrumentParameterBox.h" +#include "gui/editors/parameters/RosegardenParameterArea.h" +#include "gui/editors/parameters/SegmentParameterBox.h" +#include "gui/editors/parameters/TrackParameterBox.h" +#include "gui/editors/segment/segmentcanvas/CompositionView.h" +#include "gui/editors/segment/ControlEditorDialog.h" +#include "gui/editors/segment/MarkerEditor.h" +#include "gui/editors/segment/PlayListDialog.h" +#include "gui/editors/segment/PlayList.h" +#include "gui/editors/segment/segmentcanvas/SegmentEraser.h" +#include "gui/editors/segment/segmentcanvas/SegmentJoiner.h" +#include "gui/editors/segment/segmentcanvas/SegmentMover.h" +#include "gui/editors/segment/segmentcanvas/SegmentPencil.h" +#include "gui/editors/segment/segmentcanvas/SegmentResizer.h" +#include "gui/editors/segment/segmentcanvas/SegmentSelector.h" +#include "gui/editors/segment/segmentcanvas/SegmentSplitter.h" +#include "gui/editors/segment/segmentcanvas/SegmentToolBox.h" +#include "gui/editors/segment/TrackLabel.h" +#include "gui/editors/segment/TriggerSegmentManager.h" +#include "gui/editors/tempo/TempoView.h" +#include "gui/general/EditViewBase.h" +#include "gui/kdeext/KStartupLogo.h" +#include "gui/kdeext/KTmpStatusMsg.h" +#include "gui/seqmanager/MidiFilterDialog.h" +#include "gui/seqmanager/SequenceManager.h" +#include "gui/seqmanager/SequencerMapper.h" +#include "gui/studio/AudioMixerWindow.h" +#include "gui/studio/AudioPlugin.h" +#include "gui/studio/AudioPluginManager.h" +#include "gui/studio/AudioPluginOSCGUIManager.h" +#include "gui/studio/BankEditorDialog.h" +#include "gui/studio/DeviceManagerDialog.h" +#include "gui/studio/MidiMixerWindow.h" +#include "gui/studio/RemapInstrumentDialog.h" +#include "gui/studio/StudioControl.h" +#include "gui/studio/SynthPluginManagerDialog.h" +#include "gui/widgets/CurrentProgressDialog.h" +#include "gui/widgets/ProgressBar.h" +#include "gui/widgets/ProgressDialog.h" +#include "LircClient.h" +#include "LircCommander.h" +#include "RosegardenGUIView.h" +#include "RosegardenIface.h" +#include "SetWaitCursor.h" +#include "sound/AudioFile.h" +#include "sound/AudioFileManager.h" +#include "sound/MappedCommon.h" +#include "sound/MappedComposition.h" +#include "sound/MappedEvent.h" +#include "sound/MappedStudio.h" +#include "sound/MidiFile.h" +#include "sound/PluginIdentifier.h" +#include "sound/SoundDriver.h" +#include "StartupTester.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBJACK +#include +#endif + + +namespace Rosegarden +{ + +RosegardenGUIApp::RosegardenGUIApp(bool useSequencer, + bool useExistingSequencer, + QObject *startupStatusMessageReceiver) + : DCOPObject("RosegardenIface"), RosegardenIface(this), KDockMainWindow(0), + m_actionsSetup(false), + m_fileRecent(0), + m_view(0), + m_swapView(0), + m_mainDockWidget(0), + m_dockLeft(0), + m_doc(0), + m_sequencerProcess(0), + m_sequencerCheckedIn(false), +#ifdef HAVE_LIBJACK + m_jackProcess(0), +#endif + m_zoomSlider(0), + m_seqManager(0), + m_transport(0), + m_audioManagerDialog(0), + m_originatingJump(false), + m_storedLoopStart(0), + m_storedLoopEnd(0), + m_useSequencer(useSequencer), + m_dockVisible(true), + m_autoSaveTimer(new QTimer(this)), + m_clipboard(new Clipboard), + m_playList(0), + m_deviceManager(0), + m_synthManager(0), + m_audioMixer(0), + m_midiMixer(0), + m_bankEditor(0), + m_markerEditor(0), + m_tempoView(0), + m_triggerSegmentManager(0), +#ifdef HAVE_LIBLO + m_pluginGUIManager(new AudioPluginOSCGUIManager(this)), +#endif + m_playTimer(new QTimer(this)), + m_stopTimer(new QTimer(this)), + m_startupTester(0), +#ifdef HAVE_LIRC + m_lircClient(0), + m_lircCommander(0), +#endif + m_haveAudioImporter(false), + m_firstRun(false), + m_parameterArea(0) +{ + m_myself = this; + + + if (startupStatusMessageReceiver) { + QObject::connect(this, SIGNAL(startupStatusMessage(QString)), + startupStatusMessageReceiver, + SLOT(slotShowStatusMessage(QString))); + } + + // Try to start the sequencer + // + if (m_useSequencer) { + +#ifdef HAVE_LIBJACK +#define OFFER_JACK_START_OPTION 1 +#ifdef OFFER_JACK_START_OPTION + // First we check if jackd is running allready + + std::string jackClientName = "rosegarden"; + + // attempt connection to JACK server + // + jack_client_t* testJackClient; + testJackClient = jack_client_new(jackClientName.c_str()); + if (testJackClient == 0 ) { + + // If no connection to JACK + // try to launch JACK - if the configuration wants us to. + if (!launchJack()) { + KStartupLogo::hideIfStillThere(); + KMessageBox::error(this, i18n("Attempted to launch JACK audio daemon failed. Audio will be disabled.\nPlease check configuration (Settings -> Configure Rosegarden -> Audio -> Startup)\n and restart.")); + } + } else { + //this client was just for testing + jack_client_close(testJackClient); + } +#endif // OFFER_JACK_START_OPTION +#endif // HAVE_LIBJACK + + emit startupStatusMessage(i18n("Starting sequencer...")); + launchSequencer(useExistingSequencer); + + } else + RG_DEBUG << "RosegardenGUIApp : don't use sequencer\n"; + + // Plugin manager + // + emit startupStatusMessage(i18n("Initializing plugin manager...")); + m_pluginManager = new AudioPluginManager(); + + // call inits to invoke all other construction parts + // + emit startupStatusMessage(i18n("Initializing view...")); + initStatusBar(); + setupActions(); + iFaceDelayedInit(this); + initZoomToolbar(); + + QPixmap dummyPixmap; // any icon will do + m_mainDockWidget = createDockWidget("Rosegarden MainDockWidget", dummyPixmap, 0L, "main_dock_widget"); + // allow others to dock to the left and right sides only + m_mainDockWidget->setDockSite(KDockWidget::DockLeft | KDockWidget::DockRight); + // forbit docking abilities of m_mainDockWidget itself + m_mainDockWidget->setEnableDocking(KDockWidget::DockNone); + setView(m_mainDockWidget); // central widget in a KDE mainwindow + setMainDockWidget(m_mainDockWidget); // master dockwidget + + m_dockLeft = createDockWidget("params dock", dummyPixmap, 0L, + i18n("Special 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))); + + stateChanged("parametersbox_closed", KXMLGUIClient::StateReverse); + + RosegardenGUIDoc* doc = new RosegardenGUIDoc(this, m_pluginManager); + + m_parameterArea = new RosegardenParameterArea(m_dockLeft); + m_dockLeft->setWidget(m_parameterArea); + + // Populate the parameter-box area with the respective + // parameter box widgets. + + m_segmentParameterBox = new SegmentParameterBox(doc, m_parameterArea); + m_parameterArea->addRosegardenParameterBox(m_segmentParameterBox); + m_trackParameterBox = new TrackParameterBox(doc, m_parameterArea); + m_parameterArea->addRosegardenParameterBox(m_trackParameterBox); + m_instrumentParameterBox = new InstrumentParameterBox(doc, m_parameterArea); + m_parameterArea->addRosegardenParameterBox(m_instrumentParameterBox); + + // Lookup the configuration parameter that specifies the default + // arrangement, and instantiate it. + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + m_parameterArea->setArrangement((RosegardenParameterArea::Arrangement) + kapp->config()->readUnsignedNumEntry("sidebarstyle", + RosegardenParameterArea::CLASSIC_STYLE)); + + m_dockLeft->update(); + + connect(m_instrumentParameterBox, + SIGNAL(selectPlugin(QWidget *, InstrumentId, int)), + this, + SLOT(slotShowPluginDialog(QWidget *, InstrumentId, int))); + + connect(m_instrumentParameterBox, + SIGNAL(showPluginGUI(InstrumentId, int)), + this, + SLOT(slotShowPluginGUI(InstrumentId, int))); + + // relay this through our own signal so that others can use it too + connect(m_instrumentParameterBox, + SIGNAL(instrumentParametersChanged(InstrumentId)), + this, + SIGNAL(instrumentParametersChanged(InstrumentId))); + + connect(this, + SIGNAL(instrumentParametersChanged(InstrumentId)), + m_instrumentParameterBox, + SLOT(slotInstrumentParametersChanged(InstrumentId))); + + connect(this, + SIGNAL(pluginSelected(InstrumentId, int, int)), + m_instrumentParameterBox, + SLOT(slotPluginSelected(InstrumentId, int, int))); + + connect(this, + SIGNAL(pluginBypassed(InstrumentId, int, bool)), + m_instrumentParameterBox, + SLOT(slotPluginBypassed(InstrumentId, int, bool))); + + // Load the initial document (this includes doc's own autoload) + // + setDocument(doc); + + emit startupStatusMessage(i18n("Starting sequence manager...")); + + // transport is created by setupActions() + m_seqManager = new SequenceManager(m_doc, getTransport()); + + if (m_useSequencer) { + // Check the sound driver status and warn the user of any + // problems. This warning has to happen early, in case it + // affects the ability to load plugins etc from a file on the + // command line. + m_seqManager->checkSoundDriverStatus(true); + } + + if (m_view) { + connect(m_seqManager, SIGNAL(controllerDeviceEventReceived(MappedEvent *)), + m_view, SLOT(slotControllerDeviceEventReceived(MappedEvent *))); + } + + if (m_seqManager->getSoundDriverStatus() & AUDIO_OK) { + slotStateChanged("got_audio", true); + } else { + slotStateChanged("got_audio", false); + } + + // If we're restarting the gui then make sure any transient + // studio objects are cleared away. + emit startupStatusMessage(i18n("Clearing studio data...")); + m_seqManager->reinitialiseSequencerStudio(); + + // Send the transport control statuses for MMC and JACK + // + m_seqManager->sendTransportControlStatuses(); + + // Now autoload + // + stateChanged("new_file"); + stateChanged("have_segments", KXMLGUIClient::StateReverse); + stateChanged("have_selection", KXMLGUIClient::StateReverse); + slotTestClipboard(); + + // Check for lack of MIDI devices and disable Studio options accordingly + // + if (!m_doc->getStudio().haveMidiDevices()) + stateChanged("got_midi_devices", KXMLGUIClient::StateReverse); + + emit startupStatusMessage(i18n("Starting...")); + + setupFileDialogSpeedbar(); + readOptions(); + + // All toolbars should be created before this is called + setAutoSaveSettings(MainWindowConfigGroup, true); + +#ifdef HAVE_LIRC + + try { + m_lircClient = new LircClient(); + } catch (Exception e) { + RG_DEBUG << e.getMessage().c_str() << endl; + // continue without + m_lircClient = 0; + } + if (m_lircClient) { + m_lircCommander = new LircCommander(m_lircClient, this); + } +#endif + + stateChanged("have_project_packager", KXMLGUIClient::StateReverse); + stateChanged("have_lilypondview", KXMLGUIClient::StateReverse); + QTimer::singleShot(1000, this, SLOT(slotTestStartupTester())); +} + +RosegardenGUIApp::~RosegardenGUIApp() +{ + RG_DEBUG << "~RosegardenGUIApp()\n"; + + if (getView() && + getView()->getTrackEditor() && + getView()->getTrackEditor()->getSegmentCanvas()) { + getView()->getTrackEditor()->getSegmentCanvas()->endAudioPreviewGeneration(); + } + +#ifdef HAVE_LIBLO + delete m_pluginGUIManager; +#endif + + if (isSequencerRunning() && !isSequencerExternal()) { + m_sequencerProcess->blockSignals(true); + rgapp->sequencerSend("quit()"); + usleep(300000); + delete m_sequencerProcess; + } + + delete m_jumpToQuickMarkerAction; + delete m_setQuickMarkerAction; + + delete m_transport; + + delete m_seqManager; + +#ifdef HAVE_LIRC + + delete m_lircCommander; + delete m_lircClient; +#endif + + delete m_doc; + Profiles::getInstance()->dump(); +} + +void RosegardenGUIApp::setupActions() +{ + // setup File menu + // New Window ? + KStdAction::openNew (this, SLOT(slotFileNew()), actionCollection()); + KStdAction::open (this, SLOT(slotFileOpen()), actionCollection()); + m_fileRecent = KStdAction::openRecent(this, + SLOT(slotFileOpenRecent(const KURL&)), + actionCollection()); + KStdAction::save (this, SLOT(slotFileSave()), actionCollection()); + KStdAction::saveAs(this, SLOT(slotFileSaveAs()), actionCollection()); + KStdAction::revert(this, SLOT(slotRevertToSaved()), actionCollection()); + KStdAction::close (this, SLOT(slotFileClose()), actionCollection()); + KStdAction::print (this, SLOT(slotFilePrint()), actionCollection()); + KStdAction::printPreview (this, SLOT(slotFilePrintPreview()), actionCollection()); + + new KAction(i18n("Import Rosegarden &Project file..."), 0, 0, this, + SLOT(slotImportProject()), actionCollection(), + "file_import_project"); + + new KAction(i18n("Import &MIDI file..."), 0, 0, this, + SLOT(slotImportMIDI()), actionCollection(), + "file_import_midi"); + + new KAction(i18n("Import &Rosegarden 2.1 file..."), 0, 0, this, + SLOT(slotImportRG21()), actionCollection(), + "file_import_rg21"); + + new KAction(i18n("Import &Hydrogen file..."), 0, 0, this, + SLOT(slotImportHydrogen()), actionCollection(), + "file_import_hydrogen"); + + new KAction(i18n("Merge &File..."), 0, 0, this, + SLOT(slotMerge()), actionCollection(), + "file_merge"); + + new KAction(i18n("Merge &MIDI file..."), 0, 0, this, + SLOT(slotMergeMIDI()), actionCollection(), + "file_merge_midi"); + + new KAction(i18n("Merge &Rosegarden 2.1 file..."), 0, 0, this, + SLOT(slotMergeRG21()), actionCollection(), + "file_merge_rg21"); + + new KAction(i18n("Merge &Hydrogen file..."), 0, 0, this, + SLOT(slotMergeHydrogen()), actionCollection(), + "file_merge_hydrogen"); + + new KAction(i18n("Export Rosegarden &Project file..."), 0, 0, this, + SLOT(slotExportProject()), actionCollection(), + "file_export_project"); + + new KAction(i18n("Export &MIDI file..."), 0, 0, this, + SLOT(slotExportMIDI()), actionCollection(), + "file_export_midi"); + + new KAction(i18n("Export &LilyPond file..."), 0, 0, this, + SLOT(slotExportLilyPond()), actionCollection(), + "file_export_lilypond"); + + new KAction(i18n("Export Music&XML file..."), 0, 0, this, + SLOT(slotExportMusicXml()), actionCollection(), + "file_export_musicxml"); + + new KAction(i18n("Export &Csound score file..."), 0, 0, this, + SLOT(slotExportCsound()), actionCollection(), + "file_export_csound"); + + new KAction(i18n("Export M&up file..."), 0, 0, this, + SLOT(slotExportMup()), actionCollection(), + "file_export_mup"); + + 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"); + + new KAction(i18n("Play&list"), 0, 0, this, + SLOT(slotPlayList()), actionCollection(), + "file_show_playlist"); + + KStdAction::quit (this, SLOT(slotQuit()), actionCollection()); + + // help menu + new KAction(i18n("Rosegarden &Tutorial"), 0, 0, this, + SLOT(slotTutorial()), actionCollection(), + "tutorial"); + + new KAction(i18n("&Bug Reporting Guidelines"), 0, 0, this, + SLOT(slotBugGuidelines()), actionCollection(), + "guidelines"); + + // setup edit menu + KStdAction::cut (this, SLOT(slotEditCut()), actionCollection()); + KStdAction::copy (this, SLOT(slotEditCopy()), actionCollection()); + KStdAction::paste (this, SLOT(slotEditPaste()), actionCollection()); + + // + // undo/redo actions are special in that they are connected to + // slots later on, when the current document is set up - see + // MultiViewCommandHistory::attachView + // + new KToolBarPopupAction(i18n("Und&o"), + "undo", + KStdAccel::shortcut(KStdAccel::Undo), + actionCollection(), + KStdAction::stdName(KStdAction::Undo)); + + new KToolBarPopupAction(i18n("Re&do"), + "redo", + KStdAccel::shortcut(KStdAccel::Redo), + actionCollection(), + KStdAction::stdName(KStdAction::Redo)); + ///// + + + + // setup Settings menu + // + m_viewToolBar = KStdAction::showToolbar (this, SLOT(slotToggleToolBar()), actionCollection(), + "show_stock_toolbar"); + + m_viewToolsToolBar = new KToggleAction(i18n("Show T&ools Toolbar"), 0, this, + SLOT(slotToggleToolsToolBar()), actionCollection(), + "show_tools_toolbar"); + + m_viewTracksToolBar = new KToggleAction(i18n("Show Trac&ks Toolbar"), 0, this, + SLOT(slotToggleTracksToolBar()), actionCollection(), + "show_tracks_toolbar"); + + m_viewEditorsToolBar = new KToggleAction(i18n("Show &Editors Toolbar"), 0, this, + SLOT(slotToggleEditorsToolBar()), actionCollection(), + "show_editors_toolbar"); + + m_viewTransportToolBar = new KToggleAction(i18n("Show Trans&port Toolbar"), 0, this, + SLOT(slotToggleTransportToolBar()), actionCollection(), + "show_transport_toolbar"); + + m_viewZoomToolBar = new KToggleAction(i18n("Show &Zoom Toolbar"), 0, this, + SLOT(slotToggleZoomToolBar()), actionCollection(), + "show_zoom_toolbar"); + + m_viewStatusBar = KStdAction::showStatusbar(this, SLOT(slotToggleStatusBar()), + actionCollection(), "show_status_bar"); + + m_viewTransport = new KToggleAction(i18n("Show Tra&nsport"), Key_T, this, + SLOT(slotToggleTransport()), + actionCollection(), + "show_transport"); + + m_viewTrackLabels = new KToggleAction(i18n("Show Track &Labels"), 0, this, + SLOT(slotToggleTrackLabels()), + actionCollection(), + "show_tracklabels"); + + m_viewRulers = new KToggleAction(i18n("Show Playback Position R&uler"), 0, this, + SLOT(slotToggleRulers()), + actionCollection(), + "show_rulers"); + + m_viewTempoRuler = new KToggleAction(i18n("Show Te&mpo Ruler"), 0, this, + SLOT(slotToggleTempoRuler()), + actionCollection(), + "show_tempo_ruler"); + + m_viewChordNameRuler = new KToggleAction(i18n("Show Cho&rd Name Ruler"), 0, this, + SLOT(slotToggleChordNameRuler()), + actionCollection(), + "show_chord_name_ruler"); + + + m_viewPreviews = new KToggleAction(i18n("Show Segment Pre&views"), 0, this, + SLOT(slotTogglePreviews()), + actionCollection(), + "show_previews"); + + new KAction(i18n("Show Special &Parameters"), Key_P, this, + SLOT(slotDockParametersBack()), + actionCollection(), + "show_inst_segment_parameters"); + + KStdAction::tipOfDay( this, SLOT( slotShowTip() ), actionCollection() ); + + // Standard Actions + // + KStdAction::saveOptions(this, + SLOT(slotSaveOptions()), + actionCollection()); + + KStdAction::preferences(this, + SLOT(slotConfigure()), + actionCollection()); + + KStdAction::keyBindings(this, + SLOT(slotEditKeys()), + actionCollection()); + + KStdAction::configureToolbars(this, + SLOT(slotEditToolbars()), + actionCollection()); + + KRadioAction *action = 0; + + // Create the select icon + // + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QCanvasPixmap pixmap(pixmapDir + "/toolbar/select.xpm"); + QIconSet icon = QIconSet(pixmap); + + // TODO : add some shortcuts here + action = new KRadioAction(i18n("&Select and Edit"), icon, Key_F2, + this, SLOT(slotPointerSelected()), + actionCollection(), "select"); + action->setExclusiveGroup("segmenttools"); + + action = new KRadioAction(i18n("&Draw"), "pencil", Key_F3, + this, SLOT(slotDrawSelected()), + actionCollection(), "draw"); + action->setExclusiveGroup("segmenttools"); + + action = new KRadioAction(i18n("&Erase"), "eraser", Key_F4, + this, SLOT(slotEraseSelected()), + actionCollection(), "erase"); + action->setExclusiveGroup("segmenttools"); + + action = new KRadioAction(i18n("&Move"), "move", Key_F5, + this, SLOT(slotMoveSelected()), + actionCollection(), "move"); + action->setExclusiveGroup("segmenttools"); + + pixmap.load(pixmapDir + "/toolbar/resize.xpm"); + icon = QIconSet(pixmap); + action = new KRadioAction(i18n("&Resize"), icon, Key_F6, + this, SLOT(slotResizeSelected()), + actionCollection(), "resize"); + action->setExclusiveGroup("segmenttools"); + + pixmap.load(pixmapDir + "/toolbar/split.xpm"); + icon = QIconSet(pixmap); + action = new KRadioAction(i18n("&Split"), icon, Key_F7, + this, SLOT(slotSplitSelected()), + actionCollection(), "split"); + action->setExclusiveGroup("segmenttools"); + + pixmap.load(pixmapDir + "/toolbar/join.xpm"); + icon = QIconSet(pixmap); + action = new KRadioAction(i18n("&Join"), icon, 0, + this, SLOT(slotJoinSelected()), + actionCollection(), "join"); + action->setExclusiveGroup("segmenttools"); + + + new KAction(i18n("&Harmonize"), 0, this, + SLOT(slotHarmonizeSelection()), actionCollection(), + "harmonize_selection"); + + pixmap.load(pixmapDir + "/toolbar/event-insert-timesig.png"); + icon = QIconSet(pixmap); + new KAction(AddTimeSignatureCommand::getGlobalName(), + icon, 0, + this, SLOT(slotEditTimeSignature()), + actionCollection(), "add_time_signature"); + + new KAction(i18n("Open Tempo and Time Signature Editor"), 0, this, + SLOT(slotEditTempos()), actionCollection(), "edit_tempos"); + + // + // Edit menu + // + new KAction(i18n("Cut Range"), Key_X + CTRL + SHIFT, this, + SLOT(slotCutRange()), actionCollection(), + "cut_range"); + + new KAction(i18n("Copy Range"), Key_C + CTRL + SHIFT, this, + SLOT(slotCopyRange()), actionCollection(), + "copy_range"); + + new KAction(i18n("Paste Range"), Key_V + CTRL + SHIFT, this, + SLOT(slotPasteRange()), actionCollection(), + "paste_range"); +/* + new KAction(i18n("Delete Range"), Key_Delete + SHIFT, this, + SLOT(slotDeleteRange()), actionCollection(), + "delete_range"); +*/ + new KAction(i18n("Insert Range..."), Key_Insert + SHIFT, this, + SLOT(slotInsertRange()), actionCollection(), + "insert_range"); + + new KAction(i18n("De&lete"), Key_Delete, this, + SLOT(slotDeleteSelectedSegments()), actionCollection(), + "delete"); + + new KAction(i18n("Select &All Segments"), Key_A + CTRL, this, + SLOT(slotSelectAll()), actionCollection(), + "select_all"); + + pixmap.load(pixmapDir + "/toolbar/event-insert-tempo.png"); + icon = QIconSet(pixmap); + new KAction(AddTempoChangeCommand::getGlobalName(), + icon, 0, + this, SLOT(slotEditTempo()), + actionCollection(), "add_tempo"); + + new KAction(ChangeCompositionLengthCommand::getGlobalName(), + 0, + this, SLOT(slotChangeCompositionLength()), + actionCollection(), "change_composition_length"); + + new KAction(i18n("Edit Mar&kers..."), Key_K + CTRL, this, + SLOT(slotEditMarkers()), + actionCollection(), "edit_markers"); + + new KAction(i18n("Edit Document P&roperties..."), 0, this, + SLOT(slotEditDocumentProperties()), + actionCollection(), "edit_doc_properties"); + + + // + // Segments menu + // + new KAction(i18n("Open in &Default Editor"), Key_Return, this, + SLOT(slotEdit()), actionCollection(), + "edit_default"); + + pixmap.load(pixmapDir + "/toolbar/matrix.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Open in Matri&x Editor"), icon, Key_M, this, + SLOT(slotEditInMatrix()), actionCollection(), + "edit_matrix"); + + pixmap.load(pixmapDir + "/toolbar/matrix-percussion.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Open in &Percussion Matrix Editor"), icon, Key_D, this, + SLOT(slotEditInPercussionMatrix()), actionCollection(), + "edit_percussion_matrix"); + + pixmap.load(pixmapDir + "/toolbar/notation.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Open in &Notation Editor"), icon, Key_N, this, + SLOT(slotEditAsNotation()), actionCollection(), + "edit_notation"); + + pixmap.load(pixmapDir + "/toolbar/eventlist.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Open in &Event List Editor"), icon, Key_E, this, + SLOT(slotEditInEventList()), actionCollection(), + "edit_event_list"); + + pixmap.load(pixmapDir + "/toolbar/quantize.png"); + icon = QIconSet(pixmap); + new KAction(i18n("&Quantize..."), icon, Key_Equal, this, + SLOT(slotQuantizeSelection()), actionCollection(), + "quantize_selection"); + + new KAction(SegmentLabelCommand::getGlobalName(), + 0, + this, SLOT(slotRelabelSegments()), + actionCollection(), "relabel_segment"); + + new KAction(SegmentTransposeCommand::getGlobalName(), + 0, + this, SLOT(slotTransposeSegments()), + actionCollection(), "transpose"); + + new KAction(i18n("Repeat Last Quantize"), Key_Plus, this, + SLOT(slotRepeatQuantizeSelection()), actionCollection(), + "repeat_quantize"); + + new KAction(SegmentRescaleCommand::getGlobalName(), 0, this, + SLOT(slotRescaleSelection()), actionCollection(), + "rescale"); + + new KAction(SegmentAutoSplitCommand::getGlobalName(), 0, this, + SLOT(slotAutoSplitSelection()), actionCollection(), + "auto_split"); + + new KAction(SegmentSplitByPitchCommand::getGlobalName(), 0, this, + SLOT(slotSplitSelectionByPitch()), actionCollection(), + "split_by_pitch"); + + new KAction(SegmentSplitByRecordingSrcCommand::getGlobalName(), 0, this, + SLOT(slotSplitSelectionByRecordedSrc()), actionCollection(), + "split_by_recording"); + + new KAction(i18n("Split at Time..."), 0, this, + SLOT(slotSplitSelectionAtTime()), actionCollection(), + "split_at_time"); + + new KAction(i18n("Jog &Left"), Key_Left + ALT, this, + SLOT(slotJogLeft()), actionCollection(), + "jog_left"); + + new KAction(i18n("Jog &Right"), Key_Right + ALT, this, + SLOT(slotJogRight()), actionCollection(), + "jog_right"); + + new KAction(i18n("Set Start Time..."), 0, this, + SLOT(slotSetSegmentStartTimes()), actionCollection(), + "set_segment_start"); + + new KAction(i18n("Set Duration..."), 0, this, + SLOT(slotSetSegmentDurations()), actionCollection(), + "set_segment_duration"); + + new KAction(SegmentJoinCommand::getGlobalName(), + Key_J + CTRL, + this, SLOT(slotJoinSegments()), + actionCollection(), "join_segments"); + + new KAction(i18n("Turn Re&peats into Copies"), + 0, + this, SLOT(slotRepeatingSegments()), + actionCollection(), "repeats_to_real_copies"); + + new KAction(i18n("Manage Tri&ggered Segments"), 0, + this, SLOT(slotManageTriggerSegments()), + actionCollection(), "manage_trigger_segments"); + + new KAction(i18n("Set Tempos from &Beat Segment"), 0, this, + SLOT(slotGrooveQuantize()), actionCollection(), + "groove_quantize"); + + new KAction(i18n("Set &Tempo to Audio Segment Duration"), 0, this, + SLOT(slotTempoToSegmentLength()), actionCollection(), + "set_tempo_to_segment_length"); + + pixmap.load(pixmapDir + "/toolbar/manage-audio-segments.xpm"); + icon = QIconSet(pixmap); + new KAction(i18n("Manage A&udio Files"), icon, + Key_U + CTRL, + this, SLOT(slotAudioManager()), + actionCollection(), "audio_manager"); + + m_viewSegmentLabels = new KToggleAction(i18n("Show Segment Labels"), 0, this, + SLOT(slotToggleSegmentLabels()), actionCollection(), + "show_segment_labels"); + + // + // Tracks menu + // + pixmap.load(pixmapDir + "/toolbar/add_tracks.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Add &Track"), icon, CTRL + Key_T, + this, SLOT(slotAddTrack()), + actionCollection(), "add_track"); + + new KAction(i18n("&Add Tracks..."), 0, + this, SLOT(slotAddTracks()), + actionCollection(), "add_tracks"); + + pixmap.load(pixmapDir + "/toolbar/delete_track.png"); + icon = QIconSet(pixmap); + new KAction(i18n("D&elete Track"), icon, CTRL + Key_D, + this, SLOT(slotDeleteTrack()), + actionCollection(), "delete_track"); + + pixmap.load(pixmapDir + "/toolbar/move_track_down.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Move Track &Down"), icon, SHIFT + Key_Down, + this, SLOT(slotMoveTrackDown()), + actionCollection(), "move_track_down"); + + pixmap.load(pixmapDir + "/toolbar/move_track_up.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Move Track &Up"), icon, SHIFT + Key_Up, + this, SLOT(slotMoveTrackUp()), + actionCollection(), "move_track_up"); + + new KAction(i18n("Select &Next Track"), + Key_Down, + this, SLOT(slotTrackDown()), + actionCollection(), "select_next_track"); + + new KAction(i18n("Select &Previous Track"), + Key_Up, + this, SLOT(slotTrackUp()), + actionCollection(), "select_previous_track"); + + new KAction(i18n("Mute or Unmute Track"), + Key_U, + this, SLOT(slotToggleMutedCurrentTrack()), + actionCollection(), "toggle_mute_track"); + + new KAction(i18n("Arm or Un-arm Track for Record"), + Key_R, + this, SLOT(slotToggleRecordCurrentTrack()), + actionCollection(), "toggle_arm_track"); + + pixmap.load(pixmapDir + "/toolbar/mute-all.png"); + icon = QIconSet(pixmap); + new KAction(i18n("&Mute all Tracks"), icon, 0, + this, SLOT(slotMuteAllTracks()), + actionCollection(), "mute_all_tracks"); + + pixmap.load(pixmapDir + "/toolbar/un-mute-all.png"); + icon = QIconSet(pixmap); + new KAction(i18n("&Unmute all Tracks"), icon, 0, + this, SLOT(slotUnmuteAllTracks()), + actionCollection(), "unmute_all_tracks"); + + new KAction(i18n("&Remap Instruments..."), 0, this, + SLOT(slotRemapInstruments()), + actionCollection(), "remap_instruments"); + + // + // Studio menu + // + pixmap.load(pixmapDir + "/toolbar/mixer.png"); + icon = QIconSet(pixmap); + new KAction(i18n("&Audio Mixer"), icon, 0, this, + SLOT(slotOpenAudioMixer()), + actionCollection(), "audio_mixer"); + + pixmap.load(pixmapDir + "/toolbar/midimixer.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Midi Mi&xer"), icon, 0, this, + SLOT(slotOpenMidiMixer()), + actionCollection(), "midi_mixer"); + + pixmap.load(pixmapDir + "/toolbar/manage-midi-devices.xpm"); + icon = QIconSet(pixmap); + new KAction(i18n("Manage MIDI &Devices"), icon, 0, this, + SLOT(slotManageMIDIDevices()), + actionCollection(), "manage_devices"); + + pixmap.load(pixmapDir + "/toolbar/manage-synth-plugins.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Manage S&ynth Plugins"), icon, 0, this, + SLOT(slotManageSynths()), + actionCollection(), "manage_synths"); + + new KAction(i18n("Modify MIDI &Filters"), "filter", 0, this, + SLOT(slotModifyMIDIFilters()), + actionCollection(), "modify_midi_filters"); + + m_enableMIDIrouting = new KToggleAction(i18n("MIDI Thru Routing"), 0, this, + SLOT(slotEnableMIDIThruRouting()), + actionCollection(), "enable_midi_routing"); + + pixmap.load(pixmapDir + "/toolbar/time-musical.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Manage &Metronome"), 0, this, + SLOT(slotManageMetronome()), + actionCollection(), "manage_metronome"); + + new KAction(i18n("&Save Current Document as Default Studio"), 0, this, + SLOT(slotSaveDefaultStudio()), + actionCollection(), "save_default_studio"); + + new KAction(i18n("&Import Default Studio"), 0, this, + SLOT(slotImportDefaultStudio()), + actionCollection(), "load_default_studio"); + + new KAction(i18n("Im&port Studio from File..."), 0, this, + SLOT(slotImportStudio()), + actionCollection(), "load_studio"); + + new KAction(i18n("&Reset MIDI Network"), 0, this, + SLOT(slotResetMidiNetwork()), + actionCollection(), "reset_midi_network"); + + m_setQuickMarkerAction = new KAction(i18n("Set Quick Marker at Playback Position"), 0, CTRL + Key_1, this, + SLOT(slotSetQuickMarker()), actionCollection(), + "set_quick_marker"); + + m_jumpToQuickMarkerAction = new KAction(i18n("Jump to Quick Marker"), 0, Key_1, this, + SLOT(slotJumpToQuickMarker()), actionCollection(), + "jump_to_quick_marker"); + + // + // Marker Ruler popup menu + // +// new KAction(i18n("Insert Marker"), 0, 0, this, +// SLOT(slotInsertMarkerHere()), actionCollection(), +// "insert_marker_here"); +// +// new KAction(i18n("Insert Marker at Playback Position"), 0, 0, this, +// SLOT(slotInsertMarkerAtPointer()), actionCollection(), +// "insert_marker_at_pointer"); +// +// new KAction(i18n("Delete Marker"), 0, 0, this, +// SLOT(slotDeleteMarker()), actionCollection(), +// "delete_marker"); + + + + // + // Transport menu + // + + // Transport controls [rwb] + // + // We set some default key bindings - with numlock off + // use 1 (End) and 3 (Page Down) for Rwd and Ffwd and + // 0 (insert) and keypad Enter for Play and Stop + // + pixmap.load(pixmapDir + "/toolbar/transport-play.png"); + icon = QIconSet(pixmap); + m_playTransport = new KAction(i18n("&Play"), icon, Key_Enter, this, + SLOT(slotPlay()), actionCollection(), + "play"); + // Alternative shortcut for Play + KShortcut playShortcut = m_playTransport->shortcut(); + playShortcut.append( KKey(Key_Return + CTRL) ); + m_playTransport->setShortcut(playShortcut); + m_playTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-stop.png"); + icon = QIconSet(pixmap); + m_stopTransport = new KAction(i18n("&Stop"), icon, Key_Insert, this, + SLOT(slotStop()), actionCollection(), + "stop"); + m_stopTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-ffwd.png"); + icon = QIconSet(pixmap); + m_ffwdTransport = new KAction(i18n("&Fast Forward"), icon, Key_PageDown, + this, + SLOT(slotFastforward()), actionCollection(), + "fast_forward"); + m_ffwdTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-rewind.png"); + icon = QIconSet(pixmap); + m_rewindTransport = new KAction(i18n("Re&wind"), icon, Key_End, this, + SLOT(slotRewind()), actionCollection(), + "rewind"); + m_rewindTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-record.png"); + icon = QIconSet(pixmap); + m_recordTransport = new KAction(i18n("P&unch in Record"), icon, Key_Space, this, + SLOT(slotToggleRecord()), actionCollection(), + "recordtoggle"); + m_recordTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-record.png"); + icon = QIconSet(pixmap); + m_recordTransport = new KAction(i18n("&Record"), icon, 0, this, + SLOT(slotRecord()), actionCollection(), + "record"); + m_recordTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-rewind-end.png"); + icon = QIconSet(pixmap); + m_rewindEndTransport = new KAction(i18n("Rewind to &Beginning"), icon, 0, this, + SLOT(slotRewindToBeginning()), actionCollection(), + "rewindtobeginning"); + m_rewindEndTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-ffwd-end.png"); + icon = QIconSet(pixmap); + m_ffwdEndTransport = new KAction(i18n("Fast Forward to &End"), icon, 0, this, + SLOT(slotFastForwardToEnd()), actionCollection(), + "fastforwardtoend"); + m_ffwdEndTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-tracking.png"); + icon = QIconSet(pixmap); + (new KToggleAction(i18n("Scro&ll to Follow Playback"), icon, Key_Pause, this, + SLOT(slotToggleTracking()), actionCollection(), + "toggle_tracking"))->setChecked(true); + + pixmap.load(pixmapDir + "/toolbar/transport-panic.png"); + icon = QIconSet(pixmap); + new KAction( i18n("Panic"), icon, Key_P + CTRL + ALT, this, SLOT(slotPanic()), + actionCollection(), "panic"); + + // DEBUG FACILITY + new KAction(i18n("Segment Debug Dump "), 0, this, + SLOT(slotDebugDump()), actionCollection(), + "debug_dump_segments"); + + // create main gui + // + createGUI("rosegardenui.rc", false); + + createAndSetupTransport(); + + // transport toolbar is hidden by default - TODO : this should be in options + // + //toolBar("Transport Toolbar")->hide(); + + QPopupMenu* setTrackInstrumentMenu = dynamic_cast(factory()->container("set_track_instrument", this)); + + if (setTrackInstrumentMenu) { + connect(setTrackInstrumentMenu, SIGNAL(aboutToShow()), + this, SLOT(slotPopulateTrackInstrumentPopup())); + } else { + RG_DEBUG << "RosegardenGUIApp::setupActions() : couldn't find set_track_instrument menu - check rosegardenui.rcn\n"; + } + + setRewFFwdToAutoRepeat(); +} + +void RosegardenGUIApp::setRewFFwdToAutoRepeat() +{ + QWidget* transportToolbar = factory()->container("Transport Toolbar", this); + + if (transportToolbar) { + QObjectList *l = transportToolbar->queryList(); + QObjectListIt it(*l); // iterate over the buttons + QObject *obj; + + while ( (obj = it.current()) != 0 ) { + // for each found object... + ++it; + // RG_DEBUG << "obj name : " << obj->name() << endl; + QString objName = obj->name(); + + if (objName.endsWith("rewind") || objName.endsWith("fast_forward")) { + QButton* btn = dynamic_cast(obj); + if (!btn) { + RG_DEBUG << "Very strange - found widgets in transport_toolbar which aren't buttons\n"; + + continue; + } + btn->setAutoRepeat(true); + } + + + } + delete l; + + } else { + RG_DEBUG << "transportToolbar == 0\n"; + } + +} + +void RosegardenGUIApp::initZoomToolbar() +{ + KToolBar *zoomToolbar = toolBar("Zoom Toolbar"); + if (!zoomToolbar) { + RG_DEBUG << "RosegardenGUIApp::initZoomToolbar() : " + << "zoom toolbar not found" << endl; + return ; + } + + new QLabel(i18n(" Zoom: "), zoomToolbar, "kde toolbar widget"); + + std::vector 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 }; + + for (unsigned int i = 0; i < sizeof(factors) / sizeof(factors[0]); ++i) { + zoomSizes.push_back(duration44 / (defaultBarWidth44 * factors[i])); + } + + // zoom labels + QString minZoom = QString("%1%").arg(factors[0] * 100.0); + QString maxZoom = QString("%1%").arg(factors[(sizeof(factors) / sizeof(factors[0])) - 1] * 100.0); + + m_zoomSlider = new ZoomSlider + (zoomSizes, -1, QSlider::Horizontal, zoomToolbar, "kde toolbar widget"); + m_zoomSlider->setTracking(true); + m_zoomSlider->setFocusPolicy(QWidget::NoFocus); + m_zoomLabel = new QLabel(minZoom, zoomToolbar, "kde toolbar widget"); + m_zoomLabel->setIndent(10); + + connect(m_zoomSlider, SIGNAL(valueChanged(int)), + this, SLOT(slotChangeZoom(int))); + + // set initial zoom - we might want to make this a config option + // m_zoomSlider->setToDefault(); + +} + +void RosegardenGUIApp::initStatusBar() +{ + KTmpStatusMsg::setDefaultMsg(""); + statusBar()->insertItem(KTmpStatusMsg::getDefaultMsg(), + KTmpStatusMsg::getDefaultId(), 1); + statusBar()->setItemAlignment(KTmpStatusMsg::getDefaultId(), + AlignLeft | AlignVCenter); + + m_progressBar = new ProgressBar(100, true, statusBar()); + // m_progressBar->setMinimumWidth(100); + m_progressBar->setFixedWidth(60); + m_progressBar->setFixedHeight(18); + m_progressBar->setTextEnabled(false); + statusBar()->addWidget(m_progressBar); +} + +void RosegardenGUIApp::initView() +{ + //////////////////////////////////////////////////////////////////// + // create the main widget here that is managed by KTMainWindow's view-region and + // connect the widget to your document to display document contents. + + RG_DEBUG << "RosegardenGUIApp::initView()" << endl; + + Composition &comp = m_doc->getComposition(); + + // Ensure that the start and end markers for the piece are set + // to something reasonable + // + if (comp.getStartMarker() == 0 && + comp.getEndMarker() == 0) { + int endMarker = comp.getBarRange(100 + comp.getNbBars()).second; + comp.setEndMarker(endMarker); + } + + m_swapView = new RosegardenGUIView(m_viewTrackLabels->isChecked(), + m_segmentParameterBox, + m_instrumentParameterBox, + m_trackParameterBox, this); + + // Connect up this signal so that we can force tool mode + // changes from the view + connect(m_swapView, SIGNAL(activateTool(QString)), + this, SLOT(slotActivateTool(QString))); + + connect(m_swapView, + SIGNAL(segmentsSelected(const SegmentSelection &)), + SIGNAL(segmentsSelected(const SegmentSelection &))); + + connect(m_swapView, + SIGNAL(addAudioFile(AudioFileId)), + SLOT(slotAddAudioFile(AudioFileId))); + + connect(m_swapView, SIGNAL(toggleSolo(bool)), SLOT(slotToggleSolo(bool))); + + m_doc->attachView(m_swapView); + + m_mainDockWidget->setWidget(m_swapView); + + // setCentralWidget(m_swapView); + setCaption(m_doc->getTitle()); + + + // Transport setup + // + std::string transportMode = m_doc->getConfiguration(). + get + + (DocumentConfiguration::TransportMode); + + + slotEnableTransport(true); + + // and the time signature + // + getTransport()->setTimeSignature(comp.getTimeSignatureAt(comp.getPosition())); + + // set the tempo in the transport + // + getTransport()->setTempo(comp.getCurrentTempo()); + + // bring the transport to the front + // + getTransport()->raise(); + + // set the play metronome button + getTransport()->MetronomeButton()->setOn(comp.usePlayMetronome()); + + // Set the solo button + getTransport()->SoloButton()->setOn(comp.isSolo()); + + // set the transport mode found in the configuration + getTransport()->setNewMode(transportMode); + + // set the pointer position + // + slotSetPointerPosition(m_doc->getComposition().getPosition()); + + // make sure we show + // + RosegardenGUIView *oldView = m_view; + m_view = m_swapView; + + connect(m_view, SIGNAL(stateChange(QString, bool)), + this, SLOT (slotStateChanged(QString, bool))); + + connect(m_view, SIGNAL(instrumentParametersChanged(InstrumentId)), + this, SIGNAL(instrumentParametersChanged(InstrumentId))); + + // We only check for the SequenceManager to make sure + // we're not on the first pass though - we don't want + // to send these toggles twice on initialisation. + // + // Clunky but we just about get away with it for the + // moment. + // + if (m_seqManager != 0) { + slotToggleChordNameRuler(); + slotToggleRulers(); + slotToggleTempoRuler(); + slotTogglePreviews(); + slotToggleSegmentLabels(); + + // Reset any loop on the sequencer + // + try { + if (isUsingSequencer()) + m_seqManager->setLoop(0, 0); + stateChanged("have_range", KXMLGUIClient::StateReverse); + } catch (QString s) { + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + KMessageBox::error(this, s); + CurrentProgressDialog::thaw(); + } + + connect(m_seqManager, SIGNAL(controllerDeviceEventReceived(MappedEvent *)), + m_view, SLOT(slotControllerDeviceEventReceived(MappedEvent *))); + } + + // delete m_playList; + // m_playList = 0; + + delete m_deviceManager; + m_deviceManager = 0; + + delete m_synthManager; + m_synthManager = 0; + + delete m_audioMixer; + m_audioMixer = 0; + + delete m_bankEditor; + m_bankEditor = 0; + + delete m_markerEditor; + m_markerEditor = 0; + + delete m_tempoView; + m_tempoView = 0; + + delete m_triggerSegmentManager; + m_triggerSegmentManager = 0; + + delete oldView; + + // set the highlighted track + m_view->slotSelectTrackSegments(comp.getSelectedTrack()); + + // play tracking on in the editor by default: turn off if need be + KToggleAction *trackingAction = dynamic_cast + (actionCollection()->action("toggle_tracking")); + if (trackingAction && !trackingAction->isChecked()) { + m_view->getTrackEditor()->slotToggleTracking(); + } + + m_view->show(); + + connect(m_view->getTrackEditor()->getSegmentCanvas(), + SIGNAL(showContextHelp(const QString &)), + this, + SLOT(slotShowToolHelp(const QString &))); + + // We have to do this to make sure that the 2nd call ("select") + // actually has any effect. Activating the same radio action + // doesn't work the 2nd time (like pressing down the same radio + // button twice - it doesn't have any effect), so if you load two + // files in a row, on the 2nd file a new SegmentCanvas will be + // created but its tool won't be set, even though it will appear + // to be selected. + // + actionCollection()->action("move")->activate(); + if (m_doc->getComposition().getNbSegments() > 0) + actionCollection()->action("select")->activate(); + else + actionCollection()->action("draw")->activate(); + + int zoomLevel = m_doc->getConfiguration(). + get + + (DocumentConfiguration::ZoomLevel); + + m_zoomSlider->setSize(double(zoomLevel) / 1000.0); + slotChangeZoom(zoomLevel); + + //slotChangeZoom(int(m_zoomSlider->getCurrentSize())); + + stateChanged("new_file"); + + ProgressDialog::processEvents(); + + if (m_viewChordNameRuler->isChecked()) { + SetWaitCursor swc; + m_view->initChordNameRuler(); + } else { + m_view->initChordNameRuler(); + } +} + +void RosegardenGUIApp::setDocument(RosegardenGUIDoc* newDocument) +{ + if (m_doc == newDocument) + return ; + + emit documentAboutToChange(); + kapp->processEvents(); // to make sure all opened dialogs (mixer, midi devices...) are closed + + // Take care of all subparts which depend on the document + + // Caption + // + QString caption = kapp->caption(); + setCaption(caption + ": " + newDocument->getTitle()); + + // // reset AudioManagerDialog + // // + // delete m_audioManagerDialog; // TODO : replace this with a connection to documentAboutToChange() sig. + // m_audioManagerDialog = 0; + + RosegardenGUIDoc* oldDoc = m_doc; + + m_doc = newDocument; + + if (m_seqManager) // when we're called at startup, the seq. man. isn't created yet + m_seqManager->setDocument(m_doc); + + if (m_markerEditor) + m_markerEditor->setDocument(m_doc); + if (m_tempoView) { + delete m_tempoView; + m_tempoView = 0; + } + if (m_triggerSegmentManager) + m_triggerSegmentManager->setDocument(m_doc); + + m_trackParameterBox->setDocument(m_doc); + m_segmentParameterBox->setDocument(m_doc); + m_instrumentParameterBox->setDocument(m_doc); + +#ifdef HAVE_LIBLO + + if (m_pluginGUIManager) { + m_pluginGUIManager->stopAllGUIs(); + m_pluginGUIManager->setStudio(&m_doc->getStudio()); + } +#endif + + if (getView() && + getView()->getTrackEditor() && + getView()->getTrackEditor()->getSegmentCanvas()) { + getView()->getTrackEditor()->getSegmentCanvas()->endAudioPreviewGeneration(); + } + + // this will delete all edit views + // + delete oldDoc; + + // connect needed signals + // + connect(m_segmentParameterBox, SIGNAL(documentModified()), + m_doc, SLOT(slotDocumentModified())); + + connect(m_doc, SIGNAL(pointerPositionChanged(timeT)), + this, SLOT(slotSetPointerPosition(timeT))); + + connect(m_doc, SIGNAL(documentModified(bool)), + this, SLOT(slotDocumentModified(bool))); + + connect(m_doc, SIGNAL(loopChanged(timeT, timeT)), + this, SLOT(slotSetLoop(timeT, timeT))); + + m_doc->getCommandHistory()->attachView(actionCollection()); + + connect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()), + SLOT(update())); + connect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()), + SLOT(slotTestClipboard())); + + // connect and start the autosave timer + connect(m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); + m_autoSaveTimer->start(m_doc->getAutoSavePeriod() * 1000); + + // Connect the playback timer + // + connect(m_playTimer, SIGNAL(timeout()), this, SLOT(slotUpdatePlaybackPosition())); + connect(m_stopTimer, SIGNAL(timeout()), this, SLOT(slotUpdateMonitoring())); + + // finally recreate the main view + // + initView(); + + if (getView() && getView()->getTrackEditor()) { + connect(m_doc, SIGNAL(makeTrackVisible(int)), + getView()->getTrackEditor(), SLOT(slotScrollToTrack(int))); + } + + connect(m_doc, SIGNAL(devicesResyncd()), + this, SLOT(slotDocumentDevicesResyncd())); + + m_doc->syncDevices(); + m_doc->clearModifiedStatus(); + + if (newDocument->getStudio().haveMidiDevices()) { + stateChanged("got_midi_devices"); + } else { + stateChanged("got_midi_devices", KXMLGUIClient::StateReverse); + } + + // Ensure the sequencer knows about any audio files + // we've loaded as part of the new Composition + // + m_doc->prepareAudio(); + + // Do not reset instrument prog. changes after all. + // if (m_seqManager) + // m_seqManager->preparePlayback(true); + + Composition &comp = m_doc->getComposition(); + + // Set any loaded loop at the Composition and + // on the marker on SegmentCanvas and clients + // + if (m_seqManager) + m_doc->setLoop(comp.getLoopStart(), comp.getLoopEnd()); + + emit documentChanged(m_doc); + + m_doc->clearModifiedStatus(); // because it's set as modified by the various + // init operations + // TODO: this sucks, have to sort it out somehow. + + // Readjust canvas size + // + m_view->getTrackEditor()->slotReadjustCanvasSize(); + + m_stopTimer->start(100); +} + +void +RosegardenGUIApp::openFile(QString filePath, ImportType type) +{ + RG_DEBUG << "RosegardenGUIApp::openFile " << filePath << endl; + + if (type == ImportCheckType && filePath.endsWith(".rgp")) { + importProject(filePath); + return ; + } + + RosegardenGUIDoc *doc = createDocument(filePath, type); + if (doc) { + setDocument(doc); + + // fix # 1235755, "SPB combo not updating after document swap" + RG_DEBUG << "RosegardenGUIApp::openFile(): calling slotDocColoursChanged() in doc" << endl; + doc->slotDocColoursChanged(); + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + if (kapp->config()->readBoolEntry("alwaysusedefaultstudio", false)) { + + QString autoloadFile = + KGlobal::dirs()->findResource("appdata", "autoload.rg"); + + QFileInfo autoloadFileInfo(autoloadFile); + if (autoloadFileInfo.isReadable()) { + + RG_DEBUG << "Importing default studio from " << autoloadFile << endl; + + slotImportStudioFromFile(autoloadFile); + } + } + + QFileInfo fInfo(filePath); + m_fileRecent->addURL(fInfo.absFilePath()); + } +} + +RosegardenGUIDoc* +RosegardenGUIApp::createDocument(QString filePath, ImportType importType) +{ + QFileInfo info(filePath); + RosegardenGUIDoc *doc = 0; + + if (!info.exists()) { + // can happen with command-line arg, so... + KStartupLogo::hideIfStillThere(); + KMessageBox::sorry(this, i18n("File \"%1\" does not exist").arg(filePath)); + return 0; + } + + if (info.isDir()) { + KStartupLogo::hideIfStillThere(); + KMessageBox::sorry(this, i18n("File \"%1\" is actually a directory")); + return 0; + } + + QFile file(filePath); + + if (!file.open(IO_ReadOnly)) { + KStartupLogo::hideIfStillThere(); + QString errStr = + i18n("You do not have read permission for \"%1\"").arg(filePath); + + KMessageBox::sorry(this, errStr); + return 0; + } + + // Stop if playing + // + if (m_seqManager && m_seqManager->getTransportStatus() == PLAYING) + slotStop(); + + slotEnableTransport(false); + + if (importType == ImportCheckType) { + KMimeType::Ptr fileMimeType = KMimeType::findByPath(filePath); + if (fileMimeType->name() == "audio/x-midi") + importType = ImportMIDI; + else if (fileMimeType->name() == "audio/x-rosegarden") + importType = ImportRG4; + else if (filePath.endsWith(".rose")) + importType = ImportRG21; + else if (filePath.endsWith(".h2song")) + importType = ImportHydrogen; + } + + + switch (importType) { + case ImportMIDI: + doc = createDocumentFromMIDIFile(filePath); + break; + case ImportRG21: + doc = createDocumentFromRG21File(filePath); + break; + case ImportHydrogen: + doc = createDocumentFromHydrogenFile(filePath); + break; + default: + doc = createDocumentFromRGFile(filePath); + } + + slotEnableTransport(true); + + return doc; +} + +RosegardenGUIDoc* +RosegardenGUIApp::createDocumentFromRGFile(QString filePath) +{ + // Check for an autosaved file to recover + QString effectiveFilePath = filePath; + bool canRecover = false; + QString autoSaveFileName = kapp->checkRecoverFile(filePath, canRecover); + + if (canRecover) { + // First check if the auto-save file is more recent than the doc + QFileInfo docFileInfo(filePath), autoSaveFileInfo(autoSaveFileName); + + if (docFileInfo.lastModified() < autoSaveFileInfo.lastModified()) { + + RG_DEBUG << "RosegardenGUIApp::openFile : " + << "found a more recent autosave file\n"; + + // At this point the splash screen may still be there, hide it if + // it's the case + KStartupLogo::hideIfStillThere(); + + // It is, so ask the user if he wants to use the autosave file + int reply = KMessageBox::questionYesNo(this, + i18n("An auto-save file for this document has been found\nDo you want to open it instead ?")); + + if (reply == KMessageBox::Yes) + // open the autosave file instead + effectiveFilePath = autoSaveFileName; + else { + // user doesn't want the autosave, so delete it + // so it won't bother us again if we reload + canRecover = false; + QFile::remove + (autoSaveFileName); + } + + } else + canRecover = false; + } + + // Create a new blank document + // + RosegardenGUIDoc *newDoc = new RosegardenGUIDoc(this, m_pluginManager, + true); // skipAutoload + + // ignore return thingy + // + if (newDoc->openDocument(effectiveFilePath)) { + if (canRecover) { + // Mark the document as modified, + // set the "regular" filepath and name (not those of + // the autosaved doc) + // + newDoc->slotDocumentModified(); + QFileInfo info(filePath); + newDoc->setAbsFilePath(info.absFilePath()); + newDoc->setTitle(info.fileName()); + } else { + newDoc->clearModifiedStatus(); + } + } else { + delete newDoc; + return 0; + } + + return newDoc; +} + +void RosegardenGUIApp::slotSaveOptions() +{ + RG_DEBUG << "RosegardenGUIApp::slotSaveOptions()\n"; + +#ifdef SETTING_LOG_DEBUG + + _settingLog(QString("SETTING 2 : transport flap extended = %1").arg(getTransport()->isExpanded())); + _settingLog(QString("SETTING 2 : show track labels = %1").arg(m_viewTrackLabels->isChecked())); +#endif + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + kapp->config()->writeEntry("Show Transport", m_viewTransport->isChecked()); + kapp->config()->writeEntry("Expanded Transport", m_transport ? getTransport()->isExpanded() : true); + kapp->config()->writeEntry("Show Track labels", m_viewTrackLabels->isChecked()); + kapp->config()->writeEntry("Show Rulers", m_viewRulers->isChecked()); + kapp->config()->writeEntry("Show Tempo Ruler", m_viewTempoRuler->isChecked()); + kapp->config()->writeEntry("Show Chord Name Ruler", m_viewChordNameRuler->isChecked()); + kapp->config()->writeEntry("Show Previews", m_viewPreviews->isChecked()); + kapp->config()->writeEntry("Show Segment Labels", m_viewSegmentLabels->isChecked()); + kapp->config()->writeEntry("Show Parameters", m_dockVisible); + kapp->config()->writeEntry("MIDI Thru Routing", m_enableMIDIrouting->isChecked()); + +#ifdef SETTING_LOG_DEBUG + + RG_DEBUG << "SHOW PARAMETERS = " << m_dockVisible << endl; +#endif + + m_fileRecent->saveEntries(kapp->config()); + + // saveMainWindowSettings(kapp->config(), RosegardenGUIApp::MainWindowConfigGroup); - no need to, done by KMainWindow + kapp->config()->sync(); +} + +void RosegardenGUIApp::setupFileDialogSpeedbar() +{ + KConfig *config = kapp->config(); + + config->setGroup("KFileDialog Speedbar"); + + RG_DEBUG << "RosegardenGUIApp::setupFileDialogSpeedbar" << endl; + + bool hasSetExamplesItem = config->readBoolEntry("Examples Set", false); + + RG_DEBUG << "RosegardenGUIApp::setupFileDialogSpeedbar: examples set " << hasSetExamplesItem << endl; + + if (!hasSetExamplesItem) { + + unsigned int n = config->readUnsignedNumEntry("Number of Entries", 0); + + config->writeEntry(QString("Description_%1").arg(n), i18n("Example Files")); + config->writeEntry(QString("IconGroup_%1").arg(n), 4); + config->writeEntry(QString("Icon_%1").arg(n), "folder"); + config->writeEntry(QString("URL_%1").arg(n), + KGlobal::dirs()->findResource("appdata", "examples/")); + + RG_DEBUG << "wrote url " << config->readEntry(QString("URL_%1").arg(n)) << endl; + + config->writeEntry("Examples Set", true); + config->writeEntry("Number of Entries", n + 1); + config->sync(); + } + +} + +void RosegardenGUIApp::readOptions() +{ + applyMainWindowSettings(kapp->config(), MainWindowConfigGroup); + + kapp->config()->reparseConfiguration(); + + // Statusbar and toolbars toggling action status + // + m_viewStatusBar ->setChecked(!statusBar() ->isHidden()); + m_viewToolBar ->setChecked(!toolBar() ->isHidden()); + m_viewToolsToolBar ->setChecked(!toolBar("Tools Toolbar") ->isHidden()); + m_viewTracksToolBar ->setChecked(!toolBar("Tracks Toolbar") ->isHidden()); + m_viewEditorsToolBar ->setChecked(!toolBar("Editors Toolbar") ->isHidden()); + m_viewTransportToolBar->setChecked(!toolBar("Transport Toolbar")->isHidden()); + m_viewZoomToolBar ->setChecked(!toolBar("Zoom Toolbar") ->isHidden()); + + bool opt; + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + + opt = kapp->config()->readBoolEntry("Show Transport", true); + m_viewTransport->setChecked(opt); + slotToggleTransport(); + + opt = kapp->config()->readBoolEntry("Expanded Transport", true); + +#ifdef SETTING_LOG_DEBUG + + _settingLog(QString("SETTING 3 : transport flap extended = %1").arg(opt)); +#endif + + if (opt) + getTransport()->slotPanelOpenButtonClicked(); + else + getTransport()->slotPanelCloseButtonClicked(); + + opt = kapp->config()->readBoolEntry("Show Track labels", true); + +#ifdef SETTING_LOG_DEBUG + + _settingLog(QString("SETTING 3 : show track labels = %1").arg(opt)); +#endif + + m_viewTrackLabels->setChecked(opt); + slotToggleTrackLabels(); + + opt = kapp->config()->readBoolEntry("Show Rulers", true); + m_viewRulers->setChecked(opt); + slotToggleRulers(); + + opt = kapp->config()->readBoolEntry("Show Tempo Ruler", true); + m_viewTempoRuler->setChecked(opt); + slotToggleTempoRuler(); + + opt = kapp->config()->readBoolEntry("Show Chord Name Ruler", false); + m_viewChordNameRuler->setChecked(opt); + slotToggleChordNameRuler(); + + opt = kapp->config()->readBoolEntry("Show Previews", true); + m_viewPreviews->setChecked(opt); + slotTogglePreviews(); + + opt = kapp->config()->readBoolEntry("Show Segment Labels", true); + m_viewSegmentLabels->setChecked(opt); + slotToggleSegmentLabels(); + + opt = kapp->config()->readBoolEntry("Show Parameters", true); + if (!opt) { + m_dockLeft->undock(); + m_dockLeft->hide(); + stateChanged("parametersbox_closed", KXMLGUIClient::StateNoReverse); + m_dockVisible = false; + } + + // MIDI Thru routing + opt = kapp->config()->readBoolEntry("MIDI Thru Routing", true); + m_enableMIDIrouting->setChecked(opt); + slotEnableMIDIThruRouting(); + + // initialise the recent file list + // + m_fileRecent->loadEntries(kapp->config()); + + m_actionsSetup = true; + +} + +void RosegardenGUIApp::saveGlobalProperties(KConfig *cfg) +{ + if (m_doc->getTitle() != i18n("Untitled") && !m_doc->isModified()) { + // saving to tempfile not necessary + } else { + QString filename = m_doc->getAbsFilePath(); + cfg->writeEntry("filename", filename); + cfg->writeEntry("modified", m_doc->isModified()); + + QString tempname = kapp->tempSaveName(filename); + QString errMsg; + bool res = m_doc->saveDocument(tempname, errMsg); + if (!res) { + if (errMsg) + KMessageBox::error(this, i18n(QString("Could not save document at %1\nError was : %2") + .arg(tempname).arg(errMsg))); + else + KMessageBox::error(this, i18n(QString("Could not save document at %1") + .arg(tempname))); + } + } +} + +void RosegardenGUIApp::readGlobalProperties(KConfig* _cfg) +{ + QString filename = _cfg->readEntry("filename", ""); + bool modified = _cfg->readBoolEntry("modified", false); + + if (modified) { + bool canRecover; + QString tempname = kapp->checkRecoverFile(filename, canRecover); + + if (canRecover) { + slotEnableTransport(false); + m_doc->openDocument(tempname); + slotEnableTransport(true); + m_doc->slotDocumentModified(); + QFileInfo info(filename); + m_doc->setAbsFilePath(info.absFilePath()); + m_doc->setTitle(info.fileName()); + } + } else { + if (!filename.isEmpty()) { + slotEnableTransport(false); + m_doc->openDocument(filename); + slotEnableTransport(true); + } + } + + QString caption = kapp->caption(); + setCaption(caption + ": " + m_doc->getTitle()); +} + +void RosegardenGUIApp::showEvent(QShowEvent* e) +{ + RG_DEBUG << "RosegardenGUIApp::showEvent()\n"; + + getTransport()->raise(); + KMainWindow::showEvent(e); +} + +bool RosegardenGUIApp::queryClose() +{ + RG_DEBUG << "RosegardenGUIApp::queryClose" << endl; +#ifdef SETTING_LOG_DEBUG + + _settingLog(QString("SETTING 1 : transport flap extended = %1").arg(getTransport()->isExpanded())); + _settingLog(QString("SETTING 1 : show track labels = %1").arg(m_viewTrackLabels->isChecked())); +#endif + + QString errMsg; + + bool canClose = m_doc->saveIfModified(); + + /* + if (canClose && m_transport) { + + // or else the closing of the transport will toggle off the + // 'view transport' action, and its state will be saved as + // 'off' + // + + disconnect(m_transport, SIGNAL(closed()), + this, SLOT(slotCloseTransport())); + } + */ + + return canClose; + +} + +bool RosegardenGUIApp::queryExit() +{ + RG_DEBUG << "RosegardenGUIApp::queryExit" << endl; + if (m_actionsSetup) + slotSaveOptions(); + + return true; +} + +void RosegardenGUIApp::slotFileNewWindow() +{ + KTmpStatusMsg msg(i18n("Opening a new application window..."), this); + + RosegardenGUIApp *new_window = new RosegardenGUIApp(); + new_window->show(); +} + +void RosegardenGUIApp::slotFileNew() +{ + RG_DEBUG << "RosegardenGUIApp::slotFileNew()\n"; + + KTmpStatusMsg msg(i18n("Creating new document..."), this); + + bool makeNew = false; + + if (!m_doc->isModified()) { + makeNew = true; + // m_doc->closeDocument(); + } else if (m_doc->saveIfModified()) { + makeNew = true; + } + + if (makeNew) { + + setDocument(new RosegardenGUIDoc(this, m_pluginManager)); + } +} + +void RosegardenGUIApp::slotOpenDroppedURL(QString url) +{ + ProgressDialog::processEvents(); // or else we get a crash because the + // track editor is erased too soon - it is the originator of the signal + // this slot is connected to. + + if (!m_doc->saveIfModified()) + return ; + + openURL(KURL(url)); +} + +void RosegardenGUIApp::openURL(QString url) +{ + RG_DEBUG << "RosegardenGUIApp::openURL: QString " << url << endl; + openURL(KURL(url)); +} + +void RosegardenGUIApp::openURL(const KURL& url) +{ + SetWaitCursor waitCursor; + + QString netFile = url.prettyURL(); + RG_DEBUG << "RosegardenGUIApp::openURL: KURL " << netFile << endl; + + if (!url.isValid()) { + QString string; + string = i18n( "Malformed URL\n%1").arg(netFile); + + KMessageBox::sorry(this, string); + return ; + } + + QString target, caption(url.path()); + + if (KIO::NetAccess::download(url, target, this) == false) { + KMessageBox::error(this, i18n("Cannot download file %1").arg(url.prettyURL())); + return ; + } + + RG_DEBUG << "RosegardenGUIApp::openURL: target : " << target << endl; + + if (!m_doc->saveIfModified()) + return ; + + openFile(target); + + setCaption(caption); +} + +void RosegardenGUIApp::slotFileOpen() +{ + slotStatusHelpMsg(i18n("Opening file...")); + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + + QString lastOpenedVersion = + kapp->config()->readEntry("Last File Opened Version", "none"); + + if (lastOpenedVersion != VERSION) { + + // We haven't opened any files with this version of the + // program before. Default to the examples directory. + + QString examplesDir = KGlobal::dirs()->findResource("appdata", "examples/"); + kapp->config()->setGroup("Recent Dirs"); + QString recentString = kapp->config()->readEntry("ROSEGARDEN", ""); + kapp->config()->writeEntry + ("ROSEGARDEN", QString("file:%1,%2").arg(examplesDir).arg(recentString)); + } + + KURL url = KFileDialog::getOpenURL + (":ROSEGARDEN", + "audio/x-rosegarden audio/x-midi audio/x-rosegarden21", this, + i18n("Open File")); + if ( url.isEmpty() ) { + return ; + } + + if (m_doc && !m_doc->saveIfModified()) + return ; + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + kapp->config()->writeEntry("Last File Opened Version", VERSION); + + openURL(url); +} + +void RosegardenGUIApp::slotMerge() +{ + KURL url = KFileDialog::getOpenURL + (":ROSEGARDEN", + "audio/x-rosegarden audio/x-midi audio/x-rosegarden21", this, + i18n("Open File")); + if ( url.isEmpty() ) { + return ; + } + + + QString target; + + if (KIO::NetAccess::download(url, target, this) == false) { + KMessageBox::error(this, i18n("Cannot download file %1").arg(url.prettyURL())); + return ; + } + + mergeFile(target); + + KIO::NetAccess::removeTempFile( target ); +} + +void RosegardenGUIApp::slotFileOpenRecent(const KURL &url) +{ + KTmpStatusMsg msg(i18n("Opening file..."), this); + + if (m_doc) { + + if (!m_doc->saveIfModified()) { + return ; + + } + } + + openURL(url); +} + +void RosegardenGUIApp::slotFileSave() +{ + if (!m_doc /*|| !m_doc->isModified()*/) + return ; // ALWAYS save, even if doc is not modified. + + KTmpStatusMsg msg(i18n("Saving file..."), this); + + // if it's a new file (no file path), or an imported file + // (file path doesn't end with .rg), call saveAs + // + if (!m_doc->isRegularDotRGFile()) { + + slotFileSaveAs(); + + } else { + + SetWaitCursor waitCursor; + QString errMsg, docFilePath = m_doc->getAbsFilePath(); + + bool res = m_doc->saveDocument(docFilePath, errMsg); + if (!res) { + if (errMsg) + KMessageBox::error(this, i18n(QString("Could not save document at %1\nError was : %2") + .arg(docFilePath).arg(errMsg))); + else + KMessageBox::error(this, i18n(QString("Could not save document at %1") + .arg(docFilePath))); + } + } +} + +QString +RosegardenGUIApp::getValidWriteFile(QString descriptiveExtension, + QString label) +{ + // extract first extension listed in descriptiveExtension, for instance, + // ".rg" from "*.rg|Rosegarden files", or ".mid" from "*.mid *.midi|MIDI Files" + // + QString extension = descriptiveExtension.left(descriptiveExtension.find('|')).mid(1).section(' ', 0, 0); + + RG_DEBUG << "RosegardenGUIApp::getValidWriteFile() : extension = " << extension << endl; + + // It's too bad there isn't this functionality within + // KFileDialog::getSaveFileName + KFileDialog saveFileDialog(":ROSEGARDEN", descriptiveExtension, this, label, true); + saveFileDialog.setOperationMode(KFileDialog::Saving); + if (m_doc) { + QString saveFileName = m_doc->getAbsFilePath(); + // Show filename without the old extension + int dotLoc = saveFileName.findRev('.'); + if (dotLoc >= int(saveFileName.length() - 4)) { + saveFileName = saveFileName.left(dotLoc); + } + saveFileDialog.setSelection(saveFileName); + } + saveFileDialog.exec(); + QString name = saveFileDialog.selectedFile(); + + // RG_DEBUG << "RosegardenGUIApp::getValidWriteFile() : KFileDialog::getSaveFileName returned " + // << name << endl; + + + if (name.isEmpty()) + return name; + + // Append extension if we don't have one + // + if (!extension.isEmpty()) { + static QRegExp rgFile("\\..{1,4}$"); + if (rgFile.match(name) == -1) { + name += extension; + } + } + + KURL *u = new KURL(name); + + if (!u->isValid()) { + KMessageBox::sorry(this, i18n("This is not a valid filename.\n")); + return ""; + } + + if (!u->isLocalFile()) { + KMessageBox::sorry(this, i18n("This is not a local file.\n")); + return ""; + } + + QFileInfo info(name); + + if (info.isDir()) { + KMessageBox::sorry(this, i18n("You have specified a directory")); + return ""; + } + + if (info.exists()) { + int overwrite = KMessageBox::questionYesNo + (this, i18n("The specified file exists. Overwrite?")); + + if (overwrite != KMessageBox::Yes) + return ""; + } + + return name; +} + +bool RosegardenGUIApp::slotFileSaveAs() +{ + if (!m_doc) + return false; + + KTmpStatusMsg msg(i18n("Saving file with a new filename..."), this); + + QString newName = getValidWriteFile("*.rg|" + i18n("Rosegarden files") + + "\n*|" + i18n("All files"), + i18n("Save as...")); + if (newName.isEmpty()) + return false; + + SetWaitCursor waitCursor; + QFileInfo saveAsInfo(newName); + m_doc->setTitle(saveAsInfo.fileName()); + m_doc->setAbsFilePath(saveAsInfo.absFilePath()); + QString errMsg; + bool res = m_doc->saveDocument(newName, errMsg); + if (!res) { + if (errMsg) + KMessageBox::error(this, i18n(QString("Could not save document at %1\nError was : %2") + .arg(newName).arg(errMsg))); + else + KMessageBox::error(this, i18n(QString("Could not save document at %1") + .arg(newName))); + + } else { + + m_fileRecent->addURL(newName); + + QString caption = kapp->caption(); + setCaption(caption + ": " + m_doc->getTitle()); + // update the edit view's captions too + emit compositionStateUpdate(); + } + + return res; +} + +void RosegardenGUIApp::slotFileClose() +{ + RG_DEBUG << "RosegardenGUIApp::slotFileClose()" << endl; + + if (!m_doc) + return ; + + KTmpStatusMsg msg(i18n("Closing file..."), this); + + if (m_doc->saveIfModified()) { + setDocument(new RosegardenGUIDoc(this, m_pluginManager)); + } + + // Don't close the whole view (i.e. Quit), just close the doc. + // close(); +} + +void RosegardenGUIApp::slotFilePrint() +{ + if (m_doc->getComposition().getNbSegments() == 0) { + KMessageBox::sorry(0, "Please create some tracks first (until we implement menu state management)"); + return ; + } + + KTmpStatusMsg msg(i18n("Printing..."), this); + + m_view->print(&m_doc->getComposition()); +} + +void RosegardenGUIApp::slotFilePrintPreview() +{ + if (m_doc->getComposition().getNbSegments() == 0) { + KMessageBox::sorry(0, "Please create some tracks first (until we implement menu state management)"); + return ; + } + + KTmpStatusMsg msg(i18n("Previewing..."), this); + + m_view->print(&m_doc->getComposition(), true); +} + +void RosegardenGUIApp::slotQuit() +{ + slotStatusMsg(i18n("Exiting...")); + + Profiles::getInstance()->dump(); + + // close the first window, the list makes the next one the first again. + // This ensures that queryClose() is called on each window to ask for closing + KMainWindow* w; + if (memberList) { + + for (w = memberList->first(); w != 0; w = memberList->next()) { + // only close the window if the closeEvent is accepted. If + // the user presses Cancel on the saveIfModified() dialog, + // the window and the application stay open. + if (!w->close()) + break; + } + } +} + +void RosegardenGUIApp::slotEditCut() +{ + if (!m_view->haveSelection()) + return ; + KTmpStatusMsg msg(i18n("Cutting selection..."), this); + + SegmentSelection selection(m_view->getSelection()); + m_doc->getCommandHistory()->addCommand + (new CutCommand(selection, m_clipboard)); +} + +void RosegardenGUIApp::slotEditCopy() +{ + if (!m_view->haveSelection()) + return ; + KTmpStatusMsg msg(i18n("Copying selection to clipboard..."), this); + + SegmentSelection selection(m_view->getSelection()); + m_doc->getCommandHistory()->addCommand + (new CopyCommand(selection, m_clipboard)); +} + +void RosegardenGUIApp::slotEditPaste() +{ + if (m_clipboard->isEmpty()) { + KTmpStatusMsg msg(i18n("Clipboard is empty"), this); + return ; + } + KTmpStatusMsg msg(i18n("Inserting clipboard contents..."), this); + + // for now, but we could paste at the time of the first copied + // segment and then do ghosting drag or something + timeT insertionTime = m_doc->getComposition().getPosition(); + m_doc->getCommandHistory()->addCommand + (new PasteSegmentsCommand(&m_doc->getComposition(), + m_clipboard, insertionTime, + m_doc->getComposition().getSelectedTrack(), + false)); + + // User preference? Update song pointer position on paste + m_doc->slotSetPointerPosition(m_doc->getComposition().getPosition()); +} + +void RosegardenGUIApp::slotCutRange() +{ + timeT t0 = m_doc->getComposition().getLoopStart(); + timeT t1 = m_doc->getComposition().getLoopEnd(); + + if (t0 == t1) + return ; + + m_doc->getCommandHistory()->addCommand + (new CutRangeCommand(&m_doc->getComposition(), t0, t1, m_clipboard)); +} + +void RosegardenGUIApp::slotCopyRange() +{ + timeT t0 = m_doc->getComposition().getLoopStart(); + timeT t1 = m_doc->getComposition().getLoopEnd(); + + if (t0 == t1) + return ; + + m_doc->getCommandHistory()->addCommand + (new CopyCommand(&m_doc->getComposition(), t0, t1, m_clipboard)); +} + +void RosegardenGUIApp::slotPasteRange() +{ + if (m_clipboard->isEmpty()) + return ; + + m_doc->getCommandHistory()->addCommand + (new PasteRangeCommand(&m_doc->getComposition(), m_clipboard, + m_doc->getComposition().getPosition())); + + m_doc->setLoop(0, 0); +} + +void RosegardenGUIApp::slotDeleteRange() +{ + timeT t0 = m_doc->getComposition().getLoopStart(); + timeT t1 = m_doc->getComposition().getLoopEnd(); + + if (t0 == t1) + return ; + + m_doc->getCommandHistory()->addCommand + (new DeleteRangeCommand(&m_doc->getComposition(), t0, t1)); + + m_doc->setLoop(0, 0); +} + +void RosegardenGUIApp::slotInsertRange() +{ + timeT t0 = m_doc->getComposition().getPosition(); + std::pair r = m_doc->getComposition().getBarRangeForTime(t0); + TimeDialog dialog(m_view, i18n("Duration of empty range to insert"), + &m_doc->getComposition(), t0, r.second - r.first, false); + if (dialog.exec() == QDialog::Accepted) { + m_doc->getCommandHistory()->addCommand + (new InsertRangeCommand(&m_doc->getComposition(), t0, dialog.getTime())); + m_doc->setLoop(0, 0); + } +} + +void RosegardenGUIApp::slotSelectAll() +{ + m_view->slotSelectAllSegments(); +} + +void RosegardenGUIApp::slotDeleteSelectedSegments() +{ + m_view->getTrackEditor()->slotDeleteSelectedSegments(); +} + +void RosegardenGUIApp::slotQuantizeSelection() +{ + if (!m_view->haveSelection()) + return ; + + //!!! this should all be in rosegardenguiview + + QuantizeDialog dialog(m_view); + if (dialog.exec() != QDialog::Accepted) + return ; + + SegmentSelection selection = m_view->getSelection(); + + KMacroCommand *command = new KMacroCommand + (EventQuantizeCommand::getGlobalName()); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + command->addCommand(new EventQuantizeCommand + (**i, (*i)->getStartTime(), (*i)->getEndTime(), + dialog.getQuantizer())); + } + + m_view->slotAddCommandToHistory(command); +} + +void RosegardenGUIApp::slotRepeatQuantizeSelection() +{ + if (!m_view->haveSelection()) + return ; + + //!!! this should all be in rosegardenguiview + + SegmentSelection selection = m_view->getSelection(); + + KMacroCommand *command = new KMacroCommand + (EventQuantizeCommand::getGlobalName()); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + command->addCommand(new EventQuantizeCommand + (**i, (*i)->getStartTime(), (*i)->getEndTime(), + "Quantize Dialog Grid", false)); // no i18n (config group name) + } + + m_view->slotAddCommandToHistory(command); +} + +void RosegardenGUIApp::slotGrooveQuantize() +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + + if (selection.size() != 1) { + KMessageBox::sorry(this, i18n("This function needs no more than one segment to be selected.")); + return ; + } + + Segment *s = *selection.begin(); + m_view->slotAddCommandToHistory(new CreateTempoMapFromSegmentCommand(s)); +} + +void RosegardenGUIApp::slotJoinSegments() +{ + if (!m_view->haveSelection()) + return ; + + //!!! this should all be in rosegardenguiview + //!!! should it? + + SegmentSelection selection = m_view->getSelection(); + if (selection.size() == 0) + return ; + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + if ((*i)->getType() != Segment::Internal) { + KMessageBox::sorry(this, i18n("Can't join Audio segments")); + return ; + } + } + + m_view->slotAddCommandToHistory(new SegmentJoinCommand(selection)); + m_view->updateSelectionContents(); +} + +void RosegardenGUIApp::slotRescaleSelection() +{ + if (!m_view->haveSelection()) + return ; + + //!!! this should all be in rosegardenguiview + //!!! should it? + + SegmentSelection selection = m_view->getSelection(); + + timeT startTime = 0, endTime = 0; + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + if ((i == selection.begin()) || ((*i)->getStartTime() < startTime)) { + startTime = (*i)->getStartTime(); + } + if ((i == selection.begin()) || ((*i)->getEndMarkerTime() > endTime)) { + endTime = (*i)->getEndMarkerTime(); + } + } + + RescaleDialog dialog(m_view, &m_doc->getComposition(), + startTime, endTime - startTime, + false, false); + if (dialog.exec() != QDialog::Accepted) + return ; + + std::vector asrcs; + + int mult = dialog.getNewDuration(); + int div = endTime - startTime; + float ratio = float(mult) / float(div); + + std::cerr << "slotRescaleSelection: mult = " << mult << ", div = " << div << ", ratio = " << ratio << std::endl; + + KMacroCommand *command = new KMacroCommand + (SegmentRescaleCommand::getGlobalName()); + + bool pathTested = false; + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + if ((*i)->getType() == Segment::Audio) { + if (!pathTested) { + testAudioPath(i18n("rescaling an audio file")); + pathTested = true; + } + AudioSegmentRescaleCommand *asrc = new AudioSegmentRescaleCommand + (m_doc, *i, ratio); + command->addCommand(asrc); + asrcs.push_back(asrc); + } else { + command->addCommand(new SegmentRescaleCommand(*i, mult, div)); + } + } + + ProgressDialog *progressDlg = 0; + + if (!asrcs.empty()) { + progressDlg = new ProgressDialog + (i18n("Rescaling audio file..."), 100, this); + progressDlg->setAutoClose(false); + progressDlg->setAutoReset(false); + progressDlg->show(); + for (size_t i = 0; i < asrcs.size(); ++i) { + asrcs[i]->connectProgressDialog(progressDlg); + } + } + + m_view->slotAddCommandToHistory(command); + + if (!asrcs.empty()) { + + progressDlg->setLabel(i18n("Generating audio preview...")); + + for (size_t i = 0; i < asrcs.size(); ++i) { + asrcs[i]->disconnectProgressDialog(progressDlg); + } + + connect(&m_doc->getAudioFileManager(), SIGNAL(setProgress(int)), + progressDlg->progressBar(), SLOT(setValue(int))); + connect(progressDlg, SIGNAL(cancelClicked()), + &m_doc->getAudioFileManager(), SLOT(slotStopPreview())); + + for (size_t i = 0; i < asrcs.size(); ++i) { + int fid = asrcs[i]->getNewAudioFileId(); + if (fid >= 0) { + slotAddAudioFile(fid); + m_doc->getAudioFileManager().generatePreview(fid); + } + } + } + + if (progressDlg) delete progressDlg; +} + +bool +RosegardenGUIApp::testAudioPath(QString op) +{ + try { + m_doc->getAudioFileManager().testAudioPath(); + } catch (AudioFileManager::BadAudioPathException) { + if (KMessageBox::warningContinueCancel + (this, + 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 %1.\nWould you like to set it now?").arg(op), + i18n("Warning"), + i18n("Set audio file path")) == KMessageBox::Continue) { + slotOpenAudioPathSettings(); + } + return false; + } + return true; +} + +void RosegardenGUIApp::slotAutoSplitSelection() +{ + if (!m_view->haveSelection()) + return ; + + //!!! this should all be in rosegardenguiview + //!!! or should it? + + SegmentSelection selection = m_view->getSelection(); + + KMacroCommand *command = new KMacroCommand + (SegmentAutoSplitCommand::getGlobalName()); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + if ((*i)->getType() == Segment::Audio) { + AudioSplitDialog aSD(this, (*i), m_doc); + + if (aSD.exec() == QDialog::Accepted) { + // split to threshold + // + command->addCommand( + new AudioSegmentAutoSplitCommand(m_doc, + *i, + aSD.getThreshold())); + // dmm - verifying that widget->value() accessors *can* work without crashing + // std::cout << "SILVAN: getThreshold() = " << aSD.getThreshold() << std::endl; + } + } else { + command->addCommand(new SegmentAutoSplitCommand(*i)); + } + } + + m_view->slotAddCommandToHistory(command); +} + +void RosegardenGUIApp::slotJogLeft() +{ + RG_DEBUG << "RosegardenGUIApp::slotJogLeft" << endl; + jogSelection( -Note(Note::Demisemiquaver).getDuration()); +} + +void RosegardenGUIApp::slotJogRight() +{ + RG_DEBUG << "RosegardenGUIApp::slotJogRight" << endl; + jogSelection(Note(Note::Demisemiquaver).getDuration()); +} + +void RosegardenGUIApp::jogSelection(timeT amount) +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + + SegmentReconfigureCommand *command = new SegmentReconfigureCommand(i18n("Jog Selection")); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + command->addSegment((*i), + (*i)->getStartTime() + amount, + (*i)->getEndMarkerTime() + amount, + (*i)->getTrack()); + } + + m_view->slotAddCommandToHistory(command); +} + +void RosegardenGUIApp::createAndSetupTransport() +{ + // create the Transport GUI and add the callbacks to the + // buttons and keyboard accelerators + // + m_transport = + new TransportDialog(this); + plugAccelerators(m_transport, m_transport->getAccelerators()); + + m_transport->getAccelerators()->connectItem + (m_transport->getAccelerators()->insertItem(Key_T), + this, + SLOT(slotHideTransport())); + + // Ensure that the checkbox is unchecked if the dialog + // is closed + connect(m_transport, SIGNAL(closed()), + SLOT(slotCloseTransport())); + + // Handle loop setting and unsetting from the transport loop button + // + + connect(m_transport, SIGNAL(setLoop()), SLOT(slotSetLoop())); + connect(m_transport, SIGNAL(unsetLoop()), SLOT(slotUnsetLoop())); + connect(m_transport, SIGNAL(panic()), SLOT(slotPanic())); + + connect(m_transport, SIGNAL(editTempo(QWidget*)), + SLOT(slotEditTempo(QWidget*))); + + connect(m_transport, SIGNAL(editTimeSignature(QWidget*)), + SLOT(slotEditTimeSignature(QWidget*))); + + connect(m_transport, SIGNAL(editTransportTime(QWidget*)), + SLOT(slotEditTransportTime(QWidget*))); + + // Handle set loop start/stop time buttons. + // + connect(m_transport, SIGNAL(setLoopStartTime()), SLOT(slotSetLoopStart())); + connect(m_transport, SIGNAL(setLoopStopTime()), SLOT(slotSetLoopStop())); + + if (m_seqManager != 0) + m_seqManager->setTransport(m_transport); + +} + +void RosegardenGUIApp::slotSplitSelectionByPitch() +{ + if (!m_view->haveSelection()) + return ; + + SplitByPitchDialog dialog(m_view); + if (dialog.exec() != QDialog::Accepted) + return ; + + SegmentSelection selection = m_view->getSelection(); + + KMacroCommand *command = new KMacroCommand + (SegmentSplitByPitchCommand::getGlobalName()); + + bool haveSomething = false; + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + if ((*i)->getType() == Segment::Audio) { + // nothing + } else { + command->addCommand + (new SegmentSplitByPitchCommand + (*i, + dialog.getPitch(), + dialog.getShouldRange(), + dialog.getShouldDuplicateNonNoteEvents(), + (SegmentSplitByPitchCommand::ClefHandling) + dialog.getClefHandling())); + haveSomething = true; + } + } + + if (haveSomething) + m_view->slotAddCommandToHistory(command); + //!!! else complain +} + +void +RosegardenGUIApp::slotSplitSelectionByRecordedSrc() +{ + if (!m_view->haveSelection()) + return ; + + SplitByRecordingSrcDialog dialog(m_view, m_doc); + if (dialog.exec() != QDialog::Accepted) + return ; + + SegmentSelection selection = m_view->getSelection(); + + KMacroCommand *command = new KMacroCommand + (SegmentSplitByRecordingSrcCommand::getGlobalName()); + + bool haveSomething = false; + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + if ((*i)->getType() == Segment::Audio) { + // nothing + } else { + command->addCommand + (new SegmentSplitByRecordingSrcCommand(*i, + dialog.getChannel(), + dialog.getDevice())); + haveSomething = true; + } + } + if (haveSomething) + m_view->slotAddCommandToHistory(command); +} + +void +RosegardenGUIApp::slotSplitSelectionAtTime() +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + if (selection.empty()) + return ; + + timeT now = m_doc->getComposition().getPosition(); + + QString title = i18n("Split Segment at Time", + "Split %n Segments at Time", + selection.size()); + + TimeDialog dialog(m_view, title, + &m_doc->getComposition(), + now, true); + + KMacroCommand *command = new KMacroCommand( title ); + + if (dialog.exec() == QDialog::Accepted) { + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + if ((*i)->getType() == Segment::Audio) { + command->addCommand(new AudioSegmentSplitCommand(*i, dialog.getTime())); + } else { + command->addCommand(new SegmentSplitCommand(*i, dialog.getTime())); + } + } + m_view->slotAddCommandToHistory(command); + } +} + +void +RosegardenGUIApp::slotSetSegmentStartTimes() +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + if (selection.empty()) + return ; + + timeT someTime = (*selection.begin())->getStartTime(); + + TimeDialog dialog(m_view, i18n("Segment Start Time"), + &m_doc->getComposition(), + someTime, false); + + if (dialog.exec() == QDialog::Accepted) { + + bool plural = (selection.size() > 1); + + SegmentReconfigureCommand *command = + new SegmentReconfigureCommand(plural ? + i18n("Set Segment Start Times") : + i18n("Set Segment Start Time")); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + command->addSegment + (*i, dialog.getTime(), + (*i)->getEndMarkerTime() - (*i)->getStartTime() + dialog.getTime(), + (*i)->getTrack()); + } + + m_view->slotAddCommandToHistory(command); + } +} + +void +RosegardenGUIApp::slotSetSegmentDurations() +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + if (selection.empty()) + return ; + + timeT someTime = + (*selection.begin())->getStartTime(); + + timeT someDuration = + (*selection.begin())->getEndMarkerTime() - + (*selection.begin())->getStartTime(); + + TimeDialog dialog(m_view, i18n("Segment Duration"), + &m_doc->getComposition(), + someTime, + someDuration, + false); + + if (dialog.exec() == QDialog::Accepted) { + + bool plural = (selection.size() > 1); + + SegmentReconfigureCommand *command = + new SegmentReconfigureCommand(plural ? + i18n("Set Segment Durations") : + i18n("Set Segment Duration")); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + command->addSegment + (*i, (*i)->getStartTime(), + (*i)->getStartTime() + dialog.getTime(), + (*i)->getTrack()); + } + + m_view->slotAddCommandToHistory(command); + } +} + +void RosegardenGUIApp::slotHarmonizeSelection() +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + //!!! This should be somewhere else too + + CompositionTimeSliceAdapter adapter(&m_doc->getComposition(), + &selection); + + AnalysisHelper helper; + Segment *segment = new Segment; + helper.guessHarmonies(adapter, *segment); + + //!!! do nothing with the results yet + delete segment; +} + +void RosegardenGUIApp::slotTempoToSegmentLength() +{ + slotTempoToSegmentLength(this); +} + +void RosegardenGUIApp::slotTempoToSegmentLength(QWidget* parent) +{ + RG_DEBUG << "RosegardenGUIApp::slotTempoToSegmentLength" << endl; + + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + + // Only set for a single selection + // + if (selection.size() == 1 && + (*selection.begin())->getType() == Segment::Audio) { + Composition &comp = m_doc->getComposition(); + Segment *seg = *selection.begin(); + + TimeSignature timeSig = + comp.getTimeSignatureAt( seg->getStartTime()); + + timeT endTime = seg->getEndTime(); + + if (seg->getRawEndMarkerTime()) + endTime = seg->getEndMarkerTime(); + + RealTime segDuration = + seg->getAudioEndTime() - seg->getAudioStartTime(); + + int beats = 0; + + // Get user to tell us how many beats or bars the segment contains + BeatsBarsDialog dialog(parent); + if (dialog.exec() == QDialog::Accepted) { + beats = dialog.getQuantity(); // beats (or bars) + if (dialog.getMode() == 1) // bars (multiply by time sig) + beats *= timeSig.getBeatsPerBar(); +#ifdef DEBUG_TEMPO_FROM_AUDIO + + RG_DEBUG << "RosegardenGUIApp::slotTempoToSegmentLength - beats = " << beats + << " mode = " << ((dialog.getMode() == 0) ? "bars" : "beats") << endl + << " beats per bar = " << timeSig.getBeatsPerBar() + << " user quantity = " << dialog.getQuantity() + << " user mode = " << dialog.getMode() << endl; +#endif + + } else { + RG_DEBUG << "RosegardenGUIApp::slotTempoToSegmentLength - BeatsBarsDialog aborted" + << endl; + return ; + } + + double beatLengthUsec = + double(segDuration.sec * 1000000 + segDuration.usec()) / + double(beats); + + // New tempo is a minute divided by time of beat + // converted up (#1414252) to a sane value via getTempoFoQpm() + // + tempoT newTempo = + comp.getTempoForQpm(60.0 * 1000000.0 / beatLengthUsec); + +#ifdef DEBUG_TEMPO_FROM_AUDIO + + RG_DEBUG << "RosegardenGUIApp::slotTempoToSegmentLength info: " << endl + << " beatLengthUsec = " << beatLengthUsec << endl + << " segDuration.usec = " << segDuration.usec() << endl + << " newTempo = " << newTempo << endl; +#endif + + KMacroCommand *macro = new KMacroCommand(i18n("Set Global Tempo")); + + // Remove all tempo changes in reverse order so as the index numbers + // don't becoming meaningless as the command gets unwound. + // + for (int i = 0; i < comp.getTempoChangeCount(); i++) + macro->addCommand(new RemoveTempoChangeCommand(&comp, + (comp.getTempoChangeCount() - 1 - i))); + + // add tempo change at time zero + // + macro->addCommand(new AddTempoChangeCommand(&comp, 0, newTempo)); + + // execute + m_doc->getCommandHistory()->addCommand(macro); + } +} + +void RosegardenGUIApp::slotToggleSegmentLabels() +{ + KToggleAction* act = dynamic_cast(actionCollection()->action("show_segment_labels")); + if (act) { + m_view->slotShowSegmentLabels(act->isChecked()); + } +} + +void RosegardenGUIApp::slotEdit() +{ + m_view->slotEditSegment(0); +} + +void RosegardenGUIApp::slotEditAsNotation() +{ + m_view->slotEditSegmentNotation(0); +} + +void RosegardenGUIApp::slotEditInMatrix() +{ + m_view->slotEditSegmentMatrix(0); +} + +void RosegardenGUIApp::slotEditInPercussionMatrix() +{ + m_view->slotEditSegmentPercussionMatrix(0); +} + +void RosegardenGUIApp::slotEditInEventList() +{ + m_view->slotEditSegmentEventList(0); +} + +void RosegardenGUIApp::slotEditTempos() +{ + slotEditTempos(m_doc->getComposition().getPosition()); +} + +void RosegardenGUIApp::slotToggleToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the toolbar..."), this); + + if (m_viewToolBar->isChecked()) + toolBar("mainToolBar")->show(); + else + toolBar("mainToolBar")->hide(); +} + +void RosegardenGUIApp::slotToggleToolsToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the tools toolbar..."), this); + + if (m_viewToolsToolBar->isChecked()) + toolBar("Tools Toolbar")->show(); + else + toolBar("Tools Toolbar")->hide(); +} + +void RosegardenGUIApp::slotToggleTracksToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the tracks toolbar..."), this); + + if (m_viewTracksToolBar->isChecked()) + toolBar("Tracks Toolbar")->show(); + else + toolBar("Tracks Toolbar")->hide(); +} + +void RosegardenGUIApp::slotToggleEditorsToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the editor toolbar..."), this); + + if (m_viewEditorsToolBar->isChecked()) + toolBar("Editors Toolbar")->show(); + else + toolBar("Editors Toolbar")->hide(); +} + +void RosegardenGUIApp::slotToggleTransportToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the transport toolbar..."), this); + + if (m_viewTransportToolBar->isChecked()) + toolBar("Transport Toolbar")->show(); + else + toolBar("Transport Toolbar")->hide(); +} + +void RosegardenGUIApp::slotToggleZoomToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the zoom toolbar..."), this); + + if (m_viewZoomToolBar->isChecked()) + toolBar("Zoom Toolbar")->show(); + else + toolBar("Zoom Toolbar")->hide(); +} + +void RosegardenGUIApp::slotToggleTransport() +{ + KTmpStatusMsg msg(i18n("Toggle the Transport"), this); + + if (m_viewTransport->isChecked()) { + getTransport()->show(); + getTransport()->raise(); + getTransport()->blockSignals(false); + } else { + getTransport()->hide(); + getTransport()->blockSignals(true); + } +} + +void RosegardenGUIApp::slotHideTransport() +{ + if (m_viewTransport->isChecked()) { + m_viewTransport->blockSignals(true); + m_viewTransport->setChecked(false); + m_viewTransport->blockSignals(false); + } + getTransport()->hide(); + getTransport()->blockSignals(true); +} + +void RosegardenGUIApp::slotToggleTrackLabels() +{ + if (m_viewTrackLabels->isChecked()) { +#ifdef SETTING_LOG_DEBUG + _settingLog("toggle track labels on"); +#endif + + m_view->getTrackEditor()->getTrackButtons()-> + changeTrackInstrumentLabels(TrackLabel::ShowTrack); + } else { +#ifdef SETTING_LOG_DEBUG + _settingLog("toggle track labels off"); +#endif + + m_view->getTrackEditor()->getTrackButtons()-> + changeTrackInstrumentLabels(TrackLabel::ShowInstrument); + } +} + +void RosegardenGUIApp::slotToggleRulers() +{ + m_view->slotShowRulers(m_viewRulers->isChecked()); +} + +void RosegardenGUIApp::slotToggleTempoRuler() +{ + m_view->slotShowTempoRuler(m_viewTempoRuler->isChecked()); +} + +void RosegardenGUIApp::slotToggleChordNameRuler() +{ + m_view->slotShowChordNameRuler(m_viewChordNameRuler->isChecked()); +} + +void RosegardenGUIApp::slotTogglePreviews() +{ + m_view->slotShowPreviews(m_viewPreviews->isChecked()); +} + +void RosegardenGUIApp::slotDockParametersBack() +{ + m_dockLeft->dockBack(); +} + +void RosegardenGUIApp::slotParametersClosed() +{ + stateChanged("parametersbox_closed"); + m_dockVisible = false; +} + +void RosegardenGUIApp::slotParametersDockedBack(KDockWidget* dw, KDockWidget::DockPosition) +{ + if (dw == m_dockLeft) { + stateChanged("parametersbox_closed", KXMLGUIClient::StateReverse); + m_dockVisible = true; + } +} + +void RosegardenGUIApp::slotToggleStatusBar() +{ + KTmpStatusMsg msg(i18n("Toggle the statusbar..."), this); + + if (!m_viewStatusBar->isChecked()) + statusBar()->hide(); + else + statusBar()->show(); +} + +void RosegardenGUIApp::slotStatusMsg(QString text) +{ + /////////////////////////////////////////////////////////////////// + // change status message permanently + statusBar()->clear(); + statusBar()->changeItem(text, EditViewBase::ID_STATUS_MSG); +} + +void RosegardenGUIApp::slotStatusHelpMsg(QString text) +{ + /////////////////////////////////////////////////////////////////// + // change status message of whole statusbar temporary (text, msec) + statusBar()->message(text, 2000); +} + +void RosegardenGUIApp::slotEnableTransport(bool enable) +{ + if (m_transport) + getTransport()->setEnabled(enable); +} + +void RosegardenGUIApp::slotPointerSelected() +{ + m_view->selectTool(SegmentSelector::ToolName); +} + +void RosegardenGUIApp::slotEraseSelected() +{ + m_view->selectTool(SegmentEraser::ToolName); +} + +void RosegardenGUIApp::slotDrawSelected() +{ + m_view->selectTool(SegmentPencil::ToolName); +} + +void RosegardenGUIApp::slotMoveSelected() +{ + m_view->selectTool(SegmentMover::ToolName); +} + +void RosegardenGUIApp::slotResizeSelected() +{ + m_view->selectTool(SegmentResizer::ToolName); +} + +void RosegardenGUIApp::slotJoinSelected() +{ + KMessageBox::information(this, + i18n("The join tool isn't implemented yet. Instead please highlight " + "the segments you want to join and then use the menu option:\n\n" + " Segments->Collapse Segments.\n"), + i18n("Join tool not yet implemented")); + + m_view->selectTool(SegmentJoiner::ToolName); +} + +void RosegardenGUIApp::slotSplitSelected() +{ + m_view->selectTool(SegmentSplitter::ToolName); +} + +void RosegardenGUIApp::slotAddTrack() +{ + if (!m_view) + return ; + + // default to the base number - might not actually exist though + // + InstrumentId id = MidiInstrumentBase; + + // Get the first Internal/MIDI instrument + // + DeviceList *devices = m_doc->getStudio().getDevices(); + bool have = false; + + for (DeviceList::iterator it = devices->begin(); + it != devices->end() && !have; it++) { + + if ((*it)->getType() != Device::Midi) + continue; + + InstrumentList instruments = (*it)->getAllInstruments(); + for (InstrumentList::iterator iit = instruments.begin(); + iit != instruments.end(); iit++) { + + if ((*iit)->getId() >= MidiInstrumentBase) { + id = (*iit)->getId(); + have = true; + break; + } + } + } + + Composition &comp = m_doc->getComposition(); + TrackId trackId = comp.getSelectedTrack(); + Track *track = comp.getTrackById(trackId); + + int pos = -1; + if (track) pos = track->getPosition() + 1; + + m_view->slotAddTracks(1, id, pos); +} + +void RosegardenGUIApp::slotAddTracks() +{ + if (!m_view) + return ; + + // default to the base number - might not actually exist though + // + InstrumentId id = MidiInstrumentBase; + + // Get the first Internal/MIDI instrument + // + DeviceList *devices = m_doc->getStudio().getDevices(); + bool have = false; + + for (DeviceList::iterator it = devices->begin(); + it != devices->end() && !have; it++) { + + if ((*it)->getType() != Device::Midi) + continue; + + InstrumentList instruments = (*it)->getAllInstruments(); + for (InstrumentList::iterator iit = instruments.begin(); + iit != instruments.end(); iit++) { + + if ((*iit)->getId() >= MidiInstrumentBase) { + id = (*iit)->getId(); + have = true; + break; + } + } + } + + Composition &comp = m_doc->getComposition(); + TrackId trackId = comp.getSelectedTrack(); + Track *track = comp.getTrackById(trackId); + + int pos = 0; + if (track) pos = track->getPosition(); + + bool ok = false; + + AddTracksDialog dialog(this, pos); + + if (dialog.exec() == QDialog::Accepted) { + m_view->slotAddTracks(dialog.getTracks(), id, + dialog.getInsertPosition()); + } +} + +void RosegardenGUIApp::slotDeleteTrack() +{ + if (!m_view) + return ; + + Composition &comp = m_doc->getComposition(); + TrackId trackId = comp.getSelectedTrack(); + Track *track = comp.getTrackById(trackId); + + RG_DEBUG << "RosegardenGUIApp::slotDeleteTrack() : about to delete track id " + << trackId << endl; + + if (track == 0) + return ; + + // Always have at least one track in a composition + // + if (comp.getNbTracks() == 1) + return ; + + // VLADA + if (m_view->haveSelection()) { + + SegmentSelection selection = m_view->getSelection(); + m_view->slotSelectTrackSegments(trackId); + m_view->getTrackEditor()->slotDeleteSelectedSegments(); + m_view->slotPropagateSegmentSelection(selection); + + } else { + + m_view->slotSelectTrackSegments(trackId); + m_view->getTrackEditor()->slotDeleteSelectedSegments(); + } + //VLADA + + int position = track->getPosition(); + + // Delete the track + // + std::vector tracks; + tracks.push_back(trackId); + + m_view->slotDeleteTracks(tracks); + + // Select a new valid track + // + if (comp.getTrackByPosition(position)) + trackId = comp.getTrackByPosition(position)->getId(); + else if (comp.getTrackByPosition(position - 1)) + trackId = comp.getTrackByPosition(position - 1)->getId(); + else { + RG_DEBUG << "RosegardenGUIApp::slotDeleteTrack - " + << "can't select a highlighted track after delete" + << endl; + } + + comp.setSelectedTrack(trackId); + + Instrument *inst = m_doc->getStudio(). + getInstrumentById(comp.getTrackById(trackId)->getInstrument()); + + //VLADA + // m_view->slotSelectTrackSegments(trackId); + //VLADA +} + +void RosegardenGUIApp::slotMoveTrackDown() +{ + RG_DEBUG << "RosegardenGUIApp::slotMoveTrackDown" << endl; + + Composition &comp = m_doc->getComposition(); + Track *srcTrack = comp.getTrackById(comp.getSelectedTrack()); + + // Check for track object + // + if (srcTrack == 0) + return ; + + // Check destination track exists + // + Track *destTrack = + comp.getTrackByPosition(srcTrack->getPosition() + 1); + + if (destTrack == 0) + return ; + + MoveTracksCommand *command = + new MoveTracksCommand(&comp, srcTrack->getId(), destTrack->getId()); + + m_doc->getCommandHistory()->addCommand(command); + + // make sure we're showing the right selection + m_view->slotSelectTrackSegments(comp.getSelectedTrack()); + +} + +void RosegardenGUIApp::slotMoveTrackUp() +{ + RG_DEBUG << "RosegardenGUIApp::slotMoveTrackUp" << endl; + + Composition &comp = m_doc->getComposition(); + Track *srcTrack = comp.getTrackById(comp.getSelectedTrack()); + + // Check for track object + // + if (srcTrack == 0) + return ; + + // Check we're not at the top already + // + if (srcTrack->getPosition() == 0) + return ; + + // Check destination track exists + // + Track *destTrack = + comp.getTrackByPosition(srcTrack->getPosition() - 1); + + if (destTrack == 0) + return ; + + MoveTracksCommand *command = + new MoveTracksCommand(&comp, srcTrack->getId(), destTrack->getId()); + + m_doc->getCommandHistory()->addCommand(command); + + // make sure we're showing the right selection + m_view->slotSelectTrackSegments(comp.getSelectedTrack()); +} + +void RosegardenGUIApp::slotRevertToSaved() +{ + RG_DEBUG << "RosegardenGUIApp::slotRevertToSaved" << endl; + + if (m_doc->isModified()) { + int revert = + KMessageBox::questionYesNo(this, + i18n("Revert modified document to previous saved version?")); + + if (revert == KMessageBox::No) + return ; + + openFile(m_doc->getAbsFilePath()); + } +} + +void RosegardenGUIApp::slotImportProject() +{ + if (m_doc && !m_doc->saveIfModified()) + return ; + + KURL url = KFileDialog::getOpenURL + (":RGPROJECT", + i18n("*.rgp|Rosegarden Project files\n*|All files"), this, + i18n("Import Rosegarden Project File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + + importProject(tmpfile); + + KIO::NetAccess::removeTempFile(tmpfile); +} + +void RosegardenGUIApp::importProject(QString filePath) +{ + KProcess *proc = new KProcess; + *proc << "rosegarden-project-package"; + *proc << "--unpack"; + *proc << filePath; + + KStartupLogo::hideIfStillThere(); + proc->start(KProcess::Block, KProcess::All); + + if (!proc->normalExit() || proc->exitStatus()) { + CurrentProgressDialog::freeze(); + KMessageBox::sorry(this, i18n("Failed to import project file \"%1\"").arg(filePath)); + CurrentProgressDialog::thaw(); + delete proc; + return ; + } + + delete proc; + + QString rgFile = filePath; + rgFile.replace(QRegExp(".rg.rgp$"), ".rg"); + rgFile.replace(QRegExp(".rgp$"), ".rg"); + openURL(rgFile); +} + +void RosegardenGUIApp::slotImportMIDI() +{ + if (m_doc && !m_doc->saveIfModified()) + return ; + + KURL url = KFileDialog::getOpenURL + (":MIDI", + "audio/x-midi", this, + i18n("Open MIDI File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + openFile(tmpfile, ImportMIDI); // does everything including setting the document + + KIO::NetAccess::removeTempFile( tmpfile ); +} + +void RosegardenGUIApp::slotMergeMIDI() +{ + KURL url = KFileDialog::getOpenURL + (":MIDI", + "audio/x-midi", this, + i18n("Merge MIDI File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + mergeFile(tmpfile, ImportMIDI); + + KIO::NetAccess::removeTempFile( tmpfile ); +} + +QTextCodec * +RosegardenGUIApp::guessTextCodec(std::string text) +{ + QTextCodec *codec = 0; + + for (int c = 0; c < text.length(); ++c) { + if (text[c] & 0x80) { + + CurrentProgressDialog::freeze(); + KStartupLogo::hideIfStillThere(); + + IdentifyTextCodecDialog dialog(0, text); + dialog.exec(); + + std::string codecName = dialog.getCodec(); + + CurrentProgressDialog::thaw(); + + if (codecName != "") { + codec = QTextCodec::codecForName(codecName.c_str()); + } + break; + } + } + + return codec; +} + +void +RosegardenGUIApp::fixTextEncodings(Composition *c) + +{ + QTextCodec *codec = 0; + + for (Composition::iterator i = c->begin(); + i != c->end(); ++i) { + + for (Segment::iterator j = (*i)->begin(); + j != (*i)->end(); ++j) { + + if ((*j)->isa(Text::EventType)) { + + std::string text; + + if ((*j)->get + + (Text::TextPropertyName, text)) { + + if (!codec) + codec = guessTextCodec(text); + + if (codec) { + (*j)->set + + (Text::TextPropertyName, + convertFromCodec(text, codec)); + } + } + } + } + } + + if (!codec) + codec = guessTextCodec(c->getCopyrightNote()); + if (codec) + c->setCopyrightNote(convertFromCodec(c->getCopyrightNote(), codec)); + + for (Composition::trackcontainer::iterator i = + c->getTracks().begin(); i != c->getTracks().end(); ++i) { + if (!codec) + codec = guessTextCodec(i->second->getLabel()); + if (codec) + i->second->setLabel(convertFromCodec(i->second->getLabel(), codec)); + } + + for (Composition::iterator i = c->begin(); i != c->end(); ++i) { + if (!codec) + codec = guessTextCodec((*i)->getLabel()); + if (codec) + (*i)->setLabel(convertFromCodec((*i)->getLabel(), codec)); + } +} + +RosegardenGUIDoc* +RosegardenGUIApp::createDocumentFromMIDIFile(QString file) +{ + //if (!merge && !m_doc->saveIfModified()) return; + + // Create new document (autoload is inherent) + // + RosegardenGUIDoc *newDoc = new RosegardenGUIDoc(this, m_pluginManager); + + std::string fname(QFile::encodeName(file)); + + MidiFile midiFile(fname, + &newDoc->getStudio()); + + KStartupLogo::hideIfStillThere(); + ProgressDialog progressDlg(i18n("Importing MIDI file..."), + 200, + this); + + CurrentProgressDialog::set + (&progressDlg); + + connect(&midiFile, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + + connect(&midiFile, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + if (!midiFile.open()) { + CurrentProgressDialog::freeze(); + KMessageBox::error(this, strtoqstr(midiFile.getError())); //!!! i18n + delete newDoc; + return 0; + } + + midiFile.convertToRosegarden(newDoc->getComposition(), + MidiFile::CONVERT_REPLACE); + + fixTextEncodings(&newDoc->getComposition()); + + // Set modification flag + // + newDoc->slotDocumentModified(); + + // Set the caption + // + newDoc->setTitle(QFileInfo(file).fileName()); + newDoc->setAbsFilePath(QFileInfo(file).absFilePath()); + + // Clean up for notation purposes (after reinitialise, because that + // sets the composition's end marker time which is needed here) + + progressDlg.slotSetOperationName(i18n("Calculating notation...")); + ProgressDialog::processEvents(); + + Composition *comp = &newDoc->getComposition(); + + for (Composition::iterator i = comp->begin(); + i != comp->end(); ++i) { + + Segment &segment = **i; + SegmentNotationHelper helper(segment); + segment.insert(helper.guessClef(segment.begin(), + segment.getEndMarker()).getAsEvent + (segment.getStartTime())); + } + + progressDlg.progressBar()->setProgress(100); + + for (Composition::iterator i = comp->begin(); + i != comp->end(); ++i) { + + // find first key event in each segment (we'd have done the + // same for clefs, except there is no MIDI clef event) + + Segment &segment = **i; + timeT firstKeyTime = segment.getEndMarkerTime(); + + for (Segment::iterator si = segment.begin(); + segment.isBeforeEndMarker(si); ++si) { + if ((*si)->isa(Rosegarden::Key::EventType)) { + firstKeyTime = (*si)->getAbsoluteTime(); + break; + } + } + + if (firstKeyTime > segment.getStartTime()) { + CompositionTimeSliceAdapter adapter + (comp, timeT(0), firstKeyTime); + AnalysisHelper helper; + segment.insert(helper.guessKey(adapter).getAsEvent + (segment.getStartTime())); + } + } + + int progressPer = 100; + if (comp->getNbSegments() > 0) + progressPer = (int)(100.0 / double(comp->getNbSegments())); + + KMacroCommand *command = new KMacroCommand(i18n("Calculate Notation")); + + for (Composition::iterator i = comp->begin(); + i != comp->end(); ++i) { + + Segment &segment = **i; + timeT startTime(segment.getStartTime()); + timeT endTime(segment.getEndMarkerTime()); + +// std::cerr << "segment: start time " << segment.getStartTime() << ", end time " << segment.getEndTime() << ", end marker time " << segment.getEndMarkerTime() << ", events " << segment.size() << std::endl; + + EventQuantizeCommand *subCommand = new EventQuantizeCommand + (segment, startTime, endTime, "Notation Options", true); + + subCommand->setProgressTotal(progressPer + 1); + QObject::connect(subCommand, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + command->addCommand(subCommand); + } + + newDoc->getCommandHistory()->addCommand(command); + + if (comp->getTimeSignatureCount() == 0) { + CompositionTimeSliceAdapter adapter(comp); + AnalysisHelper analysisHelper; + TimeSignature timeSig = + analysisHelper.guessTimeSignature(adapter); + comp->addTimeSignature(0, timeSig); + } + + return newDoc; +} + +void RosegardenGUIApp::slotImportRG21() +{ + if (m_doc && !m_doc->saveIfModified()) + return ; + + KURL url = KFileDialog::getOpenURL + (":ROSEGARDEN21", + i18n("*.rose|Rosegarden-2 files\n*|All files"), this, + i18n("Open Rosegarden 2.1 File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + openFile(tmpfile, ImportRG21); + + KIO::NetAccess::removeTempFile(tmpfile); +} + +void RosegardenGUIApp::slotMergeRG21() +{ + KURL url = KFileDialog::getOpenURL + (":ROSEGARDEN21", + i18n("*.rose|Rosegarden-2 files\n*|All files"), this, + i18n("Open Rosegarden 2.1 File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + mergeFile(tmpfile, ImportRG21); + + KIO::NetAccess::removeTempFile( tmpfile ); +} + +RosegardenGUIDoc* +RosegardenGUIApp::createDocumentFromRG21File(QString file) +{ + KStartupLogo::hideIfStillThere(); + ProgressDialog progressDlg( + i18n("Importing Rosegarden 2.1 file..."), 100, this); + + CurrentProgressDialog::set + (&progressDlg); + + // Inherent autoload + // + RosegardenGUIDoc *newDoc = new RosegardenGUIDoc(this, m_pluginManager); + + RG21Loader rg21Loader(&newDoc->getStudio()); + + // TODO: make RG21Loader to actually emit these signals + // + connect(&rg21Loader, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + + connect(&rg21Loader, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + // "your starter for 40%" - helps the "freeze" work + // + progressDlg.progressBar()->advance(40); + + if (!rg21Loader.load(file, newDoc->getComposition())) { + CurrentProgressDialog::freeze(); + KMessageBox::error(this, + i18n("Can't load Rosegarden 2.1 file. It appears to be corrupted.")); + delete newDoc; + return 0; + } + + // Set modification flag + // + newDoc->slotDocumentModified(); + + // Set the caption and add recent + // + newDoc->setTitle(QFileInfo(file).fileName()); + newDoc->setAbsFilePath(QFileInfo(file).absFilePath()); + + return newDoc; + +} + +void +RosegardenGUIApp::slotImportHydrogen() +{ + if (m_doc && !m_doc->saveIfModified()) + return ; + + KURL url = KFileDialog::getOpenURL + (":HYDROGEN", + i18n("*.h2song|Hydrogen files\n*|All files"), this, + i18n("Open Hydrogen File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + openFile(tmpfile, ImportHydrogen); + + KIO::NetAccess::removeTempFile(tmpfile); +} + +void RosegardenGUIApp::slotMergeHydrogen() +{ + KURL url = KFileDialog::getOpenURL + (":HYDROGEN", + i18n("*.h2song|Hydrogen files\n*|All files"), this, + i18n("Open Hydrogen File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + mergeFile(tmpfile, ImportHydrogen); + + KIO::NetAccess::removeTempFile( tmpfile ); +} + +RosegardenGUIDoc* +RosegardenGUIApp::createDocumentFromHydrogenFile(QString file) +{ + KStartupLogo::hideIfStillThere(); + ProgressDialog progressDlg( + i18n("Importing Hydrogen file..."), 100, this); + + CurrentProgressDialog::set + (&progressDlg); + + // Inherent autoload + // + RosegardenGUIDoc *newDoc = new RosegardenGUIDoc(this, m_pluginManager); + + HydrogenLoader hydrogenLoader(&newDoc->getStudio()); + + // TODO: make RG21Loader to actually emit these signals + // + connect(&hydrogenLoader, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + + connect(&hydrogenLoader, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + // "your starter for 40%" - helps the "freeze" work + // + progressDlg.progressBar()->advance(40); + + if (!hydrogenLoader.load(file, newDoc->getComposition())) { + CurrentProgressDialog::freeze(); + KMessageBox::error(this, + i18n("Can't load Hydrogen file. It appears to be corrupted.")); + delete newDoc; + return 0; + } + + // Set modification flag + // + newDoc->slotDocumentModified(); + + // Set the caption and add recent + // + newDoc->setTitle(QFileInfo(file).fileName()); + newDoc->setAbsFilePath(QFileInfo(file).absFilePath()); + + return newDoc; + +} + +void +RosegardenGUIApp::mergeFile(QString filePath, ImportType type) +{ + RosegardenGUIDoc *doc = createDocument(filePath, type); + + if (doc) { + if (m_doc) { + + bool timingsDiffer = false; + Composition &c1 = m_doc->getComposition(); + Composition &c2 = doc->getComposition(); + + // compare tempos and time sigs in the two -- rather laborious + + if (c1.getTimeSignatureCount() != c2.getTimeSignatureCount()) { + timingsDiffer = true; + } else { + for (int i = 0; i < c1.getTimeSignatureCount(); ++i) { + std::pair t1 = + c1.getTimeSignatureChange(i); + std::pair t2 = + c2.getTimeSignatureChange(i); + if (t1.first != t2.first || t1.second != t2.second) { + timingsDiffer = true; + break; + } + } + } + + if (c1.getTempoChangeCount() != c2.getTempoChangeCount()) { + timingsDiffer = true; + } else { + for (int i = 0; i < c1.getTempoChangeCount(); ++i) { + std::pair t1 = c1.getTempoChange(i); + std::pair t2 = c2.getTempoChange(i); + if (t1.first != t2.first || t1.second != t2.second) { + timingsDiffer = true; + break; + } + } + } + + FileMergeDialog dialog(this, filePath, timingsDiffer); + if (dialog.exec() == QDialog::Accepted) { + m_doc->mergeDocument(doc, dialog.getMergeOptions()); + } + + delete doc; + + } else { + setDocument(doc); + } + } +} + +void +RosegardenGUIApp::slotUpdatePlaybackPosition() +{ + static int callbackCount = 0; + + // Either sequencer mappper or the sequence manager could be missing at + // this point. + // + if (!m_seqManager || !m_seqManager->getSequencerMapper()) + return ; + + SequencerMapper *mapper = m_seqManager->getSequencerMapper(); + + MappedEvent ev; + bool haveEvent = mapper->getVisual(ev); + if (haveEvent) + getTransport()->setMidiOutLabel(&ev); + + RealTime position = mapper->getPositionPointer(); + + // std::cerr << "RosegardenGUIApp::slotUpdatePlaybackPosition: mapper pos = " << position << std::endl; + + Composition &comp = m_doc->getComposition(); + timeT elapsedTime = comp.getElapsedTimeForRealTime(position); + + // std::cerr << "RosegardenGUIApp::slotUpdatePlaybackPosition: mapper timeT = " << elapsedTime << std::endl; + + if (m_seqManager->getTransportStatus() == RECORDING) { + + MappedComposition mC; + if (mapper->getRecordedEvents(mC) > 0) { + m_seqManager->processAsynchronousMidi(mC, 0); + m_doc->insertRecordedMidi(mC); + } + + m_doc->updateRecordingMIDISegment(); + m_doc->updateRecordingAudioSegments(); + } + + m_originatingJump = true; + m_doc->slotSetPointerPosition(elapsedTime); + m_originatingJump = false; + + if (m_audioMixer && m_audioMixer->isVisible()) + m_audioMixer->updateMeters(mapper); + + if (m_midiMixer && m_midiMixer->isVisible()) + m_midiMixer->updateMeters(mapper); + + m_view->updateMeters(mapper); + + if (++callbackCount == 60) { + slotUpdateCPUMeter(true); + callbackCount = 0; + } + + // if (elapsedTime >= comp.getEndMarker()) + // slotStop(); +} + +void +RosegardenGUIApp::slotUpdateCPUMeter(bool playing) +{ + static std::ifstream *statstream = 0; + static bool modified = false; + static unsigned long lastBusy = 0, lastIdle = 0; + + if (playing) { + + if (!statstream) { + statstream = new std::ifstream("/proc/stat", std::ios::in); + } + + if (!statstream || !*statstream) + return ; + statstream->seekg(0, std::ios::beg); + + std::string cpu; + unsigned long user, nice, sys, idle; + *statstream >> cpu; + *statstream >> user; + *statstream >> nice; + *statstream >> sys; + *statstream >> idle; + + unsigned long busy = user + nice + sys; + unsigned long count = 0; + + if (lastBusy > 0) { + unsigned long bd = busy - lastBusy; + unsigned long id = idle - lastIdle; + if (bd + id > 0) + count = bd * 100 / (bd + id); + if (count > 100) + count = 100; + } + + lastBusy = busy; + lastIdle = idle; + + if (m_progressBar) { + if (!modified) { + m_progressBar->setTextEnabled(true); + m_progressBar->setFormat("CPU"); + } + m_progressBar->setProgress(count); + } + + modified = true; + + } else if (modified) { + if (m_progressBar) { + m_progressBar->setTextEnabled(false); + m_progressBar->setFormat("%p%"); + m_progressBar->setProgress(0); + } + modified = false; + } +} + +void +RosegardenGUIApp::slotUpdateMonitoring() +{ + // Either sequencer mappper or the sequence manager could be missing at + // this point. + // + if (!m_seqManager || !m_seqManager->getSequencerMapper()) + return ; + + SequencerMapper *mapper = m_seqManager->getSequencerMapper(); + + if (m_audioMixer && m_audioMixer->isVisible()) + m_audioMixer->updateMonitorMeters(mapper); + + if (m_midiMixer && m_midiMixer->isVisible()) + m_midiMixer->updateMonitorMeter(mapper); + + m_view->updateMonitorMeters(mapper); + + slotUpdateCPUMeter(false); +} + +void RosegardenGUIApp::slotSetPointerPosition(timeT t) +{ + Composition &comp = m_doc->getComposition(); + + // std::cerr << "RosegardenGUIApp::slotSetPointerPosition: t = " << t << std::endl; + + if (m_seqManager) { + if ( m_seqManager->getTransportStatus() == PLAYING || + m_seqManager->getTransportStatus() == RECORDING ) { + if (t > comp.getEndMarker()) { + if (m_seqManager->getTransportStatus() == PLAYING) { + + slotStop(); + t = comp.getEndMarker(); + m_doc->slotSetPointerPosition(t); //causes this method to be re-invoked + return ; + + } else { // if recording, increase composition duration + std::pair timeRange = comp.getBarRangeForTime(t); + timeT barDuration = timeRange.second - timeRange.first; + timeT newEndMarker = t + 10 * barDuration; + comp.setEndMarker(newEndMarker); + getView()->getTrackEditor()->slotReadjustCanvasSize(); + getView()->getTrackEditor()->updateRulers(); + } + } + } + + // cc 20050520 - jump at the sequencer even if we're not playing, + // because we might be a transport master of some kind + try { + if (!m_originatingJump) { + m_seqManager->sendSequencerJump(comp.getElapsedRealTime(t)); + } + } catch (QString s) { + KMessageBox::error(this, s); + } + } + + // set the time sig + getTransport()->setTimeSignature(comp.getTimeSignatureAt(t)); + + // and the tempo + getTransport()->setTempo(comp.getTempoAtTime(t)); + + // and the time + // + TransportDialog::TimeDisplayMode mode = + getTransport()->getCurrentMode(); + + if (mode == TransportDialog::BarMode || + mode == TransportDialog::BarMetronomeMode) { + + slotDisplayBarTime(t); + + } else { + + RealTime rT(comp.getElapsedRealTime(t)); + + if (getTransport()->isShowingTimeToEnd()) { + rT = rT - comp.getElapsedRealTime(comp.getDuration()); + } + + if (mode == TransportDialog::RealMode) { + + getTransport()->displayRealTime(rT); + + } else if (mode == TransportDialog::SMPTEMode) { + + getTransport()->displaySMPTETime(rT); + + } else { + + getTransport()->displayFrameTime(rT); + } + } + + // handle transport mode configuration changes + std::string modeAsString = getTransport()->getCurrentModeAsString(); + + if (m_doc->getConfiguration().get + (DocumentConfiguration::TransportMode) != modeAsString) { + + m_doc->getConfiguration().set + (DocumentConfiguration::TransportMode, modeAsString); + + //m_doc->slotDocumentModified(); to avoid being prompted for a file change when merely changing the transport display + } + + // Update position on the marker editor if it's available + // + if (m_markerEditor) + m_markerEditor->updatePosition(); +} + +void RosegardenGUIApp::slotDisplayBarTime(timeT t) +{ + Composition &comp = m_doc->getComposition(); + + int barNo = comp.getBarNumber(t); + timeT barStart = comp.getBarStart(barNo); + + TimeSignature timeSig = comp.getTimeSignatureAt(t); + timeT beatDuration = timeSig.getBeatDuration(); + + int beatNo = (t - barStart) / beatDuration; + int unitNo = (t - barStart) - (beatNo * beatDuration); + + if (getTransport()->isShowingTimeToEnd()) { + barNo = barNo + 1 - comp.getNbBars(); + beatNo = timeSig.getBeatsPerBar() - 1 - beatNo; + unitNo = timeSig.getBeatDuration() - 1 - unitNo; + } else { + // convert to 1-based display bar numbers + barNo += 1; + beatNo += 1; + } + + // show units in hemidemis (or whatever), not in raw time ticks + unitNo /= Note(Note::Shortest).getDuration(); + + getTransport()->displayBarTime(barNo, beatNo, unitNo); +} + +void RosegardenGUIApp::slotRefreshTimeDisplay() +{ + if ( m_seqManager->getTransportStatus() == PLAYING || + m_seqManager->getTransportStatus() == RECORDING ) { + return ; // it'll be refreshed in a moment anyway + } + slotSetPointerPosition(m_doc->getComposition().getPosition()); +} + +bool +RosegardenGUIApp::isTrackEditorPlayTracking() const +{ + return m_view->getTrackEditor()->isTracking(); +} + +void RosegardenGUIApp::slotToggleTracking() +{ + m_view->getTrackEditor()->slotToggleTracking(); +} + +void RosegardenGUIApp::slotTestStartupTester() +{ + RG_DEBUG << "RosegardenGUIApp::slotTestStartupTester" << endl; + + if (!m_startupTester) { + m_startupTester = new StartupTester(); + connect(m_startupTester, SIGNAL(newerVersionAvailable(QString)), + this, SLOT(slotNewerVersionAvailable(QString))); + m_startupTester->start(); + QTimer::singleShot(100, this, SLOT(slotTestStartupTester())); + return ; + } + + if (!m_startupTester->isReady()) { + QTimer::singleShot(100, this, SLOT(slotTestStartupTester())); + return ; + } + + QStringList missingFeatures; + QStringList allMissing; + + QStringList missing; + bool have = m_startupTester->haveProjectPackager(&missing); + + stateChanged("have_project_packager", + have ? + KXMLGUIClient::StateNoReverse : KXMLGUIClient::StateReverse); + + if (!have) { + missingFeatures.push_back(i18n("Export and import of Rosegarden Project files")); + if (missing.count() == 0) { + allMissing.push_back(i18n("The Rosegarden Project Packager helper script")); + } else { + for (int i = 0; i < missing.count(); ++i) { +// if (missingFeatures.count() > 1) { + allMissing.push_back(i18n("%1 - for project file support").arg(missing[i])); +// } else { +// allMissing.push_back(missing[i]); +// } + } + } + } + + have = m_startupTester->haveLilyPondView(&missing); + + stateChanged("have_lilypondview", + have ? + KXMLGUIClient::StateNoReverse : KXMLGUIClient::StateReverse); + + if (!have) { + missingFeatures.push_back("Notation previews through LilyPond"); + if (missing.count() == 0) { + allMissing.push_back(i18n("The Rosegarden LilyPondView helper script")); + } else { + for (int i = 0; i < missing.count(); ++i) { + if (missingFeatures.count() > 1) { + allMissing.push_back(i18n("%1 - for LilyPond preview support").arg(missing[i])); + } else { + allMissing.push_back(missing[i]); + } + } + } + } + +#ifdef HAVE_LIBJACK + if (m_seqManager && (m_seqManager->getSoundDriverStatus() & AUDIO_OK)) { + + m_haveAudioImporter = m_startupTester->haveAudioFileImporter(&missing); + + if (!m_haveAudioImporter) { + missingFeatures.push_back("General audio file import and conversion"); + if (missing.count() == 0) { + allMissing.push_back(i18n("The Rosegarden Audio File Importer helper script")); + } else { + for (int i = 0; i < missing.count(); ++i) { + if (missingFeatures.count() > 1) { + allMissing.push_back(i18n("%1 - for audio file import").arg(missing[i])); + } else { + allMissing.push_back(missing[i]); + } + } + } + } + } +#endif + + if (missingFeatures.count() > 0) { + QString message = i18n("

Helper programs not found

Rosegarden could not find one or more helper programs which it needs to provide some features. The following features will not be available:

"); + message += i18n("
    "); + for (int i = 0; i < missingFeatures.count(); ++i) { + message += i18n("
  • %1
  • ").arg(missingFeatures[i]); + } + message += i18n("
"); + message += i18n("

To fix this, you should install the following additional programs:

"); + message += i18n("
    "); + for (int i = 0; i < allMissing.count(); ++i) { + message += i18n("
  • %1
  • ").arg(allMissing[i]); + } + message += i18n("
"); + + awaitDialogClearance(); + + KMessageBox::information + (m_view, + message, + i18n("Helper programs not found"), + "startup-helpers-missing"); + } + + delete m_startupTester; + m_startupTester = 0; +} + +void RosegardenGUIApp::slotDebugDump() +{ + Composition &comp = m_doc->getComposition(); + comp.dump(std::cerr); +} + +bool RosegardenGUIApp::launchSequencer(bool useExisting) +{ + if (!isUsingSequencer()) { + RG_DEBUG << "RosegardenGUIApp::launchSequencer() - not using seq. - returning\n"; + return false; // no need to launch anything + } + + if (isSequencerRunning()) { + RG_DEBUG << "RosegardenGUIApp::launchSequencer() - sequencer already running - returning\n"; + if (m_seqManager) m_seqManager->checkSoundDriverStatus(false); + return true; + } + + // Check to see if we're clearing down sequencer processes - + // if we're not we check DCOP for an existing sequencer and + // try to talk to use that (that's the "developer" mode). + // + // User mode should clear down sequencer processes. + // + if (kapp->dcopClient()->isApplicationRegistered( + QCString(ROSEGARDEN_SEQUENCER_APP_NAME))) { + RG_DEBUG << "RosegardenGUIApp::launchSequencer() - " + << "existing DCOP registered sequencer found\n"; + + if (useExisting) { + if (m_seqManager) m_seqManager->checkSoundDriverStatus(false); + m_sequencerProcess = (KProcess*)SequencerExternal; + return true; + } + + KProcess *proc = new KProcess; + *proc << "/usr/bin/killall"; + *proc << "rosegardensequencer"; + *proc << "lt-rosegardensequencer"; + + proc->start(KProcess::Block, KProcess::All); + + if (!proc->normalExit() || proc->exitStatus()) { + RG_DEBUG << "couldn't kill any sequencer processes" << endl; + } + delete proc; + + sleep(1); + + if (kapp->dcopClient()->isApplicationRegistered( + QCString(ROSEGARDEN_SEQUENCER_APP_NAME))) { + RG_DEBUG << "RosegardenGUIApp::launchSequencer() - " + << "failed to kill existing sequencer\n"; + + KProcess *proc = new KProcess; + *proc << "/usr/bin/killall"; + *proc << "-9"; + *proc << "rosegardensequencer"; + *proc << "lt-rosegardensequencer"; + + proc->start(KProcess::Block, KProcess::All); + + if (proc->exitStatus()) { + RG_DEBUG << "couldn't kill any sequencer processes" << endl; + } + delete proc; + + sleep(1); + } + } + + // + // No sequencer is running, so start one + // + KTmpStatusMsg msg(i18n("Starting the sequencer..."), this); + + if (!m_sequencerProcess) { + m_sequencerProcess = new KProcess; + + (*m_sequencerProcess) << "rosegardensequencer"; + + // Command line arguments + // + KConfig *config = kapp->config(); + config->setGroup(SequencerOptionsConfigGroup); + QString options = config->readEntry("commandlineoptions"); + if (!options.isEmpty()) { + (*m_sequencerProcess) << options; + RG_DEBUG << "sequencer options \"" << options << "\"" << endl; + } + + } else { + RG_DEBUG << "RosegardenGUIApp::launchSequencer() - sequencer KProcess already created\n"; + m_sequencerProcess->disconnect(); // disconnect processExit signal + // it will be reconnected later on + } + + bool res = m_sequencerProcess->start(); + + if (!res) { + KMessageBox::error(0, i18n("Couldn't start the sequencer")); + RG_DEBUG << "Couldn't start the sequencer\n"; + m_sequencerProcess = 0; + // If starting it didn't even work, fall back to no sequencer mode + m_useSequencer = false; + } else { + // connect processExited only after start, otherwise + // a failed startup will call slotSequencerExited() + // right away and we don't get to check the result + // of m_sequencerProcess->start() and thus make the distinction + // between the case where the sequencer was successfully launched + // but crashed right away, or the case where the process couldn't + // be launched at all (missing executable, etc...) + // + // We also re-check that the process is still running at this + // point in case it crashed between the moment we check res above + // and now. + // + //usleep(1000 * 1000); // even wait half a sec. to make sure it's actually running + if (m_sequencerProcess->isRunning()) { + + try { + // if (m_seqManager) { + // RG_DEBUG << "RosegardenGUIApp::launchSequencer : checking sound driver status\n"; + // m_seqManager->checkSoundDriverStatus(); + // } + + stateChanged("sequencer_running"); + slotEnableTransport(true); + + connect(m_sequencerProcess, SIGNAL(processExited(KProcess*)), + this, SLOT(slotSequencerExited(KProcess*))); + + } catch (Exception e) { + m_sequencerProcess = 0; + m_useSequencer = false; + stateChanged("sequencer_running", KXMLGUIClient::StateReverse); + slotEnableTransport(false); + } + + } else { // if it crashed so fast, it's probably pointless + // to try restarting it later, so also fall back to no + // sequencer mode + m_sequencerProcess = 0; + m_useSequencer = false; + stateChanged("sequencer_running", KXMLGUIClient::StateReverse); + slotEnableTransport(false); + } + + } + + // Sync current devices with the sequencer + // + if (m_doc) + m_doc->syncDevices(); + + if (m_doc && m_doc->getStudio().haveMidiDevices()) { + stateChanged("got_midi_devices"); + } else { + stateChanged("got_midi_devices", KXMLGUIClient::StateReverse); + } + + return res; +} + +#ifdef HAVE_LIBJACK +bool RosegardenGUIApp::launchJack() +{ + KConfig* config = kapp->config(); + config->setGroup(SequencerOptionsConfigGroup); + + bool startJack = config->readBoolEntry("jackstart", false); + if (!startJack) + return true; // we don't do anything + + QString jackPath = config->readEntry("jackcommand", ""); + + emit startupStatusMessage(i18n("Clearing down jackd...")); + + KProcess *proc = new KProcess; // TODO: do it in a less clumsy way + *proc << "/usr/bin/killall"; + *proc << "-9"; + *proc << "jackd"; + + proc->start(KProcess::Block, KProcess::All); + + if (proc->exitStatus()) + RG_DEBUG << "couldn't kill any jackd processes" << endl; + else + RG_DEBUG << "killed old jackd processes" << endl; + + emit startupStatusMessage(i18n("Starting jackd...")); + + if (jackPath != "") { + + RG_DEBUG << "starting jack \"" << jackPath << "\"" << endl; + + QStringList splitCommand; + splitCommand = QStringList::split(" ", jackPath); + + RG_DEBUG << "RosegardenGUIApp::launchJack() : splitCommand length : " + << splitCommand.size() << endl; + + // start jack process + m_jackProcess = new KProcess; + + *m_jackProcess << splitCommand; + + m_jackProcess->start(); + } + + + return m_jackProcess != 0 ? m_jackProcess->isRunning() : true; +} +#endif + +void RosegardenGUIApp::slotDocumentDevicesResyncd() +{ + m_sequencerCheckedIn = true; + m_trackParameterBox->populateDeviceLists(); +} + +void RosegardenGUIApp::slotSequencerExited(KProcess*) +{ + RG_DEBUG << "RosegardenGUIApp::slotSequencerExited Sequencer exited\n"; + + KStartupLogo::hideIfStillThere(); + + if (m_sequencerCheckedIn) { + + KMessageBox::error(0, i18n("The Rosegarden sequencer process has exited unexpectedly. Sound and recording will no longer be available for this session.\nPlease exit and restart Rosegarden to restore sound capability.")); + + } else { + + KMessageBox::error(0, i18n("The Rosegarden sequencer could not be started, so sound and recording will be unavailable for this session.\nFor assistance with correct audio and MIDI configuration, go to http://rosegardenmusic.com.")); + } + + m_sequencerProcess = 0; // isSequencerRunning() will return false + // but isUsingSequencer() will keep returning true + // so pressing the play button may attempt to restart the sequencer +} + +void RosegardenGUIApp::slotExportProject() +{ + KTmpStatusMsg msg(i18n("Exporting Rosegarden Project file..."), this); + + QString fileName = getValidWriteFile + ("*.rgp|" + i18n("Rosegarden Project files\n") + + "\n*|" + i18n("All files"), + i18n("Export as...")); + + if (fileName.isEmpty()) + return ; + + QString rgFile = fileName; + rgFile.replace(QRegExp(".rg.rgp$"), ".rg"); + rgFile.replace(QRegExp(".rgp$"), ".rg"); + + CurrentProgressDialog::freeze(); + + QString errMsg; + if (!m_doc->saveDocument(rgFile, errMsg, + true)) { // pretend it's autosave + KMessageBox::sorry(this, i18n("Saving Rosegarden file to package failed: %1").arg(errMsg)); + CurrentProgressDialog::thaw(); + return ; + } + + KProcess *proc = new KProcess; + *proc << "rosegarden-project-package"; + *proc << "--pack"; + *proc << rgFile; + *proc << fileName; + + proc->start(KProcess::Block, KProcess::All); + + if (!proc->normalExit() || proc->exitStatus()) { + KMessageBox::sorry(this, i18n("Failed to export to project file \"%1\"").arg(fileName)); + CurrentProgressDialog::thaw(); + delete proc; + return ; + } + + delete proc; +} + +void RosegardenGUIApp::slotExportMIDI() +{ + KTmpStatusMsg msg(i18n("Exporting MIDI file..."), this); + + QString fileName = getValidWriteFile + ("*.mid *.midi|" + i18n("Standard MIDI files\n") + + "\n*|" + i18n("All files"), + i18n("Export as...")); + + if (fileName.isEmpty()) + return ; + + exportMIDIFile(fileName); +} + +void RosegardenGUIApp::exportMIDIFile(QString file) +{ + ProgressDialog progressDlg(i18n("Exporting MIDI file..."), + 100, + this); + + std::string fname(QFile::encodeName(file)); + + MidiFile midiFile(fname, + &m_doc->getStudio()); + + connect(&midiFile, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + + connect(&midiFile, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + midiFile.convertToMidi(m_doc->getComposition()); + + if (!midiFile.write()) { + CurrentProgressDialog::freeze(); + KMessageBox::sorry(this, i18n("Export failed. The file could not be opened for writing.")); + } +} + +void RosegardenGUIApp::slotExportCsound() +{ + KTmpStatusMsg msg(i18n("Exporting Csound score file..."), this); + + QString fileName = getValidWriteFile(QString("*|") + i18n("All files"), + i18n("Export as...")); + if (fileName.isEmpty()) + return ; + + exportCsoundFile(fileName); +} + +void RosegardenGUIApp::exportCsoundFile(QString file) +{ + ProgressDialog progressDlg(i18n("Exporting Csound score file..."), + 100, + this); + + CsoundExporter e(this, &m_doc->getComposition(), 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.")); + } +} + +void RosegardenGUIApp::slotExportMup() +{ + KTmpStatusMsg msg(i18n("Exporting Mup file..."), this); + + QString fileName = getValidWriteFile + ("*.mup|" + i18n("Mup files\n") + "\n*|" + i18n("All files"), + i18n("Export as...")); + if (fileName.isEmpty()) + return ; + + exportMupFile(fileName); +} + +void RosegardenGUIApp::exportMupFile(QString file) +{ + ProgressDialog progressDlg(i18n("Exporting Mup file..."), + 100, + this); + + MupExporter e(this, &m_doc->getComposition(), 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.")); + } +} + +void RosegardenGUIApp::slotExportLilyPond() +{ + KTmpStatusMsg msg(i18n("Exporting LilyPond file..."), this); + + QString fileName = getValidWriteFile + (QString("*.ly|") + i18n("LilyPond files") + + "\n*|" + i18n("All files"), + i18n("Export as...")); + + if (fileName.isEmpty()) + return ; + + exportLilyPondFile(fileName); +} + +std::map RosegardenGUIApp::m_lilyTempFileMap; + + +void RosegardenGUIApp::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 RosegardenGUIApp::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 RosegardenGUIApp::slotLilyPondViewProcessExited(KProcess *p) +{ + delete m_lilyTempFileMap[p]; + m_lilyTempFileMap.erase(p); + delete p; +} + +bool RosegardenGUIApp::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 RosegardenGUIApp::slotExportMusicXml() +{ + KTmpStatusMsg msg(i18n("Exporting MusicXML file..."), this); + + QString fileName = getValidWriteFile + (QString("*.xml|") + i18n("XML files") + + "\n*|" + i18n("All files"), i18n("Export as...")); + + if (fileName.isEmpty()) + return ; + + exportMusicXmlFile(fileName); +} + +void RosegardenGUIApp::exportMusicXmlFile(QString file) +{ + ProgressDialog progressDlg(i18n("Exporting MusicXML file..."), + 100, + this); + + MusicXmlExporter 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.")); + } +} + +void +RosegardenGUIApp::slotCloseTransport() +{ + m_viewTransport->setChecked(false); + slotToggleTransport(); // hides the transport +} + +void +RosegardenGUIApp::slotDeleteTransport() +{ + delete m_transport; + m_transport = 0; +} + +void +RosegardenGUIApp::slotActivateTool(QString toolName) +{ + if (toolName == SegmentSelector::ToolName) { + actionCollection()->action("select")->activate(); + } +} + +void +RosegardenGUIApp::slotToggleMetronome() +{ + Composition &comp = m_doc->getComposition(); + + if (m_seqManager->getTransportStatus() == STARTING_TO_RECORD || + m_seqManager->getTransportStatus() == RECORDING || + m_seqManager->getTransportStatus() == RECORDING_ARMED) { + if (comp.useRecordMetronome()) + comp.setRecordMetronome(false); + else + comp.setRecordMetronome(true); + + getTransport()->MetronomeButton()->setOn(comp.useRecordMetronome()); + } else { + if (comp.usePlayMetronome()) + comp.setPlayMetronome(false); + else + comp.setPlayMetronome(true); + + getTransport()->MetronomeButton()->setOn(comp.usePlayMetronome()); + } +} + +void +RosegardenGUIApp::slotRewindToBeginning() +{ + // ignore requests if recording + // + if (m_seqManager->getTransportStatus() == RECORDING) + return ; + + m_seqManager->rewindToBeginning(); +} + +void +RosegardenGUIApp::slotFastForwardToEnd() +{ + // ignore requests if recording + // + if (m_seqManager->getTransportStatus() == RECORDING) + return ; + + m_seqManager->fastForwardToEnd(); +} + +void +RosegardenGUIApp::slotSetPlayPosition(timeT time) +{ + RG_DEBUG << "RosegardenGUIApp::slotSetPlayPosition(" << time << ")" << endl; + if (m_seqManager->getTransportStatus() == RECORDING) + return ; + + m_doc->slotSetPointerPosition(time); + + if (m_seqManager->getTransportStatus() == PLAYING) + return ; + + slotPlay(); +} + +void RosegardenGUIApp::notifySequencerStatus(int status) +{ + stateChanged("not_playing", + (status == PLAYING || + status == RECORDING) ? + KXMLGUIClient::StateReverse : KXMLGUIClient::StateNoReverse); + + if (m_seqManager) + m_seqManager->setTransportStatus((TransportStatus) status); +} + +void RosegardenGUIApp::processAsynchronousMidi(const MappedComposition &mC) +{ + if (!m_seqManager) { + return ; // probably getting this from a not-yet-killed runaway sequencer + } + + m_seqManager->processAsynchronousMidi(mC, 0); + SequencerMapper *mapper = m_seqManager->getSequencerMapper(); + if (mapper) + m_view->updateMeters(mapper); +} + +void +RosegardenGUIApp::slotRecord() +{ + if (!isUsingSequencer()) + return ; + + if (!isSequencerRunning()) { + + // Try to launch sequencer and return if we fail + // + if (!launchSequencer(false)) + return ; + } + + if (m_seqManager->getTransportStatus() == RECORDING) { + slotStop(); + return ; + } else if (m_seqManager->getTransportStatus() == PLAYING) { + slotToggleRecord(); + return ; + } + + // Attempt to start recording + // + try { + m_seqManager->record(false); + } catch (QString s) { + // We should already be stopped by this point so just unset + // the buttons after clicking the dialog. + // + KMessageBox::error(this, s); + + getTransport()->MetronomeButton()->setOn(false); + getTransport()->RecordButton()->setOn(false); + getTransport()->PlayButton()->setOn(false); + return ; + } 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) { + slotOpenAudioPathSettings(); + } + getTransport()->MetronomeButton()->setOn(false); + getTransport()->RecordButton()->setOn(false); + getTransport()->PlayButton()->setOn(false); + return ; + } catch (Exception e) { + KMessageBox::error(this, strtoqstr(e.getMessage())); + + getTransport()->MetronomeButton()->setOn(false); + getTransport()->RecordButton()->setOn(false); + getTransport()->PlayButton()->setOn(false); + return ; + } + + // plugin the keyboard accelerators for focus on this dialog + plugAccelerators(m_seqManager->getCountdownDialog(), + m_seqManager->getCountdownDialog()->getAccelerators()); + + connect(m_seqManager->getCountdownDialog(), SIGNAL(stopped()), + this, SLOT(slotStop())); + + // Start the playback timer - this fetches the current sequencer position &c + // + m_stopTimer->stop(); + m_playTimer->start(23); // avoid multiples of 10 just so as + // to avoid always having the same digit + // in one place on the transport. How + // shallow.) +} + +void +RosegardenGUIApp::slotToggleRecord() +{ + if (!isUsingSequencer() || + (!isSequencerRunning() && !launchSequencer(false))) + return ; + + try { + m_seqManager->record(true); + } catch (QString s) { + KMessageBox::error(this, s); + } 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 you start to record audio.\nWould you like to set it now?"), + i18n("Error"), + i18n("Set audio file path")) == KMessageBox::Continue) { + slotOpenAudioPathSettings(); + } + } catch (Exception e) { + KMessageBox::error(this, strtoqstr(e.getMessage())); + } + +} + +void +RosegardenGUIApp::slotSetLoop(timeT lhs, timeT rhs) +{ + try { + m_doc->slotDocumentModified(); + + m_seqManager->setLoop(lhs, rhs); + + // toggle the loop button + if (lhs != rhs) { + getTransport()->LoopButton()->setOn(true); + stateChanged("have_range", KXMLGUIClient::StateNoReverse); + } else { + getTransport()->LoopButton()->setOn(false); + stateChanged("have_range", KXMLGUIClient::StateReverse); + } + } catch (QString s) { + KMessageBox::error(this, s); + } +} + +void RosegardenGUIApp::alive() +{ + if (m_doc) + m_doc->syncDevices(); + + if (m_doc && m_doc->getStudio().haveMidiDevices()) { + stateChanged("got_midi_devices"); + } else { + stateChanged("got_midi_devices", KXMLGUIClient::StateReverse); + } +} + +void RosegardenGUIApp::slotPlay() +{ + if (!isUsingSequencer()) + return ; + + if (!isSequencerRunning()) { + + // Try to launch sequencer and return if it fails + // + if (!launchSequencer(false)) + return ; + } + + if (!m_seqManager) + return ; + + // If we're armed and ready to record then do this instead (calling + // slotRecord ensures we don't toggle the recording state in + // SequenceManager) + // + if (m_seqManager->getTransportStatus() == RECORDING_ARMED) { + slotRecord(); + return ; + } + + // Send the controllers at start of playback if required + // + KConfig *config = kapp->config(); + config->setGroup(SequencerOptionsConfigGroup); + bool sendControllers = config->readBoolEntry("alwayssendcontrollers", false); + + if (sendControllers) + m_doc->initialiseControllers(); + + bool pausedPlayback = false; + + try { + pausedPlayback = m_seqManager->play(); // this will stop playback (pause) if it's already running + // Check the new state of the transport and start or stop timer + // accordingly + // + if (!pausedPlayback) { + + // Start the playback timer - this fetches the current sequencer position &c + // + m_stopTimer->stop(); + m_playTimer->start(23); + } else { + m_playTimer->stop(); + m_stopTimer->start(100); + } + } catch (QString s) { + KMessageBox::error(this, s); + m_playTimer->stop(); + m_stopTimer->start(100); + } catch (Exception e) { + KMessageBox::error(this, e.getMessage()); + m_playTimer->stop(); + m_stopTimer->start(100); + } + +} + +void RosegardenGUIApp::slotJumpToTime(int sec, int usec) +{ + Composition *comp = &m_doc->getComposition(); + timeT t = comp->getElapsedTimeForRealTime + (RealTime(sec, usec * 1000)); + m_doc->slotSetPointerPosition(t); +} + +void RosegardenGUIApp::slotStartAtTime(int sec, int usec) +{ + slotJumpToTime(sec, usec); + slotPlay(); +} + +void RosegardenGUIApp::slotStop() +{ + if (m_seqManager && + m_seqManager->getCountdownDialog()) { + disconnect(m_seqManager->getCountdownDialog(), SIGNAL(stopped()), + this, SLOT(slotStop())); + disconnect(m_seqManager->getCountdownDialog(), SIGNAL(completed()), + this, SLOT(slotStop())); + } + + try { + if (m_seqManager) + m_seqManager->stopping(); + } catch (Exception e) { + KMessageBox::error(this, strtoqstr(e.getMessage())); + } + + // stop the playback timer + m_playTimer->stop(); + m_stopTimer->start(100); +} + +void RosegardenGUIApp::slotRewind() +{ + // ignore requests if recording + // + if (m_seqManager->getTransportStatus() == RECORDING) + return ; + if (m_seqManager) + m_seqManager->rewind(); +} + +void RosegardenGUIApp::slotFastforward() +{ + // ignore requests if recording + // + if (m_seqManager->getTransportStatus() == RECORDING) + return ; + + if (m_seqManager) + m_seqManager->fastforward(); +} + +void +RosegardenGUIApp::slotSetLoop() +{ + // restore loop + m_doc->setLoop(m_storedLoopStart, m_storedLoopEnd); +} + +void +RosegardenGUIApp::slotUnsetLoop() +{ + Composition &comp = m_doc->getComposition(); + + // store the loop + m_storedLoopStart = comp.getLoopStart(); + m_storedLoopEnd = comp.getLoopEnd(); + + // clear the loop at the composition and propagate to the rest + // of the display items + m_doc->setLoop(0, 0); +} + +void +RosegardenGUIApp::slotSetLoopStart() +{ + // Check so that start time is before endtime, othervise move upp the + // endtime to that same pos. + if ( m_doc->getComposition().getPosition() < m_doc->getComposition().getLoopEnd() ) { + m_doc->setLoop(m_doc->getComposition().getPosition(), m_doc->getComposition().getLoopEnd()); + } else { + m_doc->setLoop(m_doc->getComposition().getPosition(), m_doc->getComposition().getPosition()); + } +} + +void +RosegardenGUIApp::slotSetLoopStop() +{ + // Check so that end time is after start time, othervise move upp the + // start time to that same pos. + if ( m_doc->getComposition().getLoopStart() < m_doc->getComposition().getPosition() ) { + m_doc->setLoop(m_doc->getComposition().getLoopStart(), m_doc->getComposition().getPosition()); + } else { + m_doc->setLoop(m_doc->getComposition().getPosition(), m_doc->getComposition().getPosition()); + } +} + +void RosegardenGUIApp::slotToggleSolo(bool value) +{ + RG_DEBUG << "RosegardenGUIApp::slotToggleSolo value = " << value << endl; + + m_doc->getComposition().setSolo(value); + getTransport()->SoloButton()->setOn(value); + + m_doc->slotDocumentModified(); + + emit compositionStateUpdate(); +} + +void RosegardenGUIApp::slotTrackUp() +{ + Composition &comp = m_doc->getComposition(); + + TrackId tid = comp.getSelectedTrack(); + TrackId pos = comp.getTrackById(tid)->getPosition(); + + // If at top already + if (pos == 0) + return ; + + Track *track = comp.getTrackByPosition(pos - 1); + + // If the track exists + if (track) { + comp.setSelectedTrack(track->getId()); + m_view->slotSelectTrackSegments(comp.getSelectedTrack()); + } + +} + +void RosegardenGUIApp::slotTrackDown() +{ + Composition &comp = m_doc->getComposition(); + + TrackId tid = comp.getSelectedTrack(); + TrackId pos = comp.getTrackById(tid)->getPosition(); + + Track *track = comp.getTrackByPosition(pos + 1); + + // If the track exists + if (track) { + comp.setSelectedTrack(track->getId()); + m_view->slotSelectTrackSegments(comp.getSelectedTrack()); + } + +} + +void RosegardenGUIApp::slotMuteAllTracks() +{ + RG_DEBUG << "RosegardenGUIApp::slotMuteAllTracks" << endl; + + Composition &comp = m_doc->getComposition(); + + Composition::trackcontainer tracks = comp.getTracks(); + Composition::trackiterator tit; + for (tit = tracks.begin(); tit != tracks.end(); ++tit) + m_view->slotSetMute((*tit).second->getInstrument(), true); +} + +void RosegardenGUIApp::slotUnmuteAllTracks() +{ + RG_DEBUG << "RosegardenGUIApp::slotUnmuteAllTracks" << endl; + + Composition &comp = m_doc->getComposition(); + + Composition::trackcontainer tracks = comp.getTracks(); + Composition::trackiterator tit; + for (tit = tracks.begin(); tit != tracks.end(); ++tit) + m_view->slotSetMute((*tit).second->getInstrument(), false); +} + +void RosegardenGUIApp::slotToggleMutedCurrentTrack() +{ + Composition &comp = m_doc->getComposition(); + TrackId tid = comp.getSelectedTrack(); + Track *track = comp.getTrackById(tid); + // If the track exists + if (track) { + bool isMuted = track->isMuted(); + m_view->slotSetMuteButton(tid, !isMuted); + } +} + +void RosegardenGUIApp::slotToggleRecordCurrentTrack() +{ + Composition &comp = m_doc->getComposition(); + TrackId tid = comp.getSelectedTrack(); + int pos = comp.getTrackPositionById(tid); + m_view->getTrackEditor()->getTrackButtons()->slotToggleRecordTrack(pos); +} + + +void RosegardenGUIApp::slotConfigure() +{ + RG_DEBUG << "RosegardenGUIApp::slotConfigure\n"; + + ConfigureDialog *configDlg = + new ConfigureDialog(m_doc, kapp->config(), this); + + connect(configDlg, SIGNAL(updateAutoSaveInterval(unsigned int)), + this, SLOT(slotUpdateAutoSaveInterval(unsigned int))); + connect(configDlg, SIGNAL(updateSidebarStyle(unsigned int)), + this, SLOT(slotUpdateSidebarStyle(unsigned int))); + + configDlg->show(); +} + +void RosegardenGUIApp::slotEditDocumentProperties() +{ + RG_DEBUG << "RosegardenGUIApp::slotEditDocumentProperties\n"; + + DocumentConfigureDialog *configDlg = + new DocumentConfigureDialog(m_doc, this); + + configDlg->show(); +} + +void RosegardenGUIApp::slotOpenAudioPathSettings() +{ + RG_DEBUG << "RosegardenGUIApp::slotOpenAudioPathSettings\n"; + + DocumentConfigureDialog *configDlg = + new DocumentConfigureDialog(m_doc, this); + + configDlg->showAudioPage(); + configDlg->show(); +} + +void RosegardenGUIApp::slotEditKeys() +{ + KKeyDialog::configure(actionCollection()); +} + +void RosegardenGUIApp::slotEditToolbars() +{ + KEditToolbar dlg(actionCollection(), "rosegardenui.rc"); + + connect(&dlg, SIGNAL(newToolbarConfig()), + SLOT(slotUpdateToolbars())); + + dlg.exec(); +} + +void RosegardenGUIApp::slotUpdateToolbars() +{ + createGUI("rosegardenui.rc"); + m_viewToolBar->setChecked(!toolBar()->isHidden()); +} + +void RosegardenGUIApp::slotEditTempo() +{ + slotEditTempo(this); +} + +void RosegardenGUIApp::slotEditTempo(timeT atTime) +{ + slotEditTempo(this, atTime); +} + +void RosegardenGUIApp::slotEditTempo(QWidget *parent) +{ + slotEditTempo(parent, m_doc->getComposition().getPosition()); +} + +void RosegardenGUIApp::slotEditTempo(QWidget *parent, timeT atTime) +{ + RG_DEBUG << "RosegardenGUIApp::slotEditTempo\n"; + + TempoDialog tempoDialog(parent, m_doc); + + connect(&tempoDialog, + SIGNAL(changeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction)), + SLOT(slotChangeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction))); + + tempoDialog.setTempoPosition(atTime); + tempoDialog.exec(); +} + +void RosegardenGUIApp::slotEditTimeSignature() +{ + slotEditTimeSignature(this); +} + +void RosegardenGUIApp::slotEditTimeSignature(timeT atTime) +{ + slotEditTimeSignature(this, atTime); +} + +void RosegardenGUIApp::slotEditTimeSignature(QWidget *parent) +{ + slotEditTimeSignature(parent, m_doc->getComposition().getPosition()); +} + +void RosegardenGUIApp::slotEditTimeSignature(QWidget *parent, + timeT time) +{ + Composition &composition(m_doc->getComposition()); + + TimeSignature sig = composition.getTimeSignatureAt(time); + + TimeSignatureDialog dialog(parent, &composition, time, sig); + + if (dialog.exec() == QDialog::Accepted) { + + time = dialog.getTime(); + + if (dialog.shouldNormalizeRests()) { + m_doc->getCommandHistory()->addCommand + (new AddTimeSignatureAndNormalizeCommand + (&composition, time, dialog.getTimeSignature())); + } else { + m_doc->getCommandHistory()->addCommand + (new AddTimeSignatureCommand + (&composition, time, dialog.getTimeSignature())); + } + } +} + +void RosegardenGUIApp::slotEditTransportTime() +{ + slotEditTransportTime(this); +} + +void RosegardenGUIApp::slotEditTransportTime(QWidget *parent) +{ + TimeDialog dialog(parent, i18n("Move playback pointer to time"), + &m_doc->getComposition(), + m_doc->getComposition().getPosition(), + true); + if (dialog.exec() == QDialog::Accepted) { + m_doc->slotSetPointerPosition(dialog.getTime()); + } +} + +void RosegardenGUIApp::slotChangeZoom(int) +{ + double duration44 = TimeSignature(4, 4).getBarDuration(); + double value = double(m_zoomSlider->getCurrentSize()); + m_zoomLabel->setText(i18n("%1%").arg(duration44 / value)); + + RG_DEBUG << "RosegardenGUIApp::slotChangeZoom : zoom size = " + << m_zoomSlider->getCurrentSize() << endl; + + // initZoomToolbar sets the zoom value. With some old versions of + // Qt3.0, this can cause slotChangeZoom() to be called while the + // view hasn't been initialized yet, so we need to check it's not + // null + // + if (m_view) + m_view->setZoomSize(m_zoomSlider->getCurrentSize()); + + long newZoom = int(m_zoomSlider->getCurrentSize() * 1000.0); + + if (m_doc->getConfiguration().get + (DocumentConfiguration::ZoomLevel) != newZoom) { + + m_doc->getConfiguration().set + (DocumentConfiguration::ZoomLevel, newZoom); + + m_doc->slotDocumentModified(); + } +} + +void +RosegardenGUIApp::slotZoomIn() +{ + m_zoomSlider->increment(); +} + +void +RosegardenGUIApp::slotZoomOut() +{ + m_zoomSlider->decrement(); +} + +void +RosegardenGUIApp::slotChangeTempo(timeT time, + tempoT value, + tempoT target, + TempoDialog::TempoDialogAction action) +{ + //!!! handle target + + Composition &comp = m_doc->getComposition(); + + // We define a macro command here and build up the command + // label as we add commands on. + // + if (action == TempoDialog::AddTempo) { + m_doc->getCommandHistory()->addCommand + (new AddTempoChangeCommand(&comp, time, value, target)); + } else if (action == TempoDialog::ReplaceTempo) { + int index = comp.getTempoChangeNumberAt(time); + + // if there's no previous tempo change then just set globally + // + if (index == -1) { + m_doc->getCommandHistory()->addCommand + (new AddTempoChangeCommand(&comp, 0, value, target)); + return ; + } + + // get time of previous tempo change + timeT prevTime = comp.getTempoChange(index).first; + + KMacroCommand *macro = + new KMacroCommand(i18n("Replace Tempo Change at %1").arg(time)); + + macro->addCommand(new RemoveTempoChangeCommand(&comp, index)); + macro->addCommand(new AddTempoChangeCommand(&comp, prevTime, value, + target)); + + m_doc->getCommandHistory()->addCommand(macro); + + } else if (action == TempoDialog::AddTempoAtBarStart) { + m_doc->getCommandHistory()->addCommand(new + AddTempoChangeCommand(&comp, comp.getBarStartForTime(time), + value, target)); + } else if (action == TempoDialog::GlobalTempo || + action == TempoDialog::GlobalTempoWithDefault) { + KMacroCommand *macro = new KMacroCommand(i18n("Set Global Tempo")); + + // Remove all tempo changes in reverse order so as the index numbers + // don't becoming meaningless as the command gets unwound. + // + for (int i = 0; i < comp.getTempoChangeCount(); i++) + macro->addCommand(new RemoveTempoChangeCommand(&comp, + (comp.getTempoChangeCount() - 1 - i))); + + // add tempo change at time zero + // + macro->addCommand(new AddTempoChangeCommand(&comp, 0, value, target)); + + // are we setting default too? + // + if (action == TempoDialog::GlobalTempoWithDefault) { + macro->setName(i18n("Set Global and Default Tempo")); + macro->addCommand(new ModifyDefaultTempoCommand(&comp, value)); + } + + m_doc->getCommandHistory()->addCommand(macro); + + } else { + RG_DEBUG << "RosegardenGUIApp::slotChangeTempo() - " + << "unrecognised tempo command" << endl; + } +} + +void +RosegardenGUIApp::slotMoveTempo(timeT oldTime, + timeT newTime) +{ + Composition &comp = m_doc->getComposition(); + int index = comp.getTempoChangeNumberAt(oldTime); + + if (index < 0) + return ; + + KMacroCommand *macro = + new KMacroCommand(i18n("Move Tempo Change")); + + std::pair tc = + comp.getTempoChange(index); + std::pair tr = + comp.getTempoRamping(index, false); + + macro->addCommand(new RemoveTempoChangeCommand(&comp, index)); + macro->addCommand(new AddTempoChangeCommand(&comp, + newTime, + tc.second, + tr.first ? tr.second : -1)); + + m_doc->getCommandHistory()->addCommand(macro); +} + +void +RosegardenGUIApp::slotDeleteTempo(timeT t) +{ + Composition &comp = m_doc->getComposition(); + int index = comp.getTempoChangeNumberAt(t); + + if (index < 0) + return ; + + m_doc->getCommandHistory()->addCommand(new RemoveTempoChangeCommand + (&comp, index)); +} + +void +RosegardenGUIApp::slotAddMarker(timeT time) +{ + AddMarkerCommand *command = + new AddMarkerCommand(&m_doc->getComposition(), + time, + i18n("new marker"), + i18n("no description")); + + m_doc->getCommandHistory()->addCommand(command); +} + +void +RosegardenGUIApp::slotDeleteMarker(int id, timeT time, QString name, QString description) +{ + RemoveMarkerCommand *command = + new RemoveMarkerCommand(&m_doc->getComposition(), + id, + time, + qstrtostr(name), + qstrtostr(description)); + + m_doc->getCommandHistory()->addCommand(command); +} + +void +RosegardenGUIApp::slotDocumentModified(bool m) +{ + RG_DEBUG << "RosegardenGUIApp::slotDocumentModified(" << m << ") - doc path = " + << m_doc->getAbsFilePath() << endl; + + if (!m_doc->getAbsFilePath().isEmpty()) { + slotStateChanged("saved_file_modified", m); + } else { + slotStateChanged("new_file_modified", m); + } + +} + +void +RosegardenGUIApp::slotStateChanged(QString s, + bool noReverse) +{ + // RG_DEBUG << "RosegardenGUIApp::slotStateChanged " << s << "," << noReverse << endl; + + stateChanged(s, noReverse ? KXMLGUIClient::StateNoReverse : KXMLGUIClient::StateReverse); +} + +void +RosegardenGUIApp::slotTestClipboard() +{ + if (m_clipboard->isEmpty()) { + stateChanged("have_clipboard", KXMLGUIClient::StateReverse); + stateChanged("have_clipboard_single_segment", + KXMLGUIClient::StateReverse); + } else { + stateChanged("have_clipboard", KXMLGUIClient::StateNoReverse); + stateChanged("have_clipboard_single_segment", + (m_clipboard->isSingleSegment() ? + KXMLGUIClient::StateNoReverse : + KXMLGUIClient::StateReverse)); + } +} + +void +RosegardenGUIApp::plugAccelerators(QWidget *widget, QAccel *acc) +{ + + acc->connectItem(acc->insertItem(Key_Enter), + this, + SLOT(slotPlay())); + // Alternative shortcut for Play + acc->connectItem(acc->insertItem(Key_Return + CTRL), + this, + SLOT(slotPlay())); + + acc->connectItem(acc->insertItem(Key_Insert), + this, + SLOT(slotStop())); + + acc->connectItem(acc->insertItem(Key_PageDown), + this, + SLOT(slotFastforward())); + + acc->connectItem(acc->insertItem(Key_End), + this, + SLOT(slotRewind())); + + acc->connectItem(acc->insertItem(Key_Space), + this, + SLOT(slotToggleRecord())); + + TransportDialog *transport = + dynamic_cast(widget); + + if (transport) { + acc->connectItem(acc->insertItem(m_jumpToQuickMarkerAction->shortcut()), + this, + SLOT(slotJumpToQuickMarker())); + + acc->connectItem(acc->insertItem(m_setQuickMarkerAction->shortcut()), + this, + SLOT(slotSetQuickMarker())); + + connect(transport->PlayButton(), + SIGNAL(clicked()), + this, + SLOT(slotPlay())); + + connect(transport->StopButton(), + SIGNAL(clicked()), + this, + SLOT(slotStop())); + + connect(transport->FfwdButton(), + SIGNAL(clicked()), + SLOT(slotFastforward())); + + connect(transport->RewindButton(), + SIGNAL(clicked()), + this, + SLOT(slotRewind())); + + connect(transport->RecordButton(), + SIGNAL(clicked()), + this, + SLOT(slotRecord())); + + connect(transport->RewindEndButton(), + SIGNAL(clicked()), + this, + SLOT(slotRewindToBeginning())); + + connect(transport->FfwdEndButton(), + SIGNAL(clicked()), + this, + SLOT(slotFastForwardToEnd())); + + connect(transport->MetronomeButton(), + SIGNAL(clicked()), + this, + SLOT(slotToggleMetronome())); + + connect(transport->SoloButton(), + SIGNAL(toggled(bool)), + this, + SLOT(slotToggleSolo(bool))); + + connect(transport->TimeDisplayButton(), + SIGNAL(clicked()), + this, + SLOT(slotRefreshTimeDisplay())); + + connect(transport->ToEndButton(), + SIGNAL(clicked()), + SLOT(slotRefreshTimeDisplay())); + } +} + +void +RosegardenGUIApp::setCursor(const QCursor& cursor) +{ + KDockMainWindow::setCursor(cursor); + + // play it safe, so we can use this class at anytime even very early in the app init + if ((getView() && + getView()->getTrackEditor() && + getView()->getTrackEditor()->getSegmentCanvas() && + getView()->getTrackEditor()->getSegmentCanvas()->viewport())) { + + getView()->getTrackEditor()->getSegmentCanvas()->viewport()->setCursor(cursor); + } + + // view, main window... + // + getView()->setCursor(cursor); + + // toolbars... + // + QPtrListIterator tbIter = toolBarIterator(); + KToolBar* tb = 0; + while ((tb = tbIter.current()) != 0) { + tb->setCursor(cursor); + ++tbIter; + } + + m_dockLeft->setCursor(cursor); +} + +QString +RosegardenGUIApp::createNewAudioFile() +{ + AudioFile *aF = 0; + try { + aF = m_doc->getAudioFileManager().createRecordingAudioFile(); + if (!aF) { + // createRecordingAudioFile doesn't actually write to the disk, + // and in principle it shouldn't fail + std::cerr << "ERROR: RosegardenGUIApp::createNewAudioFile: Failed to create recording audio file" << std::endl; + return ""; + } else { + return aF->getFilename().c_str(); + } + } catch (AudioFileManager::BadAudioPathException e) { + delete aF; + std::cerr << "ERROR: RosegardenGUIApp::createNewAudioFile: Failed to create recording audio file: " << e.getMessage() << std::endl; + return ""; + } +} + +QValueVector +RosegardenGUIApp::createRecordAudioFiles(const QValueVector &recordInstruments) +{ + QValueVector qv; + for (unsigned int i = 0; i < recordInstruments.size(); ++i) { + AudioFile *aF = 0; + try { + aF = m_doc->getAudioFileManager().createRecordingAudioFile(); + if (aF) { + // createRecordingAudioFile doesn't actually write to the disk, + // and in principle it shouldn't fail + qv.push_back(aF->getFilename().c_str()); + m_doc->addRecordAudioSegment(recordInstruments[i], + aF->getId()); + } else { + std::cerr << "ERROR: RosegardenGUIApp::createRecordAudioFiles: Failed to create recording audio file" << std::endl; + return qv; + } + } catch (AudioFileManager::BadAudioPathException e) { + delete aF; + std::cerr << "ERROR: RosegardenGUIApp::createRecordAudioFiles: Failed to create recording audio file: " << e.getMessage() << std::endl; + return qv; + } + } + return qv; +} + +QString +RosegardenGUIApp::getAudioFilePath() +{ + return QString(m_doc->getAudioFileManager().getAudioPath().c_str()); +} + +QValueVector +RosegardenGUIApp::getArmedInstruments() +{ + std::set + iid; + + const Composition::recordtrackcontainer &tr = + m_doc->getComposition().getRecordTracks(); + + for (Composition::recordtrackcontainer::const_iterator i = + tr.begin(); i != tr.end(); ++i) { + TrackId tid = (*i); + Track *track = m_doc->getComposition().getTrackById(tid); + if (track) { + iid.insert(track->getInstrument()); + } else { + std::cerr << "Warning: RosegardenGUIApp::getArmedInstruments: Armed track " << tid << " not found in Composition" << std::endl; + } + } + + QValueVector iv; + for (std::set + ::iterator ii = iid.begin(); + ii != iid.end(); ++ii) { + iv.push_back(*ii); + } + return iv; +} + +void +RosegardenGUIApp::showError(QString error) +{ + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + + // This is principally used for return values from DSSI plugin + // configure() calls. It seems some plugins return a string + // telling you when everything's OK, as well as error strings, but + // dssi.h does make it reasonably clear that configure() should + // only return a string when there is actually a problem, so we're + // going to stick with a sorry dialog here rather than an + // information one + + KMessageBox::sorry(0, error); + + CurrentProgressDialog::thaw(); +} + +void +RosegardenGUIApp::slotAudioManager() +{ + if (m_audioManagerDialog) { + m_audioManagerDialog->show(); + m_audioManagerDialog->raise(); + m_audioManagerDialog->setActiveWindow(); + return ; + } + + m_audioManagerDialog = + new AudioManagerDialog(this, m_doc); + + connect(m_audioManagerDialog, + SIGNAL(playAudioFile(AudioFileId, + const RealTime &, + const RealTime&)), + SLOT(slotPlayAudioFile(AudioFileId, + const RealTime &, + const RealTime &))); + + connect(m_audioManagerDialog, + SIGNAL(addAudioFile(AudioFileId)), + SLOT(slotAddAudioFile(AudioFileId))); + + connect(m_audioManagerDialog, + SIGNAL(deleteAudioFile(AudioFileId)), + SLOT(slotDeleteAudioFile(AudioFileId))); + + // + // Sync segment selection between audio man. dialog and main window + // + + // from dialog to us... + connect(m_audioManagerDialog, + SIGNAL(segmentsSelected(const SegmentSelection&)), + m_view, + SLOT(slotPropagateSegmentSelection(const SegmentSelection&))); + + // and from us to dialog + connect(this, SIGNAL(segmentsSelected(const SegmentSelection&)), + m_audioManagerDialog, + SLOT(slotSegmentSelection(const SegmentSelection&))); + + + connect(m_audioManagerDialog, + SIGNAL(deleteSegments(const SegmentSelection&)), + SLOT(slotDeleteSegments(const SegmentSelection&))); + + connect(m_audioManagerDialog, + SIGNAL(insertAudioSegment(AudioFileId, + const RealTime&, + const RealTime&)), + m_view, + SLOT(slotAddAudioSegmentDefaultPosition(AudioFileId, + const RealTime&, + const RealTime&))); + connect(m_audioManagerDialog, + SIGNAL(cancelPlayingAudioFile(AudioFileId)), + SLOT(slotCancelAudioPlayingFile(AudioFileId))); + + connect(m_audioManagerDialog, + SIGNAL(deleteAllAudioFiles()), + SLOT(slotDeleteAllAudioFiles())); + + // Make sure we know when the audio man. dialog is closing + // + connect(m_audioManagerDialog, + SIGNAL(closing()), + SLOT(slotAudioManagerClosed())); + + // And that it goes away when the current document is changing + // + connect(this, SIGNAL(documentAboutToChange()), + m_audioManagerDialog, SLOT(close())); + + m_audioManagerDialog->setAudioSubsystemStatus( + m_seqManager->getSoundDriverStatus() & AUDIO_OK); + + plugAccelerators(m_audioManagerDialog, + m_audioManagerDialog->getAccelerators()); + + m_audioManagerDialog->show(); +} + +void +RosegardenGUIApp::slotPlayAudioFile(unsigned int id, + const RealTime &startTime, + const RealTime &duration) +{ + AudioFile *aF = m_doc->getAudioFileManager().getAudioFile(id); + + if (aF == 0) + return ; + + MappedEvent mE(m_doc->getStudio(). + getAudioPreviewInstrument(), + id, + RealTime( -120, 0), + duration, // duration + startTime); // start index + + StudioControl::sendMappedEvent(mE); + +} + +void +RosegardenGUIApp::slotAddAudioFile(unsigned int id) +{ + AudioFile *aF = m_doc->getAudioFileManager().getAudioFile(id); + + if (aF == 0) + return ; + + QCString replyType; + QByteArray replyData; + QByteArray data; + QDataStream streamOut(data, IO_WriteOnly); + + // We have to pass the filename as a QString + // + streamOut << QString(strtoqstr(aF->getFilename())); + streamOut << (int)aF->getId(); + + if (rgapp->sequencerCall("addAudioFile(QString, int)", replyType, replyData, data)) { + QDataStream streamIn(replyData, IO_ReadOnly); + int result; + streamIn >> result; + if (!result) { + KMessageBox::error(this, i18n("Sequencer failed to add audio file %1").arg(aF->getFilename().c_str())); + } + } +} + +void +RosegardenGUIApp::slotDeleteAudioFile(unsigned int id) +{ + if (m_doc->getAudioFileManager().removeFile(id) == false) + return ; + + QCString replyType; + QByteArray replyData; + QByteArray data; + QDataStream streamOut(data, IO_WriteOnly); + + // file id + // + streamOut << (int)id; + + if (rgapp->sequencerCall("removeAudioFile(int)", replyType, replyData, data)) { + QDataStream streamIn(replyData, IO_ReadOnly); + int result; + streamIn >> result; + if (!result) { + KMessageBox::error(this, i18n("Sequencer failed to remove audio file id %1").arg(id)); + } + } +} + +void +RosegardenGUIApp::slotDeleteSegments(const SegmentSelection &selection) +{ + m_view->slotPropagateSegmentSelection(selection); + slotDeleteSelectedSegments(); +} + +void +RosegardenGUIApp::slotCancelAudioPlayingFile(AudioFileId id) +{ + AudioFile *aF = m_doc->getAudioFileManager().getAudioFile(id); + + if (aF == 0) + return ; + + MappedEvent mE(m_doc->getStudio(). + getAudioPreviewInstrument(), + MappedEvent::AudioCancel, + id); + + StudioControl::sendMappedEvent(mE); +} + +void +RosegardenGUIApp::slotDeleteAllAudioFiles() +{ + m_doc->getAudioFileManager().clear(); + + // Clear at the sequencer + // + QCString replyType; + QByteArray replyData; + QByteArray data; + + rgapp->sequencerCall("clearAllAudioFiles()", replyType, replyData, data); +} + +void +RosegardenGUIApp::slotRepeatingSegments() +{ + m_view->getTrackEditor()->slotTurnRepeatingSegmentToRealCopies(); +} + +void +RosegardenGUIApp::slotRelabelSegments() +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection(m_view->getSelection()); + QString editLabel; + + if (selection.size() == 0) + return ; + else if (selection.size() == 1) + editLabel = i18n("Modify Segment label"); + else + editLabel = i18n("Modify Segments label"); + + KTmpStatusMsg msg(i18n("Relabelling selection..."), this); + + // Generate label + QString label = strtoqstr((*selection.begin())->getLabel()); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + if (strtoqstr((*i)->getLabel()) != label) + label = ""; + } + + bool ok = false; + + QString newLabel = KInputDialog::getText(editLabel, + i18n("Enter new label"), + label, + &ok, + this); + + if (ok) { + m_doc->getCommandHistory()->addCommand + (new SegmentLabelCommand(selection, newLabel)); + m_view->getTrackEditor()->getSegmentCanvas()->slotUpdateSegmentsDrawBuffer(); + } +} + +void +RosegardenGUIApp::slotTransposeSegments() +{ + if (!m_view->haveSelection()) + return ; + + IntervalDialog intervalDialog(this, true, true); + int ok = intervalDialog.exec(); + + int semitones = intervalDialog.getChromaticDistance(); + int steps = intervalDialog.getDiatonicDistance(); + + if (!ok || (semitones == 0 && steps == 0)) return; + + m_doc->getCommandHistory()->addCommand + (new SegmentTransposeCommand(m_view->getSelection(), intervalDialog.getChangeKey(), steps, semitones, intervalDialog.getTransposeSegmentBack())); +} + +void +RosegardenGUIApp::slotChangeCompositionLength() +{ + CompositionLengthDialog dialog(this, &m_doc->getComposition()); + + if (dialog.exec() == QDialog::Accepted) { + ChangeCompositionLengthCommand *command + = new ChangeCompositionLengthCommand( + &m_doc->getComposition(), + dialog.getStartMarker(), + dialog.getEndMarker()); + + m_view->getTrackEditor()->getSegmentCanvas()->clearSegmentRectsCache(true); + m_doc->getCommandHistory()->addCommand(command); + } +} + +void +RosegardenGUIApp::slotManageMIDIDevices() +{ + if (m_deviceManager) { + m_deviceManager->show(); + m_deviceManager->raise(); + m_deviceManager->setActiveWindow(); + return ; + } + + m_deviceManager = new DeviceManagerDialog(this, m_doc); + + connect(m_deviceManager, SIGNAL(closing()), + this, SLOT(slotDeviceManagerClosed())); + + connect(this, SIGNAL(documentAboutToChange()), + m_deviceManager, SLOT(close())); + + // Cheating way of updating the track/instrument list + // + connect(m_deviceManager, SIGNAL(deviceNamesChanged()), + m_view, SLOT(slotSynchroniseWithComposition())); + + connect(m_deviceManager, SIGNAL(editBanks(DeviceId)), + this, SLOT(slotEditBanks(DeviceId))); + + connect(m_deviceManager, SIGNAL(editControllers(DeviceId)), + this, SLOT(slotEditControlParameters(DeviceId))); + + if (m_midiMixer) { + connect(m_deviceManager, SIGNAL(deviceNamesChanged()), + m_midiMixer, SLOT(slotSynchronise())); + + } + + + m_deviceManager->show(); +} + +void +RosegardenGUIApp::slotManageSynths() +{ + if (m_synthManager) { + m_synthManager->show(); + m_synthManager->raise(); + m_synthManager->setActiveWindow(); + return ; + } + + m_synthManager = new SynthPluginManagerDialog(this, m_doc +#ifdef HAVE_LIBLO + , m_pluginGUIManager +#endif + ); + + connect(m_synthManager, SIGNAL(closing()), + this, SLOT(slotSynthPluginManagerClosed())); + + connect(this, SIGNAL(documentAboutToChange()), + m_synthManager, SLOT(close())); + + connect(m_synthManager, + SIGNAL(pluginSelected(InstrumentId, int, int)), + this, + SLOT(slotPluginSelected(InstrumentId, int, int))); + + connect(m_synthManager, + SIGNAL(showPluginDialog(QWidget *, InstrumentId, int)), + this, + SLOT(slotShowPluginDialog(QWidget *, InstrumentId, int))); + + connect(m_synthManager, + SIGNAL(showPluginGUI(InstrumentId, int)), + this, + SLOT(slotShowPluginGUI(InstrumentId, int))); + + m_synthManager->show(); +} + +void +RosegardenGUIApp::slotOpenAudioMixer() +{ + if (m_audioMixer) { + m_audioMixer->show(); + m_audioMixer->raise(); + m_audioMixer->setActiveWindow(); + return ; + } + + m_audioMixer = new AudioMixerWindow(this, m_doc); + + connect(m_audioMixer, SIGNAL(windowActivated()), + m_view, SLOT(slotActiveMainWindowChanged())); + + connect(m_view, SIGNAL(controllerDeviceEventReceived(MappedEvent *, const void *)), + m_audioMixer, SLOT(slotControllerDeviceEventReceived(MappedEvent *, const void *))); + + connect(m_audioMixer, SIGNAL(closing()), + this, SLOT(slotAudioMixerClosed())); + + connect(m_audioMixer, SIGNAL(selectPlugin(QWidget *, InstrumentId, int)), + this, SLOT(slotShowPluginDialog(QWidget *, InstrumentId, int))); + + connect(this, + SIGNAL(pluginSelected(InstrumentId, int, int)), + m_audioMixer, + SLOT(slotPluginSelected(InstrumentId, int, int))); + + connect(this, + SIGNAL(pluginBypassed(InstrumentId, int, bool)), + m_audioMixer, + SLOT(slotPluginBypassed(InstrumentId, int, bool))); + + connect(this, SIGNAL(documentAboutToChange()), + m_audioMixer, SLOT(close())); + + connect(m_view, SIGNAL(checkTrackAssignments()), + m_audioMixer, SLOT(slotTrackAssignmentsChanged())); + + connect(m_audioMixer, SIGNAL(play()), + this, SLOT(slotPlay())); + connect(m_audioMixer, SIGNAL(stop()), + this, SLOT(slotStop())); + connect(m_audioMixer, SIGNAL(fastForwardPlayback()), + this, SLOT(slotFastforward())); + connect(m_audioMixer, SIGNAL(rewindPlayback()), + this, SLOT(slotRewind())); + connect(m_audioMixer, SIGNAL(fastForwardPlaybackToEnd()), + this, SLOT(slotFastForwardToEnd())); + connect(m_audioMixer, SIGNAL(rewindPlaybackToBeginning()), + this, SLOT(slotRewindToBeginning())); + connect(m_audioMixer, SIGNAL(record()), + this, SLOT(slotRecord())); + connect(m_audioMixer, SIGNAL(panic()), + this, SLOT(slotPanic())); + + connect(m_audioMixer, + SIGNAL(instrumentParametersChanged(InstrumentId)), + this, + SIGNAL(instrumentParametersChanged(InstrumentId))); + + connect(this, + SIGNAL(instrumentParametersChanged(InstrumentId)), + m_audioMixer, + SLOT(slotUpdateInstrument(InstrumentId))); + + if (m_synthManager) { + connect(m_synthManager, + SIGNAL(pluginSelected(InstrumentId, int, int)), + m_audioMixer, + SLOT(slotPluginSelected(InstrumentId, int, int))); + } + + plugAccelerators(m_audioMixer, m_audioMixer->getAccelerators()); + + m_audioMixer->show(); +} + +void +RosegardenGUIApp::slotOpenMidiMixer() +{ + if (m_midiMixer) { + m_midiMixer->show(); + m_midiMixer->raise(); + m_midiMixer->setActiveWindow(); + return ; + } + + m_midiMixer = new MidiMixerWindow(this, m_doc); + + connect(m_midiMixer, SIGNAL(windowActivated()), + m_view, SLOT(slotActiveMainWindowChanged())); + + connect(m_view, SIGNAL(controllerDeviceEventReceived(MappedEvent *, const void *)), + m_midiMixer, SLOT(slotControllerDeviceEventReceived(MappedEvent *, const void *))); + + connect(m_midiMixer, SIGNAL(closing()), + this, SLOT(slotMidiMixerClosed())); + + connect(this, SIGNAL(documentAboutToChange()), + m_midiMixer, SLOT(close())); + + connect(m_midiMixer, SIGNAL(play()), + this, SLOT(slotPlay())); + connect(m_midiMixer, SIGNAL(stop()), + this, SLOT(slotStop())); + connect(m_midiMixer, SIGNAL(fastForwardPlayback()), + this, SLOT(slotFastforward())); + connect(m_midiMixer, SIGNAL(rewindPlayback()), + this, SLOT(slotRewind())); + connect(m_midiMixer, SIGNAL(fastForwardPlaybackToEnd()), + this, SLOT(slotFastForwardToEnd())); + connect(m_midiMixer, SIGNAL(rewindPlaybackToBeginning()), + this, SLOT(slotRewindToBeginning())); + connect(m_midiMixer, SIGNAL(record()), + this, SLOT(slotRecord())); + connect(m_midiMixer, SIGNAL(panic()), + this, SLOT(slotPanic())); + + connect(m_midiMixer, + SIGNAL(instrumentParametersChanged(InstrumentId)), + this, + SIGNAL(instrumentParametersChanged(InstrumentId))); + + connect(this, + SIGNAL(instrumentParametersChanged(InstrumentId)), + m_midiMixer, + SLOT(slotUpdateInstrument(InstrumentId))); + + plugAccelerators(m_midiMixer, m_midiMixer->getAccelerators()); + + m_midiMixer->show(); +} + +void +RosegardenGUIApp::slotEditControlParameters(DeviceId device) +{ + for (std::set + ::iterator i = m_controlEditors.begin(); + i != m_controlEditors.end(); ++i) { + if ((*i)->getDevice() == device) { + (*i)->show(); + (*i)->raise(); + (*i)->setActiveWindow(); + return ; + } + } + + ControlEditorDialog *controlEditor = new ControlEditorDialog(this, m_doc, + device); + m_controlEditors.insert(controlEditor); + + RG_DEBUG << "inserting control editor dialog, have " << m_controlEditors.size() << " now" << endl; + + connect(controlEditor, SIGNAL(closing()), + SLOT(slotControlEditorClosed())); + + connect(this, SIGNAL(documentAboutToChange()), + controlEditor, SLOT(close())); + + connect(m_doc, SIGNAL(devicesResyncd()), + controlEditor, SLOT(slotUpdate())); + + controlEditor->show(); +} + +void +RosegardenGUIApp::slotEditBanks() +{ + slotEditBanks(Device::NO_DEVICE); +} + +void +RosegardenGUIApp::slotEditBanks(DeviceId device) +{ + if (m_bankEditor) { + if (device != Device::NO_DEVICE) + m_bankEditor->setCurrentDevice(device); + m_bankEditor->show(); + m_bankEditor->raise(); + m_bankEditor->setActiveWindow(); + return ; + } + + m_bankEditor = new BankEditorDialog(this, m_doc, device); + + connect(m_bankEditor, SIGNAL(closing()), + this, SLOT(slotBankEditorClosed())); + + connect(this, SIGNAL(documentAboutToChange()), + m_bankEditor, SLOT(slotFileClose())); + + // Cheating way of updating the track/instrument list + // + connect(m_bankEditor, SIGNAL(deviceNamesChanged()), + m_view, SLOT(slotSynchroniseWithComposition())); + + m_bankEditor->show(); +} + +void +RosegardenGUIApp::slotManageTriggerSegments() +{ + if (m_triggerSegmentManager) { + m_triggerSegmentManager->show(); + m_triggerSegmentManager->raise(); + m_triggerSegmentManager->setActiveWindow(); + return ; + } + + m_triggerSegmentManager = new TriggerSegmentManager(this, m_doc); + + connect(m_triggerSegmentManager, SIGNAL(closing()), + SLOT(slotTriggerManagerClosed())); + + connect(m_triggerSegmentManager, SIGNAL(editTriggerSegment(int)), + m_view, SLOT(slotEditTriggerSegment(int))); + + m_triggerSegmentManager->show(); +} + +void +RosegardenGUIApp::slotTriggerManagerClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotTriggerManagerClosed" << endl; + + m_triggerSegmentManager = 0; +} + +void +RosegardenGUIApp::slotEditMarkers() +{ + if (m_markerEditor) { + m_markerEditor->show(); + m_markerEditor->raise(); + m_markerEditor->setActiveWindow(); + return ; + } + + m_markerEditor = new MarkerEditor(this, m_doc); + + connect(m_markerEditor, SIGNAL(closing()), + SLOT(slotMarkerEditorClosed())); + + connect(m_markerEditor, SIGNAL(jumpToMarker(timeT)), + m_doc, SLOT(slotSetPointerPosition(timeT))); + + plugAccelerators(m_markerEditor, m_markerEditor->getAccelerators()); + + m_markerEditor->show(); +} + +void +RosegardenGUIApp::slotMarkerEditorClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotMarkerEditorClosed" << endl; + + m_markerEditor = 0; +} + +void +RosegardenGUIApp::slotEditTempos(timeT t) +{ + if (m_tempoView) { + m_tempoView->show(); + m_tempoView->raise(); + m_tempoView->setActiveWindow(); + return ; + } + + m_tempoView = new TempoView(m_doc, getView(), t); + + connect(m_tempoView, SIGNAL(closing()), + SLOT(slotTempoViewClosed())); + + connect(m_tempoView, SIGNAL(windowActivated()), + getView(), SLOT(slotActiveMainWindowChanged())); + + connect(m_tempoView, + SIGNAL(changeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction)), + this, + SLOT(slotChangeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction))); + + connect(m_tempoView, SIGNAL(saveFile()), this, SLOT(slotFileSave())); + + plugAccelerators(m_tempoView, m_tempoView->getAccelerators()); + + m_tempoView->show(); +} + +void +RosegardenGUIApp::slotTempoViewClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotTempoViewClosed" << endl; + + m_tempoView = 0; +} + +void +RosegardenGUIApp::slotControlEditorClosed() +{ + const QObject *s = sender(); + + RG_DEBUG << "RosegardenGUIApp::slotControlEditorClosed" << endl; + + for (std::set + ::iterator i = m_controlEditors.begin(); + i != m_controlEditors.end(); ++i) { + if (*i == s) { + m_controlEditors.erase(i); + RG_DEBUG << "removed control editor dialog, have " << m_controlEditors.size() << " left" << endl; + return ; + } + } + + std::cerr << "WARNING: control editor " << s << " closed, but couldn't find it in our control editor list (we have " << m_controlEditors.size() << " editors)" << std::endl; +} + +void +RosegardenGUIApp::slotShowPluginDialog(QWidget *parent, + InstrumentId instrumentId, + int index) +{ + if (!parent) + parent = this; + + int key = (index << 16) + instrumentId; + + if (m_pluginDialogs[key]) { + m_pluginDialogs[key]->show(); + m_pluginDialogs[key]->raise(); + m_pluginDialogs[key]->setActiveWindow(); + return ; + } + + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotShowPluginDialog - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + // only create a dialog if we've got a plugin instance + AudioPluginInstance *inst = + container->getPlugin(index); + + if (!inst) { + RG_DEBUG << "RosegardenGUIApp::slotShowPluginDialog - " + << "no AudioPluginInstance found for index " + << index << endl; + return ; + } + + // Create the plugin dialog + // + AudioPluginDialog *dialog = + new AudioPluginDialog(parent, + m_doc->getPluginManager(), +#ifdef HAVE_LIBLO + m_pluginGUIManager, +#endif + container, + index); + + connect(dialog, SIGNAL(windowActivated()), + m_view, SLOT(slotActiveMainWindowChanged())); + +/* This feature isn't provided by the plugin dialog + connect(m_view, SIGNAL(controllerDeviceEventReceived(MappedEvent *, const void *)), + dialog, SLOT(slotControllerDeviceEventReceived(MappedEvent *, const void *))); +*/ + + // Plug the new dialog into the standard keyboard accelerators so + // that we can use them still while the plugin has focus. + // + plugAccelerators(dialog, dialog->getAccelerators()); + + connect(dialog, + SIGNAL(pluginSelected(InstrumentId, int, int)), + this, + SLOT(slotPluginSelected(InstrumentId, int, int))); + + connect(dialog, + SIGNAL(pluginPortChanged(InstrumentId, int, int)), + this, + SLOT(slotPluginPortChanged(InstrumentId, int, int))); + + connect(dialog, + SIGNAL(pluginProgramChanged(InstrumentId, int)), + this, + SLOT(slotPluginProgramChanged(InstrumentId, int))); + + connect(dialog, + SIGNAL(changePluginConfiguration(InstrumentId, int, bool, QString, QString)), + this, + SLOT(slotChangePluginConfiguration(InstrumentId, int, bool, QString, QString))); + + connect(dialog, + SIGNAL(showPluginGUI(InstrumentId, int)), + this, + SLOT(slotShowPluginGUI(InstrumentId, int))); + + connect(dialog, + SIGNAL(stopPluginGUI(InstrumentId, int)), + this, + SLOT(slotStopPluginGUI(InstrumentId, int))); + + connect(dialog, + SIGNAL(bypassed(InstrumentId, int, bool)), + this, + SLOT(slotPluginBypassed(InstrumentId, int, bool))); + + connect(dialog, + SIGNAL(destroyed(InstrumentId, int)), + this, + SLOT(slotPluginDialogDestroyed(InstrumentId, int))); + + connect(this, SIGNAL(documentAboutToChange()), dialog, SLOT(close())); + + m_pluginDialogs[key] = dialog; + m_pluginDialogs[key]->show(); + + // Set modified + m_doc->slotDocumentModified(); +} + +void +RosegardenGUIApp::slotPluginSelected(InstrumentId instrumentId, + int index, int plugin) +{ + const QObject *s = sender(); + + bool fromSynthMgr = (s == m_synthManager); + + // It's assumed that ports etc will already have been set up on + // the AudioPluginInstance before this is invoked. + + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotPluginSelected - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = + container->getPlugin(index); + + if (!inst) { + RG_DEBUG << "RosegardenGUIApp::slotPluginSelected - " + << "got index of unknown plugin!" << endl; + return ; + } + + if (plugin == -1) { + // Destroy plugin instance + //!!! seems iffy -- why can't we just unassign it? + + if (StudioControl:: + destroyStudioObject(inst->getMappedId())) { + RG_DEBUG << "RosegardenGUIApp::slotPluginSelected - " + << "cannot destroy Studio object " + << inst->getMappedId() << endl; + } + + inst->setAssigned(false); + } else { + // If unassigned then create a sequencer instance of this + // AudioPluginInstance. + // + if (inst->isAssigned()) { + RG_DEBUG << "RosegardenGUIApp::slotPluginSelected - " + << " setting identifier for mapper id " << inst->getMappedId() + << " to " << inst->getIdentifier() << endl; + + StudioControl::setStudioObjectProperty + (inst->getMappedId(), + MappedPluginSlot::Identifier, + strtoqstr(inst->getIdentifier())); + } else { + // create a studio object at the sequencer + MappedObjectId newId = + StudioControl::createStudioObject + (MappedObject::PluginSlot); + + RG_DEBUG << "RosegardenGUIApp::slotPluginSelected - " + << " new MappedObjectId = " << newId << endl; + + // set the new Mapped ID and that this instance + // is assigned + inst->setMappedId(newId); + inst->setAssigned(true); + + // set the instrument id + StudioControl::setStudioObjectProperty + (newId, + MappedObject::Instrument, + MappedObjectValue(instrumentId)); + + // set the position + StudioControl::setStudioObjectProperty + (newId, + MappedObject::Position, + MappedObjectValue(index)); + + // set the plugin id + StudioControl::setStudioObjectProperty + (newId, + MappedPluginSlot::Identifier, + strtoqstr(inst->getIdentifier())); + } + } + + int pluginMappedId = inst->getMappedId(); + + //!!! much code duplicated here from RosegardenGUIDoc::initialiseStudio + + inst->setConfigurationValue + (qstrtostr(PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY), + m_doc->getAudioFileManager().getAudioPath()); + + // Set opaque string configuration data (e.g. for DSSI plugin) + // + MappedObjectPropertyList config; + for (AudioPluginInstance::ConfigMap::const_iterator + i = inst->getConfiguration().begin(); + i != inst->getConfiguration().end(); ++i) { + config.push_back(strtoqstr(i->first)); + config.push_back(strtoqstr(i->second)); + } + StudioControl::setStudioObjectPropertyList + (pluginMappedId, + MappedPluginSlot::Configuration, + config); + + // Set the bypass + // + StudioControl::setStudioObjectProperty + (pluginMappedId, + MappedPluginSlot::Bypassed, + MappedObjectValue(inst->isBypassed())); + + // Set the program + // + if (inst->getProgram() != "") { + StudioControl::setStudioObjectProperty + (pluginMappedId, + MappedPluginSlot::Program, + strtoqstr(inst->getProgram())); + } + + // Set all the port values + // + PortInstanceIterator portIt; + + for (portIt = inst->begin(); + portIt != inst->end(); ++portIt) { + StudioControl::setStudioPluginPort + (pluginMappedId, + (*portIt)->number, + (*portIt)->value); + } + + if (fromSynthMgr) { + int key = (index << 16) + instrumentId; + if (m_pluginDialogs[key]) { + m_pluginDialogs[key]->updatePlugin(plugin); + } + } else if (m_synthManager) { + m_synthManager->updatePlugin(instrumentId, plugin); + } + + emit pluginSelected(instrumentId, index, plugin); + + // Set modified + m_doc->slotDocumentModified(); +} + +void +RosegardenGUIApp::slotChangePluginPort(InstrumentId instrumentId, + int pluginIndex, + int portIndex, + float value) +{ + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotChangePluginPort - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = container->getPlugin(pluginIndex); + if (!inst) { + RG_DEBUG << "RosegardenGUIApp::slotChangePluginPort - " + << "no plugin at index " << pluginIndex << " on " << instrumentId << endl; + return ; + } + + PluginPortInstance *port = inst->getPort(portIndex); + if (!port) { + RG_DEBUG << "RosegardenGUIApp::slotChangePluginPort - no port " + << portIndex << endl; + return ; + } + + RG_DEBUG << "RosegardenGUIApp::slotPluginPortChanged - " + << "setting plugin port (" << inst->getMappedId() + << ", " << portIndex << ") from " << port->value + << " to " << value << endl; + + port->setValue(value); + + StudioControl::setStudioPluginPort(inst->getMappedId(), + portIndex, port->value); + + m_doc->slotDocumentModified(); + + // This modification came from The Outside! + int key = (pluginIndex << 16) + instrumentId; + if (m_pluginDialogs[key]) { + m_pluginDialogs[key]->updatePluginPortControl(portIndex); + } +} + +void +RosegardenGUIApp::slotPluginPortChanged(InstrumentId instrumentId, + int pluginIndex, + int portIndex) +{ + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotPluginPortChanged - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = container->getPlugin(pluginIndex); + if (!inst) { + RG_DEBUG << "RosegardenGUIApp::slotPluginPortChanged - " + << "no plugin at index " << pluginIndex << " on " << instrumentId << endl; + return ; + } + + PluginPortInstance *port = inst->getPort(portIndex); + if (!port) { + RG_DEBUG << "RosegardenGUIApp::slotPluginPortChanged - no port " + << portIndex << endl; + return ; + } + + RG_DEBUG << "RosegardenGUIApp::slotPluginPortChanged - " + << "setting plugin port (" << inst->getMappedId() + << ", " << portIndex << ") to " << port->value << endl; + + StudioControl::setStudioPluginPort(inst->getMappedId(), + portIndex, port->value); + + m_doc->slotDocumentModified(); + +#ifdef HAVE_LIBLO + // This modification came from our own plugin dialog, so update + // any external GUIs + if (m_pluginGUIManager) { + m_pluginGUIManager->updatePort(instrumentId, + pluginIndex, + portIndex); + } +#endif +} + +void +RosegardenGUIApp::slotChangePluginProgram(InstrumentId instrumentId, + int pluginIndex, + QString program) +{ + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotChangePluginProgram - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = container->getPlugin(pluginIndex); + if (!inst) { + RG_DEBUG << "RosegardenGUIApp::slotChangePluginProgram - " + << "no plugin at index " << pluginIndex << " on " << instrumentId << endl; + return ; + } + + RG_DEBUG << "RosegardenGUIApp::slotChangePluginProgram - " + << "setting plugin program (" + << inst->getMappedId() << ") from " << inst->getProgram() + << " to " << program << endl; + + inst->setProgram(qstrtostr(program)); + + StudioControl:: + setStudioObjectProperty(inst->getMappedId(), + MappedPluginSlot::Program, + program); + + PortInstanceIterator portIt; + + for (portIt = inst->begin(); + portIt != inst->end(); ++portIt) { + float value = StudioControl::getStudioPluginPort + (inst->getMappedId(), + (*portIt)->number); + (*portIt)->value = value; + } + + // Set modified + m_doc->slotDocumentModified(); + + int key = (pluginIndex << 16) + instrumentId; + if (m_pluginDialogs[key]) { + m_pluginDialogs[key]->updatePluginProgramControl(); + } +} + +void +RosegardenGUIApp::slotPluginProgramChanged(InstrumentId instrumentId, + int pluginIndex) +{ + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotPluginProgramChanged - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = container->getPlugin(pluginIndex); + if (!inst) { + RG_DEBUG << "RosegardenGUIApp::slotPluginProgramChanged - " + << "no plugin at index " << pluginIndex << " on " << instrumentId << endl; + return ; + } + + QString program = strtoqstr(inst->getProgram()); + + RG_DEBUG << "RosegardenGUIApp::slotPluginProgramChanged - " + << "setting plugin program (" + << inst->getMappedId() << ") to " << program << endl; + + StudioControl:: + setStudioObjectProperty(inst->getMappedId(), + MappedPluginSlot::Program, + program); + + PortInstanceIterator portIt; + + for (portIt = inst->begin(); + portIt != inst->end(); ++portIt) { + float value = StudioControl::getStudioPluginPort + (inst->getMappedId(), + (*portIt)->number); + (*portIt)->value = value; + } + + // Set modified + m_doc->slotDocumentModified(); + +#ifdef HAVE_LIBLO + + if (m_pluginGUIManager) + m_pluginGUIManager->updateProgram(instrumentId, + pluginIndex); +#endif +} + +void +RosegardenGUIApp::slotChangePluginConfiguration(InstrumentId instrumentId, + int index, + bool global, + QString key, + QString value) +{ + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotChangePluginConfiguration - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = container->getPlugin(index); + + if (global && inst) { + + // Set the same configuration on other plugins in the same + // instance group + + AudioPlugin *pl = + m_pluginManager->getPluginByIdentifier(strtoqstr(inst->getIdentifier())); + + if (pl && pl->isGrouped()) { + + InstrumentList il = + m_doc->getStudio().getAllInstruments(); + + for (InstrumentList::iterator i = il.begin(); + i != il.end(); ++i) { + + for (PluginInstanceIterator pli = + (*i)->beginPlugins(); + pli != (*i)->endPlugins(); ++pli) { + + if (*pli && (*pli)->isAssigned() && + (*pli)->getIdentifier() == inst->getIdentifier() && + (*pli) != inst) { + + slotChangePluginConfiguration + ((*i)->getId(), (*pli)->getPosition(), + false, key, value); + +#ifdef HAVE_LIBLO + + m_pluginGUIManager->updateConfiguration + ((*i)->getId(), (*pli)->getPosition(), key); +#endif + + } + } + } + } + } + + if (inst) { + + inst->setConfigurationValue(qstrtostr(key), qstrtostr(value)); + + MappedObjectPropertyList config; + for (AudioPluginInstance::ConfigMap::const_iterator + i = inst->getConfiguration().begin(); + i != inst->getConfiguration().end(); ++i) { + config.push_back(strtoqstr(i->first)); + config.push_back(strtoqstr(i->second)); + } + + RG_DEBUG << "RosegardenGUIApp::slotChangePluginConfiguration: setting new config on mapped id " << inst->getMappedId() << endl; + + StudioControl::setStudioObjectPropertyList + (inst->getMappedId(), + MappedPluginSlot::Configuration, + config); + + // Set modified + m_doc->slotDocumentModified(); + + int key = (index << 16) + instrumentId; + if (m_pluginDialogs[key]) { + m_pluginDialogs[key]->updatePluginProgramList(); + } + } +} + +void +RosegardenGUIApp::slotPluginDialogDestroyed(InstrumentId instrumentId, + int index) +{ + int key = (index << 16) + instrumentId; + m_pluginDialogs[key] = 0; +} + +void +RosegardenGUIApp::slotPluginBypassed(InstrumentId instrumentId, + int pluginIndex, bool bp) +{ + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotPluginBypassed - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = container->getPlugin(pluginIndex); + + if (inst) { + StudioControl::setStudioObjectProperty + (inst->getMappedId(), + MappedPluginSlot::Bypassed, + MappedObjectValue(bp)); + + // Set the bypass on the instance + // + inst->setBypass(bp); + + // Set modified + m_doc->slotDocumentModified(); + } + + emit pluginBypassed(instrumentId, pluginIndex, bp); +} + +void +RosegardenGUIApp::slotShowPluginGUI(InstrumentId instrument, + int index) +{ +#ifdef HAVE_LIBLO + m_pluginGUIManager->showGUI(instrument, index); +#endif +} + +void +RosegardenGUIApp::slotStopPluginGUI(InstrumentId instrument, + int index) +{ +#ifdef HAVE_LIBLO + m_pluginGUIManager->stopGUI(instrument, index); +#endif +} + +void +RosegardenGUIApp::slotPluginGUIExited(InstrumentId instrument, + int index) +{ + int key = (index << 16) + instrument; + if (m_pluginDialogs[key]) { + m_pluginDialogs[key]->guiExited(); + } +} + +void +RosegardenGUIApp::slotPlayList() +{ + if (!m_playList) { + m_playList = new PlayListDialog(i18n("Play List"), this); + connect(m_playList, SIGNAL(closing()), + SLOT(slotPlayListClosed())); + connect(m_playList->getPlayList(), SIGNAL(play(QString)), + SLOT(slotPlayListPlay(QString))); + } + + m_playList->show(); +} + +void +RosegardenGUIApp::slotPlayListPlay(QString url) +{ + slotStop(); + openURL(url); + slotPlay(); +} + +void +RosegardenGUIApp::slotPlayListClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotPlayListClosed()\n"; + m_playList = 0; +} + +void +RosegardenGUIApp::slotTutorial() +{ + QString exe = KStandardDirs::findExe( "x-www-browser" ); + + if( exe ) + { + KProcess *proc = new KProcess; + *proc << "x-www-browser"; + *proc << "http://rosegarden.sourceforge.net/tutorial/en/chapter-0.html"; + + proc->start(KProcess::DontCare); + proc->detach(); + delete proc; + } + else + { + QString tutorialURL = i18n("http://rosegarden.sourceforge.net/tutorial/en/chapter-0.html"); + kapp->invokeBrowser(tutorialURL); + } +} + +void +RosegardenGUIApp::slotBugGuidelines() +{ + QString exe = KStandardDirs::findExe( "x-www-browser" ); + + if( exe ) + { + KProcess *proc = new KProcess; + *proc << "x-www-browser"; + *proc << "http://rosegarden.sourceforge.net/tutorial/bug-guidelines.html"; + + proc->start(KProcess::DontCare); + proc->detach(); + delete proc; + } + else + { + QString tutorialURL = i18n("http://rosegarden.sourceforge.net/tutorial/bug-guidelines.html"); + kapp->invokeBrowser(tutorialURL); + } +} + +void +RosegardenGUIApp::slotBankEditorClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotBankEditorClosed()\n"; + + if (m_doc->isModified()) { + if (m_view) + m_view->slotSelectTrackSegments(m_doc->getComposition().getSelectedTrack()); + } + + m_bankEditor = 0; +} + +void +RosegardenGUIApp::slotDeviceManagerClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotDeviceManagerClosed()\n"; + + if (m_doc->isModified()) { + if (m_view) + m_view->slotSelectTrackSegments(m_doc->getComposition().getSelectedTrack()); + } + + m_deviceManager = 0; +} + +void +RosegardenGUIApp::slotSynthPluginManagerClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotSynthPluginManagerClosed()\n"; + + m_synthManager = 0; +} + +void +RosegardenGUIApp::slotAudioMixerClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotAudioMixerClosed()\n"; + + m_audioMixer = 0; +} + +void +RosegardenGUIApp::slotMidiMixerClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotMidiMixerClosed()\n"; + + m_midiMixer = 0; +} + +void +RosegardenGUIApp::slotAudioManagerClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotAudioManagerClosed()\n"; + + if (m_doc->isModified()) { + if (m_view) + m_view->slotSelectTrackSegments(m_doc->getComposition().getSelectedTrack()); + } + + m_audioManagerDialog = 0; +} + +void +RosegardenGUIApp::slotPanic() +{ + if (m_seqManager) { + // Stop the transport before we send a panic as the + // playback goes all to hell anyway. + // + slotStop(); + + ProgressDialog progressDlg(i18n("Queueing MIDI panic events for tranmission..."), + 100, + this); + CurrentProgressDialog::set + (&progressDlg); + ProgressDialog::processEvents(); + + connect(m_seqManager, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + connect(m_seqManager, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + m_seqManager->panic(); + + } +} + +void +RosegardenGUIApp::slotPopulateTrackInstrumentPopup() +{ + RG_DEBUG << "RosegardenGUIApp::slotSetTrackInstrument\n"; + Composition &comp = m_doc->getComposition(); + Track *track = comp.getTrackById(comp.getSelectedTrack()); + + if (!track) { + RG_DEBUG << "Weird: no track available for instrument popup!" << endl; + return ; + } + + Instrument* instrument = m_doc->getStudio().getInstrumentById(track->getInstrument()); + + QPopupMenu* popup = dynamic_cast(factory()->container("set_track_instrument", this)); + + m_view->getTrackEditor()->getTrackButtons()->populateInstrumentPopup(instrument, popup); +} + +void +RosegardenGUIApp::slotRemapInstruments() +{ + RG_DEBUG << "RosegardenGUIApp::slotRemapInstruments\n"; + RemapInstrumentDialog dialog(this, m_doc); + + connect(&dialog, SIGNAL(applyClicked()), + m_view->getTrackEditor()->getTrackButtons(), + SLOT(slotSynchroniseWithComposition())); + + if (dialog.exec() == QDialog::Accepted) { + RG_DEBUG << "slotRemapInstruments - accepted\n"; + } + +} + +void +RosegardenGUIApp::slotSaveDefaultStudio() +{ + RG_DEBUG << "RosegardenGUIApp::slotSaveDefaultStudio\n"; + + int reply = KMessageBox::warningYesNo + (this, i18n("Are you sure you want to save this as your default studio?")); + + if (reply != KMessageBox::Yes) + return ; + + KTmpStatusMsg msg(i18n("Saving current document as default studio..."), this); + + QString autoloadFile = ::locateLocal("appdata", "autoload.rg"); + + RG_DEBUG << "RosegardenGUIApp::slotSaveDefaultStudio : saving studio in " + << autoloadFile << endl; + + SetWaitCursor waitCursor; + QString errMsg; + bool res = m_doc->saveDocument(autoloadFile, errMsg); + if (!res) { + if (errMsg) + KMessageBox::error(this, i18n(QString("Could not auto-save document at %1\nError was : %2") + .arg(autoloadFile).arg(errMsg))); + else + KMessageBox::error(this, i18n(QString("Could not auto-save document at %1") + .arg(autoloadFile))); + + } +} + +void +RosegardenGUIApp::slotImportDefaultStudio() +{ + int reply = KMessageBox::warningYesNo + (this, i18n("Are you sure you want to import your default studio and lose the current one?")); + + if (reply != KMessageBox::Yes) + return ; + + QString autoloadFile = + KGlobal::dirs()->findResource("appdata", "autoload.rg"); + + QFileInfo autoloadFileInfo(autoloadFile); + + if (!autoloadFileInfo.isReadable()) { + RG_DEBUG << "RosegardenGUIDoc::slotImportDefaultStudio - " + << "can't find autoload file - defaulting" << endl; + return ; + } + + slotImportStudioFromFile(autoloadFile); +} + +void +RosegardenGUIApp::slotImportStudio() +{ + RG_DEBUG << "RosegardenGUIApp::slotImportStudio()\n"; + + QString studioDir = KGlobal::dirs()->findResource("appdata", "library/"); + QDir dir(studioDir); + if (!dir.exists()) { + studioDir = ":ROSEGARDENDEVICE"; + } else { + studioDir = "file://" + studioDir; + } + + KURL url = KFileDialog::getOpenURL + (studioDir, + "audio/x-rosegarden-device audio/x-rosegarden", + this, i18n("Import Studio from File")); + + if (url.isEmpty()) + return ; + + QString target; + if (KIO::NetAccess::download(url, target, this) == false) { + KMessageBox::error(this, i18n("Cannot download file %1") + .arg(url.prettyURL())); + return ; + } + + slotImportStudioFromFile(target); +} + +void +RosegardenGUIApp::slotImportStudioFromFile(const QString &file) +{ + RosegardenGUIDoc *doc = new RosegardenGUIDoc(this, 0, true); // skipAutoload + + Studio &oldStudio = m_doc->getStudio(); + Studio &newStudio = doc->getStudio(); + + // Add some dummy devices for when we open the document. We guess + // that the file won't have more than 32 devices. + // + // for (unsigned int i = 0; i < 32; i++) { + // newStudio.addDevice("", i, Device::Midi); + // } + + if (doc->openDocument(file, true)) { // true because we actually + // do want to create devices + // on the sequencer here + + KMacroCommand *command = new KMacroCommand(i18n("Import Studio")); + doc->syncDevices(); + + // We actually only copy across MIDI play devices... for now + std::vector midiPlayDevices; + + for (DeviceList::const_iterator i = + oldStudio.begin(); i != oldStudio.end(); ++i) { + + MidiDevice *md = + dynamic_cast(*i); + + if (md && (md->getDirection() == MidiDevice::Play)) { + midiPlayDevices.push_back((*i)->getId()); + } + } + + std::vector::iterator di(midiPlayDevices.begin()); + + for (DeviceList::const_iterator i = + newStudio.begin(); i != newStudio.end(); ++i) { + + MidiDevice *md = + dynamic_cast(*i); + + if (md && (md->getDirection() == MidiDevice::Play)) { + if (di != midiPlayDevices.end()) { + MidiDevice::VariationType variation + (md->getVariationType()); + BankList bl(md->getBanks()); + ProgramList pl(md->getPrograms()); + ControlList cl(md->getControlParameters()); + + ModifyDeviceCommand* mdCommand = new ModifyDeviceCommand(&oldStudio, + *di, + md->getName(), + md->getLibrarianName(), + md->getLibrarianEmail()); + mdCommand->setVariation(variation); + mdCommand->setBankList(bl); + mdCommand->setProgramList(pl); + mdCommand->setControlList(cl); + mdCommand->setOverwrite(true); + mdCommand->setRename(md->getName() != ""); + + command->addCommand(mdCommand); + ++di; + } + } + } + + while (di != midiPlayDevices.end()) { + command->addCommand(new CreateOrDeleteDeviceCommand + (&oldStudio, + *di)); + } + + oldStudio.setMIDIThruFilter(newStudio.getMIDIThruFilter()); + oldStudio.setMIDIRecordFilter(newStudio.getMIDIRecordFilter()); + + m_doc->getCommandHistory()->addCommand(command); + m_doc->syncDevices(); + m_doc->initialiseStudio(); // The other document will have reset it + } + + delete doc; +} + +void +RosegardenGUIApp::slotResetMidiNetwork() +{ + if (m_seqManager) { + + m_seqManager->preparePlayback(true); + + m_seqManager->resetMidiNetwork(); + } + +} + +void +RosegardenGUIApp::slotModifyMIDIFilters() +{ + RG_DEBUG << "RosegardenGUIApp::slotModifyMIDIFilters" << endl; + + MidiFilterDialog dialog(this, m_doc); + + if (dialog.exec() == QDialog::Accepted) { + RG_DEBUG << "slotModifyMIDIFilters - accepted" << endl; + } +} + +void +RosegardenGUIApp::slotManageMetronome() +{ + RG_DEBUG << "RosegardenGUIApp::slotManageMetronome" << endl; + + ManageMetronomeDialog dialog(this, m_doc); + + if (dialog.exec() == QDialog::Accepted) { + RG_DEBUG << "slotManageMetronome - accepted" << endl; + } +} + +void +RosegardenGUIApp::slotAutoSave() +{ + if (!m_seqManager || + m_seqManager->getTransportStatus() == PLAYING || + m_seqManager->getTransportStatus() == RECORDING) + return ; + + KConfig* config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + if (!config->readBoolEntry("autosave", true)) + return ; + + m_doc->slotAutoSave(); +} + +void +RosegardenGUIApp::slotUpdateAutoSaveInterval(unsigned int interval) +{ + RG_DEBUG << "RosegardenGUIApp::slotUpdateAutoSaveInterval - " + << "changed interval to " << interval << endl; + m_autoSaveTimer->changeInterval(int(interval) * 1000); +} + +void +RosegardenGUIApp::slotUpdateSidebarStyle(unsigned int style) +{ + RG_DEBUG << "RosegardenGUIApp::slotUpdateSidebarStyle - " + << "changed style to " << style << endl; + m_parameterArea->setArrangement((RosegardenParameterArea::Arrangement) style); +} + +void +RosegardenGUIApp::slotShowTip() +{ + RG_DEBUG << "RosegardenGUIApp::slotShowTip" << endl; + KTipDialog::showTip(this, locate("data", "rosegarden/tips"), true); +} + +void RosegardenGUIApp::slotShowToolHelp(const QString &s) +{ + QString msg = s; + if (msg != "") msg = " " + msg; + slotStatusMsg(msg); +} + +void +RosegardenGUIApp::slotEnableMIDIThruRouting() +{ + m_seqManager->enableMIDIThruRouting(m_enableMIDIrouting->isChecked()); +} + +TransportDialog* RosegardenGUIApp::getTransport() +{ + if (m_transport == 0) + createAndSetupTransport(); + + return m_transport; +} + +RosegardenGUIDoc *RosegardenGUIApp::getDocument() const +{ + return m_doc; +} + +void +RosegardenGUIApp::awaitDialogClearance() +{ + bool haveDialog = true; + + std::cerr << "RosegardenGUIApp::awaitDialogClearance: entering" << std::endl; + + while (haveDialog) { + + const QObjectList *c = children(); + if (!c) return; + + haveDialog = false; + for (QObjectList::const_iterator i = c->begin(); i != c->end(); ++i) { + QDialog *dialog = dynamic_cast(*i); + if (dialog && dialog->isVisible()) { + haveDialog = true; + break; + } + } + +// std::cerr << "RosegardenGUIApp::awaitDialogClearance: have dialog = " +// << haveDialog << std::endl; + + if (haveDialog) kapp->processEvents(); + } + + std::cerr << "RosegardenGUIApp::awaitDialogClearance: exiting" << std::endl; +} + +void +RosegardenGUIApp::slotNewerVersionAvailable(QString v) +{ + if (m_firstRun) return; + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + awaitDialogClearance(); + KMessageBox::information + (this, + i18n("

Newer version available

A newer version of Rosegarden may be available.
Please consult the
Rosegarden website for more information.

"), + i18n("Newer version available"), + QString("version-%1-available-show").arg(v), + KMessageBox::AllowLink); + CurrentProgressDialog::thaw(); +} + +void +RosegardenGUIApp::slotSetQuickMarker() +{ + RG_DEBUG << "RosegardenGUIApp::slotSetQuickMarker" << endl; + + m_doc->setQuickMarker(); + getView()->getTrackEditor()->updateRulers(); +} + +void +RosegardenGUIApp::slotJumpToQuickMarker() +{ + RG_DEBUG << "RosegardenGUIApp::slotJumpToQuickMarker" << endl; + + m_doc->jumpToQuickMarker(); +} + +const void* RosegardenGUIApp::SequencerExternal = (void*)-1; +RosegardenGUIApp *RosegardenGUIApp::m_myself = 0; + +} +#include "RosegardenGUIApp.moc" diff --git a/src/gui/application/RosegardenGUIApp.cpp.orig b/src/gui/application/RosegardenGUIApp.cpp.orig new file mode 100644 index 0000000..fa98530 --- /dev/null +++ b/src/gui/application/RosegardenGUIApp.cpp.orig @@ -0,0 +1,8043 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RosegardenGUIApp.h" +#include + +#include "gui/editors/segment/TrackEditor.h" +#include "gui/editors/segment/TrackButtons.h" +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "gui/application/RosegardenDCOP.h" +#include "base/AnalysisTypes.h" +#include "base/AudioPluginInstance.h" +#include "base/Clipboard.h" +#include "base/Composition.h" +#include "base/CompositionTimeSliceAdapter.h" +#include "base/Configuration.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/Profiler.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/SegmentNotationHelper.h" +#include "base/Selection.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "commands/edit/CopyCommand.h" +#include "commands/edit/CutCommand.h" +#include "commands/edit/EventQuantizeCommand.h" +#include "commands/edit/PasteSegmentsCommand.h" +#include "commands/edit/TransposeCommand.h" +#include "commands/edit/AddMarkerCommand.h" +#include "commands/edit/ModifyMarkerCommand.h" +#include "commands/edit/RemoveMarkerCommand.h" +#include "commands/notation/KeyInsertionCommand.h" +#include "commands/segment/AddTempoChangeCommand.h" +#include "commands/segment/AddTimeSignatureAndNormalizeCommand.h" +#include "commands/segment/AddTimeSignatureCommand.h" +#include "commands/segment/AudioSegmentAutoSplitCommand.h" +#include "commands/segment/AudioSegmentRescaleCommand.h" +#include "commands/segment/AudioSegmentSplitCommand.h" +#include "commands/segment/ChangeCompositionLengthCommand.h" +#include "commands/segment/CreateTempoMapFromSegmentCommand.h" +#include "commands/segment/CutRangeCommand.h" +#include "commands/segment/DeleteRangeCommand.h" +#include "commands/segment/InsertRangeCommand.h" +#include "commands/segment/ModifyDefaultTempoCommand.h" +#include "commands/segment/MoveTracksCommand.h" +#include "commands/segment/PasteRangeCommand.h" +#include "commands/segment/RemoveTempoChangeCommand.h" +#include "commands/segment/SegmentAutoSplitCommand.h" +#include "commands/segment/SegmentChangeTransposeCommand.h" +#include "commands/segment/SegmentJoinCommand.h" +#include "commands/segment/SegmentLabelCommand.h" +#include "commands/segment/SegmentReconfigureCommand.h" +#include "commands/segment/SegmentRescaleCommand.h" +#include "commands/segment/SegmentSplitByPitchCommand.h" +#include "commands/segment/SegmentSplitByRecordingSrcCommand.h" +#include "commands/segment/SegmentSplitCommand.h" +#include "commands/segment/SegmentTransposeCommand.h" +#include "commands/studio/CreateOrDeleteDeviceCommand.h" +#include "commands/studio/ModifyDeviceCommand.h" +#include "document/io/CsoundExporter.h" +#include "document/io/HydrogenLoader.h" +#include "document/io/LilyPondExporter.h" +#include "document/MultiViewCommandHistory.h" +#include "document/io/RG21Loader.h" +#include "document/io/MupExporter.h" +#include "document/io/MusicXmlExporter.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/dialogs/AddTracksDialog.h" +#include "gui/dialogs/AudioManagerDialog.h" +#include "gui/dialogs/AudioPluginDialog.h" +#include "gui/dialogs/AudioSplitDialog.h" +#include "gui/dialogs/BeatsBarsDialog.h" +#include "gui/dialogs/CompositionLengthDialog.h" +#include "gui/dialogs/ConfigureDialog.h" +#include "gui/dialogs/CountdownDialog.h" +#include "gui/dialogs/DocumentConfigureDialog.h" +#include "gui/dialogs/FileMergeDialog.h" +#include "gui/dialogs/IdentifyTextCodecDialog.h" +#include "gui/dialogs/IntervalDialog.h" +#include "gui/dialogs/LilyPondOptionsDialog.h" +#include "gui/dialogs/ManageMetronomeDialog.h" +#include "gui/dialogs/QuantizeDialog.h" +#include "gui/dialogs/RescaleDialog.h" +#include "gui/dialogs/SplitByPitchDialog.h" +#include "gui/dialogs/SplitByRecordingSrcDialog.h" +#include "gui/dialogs/TempoDialog.h" +#include "gui/dialogs/TimeDialog.h" +#include "gui/dialogs/TimeSignatureDialog.h" +#include "gui/dialogs/TransportDialog.h" +#include "gui/editors/parameters/InstrumentParameterBox.h" +#include "gui/editors/parameters/RosegardenParameterArea.h" +#include "gui/editors/parameters/SegmentParameterBox.h" +#include "gui/editors/parameters/TrackParameterBox.h" +#include "gui/editors/segment/segmentcanvas/CompositionView.h" +#include "gui/editors/segment/ControlEditorDialog.h" +#include "gui/editors/segment/MarkerEditor.h" +#include "gui/editors/segment/PlayListDialog.h" +#include "gui/editors/segment/PlayList.h" +#include "gui/editors/segment/segmentcanvas/SegmentEraser.h" +#include "gui/editors/segment/segmentcanvas/SegmentJoiner.h" +#include "gui/editors/segment/segmentcanvas/SegmentMover.h" +#include "gui/editors/segment/segmentcanvas/SegmentPencil.h" +#include "gui/editors/segment/segmentcanvas/SegmentResizer.h" +#include "gui/editors/segment/segmentcanvas/SegmentSelector.h" +#include "gui/editors/segment/segmentcanvas/SegmentSplitter.h" +#include "gui/editors/segment/segmentcanvas/SegmentToolBox.h" +#include "gui/editors/segment/TrackLabel.h" +#include "gui/editors/segment/TriggerSegmentManager.h" +#include "gui/editors/tempo/TempoView.h" +#include "gui/general/EditViewBase.h" +#include "gui/kdeext/KStartupLogo.h" +#include "gui/kdeext/KTmpStatusMsg.h" +#include "gui/seqmanager/MidiFilterDialog.h" +#include "gui/seqmanager/SequenceManager.h" +#include "gui/seqmanager/SequencerMapper.h" +#include "gui/studio/AudioMixerWindow.h" +#include "gui/studio/AudioPlugin.h" +#include "gui/studio/AudioPluginManager.h" +#include "gui/studio/AudioPluginOSCGUIManager.h" +#include "gui/studio/BankEditorDialog.h" +#include "gui/studio/DeviceManagerDialog.h" +#include "gui/studio/MidiMixerWindow.h" +#include "gui/studio/RemapInstrumentDialog.h" +#include "gui/studio/StudioControl.h" +#include "gui/studio/SynthPluginManagerDialog.h" +#include "gui/widgets/CurrentProgressDialog.h" +#include "gui/widgets/ProgressBar.h" +#include "gui/widgets/ProgressDialog.h" +#include "LircClient.h" +#include "LircCommander.h" +#include "RosegardenGUIView.h" +#include "RosegardenIface.h" +#include "SetWaitCursor.h" +#include "sound/AudioFile.h" +#include "sound/AudioFileManager.h" +#include "sound/MappedCommon.h" +#include "sound/MappedComposition.h" +#include "sound/MappedEvent.h" +#include "sound/MappedStudio.h" +#include "sound/MidiFile.h" +#include "sound/PluginIdentifier.h" +#include "sound/SoundDriver.h" +#include "StartupTester.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBJACK +#include +#endif + + +namespace Rosegarden +{ + +RosegardenGUIApp::RosegardenGUIApp(bool useSequencer, + bool useExistingSequencer, + QObject *startupStatusMessageReceiver) + : DCOPObject("RosegardenIface"), RosegardenIface(this), KDockMainWindow(0), + m_actionsSetup(false), + m_fileRecent(0), + m_view(0), + m_swapView(0), + m_mainDockWidget(0), + m_dockLeft(0), + m_doc(0), + m_sequencerProcess(0), + m_sequencerCheckedIn(false), +#ifdef HAVE_LIBJACK + m_jackProcess(0), +#endif + m_zoomSlider(0), + m_seqManager(0), + m_transport(0), + m_audioManagerDialog(0), + m_originatingJump(false), + m_storedLoopStart(0), + m_storedLoopEnd(0), + m_useSequencer(useSequencer), + m_dockVisible(true), + m_autoSaveTimer(new QTimer(this)), + m_clipboard(new Clipboard), + m_playList(0), + m_deviceManager(0), + m_synthManager(0), + m_audioMixer(0), + m_midiMixer(0), + m_bankEditor(0), + m_markerEditor(0), + m_tempoView(0), + m_triggerSegmentManager(0), +#ifdef HAVE_LIBLO + m_pluginGUIManager(new AudioPluginOSCGUIManager(this)), +#endif + m_playTimer(new QTimer(this)), + m_stopTimer(new QTimer(this)), + m_startupTester(0), +#ifdef HAVE_LIRC + m_lircClient(0), + m_lircCommander(0), +#endif + m_haveAudioImporter(false), + m_firstRun(false), + m_parameterArea(0) +{ + m_myself = this; + + + if (startupStatusMessageReceiver) { + QObject::connect(this, SIGNAL(startupStatusMessage(QString)), + startupStatusMessageReceiver, + SLOT(slotShowStatusMessage(QString))); + } + + // Try to start the sequencer + // + if (m_useSequencer) { + +#ifdef HAVE_LIBJACK +#define OFFER_JACK_START_OPTION 1 +#ifdef OFFER_JACK_START_OPTION + // First we check if jackd is running allready + + std::string jackClientName = "rosegarden"; + + // attempt connection to JACK server + // + jack_client_t* testJackClient; + testJackClient = jack_client_new(jackClientName.c_str()); + if (testJackClient == 0 ) { + + // If no connection to JACK + // try to launch JACK - if the configuration wants us to. + if (!launchJack()) { + KStartupLogo::hideIfStillThere(); + KMessageBox::error(this, i18n("Attempted to launch JACK audio daemon failed. Audio will be disabled.\nPlease check configuration (Settings -> Configure Rosegarden -> Audio -> Startup)\n and restart.")); + } + } else { + //this client was just for testing + jack_client_close(testJackClient); + } +#endif // OFFER_JACK_START_OPTION +#endif // HAVE_LIBJACK + + emit startupStatusMessage(i18n("Starting sequencer...")); + launchSequencer(useExistingSequencer); + + } else + RG_DEBUG << "RosegardenGUIApp : don't use sequencer\n"; + + // Plugin manager + // + emit startupStatusMessage(i18n("Initializing plugin manager...")); + m_pluginManager = new AudioPluginManager(); + + // call inits to invoke all other construction parts + // + emit startupStatusMessage(i18n("Initializing view...")); + initStatusBar(); + setupActions(); + iFaceDelayedInit(this); + initZoomToolbar(); + + QPixmap dummyPixmap; // any icon will do + m_mainDockWidget = createDockWidget("Rosegarden MainDockWidget", dummyPixmap, 0L, "main_dock_widget"); + // allow others to dock to the left and right sides only + m_mainDockWidget->setDockSite(KDockWidget::DockLeft | KDockWidget::DockRight); + // forbit docking abilities of m_mainDockWidget itself + m_mainDockWidget->setEnableDocking(KDockWidget::DockNone); + setView(m_mainDockWidget); // central widget in a KDE mainwindow + setMainDockWidget(m_mainDockWidget); // master dockwidget + + m_dockLeft = createDockWidget("params dock", dummyPixmap, 0L, + i18n("Special 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))); + + stateChanged("parametersbox_closed", KXMLGUIClient::StateReverse); + + RosegardenGUIDoc* doc = new RosegardenGUIDoc(this, m_pluginManager); + + m_parameterArea = new RosegardenParameterArea(m_dockLeft); + m_dockLeft->setWidget(m_parameterArea); + + // Populate the parameter-box area with the respective + // parameter box widgets. + + m_segmentParameterBox = new SegmentParameterBox(doc, m_parameterArea); + m_parameterArea->addRosegardenParameterBox(m_segmentParameterBox); + m_trackParameterBox = new TrackParameterBox(doc, m_parameterArea); + m_parameterArea->addRosegardenParameterBox(m_trackParameterBox); + m_instrumentParameterBox = new InstrumentParameterBox(doc, m_parameterArea); + m_parameterArea->addRosegardenParameterBox(m_instrumentParameterBox); + + // Lookup the configuration parameter that specifies the default + // arrangement, and instantiate it. + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + m_parameterArea->setArrangement((RosegardenParameterArea::Arrangement) + kapp->config()->readUnsignedNumEntry("sidebarstyle", + RosegardenParameterArea::CLASSIC_STYLE)); + + m_dockLeft->update(); + + connect(m_instrumentParameterBox, + SIGNAL(selectPlugin(QWidget *, InstrumentId, int)), + this, + SLOT(slotShowPluginDialog(QWidget *, InstrumentId, int))); + + connect(m_instrumentParameterBox, + SIGNAL(showPluginGUI(InstrumentId, int)), + this, + SLOT(slotShowPluginGUI(InstrumentId, int))); + + // relay this through our own signal so that others can use it too + connect(m_instrumentParameterBox, + SIGNAL(instrumentParametersChanged(InstrumentId)), + this, + SIGNAL(instrumentParametersChanged(InstrumentId))); + + connect(this, + SIGNAL(instrumentParametersChanged(InstrumentId)), + m_instrumentParameterBox, + SLOT(slotInstrumentParametersChanged(InstrumentId))); + + connect(this, + SIGNAL(pluginSelected(InstrumentId, int, int)), + m_instrumentParameterBox, + SLOT(slotPluginSelected(InstrumentId, int, int))); + + connect(this, + SIGNAL(pluginBypassed(InstrumentId, int, bool)), + m_instrumentParameterBox, + SLOT(slotPluginBypassed(InstrumentId, int, bool))); + + // Load the initial document (this includes doc's own autoload) + // + setDocument(doc); + + emit startupStatusMessage(i18n("Starting sequence manager...")); + + // transport is created by setupActions() + m_seqManager = new SequenceManager(m_doc, getTransport()); + + if (m_useSequencer) { + // Check the sound driver status and warn the user of any + // problems. This warning has to happen early, in case it + // affects the ability to load plugins etc from a file on the + // command line. + m_seqManager->checkSoundDriverStatus(true); + } + + if (m_view) { + connect(m_seqManager, SIGNAL(controllerDeviceEventReceived(MappedEvent *)), + m_view, SLOT(slotControllerDeviceEventReceived(MappedEvent *))); + } + + if (m_seqManager->getSoundDriverStatus() & AUDIO_OK) { + slotStateChanged("got_audio", true); + } else { + slotStateChanged("got_audio", false); + } + + // If we're restarting the gui then make sure any transient + // studio objects are cleared away. + emit startupStatusMessage(i18n("Clearing studio data...")); + m_seqManager->reinitialiseSequencerStudio(); + + // Send the transport control statuses for MMC and JACK + // + m_seqManager->sendTransportControlStatuses(); + + // Now autoload + // + stateChanged("new_file"); + stateChanged("have_segments", KXMLGUIClient::StateReverse); + stateChanged("have_selection", KXMLGUIClient::StateReverse); + slotTestClipboard(); + + // Check for lack of MIDI devices and disable Studio options accordingly + // + if (!m_doc->getStudio().haveMidiDevices()) + stateChanged("got_midi_devices", KXMLGUIClient::StateReverse); + + emit startupStatusMessage(i18n("Starting...")); + + setupFileDialogSpeedbar(); + readOptions(); + + // All toolbars should be created before this is called + setAutoSaveSettings(MainWindowConfigGroup, true); + +#ifdef HAVE_LIRC + + try { + m_lircClient = new LircClient(); + } catch (Exception e) { + RG_DEBUG << e.getMessage().c_str() << endl; + // continue without + m_lircClient = 0; + } + if (m_lircClient) { + m_lircCommander = new LircCommander(m_lircClient, this); + } +#endif + + stateChanged("have_project_packager", KXMLGUIClient::StateReverse); + stateChanged("have_lilypondview", KXMLGUIClient::StateReverse); + QTimer::singleShot(1000, this, SLOT(slotTestStartupTester())); +} + +RosegardenGUIApp::~RosegardenGUIApp() +{ + RG_DEBUG << "~RosegardenGUIApp()\n"; + + if (getView() && + getView()->getTrackEditor() && + getView()->getTrackEditor()->getSegmentCanvas()) { + getView()->getTrackEditor()->getSegmentCanvas()->endAudioPreviewGeneration(); + } + +#ifdef HAVE_LIBLO + delete m_pluginGUIManager; +#endif + + if (isSequencerRunning() && !isSequencerExternal()) { + m_sequencerProcess->blockSignals(true); + rgapp->sequencerSend("quit()"); + usleep(300000); + delete m_sequencerProcess; + } + + delete m_jumpToQuickMarkerAction; + delete m_setQuickMarkerAction; + + delete m_transport; + + delete m_seqManager; + +#ifdef HAVE_LIRC + + delete m_lircCommander; + delete m_lircClient; +#endif + + delete m_doc; + Profiles::getInstance()->dump(); +} + +void RosegardenGUIApp::setupActions() +{ + // setup File menu + // New Window ? + KStdAction::openNew (this, SLOT(slotFileNew()), actionCollection()); + KStdAction::open (this, SLOT(slotFileOpen()), actionCollection()); + m_fileRecent = KStdAction::openRecent(this, + SLOT(slotFileOpenRecent(const KURL&)), + actionCollection()); + KStdAction::save (this, SLOT(slotFileSave()), actionCollection()); + KStdAction::saveAs(this, SLOT(slotFileSaveAs()), actionCollection()); + KStdAction::revert(this, SLOT(slotRevertToSaved()), actionCollection()); + KStdAction::close (this, SLOT(slotFileClose()), actionCollection()); + KStdAction::print (this, SLOT(slotFilePrint()), actionCollection()); + KStdAction::printPreview (this, SLOT(slotFilePrintPreview()), actionCollection()); + + new KAction(i18n("Import Rosegarden &Project file..."), 0, 0, this, + SLOT(slotImportProject()), actionCollection(), + "file_import_project"); + + new KAction(i18n("Import &MIDI file..."), 0, 0, this, + SLOT(slotImportMIDI()), actionCollection(), + "file_import_midi"); + + new KAction(i18n("Import &Rosegarden 2.1 file..."), 0, 0, this, + SLOT(slotImportRG21()), actionCollection(), + "file_import_rg21"); + + new KAction(i18n("Import &Hydrogen file..."), 0, 0, this, + SLOT(slotImportHydrogen()), actionCollection(), + "file_import_hydrogen"); + + new KAction(i18n("Merge &File..."), 0, 0, this, + SLOT(slotMerge()), actionCollection(), + "file_merge"); + + new KAction(i18n("Merge &MIDI file..."), 0, 0, this, + SLOT(slotMergeMIDI()), actionCollection(), + "file_merge_midi"); + + new KAction(i18n("Merge &Rosegarden 2.1 file..."), 0, 0, this, + SLOT(slotMergeRG21()), actionCollection(), + "file_merge_rg21"); + + new KAction(i18n("Merge &Hydrogen file..."), 0, 0, this, + SLOT(slotMergeHydrogen()), actionCollection(), + "file_merge_hydrogen"); + + new KAction(i18n("Export Rosegarden &Project file..."), 0, 0, this, + SLOT(slotExportProject()), actionCollection(), + "file_export_project"); + + new KAction(i18n("Export &MIDI file..."), 0, 0, this, + SLOT(slotExportMIDI()), actionCollection(), + "file_export_midi"); + + new KAction(i18n("Export &LilyPond file..."), 0, 0, this, + SLOT(slotExportLilyPond()), actionCollection(), + "file_export_lilypond"); + + new KAction(i18n("Export Music&XML file..."), 0, 0, this, + SLOT(slotExportMusicXml()), actionCollection(), + "file_export_musicxml"); + + new KAction(i18n("Export &Csound score file..."), 0, 0, this, + SLOT(slotExportCsound()), actionCollection(), + "file_export_csound"); + + new KAction(i18n("Export M&up file..."), 0, 0, this, + SLOT(slotExportMup()), actionCollection(), + "file_export_mup"); + + 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"); + + new KAction(i18n("Play&list"), 0, 0, this, + SLOT(slotPlayList()), actionCollection(), + "file_show_playlist"); + + KStdAction::quit (this, SLOT(slotQuit()), actionCollection()); + + // help menu + new KAction(i18n("Rosegarden &Tutorial"), 0, 0, this, + SLOT(slotTutorial()), actionCollection(), + "tutorial"); + + new KAction(i18n("&Bug Reporting Guidelines"), 0, 0, this, + SLOT(slotBugGuidelines()), actionCollection(), + "guidelines"); + + // setup edit menu + KStdAction::cut (this, SLOT(slotEditCut()), actionCollection()); + KStdAction::copy (this, SLOT(slotEditCopy()), actionCollection()); + KStdAction::paste (this, SLOT(slotEditPaste()), actionCollection()); + + // + // undo/redo actions are special in that they are connected to + // slots later on, when the current document is set up - see + // MultiViewCommandHistory::attachView + // + new KToolBarPopupAction(i18n("Und&o"), + "undo", + KStdAccel::shortcut(KStdAccel::Undo), + actionCollection(), + KStdAction::stdName(KStdAction::Undo)); + + new KToolBarPopupAction(i18n("Re&do"), + "redo", + KStdAccel::shortcut(KStdAccel::Redo), + actionCollection(), + KStdAction::stdName(KStdAction::Redo)); + ///// + + + + // setup Settings menu + // + m_viewToolBar = KStdAction::showToolbar (this, SLOT(slotToggleToolBar()), actionCollection(), + "show_stock_toolbar"); + + m_viewToolsToolBar = new KToggleAction(i18n("Show T&ools Toolbar"), 0, this, + SLOT(slotToggleToolsToolBar()), actionCollection(), + "show_tools_toolbar"); + + m_viewTracksToolBar = new KToggleAction(i18n("Show Trac&ks Toolbar"), 0, this, + SLOT(slotToggleTracksToolBar()), actionCollection(), + "show_tracks_toolbar"); + + m_viewEditorsToolBar = new KToggleAction(i18n("Show &Editors Toolbar"), 0, this, + SLOT(slotToggleEditorsToolBar()), actionCollection(), + "show_editors_toolbar"); + + m_viewTransportToolBar = new KToggleAction(i18n("Show Trans&port Toolbar"), 0, this, + SLOT(slotToggleTransportToolBar()), actionCollection(), + "show_transport_toolbar"); + + m_viewZoomToolBar = new KToggleAction(i18n("Show &Zoom Toolbar"), 0, this, + SLOT(slotToggleZoomToolBar()), actionCollection(), + "show_zoom_toolbar"); + + m_viewStatusBar = KStdAction::showStatusbar(this, SLOT(slotToggleStatusBar()), + actionCollection(), "show_status_bar"); + + m_viewTransport = new KToggleAction(i18n("Show Tra&nsport"), Key_T, this, + SLOT(slotToggleTransport()), + actionCollection(), + "show_transport"); + + m_viewTrackLabels = new KToggleAction(i18n("Show Track &Labels"), 0, this, + SLOT(slotToggleTrackLabels()), + actionCollection(), + "show_tracklabels"); + + m_viewRulers = new KToggleAction(i18n("Show Playback Position R&uler"), 0, this, + SLOT(slotToggleRulers()), + actionCollection(), + "show_rulers"); + + m_viewTempoRuler = new KToggleAction(i18n("Show Te&mpo Ruler"), 0, this, + SLOT(slotToggleTempoRuler()), + actionCollection(), + "show_tempo_ruler"); + + m_viewChordNameRuler = new KToggleAction(i18n("Show Cho&rd Name Ruler"), 0, this, + SLOT(slotToggleChordNameRuler()), + actionCollection(), + "show_chord_name_ruler"); + + + m_viewPreviews = new KToggleAction(i18n("Show Segment Pre&views"), 0, this, + SLOT(slotTogglePreviews()), + actionCollection(), + "show_previews"); + + new KAction(i18n("Show Special &Parameters"), Key_P, this, + SLOT(slotDockParametersBack()), + actionCollection(), + "show_inst_segment_parameters"); + + KStdAction::tipOfDay( this, SLOT( slotShowTip() ), actionCollection() ); + + // Standard Actions + // + KStdAction::saveOptions(this, + SLOT(slotSaveOptions()), + actionCollection()); + + KStdAction::preferences(this, + SLOT(slotConfigure()), + actionCollection()); + + KStdAction::keyBindings(this, + SLOT(slotEditKeys()), + actionCollection()); + + KStdAction::configureToolbars(this, + SLOT(slotEditToolbars()), + actionCollection()); + + KRadioAction *action = 0; + + // Create the select icon + // + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QCanvasPixmap pixmap(pixmapDir + "/toolbar/select.xpm"); + QIconSet icon = QIconSet(pixmap); + + // TODO : add some shortcuts here + action = new KRadioAction(i18n("&Select and Edit"), icon, Key_F2, + this, SLOT(slotPointerSelected()), + actionCollection(), "select"); + action->setExclusiveGroup("segmenttools"); + + action = new KRadioAction(i18n("&Draw"), "pencil", Key_F3, + this, SLOT(slotDrawSelected()), + actionCollection(), "draw"); + action->setExclusiveGroup("segmenttools"); + + action = new KRadioAction(i18n("&Erase"), "eraser", Key_F4, + this, SLOT(slotEraseSelected()), + actionCollection(), "erase"); + action->setExclusiveGroup("segmenttools"); + + action = new KRadioAction(i18n("&Move"), "move", Key_F5, + this, SLOT(slotMoveSelected()), + actionCollection(), "move"); + action->setExclusiveGroup("segmenttools"); + + pixmap.load(pixmapDir + "/toolbar/resize.xpm"); + icon = QIconSet(pixmap); + action = new KRadioAction(i18n("&Resize"), icon, Key_F6, + this, SLOT(slotResizeSelected()), + actionCollection(), "resize"); + action->setExclusiveGroup("segmenttools"); + + pixmap.load(pixmapDir + "/toolbar/split.xpm"); + icon = QIconSet(pixmap); + action = new KRadioAction(i18n("&Split"), icon, Key_F7, + this, SLOT(slotSplitSelected()), + actionCollection(), "split"); + action->setExclusiveGroup("segmenttools"); + + pixmap.load(pixmapDir + "/toolbar/join.xpm"); + icon = QIconSet(pixmap); + action = new KRadioAction(i18n("&Join"), icon, 0, + this, SLOT(slotJoinSelected()), + actionCollection(), "join"); + action->setExclusiveGroup("segmenttools"); + + + new KAction(i18n("&Harmonize"), 0, this, + SLOT(slotHarmonizeSelection()), actionCollection(), + "harmonize_selection"); + + pixmap.load(pixmapDir + "/toolbar/event-insert-timesig.png"); + icon = QIconSet(pixmap); + new KAction(AddTimeSignatureCommand::getGlobalName(), + icon, 0, + this, SLOT(slotEditTimeSignature()), + actionCollection(), "add_time_signature"); + + new KAction(i18n("Open Tempo and Time Signature Editor"), 0, this, + SLOT(slotEditTempos()), actionCollection(), "edit_tempos"); + + // + // Edit menu + // + new KAction(i18n("Cut Range"), Key_X + CTRL + SHIFT, this, + SLOT(slotCutRange()), actionCollection(), + "cut_range"); + + new KAction(i18n("Copy Range"), Key_C + CTRL + SHIFT, this, + SLOT(slotCopyRange()), actionCollection(), + "copy_range"); + + new KAction(i18n("Paste Range"), Key_V + CTRL + SHIFT, this, + SLOT(slotPasteRange()), actionCollection(), + "paste_range"); +/* + new KAction(i18n("Delete Range"), Key_Delete + SHIFT, this, + SLOT(slotDeleteRange()), actionCollection(), + "delete_range"); +*/ + new KAction(i18n("Insert Range..."), Key_Insert + SHIFT, this, + SLOT(slotInsertRange()), actionCollection(), + "insert_range"); + + new KAction(i18n("De&lete"), Key_Delete, this, + SLOT(slotDeleteSelectedSegments()), actionCollection(), + "delete"); + + new KAction(i18n("Select &All Segments"), Key_A + CTRL, this, + SLOT(slotSelectAll()), actionCollection(), + "select_all"); + + pixmap.load(pixmapDir + "/toolbar/event-insert-tempo.png"); + icon = QIconSet(pixmap); + new KAction(AddTempoChangeCommand::getGlobalName(), + icon, 0, + this, SLOT(slotEditTempo()), + actionCollection(), "add_tempo"); + + new KAction(ChangeCompositionLengthCommand::getGlobalName(), + 0, + this, SLOT(slotChangeCompositionLength()), + actionCollection(), "change_composition_length"); + + new KAction(i18n("Edit Mar&kers..."), Key_K + CTRL, this, + SLOT(slotEditMarkers()), + actionCollection(), "edit_markers"); + + new KAction(i18n("Edit Document P&roperties..."), 0, this, + SLOT(slotEditDocumentProperties()), + actionCollection(), "edit_doc_properties"); + + + // + // Segments menu + // + new KAction(i18n("Open in &Default Editor"), Key_Return, this, + SLOT(slotEdit()), actionCollection(), + "edit_default"); + + pixmap.load(pixmapDir + "/toolbar/matrix.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Open in Matri&x Editor"), icon, Key_M, this, + SLOT(slotEditInMatrix()), actionCollection(), + "edit_matrix"); + + pixmap.load(pixmapDir + "/toolbar/matrix-percussion.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Open in &Percussion Matrix Editor"), icon, Key_D, this, + SLOT(slotEditInPercussionMatrix()), actionCollection(), + "edit_percussion_matrix"); + + pixmap.load(pixmapDir + "/toolbar/notation.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Open in &Notation Editor"), icon, Key_N, this, + SLOT(slotEditAsNotation()), actionCollection(), + "edit_notation"); + + pixmap.load(pixmapDir + "/toolbar/eventlist.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Open in &Event List Editor"), icon, Key_E, this, + SLOT(slotEditInEventList()), actionCollection(), + "edit_event_list"); + + pixmap.load(pixmapDir + "/toolbar/quantize.png"); + icon = QIconSet(pixmap); + new KAction(i18n("&Quantize..."), icon, Key_Equal, this, + SLOT(slotQuantizeSelection()), actionCollection(), + "quantize_selection"); + + new KAction(SegmentLabelCommand::getGlobalName(), + 0, + this, SLOT(slotRelabelSegments()), + actionCollection(), "relabel_segment"); + + new KAction(SegmentTransposeCommand::getGlobalName(), + 0, + this, SLOT(slotTransposeSegments()), + actionCollection(), "transpose"); + + new KAction(i18n("Repeat Last Quantize"), Key_Plus, this, + SLOT(slotRepeatQuantizeSelection()), actionCollection(), + "repeat_quantize"); + + new KAction(SegmentRescaleCommand::getGlobalName(), 0, this, + SLOT(slotRescaleSelection()), actionCollection(), + "rescale"); + + new KAction(SegmentAutoSplitCommand::getGlobalName(), 0, this, + SLOT(slotAutoSplitSelection()), actionCollection(), + "auto_split"); + + new KAction(SegmentSplitByPitchCommand::getGlobalName(), 0, this, + SLOT(slotSplitSelectionByPitch()), actionCollection(), + "split_by_pitch"); + + new KAction(SegmentSplitByRecordingSrcCommand::getGlobalName(), 0, this, + SLOT(slotSplitSelectionByRecordedSrc()), actionCollection(), + "split_by_recording"); + + new KAction(i18n("Split at Time..."), 0, this, + SLOT(slotSplitSelectionAtTime()), actionCollection(), + "split_at_time"); + + new KAction(i18n("Jog &Left"), Key_Left + ALT, this, + SLOT(slotJogLeft()), actionCollection(), + "jog_left"); + + new KAction(i18n("Jog &Right"), Key_Right + ALT, this, + SLOT(slotJogRight()), actionCollection(), + "jog_right"); + + new KAction(i18n("Set Start Time..."), 0, this, + SLOT(slotSetSegmentStartTimes()), actionCollection(), + "set_segment_start"); + + new KAction(i18n("Set Duration..."), 0, this, + SLOT(slotSetSegmentDurations()), actionCollection(), + "set_segment_duration"); + + new KAction(SegmentJoinCommand::getGlobalName(), + Key_J + CTRL, + this, SLOT(slotJoinSegments()), + actionCollection(), "join_segments"); + + new KAction(i18n("Turn Re&peats into Copies"), + 0, + this, SLOT(slotRepeatingSegments()), + actionCollection(), "repeats_to_real_copies"); + + new KAction(i18n("Manage Tri&ggered Segments"), 0, + this, SLOT(slotManageTriggerSegments()), + actionCollection(), "manage_trigger_segments"); + + new KAction(i18n("Set Tempos from &Beat Segment"), 0, this, + SLOT(slotGrooveQuantize()), actionCollection(), + "groove_quantize"); + + new KAction(i18n("Set &Tempo to Audio Segment Duration"), 0, this, + SLOT(slotTempoToSegmentLength()), actionCollection(), + "set_tempo_to_segment_length"); + + pixmap.load(pixmapDir + "/toolbar/manage-audio-segments.xpm"); + icon = QIconSet(pixmap); + new KAction(i18n("Manage A&udio Files"), icon, + Key_U + CTRL, + this, SLOT(slotAudioManager()), + actionCollection(), "audio_manager"); + + m_viewSegmentLabels = new KToggleAction(i18n("Show Segment Labels"), 0, this, + SLOT(slotToggleSegmentLabels()), actionCollection(), + "show_segment_labels"); + + // + // Tracks menu + // + pixmap.load(pixmapDir + "/toolbar/add_tracks.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Add &Track"), icon, CTRL + Key_T, + this, SLOT(slotAddTrack()), + actionCollection(), "add_track"); + + new KAction(i18n("&Add Tracks..."), 0, + this, SLOT(slotAddTracks()), + actionCollection(), "add_tracks"); + + pixmap.load(pixmapDir + "/toolbar/delete_track.png"); + icon = QIconSet(pixmap); + new KAction(i18n("D&elete Track"), icon, CTRL + Key_D, + this, SLOT(slotDeleteTrack()), + actionCollection(), "delete_track"); + + pixmap.load(pixmapDir + "/toolbar/move_track_down.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Move Track &Down"), icon, SHIFT + Key_Down, + this, SLOT(slotMoveTrackDown()), + actionCollection(), "move_track_down"); + + pixmap.load(pixmapDir + "/toolbar/move_track_up.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Move Track &Up"), icon, SHIFT + Key_Up, + this, SLOT(slotMoveTrackUp()), + actionCollection(), "move_track_up"); + + new KAction(i18n("Select &Next Track"), + Key_Down, + this, SLOT(slotTrackDown()), + actionCollection(), "select_next_track"); + + new KAction(i18n("Select &Previous Track"), + Key_Up, + this, SLOT(slotTrackUp()), + actionCollection(), "select_previous_track"); + + new KAction(i18n("Mute or Unmute Track"), + Key_U, + this, SLOT(slotToggleMutedCurrentTrack()), + actionCollection(), "toggle_mute_track"); + + new KAction(i18n("Arm or Un-arm Track for Record"), + Key_R, + this, SLOT(slotToggleRecordCurrentTrack()), + actionCollection(), "toggle_arm_track"); + + pixmap.load(pixmapDir + "/toolbar/mute-all.png"); + icon = QIconSet(pixmap); + new KAction(i18n("&Mute all Tracks"), icon, 0, + this, SLOT(slotMuteAllTracks()), + actionCollection(), "mute_all_tracks"); + + pixmap.load(pixmapDir + "/toolbar/un-mute-all.png"); + icon = QIconSet(pixmap); + new KAction(i18n("&Unmute all Tracks"), icon, 0, + this, SLOT(slotUnmuteAllTracks()), + actionCollection(), "unmute_all_tracks"); + + new KAction(i18n("&Remap Instruments..."), 0, this, + SLOT(slotRemapInstruments()), + actionCollection(), "remap_instruments"); + + // + // Studio menu + // + pixmap.load(pixmapDir + "/toolbar/mixer.png"); + icon = QIconSet(pixmap); + new KAction(i18n("&Audio Mixer"), icon, 0, this, + SLOT(slotOpenAudioMixer()), + actionCollection(), "audio_mixer"); + + pixmap.load(pixmapDir + "/toolbar/midimixer.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Midi Mi&xer"), icon, 0, this, + SLOT(slotOpenMidiMixer()), + actionCollection(), "midi_mixer"); + + pixmap.load(pixmapDir + "/toolbar/manage-midi-devices.xpm"); + icon = QIconSet(pixmap); + new KAction(i18n("Manage MIDI &Devices"), icon, 0, this, + SLOT(slotManageMIDIDevices()), + actionCollection(), "manage_devices"); + + pixmap.load(pixmapDir + "/toolbar/manage-synth-plugins.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Manage S&ynth Plugins"), icon, 0, this, + SLOT(slotManageSynths()), + actionCollection(), "manage_synths"); + + new KAction(i18n("Modify MIDI &Filters"), "filter", 0, this, + SLOT(slotModifyMIDIFilters()), + actionCollection(), "modify_midi_filters"); + + m_enableMIDIrouting = new KToggleAction(i18n("MIDI Thru Routing"), 0, this, + SLOT(slotEnableMIDIThruRouting()), + actionCollection(), "enable_midi_routing"); + + pixmap.load(pixmapDir + "/toolbar/time-musical.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Manage &Metronome"), 0, this, + SLOT(slotManageMetronome()), + actionCollection(), "manage_metronome"); + + new KAction(i18n("&Save Current Document as Default Studio"), 0, this, + SLOT(slotSaveDefaultStudio()), + actionCollection(), "save_default_studio"); + + new KAction(i18n("&Import Default Studio"), 0, this, + SLOT(slotImportDefaultStudio()), + actionCollection(), "load_default_studio"); + + new KAction(i18n("Im&port Studio from File..."), 0, this, + SLOT(slotImportStudio()), + actionCollection(), "load_studio"); + + new KAction(i18n("&Reset MIDI Network"), 0, this, + SLOT(slotResetMidiNetwork()), + actionCollection(), "reset_midi_network"); + + m_setQuickMarkerAction = new KAction(i18n("Set Quick Marker at Playback Position"), 0, CTRL + Key_1, this, + SLOT(slotSetQuickMarker()), actionCollection(), + "set_quick_marker"); + + m_jumpToQuickMarkerAction = new KAction(i18n("Jump to Quick Marker"), 0, Key_1, this, + SLOT(slotJumpToQuickMarker()), actionCollection(), + "jump_to_quick_marker"); + + // + // Marker Ruler popup menu + // +// new KAction(i18n("Insert Marker"), 0, 0, this, +// SLOT(slotInsertMarkerHere()), actionCollection(), +// "insert_marker_here"); +// +// new KAction(i18n("Insert Marker at Playback Position"), 0, 0, this, +// SLOT(slotInsertMarkerAtPointer()), actionCollection(), +// "insert_marker_at_pointer"); +// +// new KAction(i18n("Delete Marker"), 0, 0, this, +// SLOT(slotDeleteMarker()), actionCollection(), +// "delete_marker"); + + + + // + // Transport menu + // + + // Transport controls [rwb] + // + // We set some default key bindings - with numlock off + // use 1 (End) and 3 (Page Down) for Rwd and Ffwd and + // 0 (insert) and keypad Enter for Play and Stop + // + pixmap.load(pixmapDir + "/toolbar/transport-play.png"); + icon = QIconSet(pixmap); + m_playTransport = new KAction(i18n("&Play"), icon, Key_Enter, this, + SLOT(slotPlay()), actionCollection(), + "play"); + // Alternative shortcut for Play + KShortcut playShortcut = m_playTransport->shortcut(); + playShortcut.append( KKey(Key_Return + CTRL) ); + m_playTransport->setShortcut(playShortcut); + m_playTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-stop.png"); + icon = QIconSet(pixmap); + m_stopTransport = new KAction(i18n("&Stop"), icon, Key_Insert, this, + SLOT(slotStop()), actionCollection(), + "stop"); + m_stopTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-ffwd.png"); + icon = QIconSet(pixmap); + m_ffwdTransport = new KAction(i18n("&Fast Forward"), icon, Key_PageDown, + this, + SLOT(slotFastforward()), actionCollection(), + "fast_forward"); + m_ffwdTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-rewind.png"); + icon = QIconSet(pixmap); + m_rewindTransport = new KAction(i18n("Re&wind"), icon, Key_End, this, + SLOT(slotRewind()), actionCollection(), + "rewind"); + m_rewindTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-record.png"); + icon = QIconSet(pixmap); + m_recordTransport = new KAction(i18n("P&unch in Record"), icon, Key_Space, this, + SLOT(slotToggleRecord()), actionCollection(), + "recordtoggle"); + m_recordTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-record.png"); + icon = QIconSet(pixmap); + m_recordTransport = new KAction(i18n("&Record"), icon, 0, this, + SLOT(slotRecord()), actionCollection(), + "record"); + m_recordTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-rewind-end.png"); + icon = QIconSet(pixmap); + m_rewindEndTransport = new KAction(i18n("Rewind to &Beginning"), icon, 0, this, + SLOT(slotRewindToBeginning()), actionCollection(), + "rewindtobeginning"); + m_rewindEndTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-ffwd-end.png"); + icon = QIconSet(pixmap); + m_ffwdEndTransport = new KAction(i18n("Fast Forward to &End"), icon, 0, this, + SLOT(slotFastForwardToEnd()), actionCollection(), + "fastforwardtoend"); + m_ffwdEndTransport->setGroup(TransportDialogConfigGroup); + + pixmap.load(pixmapDir + "/toolbar/transport-tracking.png"); + icon = QIconSet(pixmap); + (new KToggleAction(i18n("Scro&ll to Follow Playback"), icon, Key_Pause, this, + SLOT(slotToggleTracking()), actionCollection(), + "toggle_tracking"))->setChecked(true); + + pixmap.load(pixmapDir + "/toolbar/transport-panic.png"); + icon = QIconSet(pixmap); + new KAction( i18n("Panic"), icon, Key_P + CTRL + ALT, this, SLOT(slotPanic()), + actionCollection(), "panic"); + + // DEBUG FACILITY + new KAction(i18n("Segment Debug Dump "), 0, this, + SLOT(slotDebugDump()), actionCollection(), + "debug_dump_segments"); + + // create main gui + // + createGUI("rosegardenui.rc", false); + + createAndSetupTransport(); + + // transport toolbar is hidden by default - TODO : this should be in options + // + //toolBar("Transport Toolbar")->hide(); + + QPopupMenu* setTrackInstrumentMenu = dynamic_cast(factory()->container("set_track_instrument", this)); + + if (setTrackInstrumentMenu) { + connect(setTrackInstrumentMenu, SIGNAL(aboutToShow()), + this, SLOT(slotPopulateTrackInstrumentPopup())); + } else { + RG_DEBUG << "RosegardenGUIApp::setupActions() : couldn't find set_track_instrument menu - check rosegardenui.rcn\n"; + } + + setRewFFwdToAutoRepeat(); +} + +void RosegardenGUIApp::setRewFFwdToAutoRepeat() +{ + QWidget* transportToolbar = factory()->container("Transport Toolbar", this); + + if (transportToolbar) { + QObjectList *l = transportToolbar->queryList(); + QObjectListIt it(*l); // iterate over the buttons + QObject *obj; + + while ( (obj = it.current()) != 0 ) { + // for each found object... + ++it; + // RG_DEBUG << "obj name : " << obj->name() << endl; + QString objName = obj->name(); + + if (objName.endsWith("rewind") || objName.endsWith("fast_forward")) { + QButton* btn = dynamic_cast(obj); + if (!btn) { + RG_DEBUG << "Very strange - found widgets in transport_toolbar which aren't buttons\n"; + + continue; + } + btn->setAutoRepeat(true); + } + + + } + delete l; + + } else { + RG_DEBUG << "transportToolbar == 0\n"; + } + +} + +void RosegardenGUIApp::initZoomToolbar() +{ + KToolBar *zoomToolbar = toolBar("Zoom Toolbar"); + if (!zoomToolbar) { + RG_DEBUG << "RosegardenGUIApp::initZoomToolbar() : " + << "zoom toolbar not found" << endl; + return ; + } + + new QLabel(i18n(" Zoom: "), zoomToolbar, "kde toolbar widget"); + + std::vector 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 }; + + for (unsigned int i = 0; i < sizeof(factors) / sizeof(factors[0]); ++i) { + zoomSizes.push_back(duration44 / (defaultBarWidth44 * factors[i])); + } + + // zoom labels + QString minZoom = QString("%1%").arg(factors[0] * 100.0); + QString maxZoom = QString("%1%").arg(factors[(sizeof(factors) / sizeof(factors[0])) - 1] * 100.0); + + m_zoomSlider = new ZoomSlider + (zoomSizes, -1, QSlider::Horizontal, zoomToolbar, "kde toolbar widget"); + m_zoomSlider->setTracking(true); + m_zoomSlider->setFocusPolicy(QWidget::NoFocus); + m_zoomLabel = new QLabel(minZoom, zoomToolbar, "kde toolbar widget"); + m_zoomLabel->setIndent(10); + + connect(m_zoomSlider, SIGNAL(valueChanged(int)), + this, SLOT(slotChangeZoom(int))); + + // set initial zoom - we might want to make this a config option + // m_zoomSlider->setToDefault(); + +} + +void RosegardenGUIApp::initStatusBar() +{ + KTmpStatusMsg::setDefaultMsg(""); + statusBar()->insertItem(KTmpStatusMsg::getDefaultMsg(), + KTmpStatusMsg::getDefaultId(), 1); + statusBar()->setItemAlignment(KTmpStatusMsg::getDefaultId(), + AlignLeft | AlignVCenter); + + m_progressBar = new ProgressBar(100, true, statusBar()); + // m_progressBar->setMinimumWidth(100); + m_progressBar->setFixedWidth(60); + m_progressBar->setFixedHeight(18); + m_progressBar->setTextEnabled(false); + statusBar()->addWidget(m_progressBar); +} + +void RosegardenGUIApp::initView() +{ + //////////////////////////////////////////////////////////////////// + // create the main widget here that is managed by KTMainWindow's view-region and + // connect the widget to your document to display document contents. + + RG_DEBUG << "RosegardenGUIApp::initView()" << endl; + + Composition &comp = m_doc->getComposition(); + + // Ensure that the start and end markers for the piece are set + // to something reasonable + // + if (comp.getStartMarker() == 0 && + comp.getEndMarker() == 0) { + int endMarker = comp.getBarRange(100 + comp.getNbBars()).second; + comp.setEndMarker(endMarker); + } + + m_swapView = new RosegardenGUIView(m_viewTrackLabels->isChecked(), + m_segmentParameterBox, + m_instrumentParameterBox, + m_trackParameterBox, this); + + // Connect up this signal so that we can force tool mode + // changes from the view + connect(m_swapView, SIGNAL(activateTool(QString)), + this, SLOT(slotActivateTool(QString))); + + connect(m_swapView, + SIGNAL(segmentsSelected(const SegmentSelection &)), + SIGNAL(segmentsSelected(const SegmentSelection &))); + + connect(m_swapView, + SIGNAL(addAudioFile(AudioFileId)), + SLOT(slotAddAudioFile(AudioFileId))); + + connect(m_swapView, SIGNAL(toggleSolo(bool)), SLOT(slotToggleSolo(bool))); + + m_doc->attachView(m_swapView); + + m_mainDockWidget->setWidget(m_swapView); + + // setCentralWidget(m_swapView); + setCaption(m_doc->getTitle()); + + + // Transport setup + // + std::string transportMode = m_doc->getConfiguration(). + get + + (DocumentConfiguration::TransportMode); + + + slotEnableTransport(true); + + // and the time signature + // + getTransport()->setTimeSignature(comp.getTimeSignatureAt(comp.getPosition())); + + // set the tempo in the transport + // + getTransport()->setTempo(comp.getCurrentTempo()); + + // bring the transport to the front + // + getTransport()->raise(); + + // set the play metronome button + getTransport()->MetronomeButton()->setOn(comp.usePlayMetronome()); + + // Set the solo button + getTransport()->SoloButton()->setOn(comp.isSolo()); + + // set the transport mode found in the configuration + getTransport()->setNewMode(transportMode); + + // set the pointer position + // + slotSetPointerPosition(m_doc->getComposition().getPosition()); + + // make sure we show + // + RosegardenGUIView *oldView = m_view; + m_view = m_swapView; + + connect(m_view, SIGNAL(stateChange(QString, bool)), + this, SLOT (slotStateChanged(QString, bool))); + + connect(m_view, SIGNAL(instrumentParametersChanged(InstrumentId)), + this, SIGNAL(instrumentParametersChanged(InstrumentId))); + + // We only check for the SequenceManager to make sure + // we're not on the first pass though - we don't want + // to send these toggles twice on initialisation. + // + // Clunky but we just about get away with it for the + // moment. + // + if (m_seqManager != 0) { + slotToggleChordNameRuler(); + slotToggleRulers(); + slotToggleTempoRuler(); + slotTogglePreviews(); + slotToggleSegmentLabels(); + + // Reset any loop on the sequencer + // + try { + if (isUsingSequencer()) + m_seqManager->setLoop(0, 0); + stateChanged("have_range", KXMLGUIClient::StateReverse); + } catch (QString s) { + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + KMessageBox::error(this, s); + CurrentProgressDialog::thaw(); + } + + connect(m_seqManager, SIGNAL(controllerDeviceEventReceived(MappedEvent *)), + m_view, SLOT(slotControllerDeviceEventReceived(MappedEvent *))); + } + + // delete m_playList; + // m_playList = 0; + + delete m_deviceManager; + m_deviceManager = 0; + + delete m_synthManager; + m_synthManager = 0; + + delete m_audioMixer; + m_audioMixer = 0; + + delete m_bankEditor; + m_bankEditor = 0; + + delete m_markerEditor; + m_markerEditor = 0; + + delete m_tempoView; + m_tempoView = 0; + + delete m_triggerSegmentManager; + m_triggerSegmentManager = 0; + + delete oldView; + + // set the highlighted track + m_view->slotSelectTrackSegments(comp.getSelectedTrack()); + + // play tracking on in the editor by default: turn off if need be + KToggleAction *trackingAction = dynamic_cast + (actionCollection()->action("toggle_tracking")); + if (trackingAction && !trackingAction->isChecked()) { + m_view->getTrackEditor()->slotToggleTracking(); + } + + m_view->show(); + + connect(m_view->getTrackEditor()->getSegmentCanvas(), + SIGNAL(showContextHelp(const QString &)), + this, + SLOT(slotShowToolHelp(const QString &))); + + // We have to do this to make sure that the 2nd call ("select") + // actually has any effect. Activating the same radio action + // doesn't work the 2nd time (like pressing down the same radio + // button twice - it doesn't have any effect), so if you load two + // files in a row, on the 2nd file a new SegmentCanvas will be + // created but its tool won't be set, even though it will appear + // to be selected. + // + actionCollection()->action("move")->activate(); + if (m_doc->getComposition().getNbSegments() > 0) + actionCollection()->action("select")->activate(); + else + actionCollection()->action("draw")->activate(); + + int zoomLevel = m_doc->getConfiguration(). + get + + (DocumentConfiguration::ZoomLevel); + + m_zoomSlider->setSize(double(zoomLevel) / 1000.0); + slotChangeZoom(zoomLevel); + + //slotChangeZoom(int(m_zoomSlider->getCurrentSize())); + + stateChanged("new_file"); + + ProgressDialog::processEvents(); + + if (m_viewChordNameRuler->isChecked()) { + SetWaitCursor swc; + m_view->initChordNameRuler(); + } else { + m_view->initChordNameRuler(); + } +} + +void RosegardenGUIApp::setDocument(RosegardenGUIDoc* newDocument) +{ + if (m_doc == newDocument) + return ; + + emit documentAboutToChange(); + kapp->processEvents(); // to make sure all opened dialogs (mixer, midi devices...) are closed + + // Take care of all subparts which depend on the document + + // Caption + // + QString caption = kapp->caption(); + setCaption(caption + ": " + newDocument->getTitle()); + + // // reset AudioManagerDialog + // // + // delete m_audioManagerDialog; // TODO : replace this with a connection to documentAboutToChange() sig. + // m_audioManagerDialog = 0; + + RosegardenGUIDoc* oldDoc = m_doc; + + m_doc = newDocument; + + if (m_seqManager) // when we're called at startup, the seq. man. isn't created yet + m_seqManager->setDocument(m_doc); + + if (m_markerEditor) + m_markerEditor->setDocument(m_doc); + if (m_tempoView) { + delete m_tempoView; + m_tempoView = 0; + } + if (m_triggerSegmentManager) + m_triggerSegmentManager->setDocument(m_doc); + + m_trackParameterBox->setDocument(m_doc); + m_segmentParameterBox->setDocument(m_doc); + m_instrumentParameterBox->setDocument(m_doc); + +#ifdef HAVE_LIBLO + + if (m_pluginGUIManager) { + m_pluginGUIManager->stopAllGUIs(); + m_pluginGUIManager->setStudio(&m_doc->getStudio()); + } +#endif + + if (getView() && + getView()->getTrackEditor() && + getView()->getTrackEditor()->getSegmentCanvas()) { + getView()->getTrackEditor()->getSegmentCanvas()->endAudioPreviewGeneration(); + } + + // this will delete all edit views + // + delete oldDoc; + + // connect needed signals + // + connect(m_segmentParameterBox, SIGNAL(documentModified()), + m_doc, SLOT(slotDocumentModified())); + + connect(m_doc, SIGNAL(pointerPositionChanged(timeT)), + this, SLOT(slotSetPointerPosition(timeT))); + + connect(m_doc, SIGNAL(documentModified(bool)), + this, SLOT(slotDocumentModified(bool))); + + connect(m_doc, SIGNAL(loopChanged(timeT, timeT)), + this, SLOT(slotSetLoop(timeT, timeT))); + + m_doc->getCommandHistory()->attachView(actionCollection()); + + connect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()), + SLOT(update())); + connect(m_doc->getCommandHistory(), SIGNAL(commandExecuted()), + SLOT(slotTestClipboard())); + + // connect and start the autosave timer + connect(m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); + m_autoSaveTimer->start(m_doc->getAutoSavePeriod() * 1000); + + // Connect the playback timer + // + connect(m_playTimer, SIGNAL(timeout()), this, SLOT(slotUpdatePlaybackPosition())); + connect(m_stopTimer, SIGNAL(timeout()), this, SLOT(slotUpdateMonitoring())); + + // finally recreate the main view + // + initView(); + + if (getView() && getView()->getTrackEditor()) { + connect(m_doc, SIGNAL(makeTrackVisible(int)), + getView()->getTrackEditor(), SLOT(slotScrollToTrack(int))); + } + + connect(m_doc, SIGNAL(devicesResyncd()), + this, SLOT(slotDocumentDevicesResyncd())); + + m_doc->syncDevices(); + m_doc->clearModifiedStatus(); + + if (newDocument->getStudio().haveMidiDevices()) { + stateChanged("got_midi_devices"); + } else { + stateChanged("got_midi_devices", KXMLGUIClient::StateReverse); + } + + // Ensure the sequencer knows about any audio files + // we've loaded as part of the new Composition + // + m_doc->prepareAudio(); + + // Do not reset instrument prog. changes after all. + // if (m_seqManager) + // m_seqManager->preparePlayback(true); + + Composition &comp = m_doc->getComposition(); + + // Set any loaded loop at the Composition and + // on the marker on SegmentCanvas and clients + // + if (m_seqManager) + m_doc->setLoop(comp.getLoopStart(), comp.getLoopEnd()); + + emit documentChanged(m_doc); + + m_doc->clearModifiedStatus(); // because it's set as modified by the various + // init operations + // TODO: this sucks, have to sort it out somehow. + + // Readjust canvas size + // + m_view->getTrackEditor()->slotReadjustCanvasSize(); + + m_stopTimer->start(100); +} + +void +RosegardenGUIApp::openFile(QString filePath, ImportType type) +{ + RG_DEBUG << "RosegardenGUIApp::openFile " << filePath << endl; + + if (type == ImportCheckType && filePath.endsWith(".rgp")) { + importProject(filePath); + return ; + } + + RosegardenGUIDoc *doc = createDocument(filePath, type); + if (doc) { + setDocument(doc); + + // fix # 1235755, "SPB combo not updating after document swap" + RG_DEBUG << "RosegardenGUIApp::openFile(): calling slotDocColoursChanged() in doc" << endl; + doc->slotDocColoursChanged(); + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + if (kapp->config()->readBoolEntry("alwaysusedefaultstudio", false)) { + + QString autoloadFile = + KGlobal::dirs()->findResource("appdata", "autoload.rg"); + + QFileInfo autoloadFileInfo(autoloadFile); + if (autoloadFileInfo.isReadable()) { + + RG_DEBUG << "Importing default studio from " << autoloadFile << endl; + + slotImportStudioFromFile(autoloadFile); + } + } + + QFileInfo fInfo(filePath); + m_fileRecent->addURL(fInfo.absFilePath()); + } +} + +RosegardenGUIDoc* +RosegardenGUIApp::createDocument(QString filePath, ImportType importType) +{ + QFileInfo info(filePath); + RosegardenGUIDoc *doc = 0; + + if (!info.exists()) { + // can happen with command-line arg, so... + KStartupLogo::hideIfStillThere(); + KMessageBox::sorry(this, i18n("File \"%1\" does not exist").arg(filePath)); + return 0; + } + + if (info.isDir()) { + KStartupLogo::hideIfStillThere(); + KMessageBox::sorry(this, i18n("File \"%1\" is actually a directory")); + return 0; + } + + QFile file(filePath); + + if (!file.open(IO_ReadOnly)) { + KStartupLogo::hideIfStillThere(); + QString errStr = + i18n("You do not have read permission for \"%1\"").arg(filePath); + + KMessageBox::sorry(this, errStr); + return 0; + } + + // Stop if playing + // + if (m_seqManager && m_seqManager->getTransportStatus() == PLAYING) + slotStop(); + + slotEnableTransport(false); + + if (importType == ImportCheckType) { + KMimeType::Ptr fileMimeType = KMimeType::findByPath(filePath); + if (fileMimeType->name() == "audio/x-midi") + importType = ImportMIDI; + else if (fileMimeType->name() == "audio/x-rosegarden") + importType = ImportRG4; + else if (filePath.endsWith(".rose")) + importType = ImportRG21; + else if (filePath.endsWith(".h2song")) + importType = ImportHydrogen; + } + + + switch (importType) { + case ImportMIDI: + doc = createDocumentFromMIDIFile(filePath); + break; + case ImportRG21: + doc = createDocumentFromRG21File(filePath); + break; + case ImportHydrogen: + doc = createDocumentFromHydrogenFile(filePath); + break; + default: + doc = createDocumentFromRGFile(filePath); + } + + slotEnableTransport(true); + + return doc; +} + +RosegardenGUIDoc* +RosegardenGUIApp::createDocumentFromRGFile(QString filePath) +{ + // Check for an autosaved file to recover + QString effectiveFilePath = filePath; + bool canRecover = false; + QString autoSaveFileName = kapp->checkRecoverFile(filePath, canRecover); + + if (canRecover) { + // First check if the auto-save file is more recent than the doc + QFileInfo docFileInfo(filePath), autoSaveFileInfo(autoSaveFileName); + + if (docFileInfo.lastModified() < autoSaveFileInfo.lastModified()) { + + RG_DEBUG << "RosegardenGUIApp::openFile : " + << "found a more recent autosave file\n"; + + // At this point the splash screen may still be there, hide it if + // it's the case + KStartupLogo::hideIfStillThere(); + + // It is, so ask the user if he wants to use the autosave file + int reply = KMessageBox::questionYesNo(this, + i18n("An auto-save file for this document has been found\nDo you want to open it instead ?")); + + if (reply == KMessageBox::Yes) + // open the autosave file instead + effectiveFilePath = autoSaveFileName; + else { + // user doesn't want the autosave, so delete it + // so it won't bother us again if we reload + canRecover = false; + QFile::remove + (autoSaveFileName); + } + + } else + canRecover = false; + } + + // Create a new blank document + // + RosegardenGUIDoc *newDoc = new RosegardenGUIDoc(this, m_pluginManager, + true); // skipAutoload + + // ignore return thingy + // + if (newDoc->openDocument(effectiveFilePath)) { + if (canRecover) { + // Mark the document as modified, + // set the "regular" filepath and name (not those of + // the autosaved doc) + // + newDoc->slotDocumentModified(); + QFileInfo info(filePath); + newDoc->setAbsFilePath(info.absFilePath()); + newDoc->setTitle(info.fileName()); + } else { + newDoc->clearModifiedStatus(); + } + } else { + delete newDoc; + return 0; + } + + return newDoc; +} + +void RosegardenGUIApp::slotSaveOptions() +{ + RG_DEBUG << "RosegardenGUIApp::slotSaveOptions()\n"; + +#ifdef SETTING_LOG_DEBUG + + _settingLog(QString("SETTING 2 : transport flap extended = %1").arg(getTransport()->isExpanded())); + _settingLog(QString("SETTING 2 : show track labels = %1").arg(m_viewTrackLabels->isChecked())); +#endif + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + kapp->config()->writeEntry("Show Transport", m_viewTransport->isChecked()); + kapp->config()->writeEntry("Expanded Transport", m_transport ? getTransport()->isExpanded() : true); + kapp->config()->writeEntry("Show Track labels", m_viewTrackLabels->isChecked()); + kapp->config()->writeEntry("Show Rulers", m_viewRulers->isChecked()); + kapp->config()->writeEntry("Show Tempo Ruler", m_viewTempoRuler->isChecked()); + kapp->config()->writeEntry("Show Chord Name Ruler", m_viewChordNameRuler->isChecked()); + kapp->config()->writeEntry("Show Previews", m_viewPreviews->isChecked()); + kapp->config()->writeEntry("Show Segment Labels", m_viewSegmentLabels->isChecked()); + kapp->config()->writeEntry("Show Parameters", m_dockVisible); + kapp->config()->writeEntry("MIDI Thru Routing", m_enableMIDIrouting->isChecked()); + +#ifdef SETTING_LOG_DEBUG + + RG_DEBUG << "SHOW PARAMETERS = " << m_dockVisible << endl; +#endif + + m_fileRecent->saveEntries(kapp->config()); + + // saveMainWindowSettings(kapp->config(), RosegardenGUIApp::MainWindowConfigGroup); - no need to, done by KMainWindow + kapp->config()->sync(); +} + +void RosegardenGUIApp::setupFileDialogSpeedbar() +{ + KConfig *config = kapp->config(); + + config->setGroup("KFileDialog Speedbar"); + + RG_DEBUG << "RosegardenGUIApp::setupFileDialogSpeedbar" << endl; + + bool hasSetExamplesItem = config->readBoolEntry("Examples Set", false); + + RG_DEBUG << "RosegardenGUIApp::setupFileDialogSpeedbar: examples set " << hasSetExamplesItem << endl; + + if (!hasSetExamplesItem) { + + unsigned int n = config->readUnsignedNumEntry("Number of Entries", 0); + + config->writeEntry(QString("Description_%1").arg(n), i18n("Example Files")); + config->writeEntry(QString("IconGroup_%1").arg(n), 4); + config->writeEntry(QString("Icon_%1").arg(n), "folder"); + config->writeEntry(QString("URL_%1").arg(n), + KGlobal::dirs()->findResource("appdata", "examples/")); + + RG_DEBUG << "wrote url " << config->readEntry(QString("URL_%1").arg(n)) << endl; + + config->writeEntry("Examples Set", true); + config->writeEntry("Number of Entries", n + 1); + config->sync(); + } + +} + +void RosegardenGUIApp::readOptions() +{ + applyMainWindowSettings(kapp->config(), MainWindowConfigGroup); + + kapp->config()->reparseConfiguration(); + + // Statusbar and toolbars toggling action status + // + m_viewStatusBar ->setChecked(!statusBar() ->isHidden()); + m_viewToolBar ->setChecked(!toolBar() ->isHidden()); + m_viewToolsToolBar ->setChecked(!toolBar("Tools Toolbar") ->isHidden()); + m_viewTracksToolBar ->setChecked(!toolBar("Tracks Toolbar") ->isHidden()); + m_viewEditorsToolBar ->setChecked(!toolBar("Editors Toolbar") ->isHidden()); + m_viewTransportToolBar->setChecked(!toolBar("Transport Toolbar")->isHidden()); + m_viewZoomToolBar ->setChecked(!toolBar("Zoom Toolbar") ->isHidden()); + + bool opt; + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + + opt = kapp->config()->readBoolEntry("Show Transport", true); + m_viewTransport->setChecked(opt); + slotToggleTransport(); + + opt = kapp->config()->readBoolEntry("Expanded Transport", true); + +#ifdef SETTING_LOG_DEBUG + + _settingLog(QString("SETTING 3 : transport flap extended = %1").arg(opt)); +#endif + + if (opt) + getTransport()->slotPanelOpenButtonClicked(); + else + getTransport()->slotPanelCloseButtonClicked(); + + opt = kapp->config()->readBoolEntry("Show Track labels", true); + +#ifdef SETTING_LOG_DEBUG + + _settingLog(QString("SETTING 3 : show track labels = %1").arg(opt)); +#endif + + m_viewTrackLabels->setChecked(opt); + slotToggleTrackLabels(); + + opt = kapp->config()->readBoolEntry("Show Rulers", true); + m_viewRulers->setChecked(opt); + slotToggleRulers(); + + opt = kapp->config()->readBoolEntry("Show Tempo Ruler", true); + m_viewTempoRuler->setChecked(opt); + slotToggleTempoRuler(); + + opt = kapp->config()->readBoolEntry("Show Chord Name Ruler", false); + m_viewChordNameRuler->setChecked(opt); + slotToggleChordNameRuler(); + + opt = kapp->config()->readBoolEntry("Show Previews", true); + m_viewPreviews->setChecked(opt); + slotTogglePreviews(); + + opt = kapp->config()->readBoolEntry("Show Segment Labels", true); + m_viewSegmentLabels->setChecked(opt); + slotToggleSegmentLabels(); + + opt = kapp->config()->readBoolEntry("Show Parameters", true); + if (!opt) { + m_dockLeft->undock(); + m_dockLeft->hide(); + stateChanged("parametersbox_closed", KXMLGUIClient::StateNoReverse); + m_dockVisible = false; + } + + // MIDI Thru routing + opt = kapp->config()->readBoolEntry("MIDI Thru Routing", true); + m_enableMIDIrouting->setChecked(opt); + slotEnableMIDIThruRouting(); + + // initialise the recent file list + // + m_fileRecent->loadEntries(kapp->config()); + + m_actionsSetup = true; + +} + +void RosegardenGUIApp::saveGlobalProperties(KConfig *cfg) +{ + if (m_doc->getTitle() != i18n("Untitled") && !m_doc->isModified()) { + // saving to tempfile not necessary + } else { + QString filename = m_doc->getAbsFilePath(); + cfg->writeEntry("filename", filename); + cfg->writeEntry("modified", m_doc->isModified()); + + QString tempname = kapp->tempSaveName(filename); + QString errMsg; + bool res = m_doc->saveDocument(tempname, errMsg); + if (!res) { + if (errMsg) + KMessageBox::error(this, i18n(QString("Could not save document at %1\nError was : %2") + .arg(tempname).arg(errMsg))); + else + KMessageBox::error(this, i18n(QString("Could not save document at %1") + .arg(tempname))); + } + } +} + +void RosegardenGUIApp::readGlobalProperties(KConfig* _cfg) +{ + QString filename = _cfg->readEntry("filename", ""); + bool modified = _cfg->readBoolEntry("modified", false); + + if (modified) { + bool canRecover; + QString tempname = kapp->checkRecoverFile(filename, canRecover); + + if (canRecover) { + slotEnableTransport(false); + m_doc->openDocument(tempname); + slotEnableTransport(true); + m_doc->slotDocumentModified(); + QFileInfo info(filename); + m_doc->setAbsFilePath(info.absFilePath()); + m_doc->setTitle(info.fileName()); + } + } else { + if (!filename.isEmpty()) { + slotEnableTransport(false); + m_doc->openDocument(filename); + slotEnableTransport(true); + } + } + + QString caption = kapp->caption(); + setCaption(caption + ": " + m_doc->getTitle()); +} + +void RosegardenGUIApp::showEvent(QShowEvent* e) +{ + RG_DEBUG << "RosegardenGUIApp::showEvent()\n"; + + getTransport()->raise(); + KMainWindow::showEvent(e); +} + +bool RosegardenGUIApp::queryClose() +{ + RG_DEBUG << "RosegardenGUIApp::queryClose" << endl; +#ifdef SETTING_LOG_DEBUG + + _settingLog(QString("SETTING 1 : transport flap extended = %1").arg(getTransport()->isExpanded())); + _settingLog(QString("SETTING 1 : show track labels = %1").arg(m_viewTrackLabels->isChecked())); +#endif + + QString errMsg; + + bool canClose = m_doc->saveIfModified(); + + /* + if (canClose && m_transport) { + + // or else the closing of the transport will toggle off the + // 'view transport' action, and its state will be saved as + // 'off' + // + + disconnect(m_transport, SIGNAL(closed()), + this, SLOT(slotCloseTransport())); + } + */ + + return canClose; + +} + +bool RosegardenGUIApp::queryExit() +{ + RG_DEBUG << "RosegardenGUIApp::queryExit" << endl; + if (m_actionsSetup) + slotSaveOptions(); + + return true; +} + +void RosegardenGUIApp::slotFileNewWindow() +{ + KTmpStatusMsg msg(i18n("Opening a new application window..."), this); + + RosegardenGUIApp *new_window = new RosegardenGUIApp(); + new_window->show(); +} + +void RosegardenGUIApp::slotFileNew() +{ + RG_DEBUG << "RosegardenGUIApp::slotFileNew()\n"; + + KTmpStatusMsg msg(i18n("Creating new document..."), this); + + bool makeNew = false; + + if (!m_doc->isModified()) { + makeNew = true; + // m_doc->closeDocument(); + } else if (m_doc->saveIfModified()) { + makeNew = true; + } + + if (makeNew) { + + setDocument(new RosegardenGUIDoc(this, m_pluginManager)); + } +} + +void RosegardenGUIApp::slotOpenDroppedURL(QString url) +{ + ProgressDialog::processEvents(); // or else we get a crash because the + // track editor is erased too soon - it is the originator of the signal + // this slot is connected to. + + if (!m_doc->saveIfModified()) + return ; + + openURL(KURL(url)); +} + +void RosegardenGUIApp::openURL(QString url) +{ + RG_DEBUG << "RosegardenGUIApp::openURL: QString " << url << endl; + openURL(KURL(url)); +} + +void RosegardenGUIApp::openURL(const KURL& url) +{ + SetWaitCursor waitCursor; + + QString netFile = url.prettyURL(); + RG_DEBUG << "RosegardenGUIApp::openURL: KURL " << netFile << endl; + + if (!url.isValid()) { + QString string; + string = i18n( "Malformed URL\n%1").arg(netFile); + + KMessageBox::sorry(this, string); + return ; + } + + QString target, caption(url.path()); + + if (KIO::NetAccess::download(url, target, this) == false) { + KMessageBox::error(this, i18n("Cannot download file %1").arg(url.prettyURL())); + return ; + } + + RG_DEBUG << "RosegardenGUIApp::openURL: target : " << target << endl; + + if (!m_doc->saveIfModified()) + return ; + + openFile(target); + + setCaption(caption); +} + +void RosegardenGUIApp::slotFileOpen() +{ + slotStatusHelpMsg(i18n("Opening file...")); + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + + QString lastOpenedVersion = + kapp->config()->readEntry("Last File Opened Version", "none"); + + if (lastOpenedVersion != VERSION) { + + // We haven't opened any files with this version of the + // program before. Default to the examples directory. + + QString examplesDir = KGlobal::dirs()->findResource("appdata", "examples/"); + kapp->config()->setGroup("Recent Dirs"); + QString recentString = kapp->config()->readEntry("ROSEGARDEN", ""); + kapp->config()->writeEntry + ("ROSEGARDEN", QString("file:%1,%2").arg(examplesDir).arg(recentString)); + } + + KURL url = KFileDialog::getOpenURL + (":ROSEGARDEN", + "audio/x-rosegarden audio/x-midi audio/x-rosegarden21", this, + i18n("Open File")); + if ( url.isEmpty() ) { + return ; + } + + if (m_doc && !m_doc->saveIfModified()) + return ; + + kapp->config()->setGroup(GeneralOptionsConfigGroup); + kapp->config()->writeEntry("Last File Opened Version", VERSION); + + openURL(url); +} + +void RosegardenGUIApp::slotMerge() +{ + KURL url = KFileDialog::getOpenURL + (":ROSEGARDEN", + "audio/x-rosegarden audio/x-midi audio/x-rosegarden21", this, + i18n("Open File")); + if ( url.isEmpty() ) { + return ; + } + + + QString target; + + if (KIO::NetAccess::download(url, target, this) == false) { + KMessageBox::error(this, i18n("Cannot download file %1").arg(url.prettyURL())); + return ; + } + + mergeFile(target); + + KIO::NetAccess::removeTempFile( target ); +} + +void RosegardenGUIApp::slotFileOpenRecent(const KURL &url) +{ + KTmpStatusMsg msg(i18n("Opening file..."), this); + + if (m_doc) { + + if (!m_doc->saveIfModified()) { + return ; + + } + } + + openURL(url); +} + +void RosegardenGUIApp::slotFileSave() +{ + if (!m_doc /*|| !m_doc->isModified()*/) + return ; // ALWAYS save, even if doc is not modified. + + KTmpStatusMsg msg(i18n("Saving file..."), this); + + // if it's a new file (no file path), or an imported file + // (file path doesn't end with .rg), call saveAs + // + if (!m_doc->isRegularDotRGFile()) { + + slotFileSaveAs(); + + } else { + + SetWaitCursor waitCursor; + QString errMsg, docFilePath = m_doc->getAbsFilePath(); + + bool res = m_doc->saveDocument(docFilePath, errMsg); + if (!res) { + if (errMsg) + KMessageBox::error(this, i18n(QString("Could not save document at %1\nError was : %2") + .arg(docFilePath).arg(errMsg))); + else + KMessageBox::error(this, i18n(QString("Could not save document at %1") + .arg(docFilePath))); + } + } +} + +QString +RosegardenGUIApp::getValidWriteFile(QString descriptiveExtension, + QString label) +{ + // extract first extension listed in descriptiveExtension, for instance, + // ".rg" from "*.rg|Rosegarden files", or ".mid" from "*.mid *.midi|MIDI Files" + // + QString extension = descriptiveExtension.left(descriptiveExtension.find('|')).mid(1).section(' ', 0, 0); + + RG_DEBUG << "RosegardenGUIApp::getValidWriteFile() : extension = " << extension << endl; + + // It's too bad there isn't this functionality within + // KFileDialog::getSaveFileName + KFileDialog saveFileDialog(":ROSEGARDEN", descriptiveExtension, this, label, true); + saveFileDialog.setOperationMode(KFileDialog::Saving); + if (m_doc) { + QString saveFileName = m_doc->getAbsFilePath(); + // Show filename without the old extension + int dotLoc = saveFileName.findRev('.'); + if (dotLoc >= int(saveFileName.length() - 4)) { + saveFileName = saveFileName.left(dotLoc); + } + saveFileDialog.setSelection(saveFileName); + } + saveFileDialog.exec(); + QString name = saveFileDialog.selectedFile(); + + // RG_DEBUG << "RosegardenGUIApp::getValidWriteFile() : KFileDialog::getSaveFileName returned " + // << name << endl; + + + if (name.isEmpty()) + return name; + + // Append extension if we don't have one + // + if (!extension.isEmpty()) { + static QRegExp rgFile("\\..{1,4}$"); + if (rgFile.match(name) == -1) { + name += extension; + } + } + + KURL *u = new KURL(name); + + if (!u->isValid()) { + KMessageBox::sorry(this, i18n("This is not a valid filename.\n")); + return ""; + } + + if (!u->isLocalFile()) { + KMessageBox::sorry(this, i18n("This is not a local file.\n")); + return ""; + } + + QFileInfo info(name); + + if (info.isDir()) { + KMessageBox::sorry(this, i18n("You have specified a directory")); + return ""; + } + + if (info.exists()) { + int overwrite = KMessageBox::questionYesNo + (this, i18n("The specified file exists. Overwrite?")); + + if (overwrite != KMessageBox::Yes) + return ""; + } + + return name; +} + +bool RosegardenGUIApp::slotFileSaveAs() +{ + if (!m_doc) + return false; + + KTmpStatusMsg msg(i18n("Saving file with a new filename..."), this); + + QString newName = getValidWriteFile("*.rg|" + i18n("Rosegarden files") + + "\n*|" + i18n("All files"), + i18n("Save as...")); + if (newName.isEmpty()) + return false; + + SetWaitCursor waitCursor; + QFileInfo saveAsInfo(newName); + m_doc->setTitle(saveAsInfo.fileName()); + m_doc->setAbsFilePath(saveAsInfo.absFilePath()); + QString errMsg; + bool res = m_doc->saveDocument(newName, errMsg); + if (!res) { + if (errMsg) + KMessageBox::error(this, i18n(QString("Could not save document at %1\nError was : %2") + .arg(newName).arg(errMsg))); + else + KMessageBox::error(this, i18n(QString("Could not save document at %1") + .arg(newName))); + + } else { + + m_fileRecent->addURL(newName); + + QString caption = kapp->caption(); + setCaption(caption + ": " + m_doc->getTitle()); + // update the edit view's captions too + emit compositionStateUpdate(); + } + + return res; +} + +void RosegardenGUIApp::slotFileClose() +{ + RG_DEBUG << "RosegardenGUIApp::slotFileClose()" << endl; + + if (!m_doc) + return ; + + KTmpStatusMsg msg(i18n("Closing file..."), this); + + if (m_doc->saveIfModified()) { + setDocument(new RosegardenGUIDoc(this, m_pluginManager)); + } + + // Don't close the whole view (i.e. Quit), just close the doc. + // close(); +} + +void RosegardenGUIApp::slotFilePrint() +{ + if (m_doc->getComposition().getNbSegments() == 0) { + KMessageBox::sorry(0, "Please create some tracks first (until we implement menu state management)"); + return ; + } + + KTmpStatusMsg msg(i18n("Printing..."), this); + + m_view->print(&m_doc->getComposition()); +} + +void RosegardenGUIApp::slotFilePrintPreview() +{ + if (m_doc->getComposition().getNbSegments() == 0) { + KMessageBox::sorry(0, "Please create some tracks first (until we implement menu state management)"); + return ; + } + + KTmpStatusMsg msg(i18n("Previewing..."), this); + + m_view->print(&m_doc->getComposition(), true); +} + +void RosegardenGUIApp::slotQuit() +{ + slotStatusMsg(i18n("Exiting...")); + + Profiles::getInstance()->dump(); + + // close the first window, the list makes the next one the first again. + // This ensures that queryClose() is called on each window to ask for closing + KMainWindow* w; + if (memberList) { + + for (w = memberList->first(); w != 0; w = memberList->next()) { + // only close the window if the closeEvent is accepted. If + // the user presses Cancel on the saveIfModified() dialog, + // the window and the application stay open. + if (!w->close()) + break; + } + } +} + +void RosegardenGUIApp::slotEditCut() +{ + if (!m_view->haveSelection()) + return ; + KTmpStatusMsg msg(i18n("Cutting selection..."), this); + + SegmentSelection selection(m_view->getSelection()); + m_doc->getCommandHistory()->addCommand + (new CutCommand(selection, m_clipboard)); +} + +void RosegardenGUIApp::slotEditCopy() +{ + if (!m_view->haveSelection()) + return ; + KTmpStatusMsg msg(i18n("Copying selection to clipboard..."), this); + + SegmentSelection selection(m_view->getSelection()); + m_doc->getCommandHistory()->addCommand + (new CopyCommand(selection, m_clipboard)); +} + +void RosegardenGUIApp::slotEditPaste() +{ + if (m_clipboard->isEmpty()) { + KTmpStatusMsg msg(i18n("Clipboard is empty"), this); + return ; + } + KTmpStatusMsg msg(i18n("Inserting clipboard contents..."), this); + + // for now, but we could paste at the time of the first copied + // segment and then do ghosting drag or something + timeT insertionTime = m_doc->getComposition().getPosition(); + m_doc->getCommandHistory()->addCommand + (new PasteSegmentsCommand(&m_doc->getComposition(), + m_clipboard, insertionTime, + m_doc->getComposition().getSelectedTrack(), + false)); + + // User preference? Update song pointer position on paste + m_doc->slotSetPointerPosition(m_doc->getComposition().getPosition()); +} + +void RosegardenGUIApp::slotCutRange() +{ + timeT t0 = m_doc->getComposition().getLoopStart(); + timeT t1 = m_doc->getComposition().getLoopEnd(); + + if (t0 == t1) + return ; + + m_doc->getCommandHistory()->addCommand + (new CutRangeCommand(&m_doc->getComposition(), t0, t1, m_clipboard)); +} + +void RosegardenGUIApp::slotCopyRange() +{ + timeT t0 = m_doc->getComposition().getLoopStart(); + timeT t1 = m_doc->getComposition().getLoopEnd(); + + if (t0 == t1) + return ; + + m_doc->getCommandHistory()->addCommand + (new CopyCommand(&m_doc->getComposition(), t0, t1, m_clipboard)); +} + +void RosegardenGUIApp::slotPasteRange() +{ + if (m_clipboard->isEmpty()) + return ; + + m_doc->getCommandHistory()->addCommand + (new PasteRangeCommand(&m_doc->getComposition(), m_clipboard, + m_doc->getComposition().getPosition())); + + m_doc->setLoop(0, 0); +} + +void RosegardenGUIApp::slotDeleteRange() +{ + timeT t0 = m_doc->getComposition().getLoopStart(); + timeT t1 = m_doc->getComposition().getLoopEnd(); + + if (t0 == t1) + return ; + + m_doc->getCommandHistory()->addCommand + (new DeleteRangeCommand(&m_doc->getComposition(), t0, t1)); + + m_doc->setLoop(0, 0); +} + +void RosegardenGUIApp::slotInsertRange() +{ + timeT t0 = m_doc->getComposition().getPosition(); + std::pair r = m_doc->getComposition().getBarRangeForTime(t0); + TimeDialog dialog(m_view, i18n("Duration of empty range to insert"), + &m_doc->getComposition(), t0, r.second - r.first, false); + if (dialog.exec() == QDialog::Accepted) { + m_doc->getCommandHistory()->addCommand + (new InsertRangeCommand(&m_doc->getComposition(), t0, dialog.getTime())); + m_doc->setLoop(0, 0); + } +} + +void RosegardenGUIApp::slotSelectAll() +{ + m_view->slotSelectAllSegments(); +} + +void RosegardenGUIApp::slotDeleteSelectedSegments() +{ + m_view->getTrackEditor()->slotDeleteSelectedSegments(); +} + +void RosegardenGUIApp::slotQuantizeSelection() +{ + if (!m_view->haveSelection()) + return ; + + //!!! this should all be in rosegardenguiview + + QuantizeDialog dialog(m_view); + if (dialog.exec() != QDialog::Accepted) + return ; + + SegmentSelection selection = m_view->getSelection(); + + KMacroCommand *command = new KMacroCommand + (EventQuantizeCommand::getGlobalName()); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + command->addCommand(new EventQuantizeCommand + (**i, (*i)->getStartTime(), (*i)->getEndTime(), + dialog.getQuantizer())); + } + + m_view->slotAddCommandToHistory(command); +} + +void RosegardenGUIApp::slotRepeatQuantizeSelection() +{ + if (!m_view->haveSelection()) + return ; + + //!!! this should all be in rosegardenguiview + + SegmentSelection selection = m_view->getSelection(); + + KMacroCommand *command = new KMacroCommand + (EventQuantizeCommand::getGlobalName()); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + command->addCommand(new EventQuantizeCommand + (**i, (*i)->getStartTime(), (*i)->getEndTime(), + "Quantize Dialog Grid", false)); // no i18n (config group name) + } + + m_view->slotAddCommandToHistory(command); +} + +void RosegardenGUIApp::slotGrooveQuantize() +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + + if (selection.size() != 1) { + KMessageBox::sorry(this, i18n("This function needs no more than one segment to be selected.")); + return ; + } + + Segment *s = *selection.begin(); + m_view->slotAddCommandToHistory(new CreateTempoMapFromSegmentCommand(s)); +} + +void RosegardenGUIApp::slotJoinSegments() +{ + if (!m_view->haveSelection()) + return ; + + //!!! this should all be in rosegardenguiview + //!!! should it? + + SegmentSelection selection = m_view->getSelection(); + if (selection.size() == 0) + return ; + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + if ((*i)->getType() != Segment::Internal) { + KMessageBox::sorry(this, i18n("Can't join Audio segments")); + return ; + } + } + + m_view->slotAddCommandToHistory(new SegmentJoinCommand(selection)); + m_view->updateSelectionContents(); +} + +void RosegardenGUIApp::slotRescaleSelection() +{ + if (!m_view->haveSelection()) + return ; + + //!!! this should all be in rosegardenguiview + //!!! should it? + + SegmentSelection selection = m_view->getSelection(); + + timeT startTime = 0, endTime = 0; + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + if ((i == selection.begin()) || ((*i)->getStartTime() < startTime)) { + startTime = (*i)->getStartTime(); + } + if ((i == selection.begin()) || ((*i)->getEndMarkerTime() > endTime)) { + endTime = (*i)->getEndMarkerTime(); + } + } + + RescaleDialog dialog(m_view, &m_doc->getComposition(), + startTime, endTime - startTime, + false, false); + if (dialog.exec() != QDialog::Accepted) + return ; + + std::vector asrcs; + + int mult = dialog.getNewDuration(); + int div = endTime - startTime; + float ratio = float(mult) / float(div); + + std::cerr << "slotRescaleSelection: mult = " << mult << ", div = " << div << ", ratio = " << ratio << std::endl; + + KMacroCommand *command = new KMacroCommand + (SegmentRescaleCommand::getGlobalName()); + + bool pathTested = false; + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + if ((*i)->getType() == Segment::Audio) { + if (!pathTested) { + testAudioPath(i18n("rescaling an audio file")); + pathTested = true; + } + AudioSegmentRescaleCommand *asrc = new AudioSegmentRescaleCommand + (m_doc, *i, ratio); + command->addCommand(asrc); + asrcs.push_back(asrc); + } else { + command->addCommand(new SegmentRescaleCommand(*i, mult, div)); + } + } + + ProgressDialog *progressDlg = 0; + + if (!asrcs.empty()) { + progressDlg = new ProgressDialog + (i18n("Rescaling audio file..."), 100, this); + progressDlg->setAutoClose(false); + progressDlg->setAutoReset(false); + progressDlg->show(); + for (size_t i = 0; i < asrcs.size(); ++i) { + asrcs[i]->connectProgressDialog(progressDlg); + } + } + + m_view->slotAddCommandToHistory(command); + + if (!asrcs.empty()) { + + progressDlg->setLabel(i18n("Generating audio preview...")); + + for (size_t i = 0; i < asrcs.size(); ++i) { + asrcs[i]->disconnectProgressDialog(progressDlg); + } + + connect(&m_doc->getAudioFileManager(), SIGNAL(setProgress(int)), + progressDlg->progressBar(), SLOT(setValue(int))); + connect(progressDlg, SIGNAL(cancelClicked()), + &m_doc->getAudioFileManager(), SLOT(slotStopPreview())); + + for (size_t i = 0; i < asrcs.size(); ++i) { + int fid = asrcs[i]->getNewAudioFileId(); + if (fid >= 0) { + slotAddAudioFile(fid); + m_doc->getAudioFileManager().generatePreview(fid); + } + } + } + + if (progressDlg) delete progressDlg; +} + +bool +RosegardenGUIApp::testAudioPath(QString op) +{ + try { + m_doc->getAudioFileManager().testAudioPath(); + } catch (AudioFileManager::BadAudioPathException) { + if (KMessageBox::warningContinueCancel + (this, + 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 %1.\nWould you like to set it now?").arg(op), + i18n("Warning"), + i18n("Set audio file path")) == KMessageBox::Continue) { + slotOpenAudioPathSettings(); + } + return false; + } + return true; +} + +void RosegardenGUIApp::slotAutoSplitSelection() +{ + if (!m_view->haveSelection()) + return ; + + //!!! this should all be in rosegardenguiview + //!!! or should it? + + SegmentSelection selection = m_view->getSelection(); + + KMacroCommand *command = new KMacroCommand + (SegmentAutoSplitCommand::getGlobalName()); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + if ((*i)->getType() == Segment::Audio) { + AudioSplitDialog aSD(this, (*i), m_doc); + + if (aSD.exec() == QDialog::Accepted) { + // split to threshold + // + command->addCommand( + new AudioSegmentAutoSplitCommand(m_doc, + *i, + aSD.getThreshold())); + // dmm - verifying that widget->value() accessors *can* work without crashing + // std::cout << "SILVAN: getThreshold() = " << aSD.getThreshold() << std::endl; + } + } else { + command->addCommand(new SegmentAutoSplitCommand(*i)); + } + } + + m_view->slotAddCommandToHistory(command); +} + +void RosegardenGUIApp::slotJogLeft() +{ + RG_DEBUG << "RosegardenGUIApp::slotJogLeft" << endl; + jogSelection( -Note(Note::Demisemiquaver).getDuration()); +} + +void RosegardenGUIApp::slotJogRight() +{ + RG_DEBUG << "RosegardenGUIApp::slotJogRight" << endl; + jogSelection(Note(Note::Demisemiquaver).getDuration()); +} + +void RosegardenGUIApp::jogSelection(timeT amount) +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + + SegmentReconfigureCommand *command = new SegmentReconfigureCommand(i18n("Jog Selection")); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + command->addSegment((*i), + (*i)->getStartTime() + amount, + (*i)->getEndMarkerTime() + amount, + (*i)->getTrack()); + } + + m_view->slotAddCommandToHistory(command); +} + +void RosegardenGUIApp::createAndSetupTransport() +{ + // create the Transport GUI and add the callbacks to the + // buttons and keyboard accelerators + // + m_transport = + new TransportDialog(this); + plugAccelerators(m_transport, m_transport->getAccelerators()); + + m_transport->getAccelerators()->connectItem + (m_transport->getAccelerators()->insertItem(Key_T), + this, + SLOT(slotHideTransport())); + + // Ensure that the checkbox is unchecked if the dialog + // is closed + connect(m_transport, SIGNAL(closed()), + SLOT(slotCloseTransport())); + + // Handle loop setting and unsetting from the transport loop button + // + + connect(m_transport, SIGNAL(setLoop()), SLOT(slotSetLoop())); + connect(m_transport, SIGNAL(unsetLoop()), SLOT(slotUnsetLoop())); + connect(m_transport, SIGNAL(panic()), SLOT(slotPanic())); + + connect(m_transport, SIGNAL(editTempo(QWidget*)), + SLOT(slotEditTempo(QWidget*))); + + connect(m_transport, SIGNAL(editTimeSignature(QWidget*)), + SLOT(slotEditTimeSignature(QWidget*))); + + connect(m_transport, SIGNAL(editTransportTime(QWidget*)), + SLOT(slotEditTransportTime(QWidget*))); + + // Handle set loop start/stop time buttons. + // + connect(m_transport, SIGNAL(setLoopStartTime()), SLOT(slotSetLoopStart())); + connect(m_transport, SIGNAL(setLoopStopTime()), SLOT(slotSetLoopStop())); + + if (m_seqManager != 0) + m_seqManager->setTransport(m_transport); + +} + +void RosegardenGUIApp::slotSplitSelectionByPitch() +{ + if (!m_view->haveSelection()) + return ; + + SplitByPitchDialog dialog(m_view); + if (dialog.exec() != QDialog::Accepted) + return ; + + SegmentSelection selection = m_view->getSelection(); + + KMacroCommand *command = new KMacroCommand + (SegmentSplitByPitchCommand::getGlobalName()); + + bool haveSomething = false; + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + if ((*i)->getType() == Segment::Audio) { + // nothing + } else { + command->addCommand + (new SegmentSplitByPitchCommand + (*i, + dialog.getPitch(), + dialog.getShouldRange(), + dialog.getShouldDuplicateNonNoteEvents(), + (SegmentSplitByPitchCommand::ClefHandling) + dialog.getClefHandling())); + haveSomething = true; + } + } + + if (haveSomething) + m_view->slotAddCommandToHistory(command); + //!!! else complain +} + +void +RosegardenGUIApp::slotSplitSelectionByRecordedSrc() +{ + if (!m_view->haveSelection()) + return ; + + SplitByRecordingSrcDialog dialog(m_view, m_doc); + if (dialog.exec() != QDialog::Accepted) + return ; + + SegmentSelection selection = m_view->getSelection(); + + KMacroCommand *command = new KMacroCommand + (SegmentSplitByRecordingSrcCommand::getGlobalName()); + + bool haveSomething = false; + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + if ((*i)->getType() == Segment::Audio) { + // nothing + } else { + command->addCommand + (new SegmentSplitByRecordingSrcCommand(*i, + dialog.getChannel(), + dialog.getDevice())); + haveSomething = true; + } + } + if (haveSomething) + m_view->slotAddCommandToHistory(command); +} + +void +RosegardenGUIApp::slotSplitSelectionAtTime() +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + if (selection.empty()) + return ; + + timeT now = m_doc->getComposition().getPosition(); + + QString title = i18n("Split Segment at Time", + "Split %n Segments at Time", + selection.size()); + + TimeDialog dialog(m_view, title, + &m_doc->getComposition(), + now, true); + + KMacroCommand *command = new KMacroCommand( title ); + + if (dialog.exec() == QDialog::Accepted) { + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + if ((*i)->getType() == Segment::Audio) { + command->addCommand(new AudioSegmentSplitCommand(*i, dialog.getTime())); + } else { + command->addCommand(new SegmentSplitCommand(*i, dialog.getTime())); + } + } + m_view->slotAddCommandToHistory(command); + } +} + +void +RosegardenGUIApp::slotSetSegmentStartTimes() +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + if (selection.empty()) + return ; + + timeT someTime = (*selection.begin())->getStartTime(); + + TimeDialog dialog(m_view, i18n("Segment Start Time"), + &m_doc->getComposition(), + someTime, false); + + if (dialog.exec() == QDialog::Accepted) { + + bool plural = (selection.size() > 1); + + SegmentReconfigureCommand *command = + new SegmentReconfigureCommand(plural ? + i18n("Set Segment Start Times") : + i18n("Set Segment Start Time")); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + command->addSegment + (*i, dialog.getTime(), + (*i)->getEndMarkerTime() - (*i)->getStartTime() + dialog.getTime(), + (*i)->getTrack()); + } + + m_view->slotAddCommandToHistory(command); + } +} + +void +RosegardenGUIApp::slotSetSegmentDurations() +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + if (selection.empty()) + return ; + + timeT someTime = + (*selection.begin())->getStartTime(); + + timeT someDuration = + (*selection.begin())->getEndMarkerTime() - + (*selection.begin())->getStartTime(); + + TimeDialog dialog(m_view, i18n("Segment Duration"), + &m_doc->getComposition(), + someTime, + someDuration, + false); + + if (dialog.exec() == QDialog::Accepted) { + + bool plural = (selection.size() > 1); + + SegmentReconfigureCommand *command = + new SegmentReconfigureCommand(plural ? + i18n("Set Segment Durations") : + i18n("Set Segment Duration")); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + command->addSegment + (*i, (*i)->getStartTime(), + (*i)->getStartTime() + dialog.getTime(), + (*i)->getTrack()); + } + + m_view->slotAddCommandToHistory(command); + } +} + +void RosegardenGUIApp::slotHarmonizeSelection() +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + //!!! This should be somewhere else too + + CompositionTimeSliceAdapter adapter(&m_doc->getComposition(), + &selection); + + AnalysisHelper helper; + Segment *segment = new Segment; + helper.guessHarmonies(adapter, *segment); + + //!!! do nothing with the results yet + delete segment; +} + +void RosegardenGUIApp::slotTempoToSegmentLength() +{ + slotTempoToSegmentLength(this); +} + +void RosegardenGUIApp::slotTempoToSegmentLength(QWidget* parent) +{ + RG_DEBUG << "RosegardenGUIApp::slotTempoToSegmentLength" << endl; + + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection = m_view->getSelection(); + + // Only set for a single selection + // + if (selection.size() == 1 && + (*selection.begin())->getType() == Segment::Audio) { + Composition &comp = m_doc->getComposition(); + Segment *seg = *selection.begin(); + + TimeSignature timeSig = + comp.getTimeSignatureAt( seg->getStartTime()); + + timeT endTime = seg->getEndTime(); + + if (seg->getRawEndMarkerTime()) + endTime = seg->getEndMarkerTime(); + + RealTime segDuration = + seg->getAudioEndTime() - seg->getAudioStartTime(); + + int beats = 0; + + // Get user to tell us how many beats or bars the segment contains + BeatsBarsDialog dialog(parent); + if (dialog.exec() == QDialog::Accepted) { + beats = dialog.getQuantity(); // beats (or bars) + if (dialog.getMode() == 1) // bars (multiply by time sig) + beats *= timeSig.getBeatsPerBar(); +#ifdef DEBUG_TEMPO_FROM_AUDIO + + RG_DEBUG << "RosegardenGUIApp::slotTempoToSegmentLength - beats = " << beats + << " mode = " << ((dialog.getMode() == 0) ? "bars" : "beats") << endl + << " beats per bar = " << timeSig.getBeatsPerBar() + << " user quantity = " << dialog.getQuantity() + << " user mode = " << dialog.getMode() << endl; +#endif + + } else { + RG_DEBUG << "RosegardenGUIApp::slotTempoToSegmentLength - BeatsBarsDialog aborted" + << endl; + return ; + } + + double beatLengthUsec = + double(segDuration.sec * 1000000 + segDuration.usec()) / + double(beats); + + // New tempo is a minute divided by time of beat + // converted up (#1414252) to a sane value via getTempoFoQpm() + // + tempoT newTempo = + comp.getTempoForQpm(60.0 * 1000000.0 / beatLengthUsec); + +#ifdef DEBUG_TEMPO_FROM_AUDIO + + RG_DEBUG << "RosegardenGUIApp::slotTempoToSegmentLength info: " << endl + << " beatLengthUsec = " << beatLengthUsec << endl + << " segDuration.usec = " << segDuration.usec() << endl + << " newTempo = " << newTempo << endl; +#endif + + KMacroCommand *macro = new KMacroCommand(i18n("Set Global Tempo")); + + // Remove all tempo changes in reverse order so as the index numbers + // don't becoming meaningless as the command gets unwound. + // + for (int i = 0; i < comp.getTempoChangeCount(); i++) + macro->addCommand(new RemoveTempoChangeCommand(&comp, + (comp.getTempoChangeCount() - 1 - i))); + + // add tempo change at time zero + // + macro->addCommand(new AddTempoChangeCommand(&comp, 0, newTempo)); + + // execute + m_doc->getCommandHistory()->addCommand(macro); + } +} + +void RosegardenGUIApp::slotToggleSegmentLabels() +{ + KToggleAction* act = dynamic_cast(actionCollection()->action("show_segment_labels")); + if (act) { + m_view->slotShowSegmentLabels(act->isChecked()); + } +} + +void RosegardenGUIApp::slotEdit() +{ + m_view->slotEditSegment(0); +} + +void RosegardenGUIApp::slotEditAsNotation() +{ + m_view->slotEditSegmentNotation(0); +} + +void RosegardenGUIApp::slotEditInMatrix() +{ + m_view->slotEditSegmentMatrix(0); +} + +void RosegardenGUIApp::slotEditInPercussionMatrix() +{ + m_view->slotEditSegmentPercussionMatrix(0); +} + +void RosegardenGUIApp::slotEditInEventList() +{ + m_view->slotEditSegmentEventList(0); +} + +void RosegardenGUIApp::slotEditTempos() +{ + slotEditTempos(m_doc->getComposition().getPosition()); +} + +void RosegardenGUIApp::slotToggleToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the toolbar..."), this); + + if (m_viewToolBar->isChecked()) + toolBar("mainToolBar")->show(); + else + toolBar("mainToolBar")->hide(); +} + +void RosegardenGUIApp::slotToggleToolsToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the tools toolbar..."), this); + + if (m_viewToolsToolBar->isChecked()) + toolBar("Tools Toolbar")->show(); + else + toolBar("Tools Toolbar")->hide(); +} + +void RosegardenGUIApp::slotToggleTracksToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the tracks toolbar..."), this); + + if (m_viewTracksToolBar->isChecked()) + toolBar("Tracks Toolbar")->show(); + else + toolBar("Tracks Toolbar")->hide(); +} + +void RosegardenGUIApp::slotToggleEditorsToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the editor toolbar..."), this); + + if (m_viewEditorsToolBar->isChecked()) + toolBar("Editors Toolbar")->show(); + else + toolBar("Editors Toolbar")->hide(); +} + +void RosegardenGUIApp::slotToggleTransportToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the transport toolbar..."), this); + + if (m_viewTransportToolBar->isChecked()) + toolBar("Transport Toolbar")->show(); + else + toolBar("Transport Toolbar")->hide(); +} + +void RosegardenGUIApp::slotToggleZoomToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the zoom toolbar..."), this); + + if (m_viewZoomToolBar->isChecked()) + toolBar("Zoom Toolbar")->show(); + else + toolBar("Zoom Toolbar")->hide(); +} + +void RosegardenGUIApp::slotToggleTransport() +{ + KTmpStatusMsg msg(i18n("Toggle the Transport"), this); + + if (m_viewTransport->isChecked()) { + getTransport()->show(); + getTransport()->raise(); + getTransport()->blockSignals(false); + } else { + getTransport()->hide(); + getTransport()->blockSignals(true); + } +} + +void RosegardenGUIApp::slotHideTransport() +{ + if (m_viewTransport->isChecked()) { + m_viewTransport->blockSignals(true); + m_viewTransport->setChecked(false); + m_viewTransport->blockSignals(false); + } + getTransport()->hide(); + getTransport()->blockSignals(true); +} + +void RosegardenGUIApp::slotToggleTrackLabels() +{ + if (m_viewTrackLabels->isChecked()) { +#ifdef SETTING_LOG_DEBUG + _settingLog("toggle track labels on"); +#endif + + m_view->getTrackEditor()->getTrackButtons()-> + changeTrackInstrumentLabels(TrackLabel::ShowTrack); + } else { +#ifdef SETTING_LOG_DEBUG + _settingLog("toggle track labels off"); +#endif + + m_view->getTrackEditor()->getTrackButtons()-> + changeTrackInstrumentLabels(TrackLabel::ShowInstrument); + } +} + +void RosegardenGUIApp::slotToggleRulers() +{ + m_view->slotShowRulers(m_viewRulers->isChecked()); +} + +void RosegardenGUIApp::slotToggleTempoRuler() +{ + m_view->slotShowTempoRuler(m_viewTempoRuler->isChecked()); +} + +void RosegardenGUIApp::slotToggleChordNameRuler() +{ + m_view->slotShowChordNameRuler(m_viewChordNameRuler->isChecked()); +} + +void RosegardenGUIApp::slotTogglePreviews() +{ + m_view->slotShowPreviews(m_viewPreviews->isChecked()); +} + +void RosegardenGUIApp::slotDockParametersBack() +{ + m_dockLeft->dockBack(); +} + +void RosegardenGUIApp::slotParametersClosed() +{ + stateChanged("parametersbox_closed"); + m_dockVisible = false; +} + +void RosegardenGUIApp::slotParametersDockedBack(KDockWidget* dw, KDockWidget::DockPosition) +{ + if (dw == m_dockLeft) { + stateChanged("parametersbox_closed", KXMLGUIClient::StateReverse); + m_dockVisible = true; + } +} + +void RosegardenGUIApp::slotToggleStatusBar() +{ + KTmpStatusMsg msg(i18n("Toggle the statusbar..."), this); + + if (!m_viewStatusBar->isChecked()) + statusBar()->hide(); + else + statusBar()->show(); +} + +void RosegardenGUIApp::slotStatusMsg(QString text) +{ + /////////////////////////////////////////////////////////////////// + // change status message permanently + statusBar()->clear(); + statusBar()->changeItem(text, EditViewBase::ID_STATUS_MSG); +} + +void RosegardenGUIApp::slotStatusHelpMsg(QString text) +{ + /////////////////////////////////////////////////////////////////// + // change status message of whole statusbar temporary (text, msec) + statusBar()->message(text, 2000); +} + +void RosegardenGUIApp::slotEnableTransport(bool enable) +{ + if (m_transport) + getTransport()->setEnabled(enable); +} + +void RosegardenGUIApp::slotPointerSelected() +{ + m_view->selectTool(SegmentSelector::ToolName); +} + +void RosegardenGUIApp::slotEraseSelected() +{ + m_view->selectTool(SegmentEraser::ToolName); +} + +void RosegardenGUIApp::slotDrawSelected() +{ + m_view->selectTool(SegmentPencil::ToolName); +} + +void RosegardenGUIApp::slotMoveSelected() +{ + m_view->selectTool(SegmentMover::ToolName); +} + +void RosegardenGUIApp::slotResizeSelected() +{ + m_view->selectTool(SegmentResizer::ToolName); +} + +void RosegardenGUIApp::slotJoinSelected() +{ + KMessageBox::information(this, + i18n("The join tool isn't implemented yet. Instead please highlight " + "the segments you want to join and then use the menu option:\n\n" + " Segments->Collapse Segments.\n"), + i18n("Join tool not yet implemented")); + + m_view->selectTool(SegmentJoiner::ToolName); +} + +void RosegardenGUIApp::slotSplitSelected() +{ + m_view->selectTool(SegmentSplitter::ToolName); +} + +void RosegardenGUIApp::slotAddTrack() +{ + if (!m_view) + return ; + + // default to the base number - might not actually exist though + // + InstrumentId id = MidiInstrumentBase; + + // Get the first Internal/MIDI instrument + // + DeviceList *devices = m_doc->getStudio().getDevices(); + bool have = false; + + for (DeviceList::iterator it = devices->begin(); + it != devices->end() && !have; it++) { + + if ((*it)->getType() != Device::Midi) + continue; + + InstrumentList instruments = (*it)->getAllInstruments(); + for (InstrumentList::iterator iit = instruments.begin(); + iit != instruments.end(); iit++) { + + if ((*iit)->getId() >= MidiInstrumentBase) { + id = (*iit)->getId(); + have = true; + break; + } + } + } + + Composition &comp = m_doc->getComposition(); + TrackId trackId = comp.getSelectedTrack(); + Track *track = comp.getTrackById(trackId); + + int pos = -1; + if (track) pos = track->getPosition() + 1; + + m_view->slotAddTracks(1, id, pos); +} + +void RosegardenGUIApp::slotAddTracks() +{ + if (!m_view) + return ; + + // default to the base number - might not actually exist though + // + InstrumentId id = MidiInstrumentBase; + + // Get the first Internal/MIDI instrument + // + DeviceList *devices = m_doc->getStudio().getDevices(); + bool have = false; + + for (DeviceList::iterator it = devices->begin(); + it != devices->end() && !have; it++) { + + if ((*it)->getType() != Device::Midi) + continue; + + InstrumentList instruments = (*it)->getAllInstruments(); + for (InstrumentList::iterator iit = instruments.begin(); + iit != instruments.end(); iit++) { + + if ((*iit)->getId() >= MidiInstrumentBase) { + id = (*iit)->getId(); + have = true; + break; + } + } + } + + Composition &comp = m_doc->getComposition(); + TrackId trackId = comp.getSelectedTrack(); + Track *track = comp.getTrackById(trackId); + + int pos = 0; + if (track) pos = track->getPosition(); + + bool ok = false; + + AddTracksDialog dialog(this, pos); + + if (dialog.exec() == QDialog::Accepted) { + m_view->slotAddTracks(dialog.getTracks(), id, + dialog.getInsertPosition()); + } +} + +void RosegardenGUIApp::slotDeleteTrack() +{ + if (!m_view) + return ; + + Composition &comp = m_doc->getComposition(); + TrackId trackId = comp.getSelectedTrack(); + Track *track = comp.getTrackById(trackId); + + RG_DEBUG << "RosegardenGUIApp::slotDeleteTrack() : about to delete track id " + << trackId << endl; + + if (track == 0) + return ; + + // Always have at least one track in a composition + // + if (comp.getNbTracks() == 1) + return ; + + // VLADA + if (m_view->haveSelection()) { + + SegmentSelection selection = m_view->getSelection(); + m_view->slotSelectTrackSegments(trackId); + m_view->getTrackEditor()->slotDeleteSelectedSegments(); + m_view->slotPropagateSegmentSelection(selection); + + } else { + + m_view->slotSelectTrackSegments(trackId); + m_view->getTrackEditor()->slotDeleteSelectedSegments(); + } + //VLADA + + int position = track->getPosition(); + + // Delete the track + // + std::vector tracks; + tracks.push_back(trackId); + + m_view->slotDeleteTracks(tracks); + + // Select a new valid track + // + if (comp.getTrackByPosition(position)) + trackId = comp.getTrackByPosition(position)->getId(); + else if (comp.getTrackByPosition(position - 1)) + trackId = comp.getTrackByPosition(position - 1)->getId(); + else { + RG_DEBUG << "RosegardenGUIApp::slotDeleteTrack - " + << "can't select a highlighted track after delete" + << endl; + } + + comp.setSelectedTrack(trackId); + + Instrument *inst = m_doc->getStudio(). + getInstrumentById(comp.getTrackById(trackId)->getInstrument()); + + //VLADA + // m_view->slotSelectTrackSegments(trackId); + //VLADA +} + +void RosegardenGUIApp::slotMoveTrackDown() +{ + RG_DEBUG << "RosegardenGUIApp::slotMoveTrackDown" << endl; + + Composition &comp = m_doc->getComposition(); + Track *srcTrack = comp.getTrackById(comp.getSelectedTrack()); + + // Check for track object + // + if (srcTrack == 0) + return ; + + // Check destination track exists + // + Track *destTrack = + comp.getTrackByPosition(srcTrack->getPosition() + 1); + + if (destTrack == 0) + return ; + + MoveTracksCommand *command = + new MoveTracksCommand(&comp, srcTrack->getId(), destTrack->getId()); + + m_doc->getCommandHistory()->addCommand(command); + + // make sure we're showing the right selection + m_view->slotSelectTrackSegments(comp.getSelectedTrack()); + +} + +void RosegardenGUIApp::slotMoveTrackUp() +{ + RG_DEBUG << "RosegardenGUIApp::slotMoveTrackUp" << endl; + + Composition &comp = m_doc->getComposition(); + Track *srcTrack = comp.getTrackById(comp.getSelectedTrack()); + + // Check for track object + // + if (srcTrack == 0) + return ; + + // Check we're not at the top already + // + if (srcTrack->getPosition() == 0) + return ; + + // Check destination track exists + // + Track *destTrack = + comp.getTrackByPosition(srcTrack->getPosition() - 1); + + if (destTrack == 0) + return ; + + MoveTracksCommand *command = + new MoveTracksCommand(&comp, srcTrack->getId(), destTrack->getId()); + + m_doc->getCommandHistory()->addCommand(command); + + // make sure we're showing the right selection + m_view->slotSelectTrackSegments(comp.getSelectedTrack()); +} + +void RosegardenGUIApp::slotRevertToSaved() +{ + RG_DEBUG << "RosegardenGUIApp::slotRevertToSaved" << endl; + + if (m_doc->isModified()) { + int revert = + KMessageBox::questionYesNo(this, + i18n("Revert modified document to previous saved version?")); + + if (revert == KMessageBox::No) + return ; + + openFile(m_doc->getAbsFilePath()); + } +} + +void RosegardenGUIApp::slotImportProject() +{ + if (m_doc && !m_doc->saveIfModified()) + return ; + + KURL url = KFileDialog::getOpenURL + (":RGPROJECT", + i18n("*.rgp|Rosegarden Project files\n*|All files"), this, + i18n("Import Rosegarden Project File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + + importProject(tmpfile); + + KIO::NetAccess::removeTempFile(tmpfile); +} + +void RosegardenGUIApp::importProject(QString filePath) +{ + KProcess *proc = new KProcess; + *proc << "rosegarden-project-package"; + *proc << "--unpack"; + *proc << filePath; + + KStartupLogo::hideIfStillThere(); + proc->start(KProcess::Block, KProcess::All); + + if (!proc->normalExit() || proc->exitStatus()) { + CurrentProgressDialog::freeze(); + KMessageBox::sorry(this, i18n("Failed to import project file \"%1\"").arg(filePath)); + CurrentProgressDialog::thaw(); + delete proc; + return ; + } + + delete proc; + + QString rgFile = filePath; + rgFile.replace(QRegExp(".rg.rgp$"), ".rg"); + rgFile.replace(QRegExp(".rgp$"), ".rg"); + openURL(rgFile); +} + +void RosegardenGUIApp::slotImportMIDI() +{ + if (m_doc && !m_doc->saveIfModified()) + return ; + + KURL url = KFileDialog::getOpenURL + (":MIDI", + "audio/x-midi", this, + i18n("Open MIDI File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + openFile(tmpfile, ImportMIDI); // does everything including setting the document + + KIO::NetAccess::removeTempFile( tmpfile ); +} + +void RosegardenGUIApp::slotMergeMIDI() +{ + KURL url = KFileDialog::getOpenURL + (":MIDI", + "audio/x-midi", this, + i18n("Merge MIDI File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + mergeFile(tmpfile, ImportMIDI); + + KIO::NetAccess::removeTempFile( tmpfile ); +} + +QTextCodec * +RosegardenGUIApp::guessTextCodec(std::string text) +{ + QTextCodec *codec = 0; + + for (int c = 0; c < text.length(); ++c) { + if (text[c] & 0x80) { + + CurrentProgressDialog::freeze(); + KStartupLogo::hideIfStillThere(); + + IdentifyTextCodecDialog dialog(0, text); + dialog.exec(); + + std::string codecName = dialog.getCodec(); + + CurrentProgressDialog::thaw(); + + if (codecName != "") { + codec = QTextCodec::codecForName(codecName.c_str()); + } + break; + } + } + + return codec; +} + +void +RosegardenGUIApp::fixTextEncodings(Composition *c) + +{ + QTextCodec *codec = 0; + + for (Composition::iterator i = c->begin(); + i != c->end(); ++i) { + + for (Segment::iterator j = (*i)->begin(); + j != (*i)->end(); ++j) { + + if ((*j)->isa(Text::EventType)) { + + std::string text; + + if ((*j)->get + + (Text::TextPropertyName, text)) { + + if (!codec) + codec = guessTextCodec(text); + + if (codec) { + (*j)->set + + (Text::TextPropertyName, + convertFromCodec(text, codec)); + } + } + } + } + } + + if (!codec) + codec = guessTextCodec(c->getCopyrightNote()); + if (codec) + c->setCopyrightNote(convertFromCodec(c->getCopyrightNote(), codec)); + + for (Composition::trackcontainer::iterator i = + c->getTracks().begin(); i != c->getTracks().end(); ++i) { + if (!codec) + codec = guessTextCodec(i->second->getLabel()); + if (codec) + i->second->setLabel(convertFromCodec(i->second->getLabel(), codec)); + } + + for (Composition::iterator i = c->begin(); i != c->end(); ++i) { + if (!codec) + codec = guessTextCodec((*i)->getLabel()); + if (codec) + (*i)->setLabel(convertFromCodec((*i)->getLabel(), codec)); + } +} + +RosegardenGUIDoc* +RosegardenGUIApp::createDocumentFromMIDIFile(QString file) +{ + //if (!merge && !m_doc->saveIfModified()) return; + + // Create new document (autoload is inherent) + // + RosegardenGUIDoc *newDoc = new RosegardenGUIDoc(this, m_pluginManager); + + std::string fname(QFile::encodeName(file)); + + MidiFile midiFile(fname, + &newDoc->getStudio()); + + KStartupLogo::hideIfStillThere(); + ProgressDialog progressDlg(i18n("Importing MIDI file..."), + 200, + this); + + CurrentProgressDialog::set + (&progressDlg); + + connect(&midiFile, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + + connect(&midiFile, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + if (!midiFile.open()) { + CurrentProgressDialog::freeze(); + KMessageBox::error(this, strtoqstr(midiFile.getError())); //!!! i18n + delete newDoc; + return 0; + } + + midiFile.convertToRosegarden(newDoc->getComposition(), + MidiFile::CONVERT_REPLACE); + + fixTextEncodings(&newDoc->getComposition()); + + // Set modification flag + // + newDoc->slotDocumentModified(); + + // Set the caption + // + newDoc->setTitle(QFileInfo(file).fileName()); + newDoc->setAbsFilePath(QFileInfo(file).absFilePath()); + + // Clean up for notation purposes (after reinitialise, because that + // sets the composition's end marker time which is needed here) + + progressDlg.slotSetOperationName(i18n("Calculating notation...")); + ProgressDialog::processEvents(); + + Composition *comp = &newDoc->getComposition(); + + for (Composition::iterator i = comp->begin(); + i != comp->end(); ++i) { + + Segment &segment = **i; + SegmentNotationHelper helper(segment); + segment.insert(helper.guessClef(segment.begin(), + segment.getEndMarker()).getAsEvent + (segment.getStartTime())); + } + + progressDlg.progressBar()->setProgress(100); + + for (Composition::iterator i = comp->begin(); + i != comp->end(); ++i) { + + // find first key event in each segment (we'd have done the + // same for clefs, except there is no MIDI clef event) + + Segment &segment = **i; + timeT firstKeyTime = segment.getEndMarkerTime(); + + for (Segment::iterator si = segment.begin(); + segment.isBeforeEndMarker(si); ++si) { + if ((*si)->isa(Rosegarden::Key::EventType)) { + firstKeyTime = (*si)->getAbsoluteTime(); + break; + } + } + + if (firstKeyTime > segment.getStartTime()) { + CompositionTimeSliceAdapter adapter + (comp, timeT(0), firstKeyTime); + AnalysisHelper helper; + segment.insert(helper.guessKey(adapter).getAsEvent + (segment.getStartTime())); + } + } + + int progressPer = 100; + if (comp->getNbSegments() > 0) + progressPer = (int)(100.0 / double(comp->getNbSegments())); + + KMacroCommand *command = new KMacroCommand(i18n("Calculate Notation")); + + for (Composition::iterator i = comp->begin(); + i != comp->end(); ++i) { + + Segment &segment = **i; + timeT startTime(segment.getStartTime()); + timeT endTime(segment.getEndMarkerTime()); + +// std::cerr << "segment: start time " << segment.getStartTime() << ", end time " << segment.getEndTime() << ", end marker time " << segment.getEndMarkerTime() << ", events " << segment.size() << std::endl; + + EventQuantizeCommand *subCommand = new EventQuantizeCommand + (segment, startTime, endTime, "Notation Options", true); + + subCommand->setProgressTotal(progressPer + 1); + QObject::connect(subCommand, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + command->addCommand(subCommand); + } + + newDoc->getCommandHistory()->addCommand(command); + + if (comp->getTimeSignatureCount() == 0) { + CompositionTimeSliceAdapter adapter(comp); + AnalysisHelper analysisHelper; + TimeSignature timeSig = + analysisHelper.guessTimeSignature(adapter); + comp->addTimeSignature(0, timeSig); + } + + return newDoc; +} + +void RosegardenGUIApp::slotImportRG21() +{ + if (m_doc && !m_doc->saveIfModified()) + return ; + + KURL url = KFileDialog::getOpenURL + (":ROSEGARDEN21", + i18n("*.rose|Rosegarden-2 files\n*|All files"), this, + i18n("Open Rosegarden 2.1 File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + openFile(tmpfile, ImportRG21); + + KIO::NetAccess::removeTempFile(tmpfile); +} + +void RosegardenGUIApp::slotMergeRG21() +{ + KURL url = KFileDialog::getOpenURL + (":ROSEGARDEN21", + i18n("*.rose|Rosegarden-2 files\n*|All files"), this, + i18n("Open Rosegarden 2.1 File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + mergeFile(tmpfile, ImportRG21); + + KIO::NetAccess::removeTempFile( tmpfile ); +} + +RosegardenGUIDoc* +RosegardenGUIApp::createDocumentFromRG21File(QString file) +{ + KStartupLogo::hideIfStillThere(); + ProgressDialog progressDlg( + i18n("Importing Rosegarden 2.1 file..."), 100, this); + + CurrentProgressDialog::set + (&progressDlg); + + // Inherent autoload + // + RosegardenGUIDoc *newDoc = new RosegardenGUIDoc(this, m_pluginManager); + + RG21Loader rg21Loader(&newDoc->getStudio()); + + // TODO: make RG21Loader to actually emit these signals + // + connect(&rg21Loader, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + + connect(&rg21Loader, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + // "your starter for 40%" - helps the "freeze" work + // + progressDlg.progressBar()->advance(40); + + if (!rg21Loader.load(file, newDoc->getComposition())) { + CurrentProgressDialog::freeze(); + KMessageBox::error(this, + i18n("Can't load Rosegarden 2.1 file. It appears to be corrupted.")); + delete newDoc; + return 0; + } + + // Set modification flag + // + newDoc->slotDocumentModified(); + + // Set the caption and add recent + // + newDoc->setTitle(QFileInfo(file).fileName()); + newDoc->setAbsFilePath(QFileInfo(file).absFilePath()); + + return newDoc; + +} + +void +RosegardenGUIApp::slotImportHydrogen() +{ + if (m_doc && !m_doc->saveIfModified()) + return ; + + KURL url = KFileDialog::getOpenURL + (":HYDROGEN", + i18n("*.h2song|Hydrogen files\n*|All files"), this, + i18n("Open Hydrogen File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + openFile(tmpfile, ImportHydrogen); + + KIO::NetAccess::removeTempFile(tmpfile); +} + +void RosegardenGUIApp::slotMergeHydrogen() +{ + KURL url = KFileDialog::getOpenURL + (":HYDROGEN", + i18n("*.h2song|Hydrogen files\n*|All files"), this, + i18n("Open Hydrogen File")); + if (url.isEmpty()) { + return ; + } + + QString tmpfile; + KIO::NetAccess::download(url, tmpfile, this); + mergeFile(tmpfile, ImportHydrogen); + + KIO::NetAccess::removeTempFile( tmpfile ); +} + +RosegardenGUIDoc* +RosegardenGUIApp::createDocumentFromHydrogenFile(QString file) +{ + KStartupLogo::hideIfStillThere(); + ProgressDialog progressDlg( + i18n("Importing Hydrogen file..."), 100, this); + + CurrentProgressDialog::set + (&progressDlg); + + // Inherent autoload + // + RosegardenGUIDoc *newDoc = new RosegardenGUIDoc(this, m_pluginManager); + + HydrogenLoader hydrogenLoader(&newDoc->getStudio()); + + // TODO: make RG21Loader to actually emit these signals + // + connect(&hydrogenLoader, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + + connect(&hydrogenLoader, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + // "your starter for 40%" - helps the "freeze" work + // + progressDlg.progressBar()->advance(40); + + if (!hydrogenLoader.load(file, newDoc->getComposition())) { + CurrentProgressDialog::freeze(); + KMessageBox::error(this, + i18n("Can't load Hydrogen file. It appears to be corrupted.")); + delete newDoc; + return 0; + } + + // Set modification flag + // + newDoc->slotDocumentModified(); + + // Set the caption and add recent + // + newDoc->setTitle(QFileInfo(file).fileName()); + newDoc->setAbsFilePath(QFileInfo(file).absFilePath()); + + return newDoc; + +} + +void +RosegardenGUIApp::mergeFile(QString filePath, ImportType type) +{ + RosegardenGUIDoc *doc = createDocument(filePath, type); + + if (doc) { + if (m_doc) { + + bool timingsDiffer = false; + Composition &c1 = m_doc->getComposition(); + Composition &c2 = doc->getComposition(); + + // compare tempos and time sigs in the two -- rather laborious + + if (c1.getTimeSignatureCount() != c2.getTimeSignatureCount()) { + timingsDiffer = true; + } else { + for (int i = 0; i < c1.getTimeSignatureCount(); ++i) { + std::pair t1 = + c1.getTimeSignatureChange(i); + std::pair t2 = + c2.getTimeSignatureChange(i); + if (t1.first != t2.first || t1.second != t2.second) { + timingsDiffer = true; + break; + } + } + } + + if (c1.getTempoChangeCount() != c2.getTempoChangeCount()) { + timingsDiffer = true; + } else { + for (int i = 0; i < c1.getTempoChangeCount(); ++i) { + std::pair t1 = c1.getTempoChange(i); + std::pair t2 = c2.getTempoChange(i); + if (t1.first != t2.first || t1.second != t2.second) { + timingsDiffer = true; + break; + } + } + } + + FileMergeDialog dialog(this, filePath, timingsDiffer); + if (dialog.exec() == QDialog::Accepted) { + m_doc->mergeDocument(doc, dialog.getMergeOptions()); + } + + delete doc; + + } else { + setDocument(doc); + } + } +} + +void +RosegardenGUIApp::slotUpdatePlaybackPosition() +{ + static int callbackCount = 0; + + // Either sequencer mappper or the sequence manager could be missing at + // this point. + // + if (!m_seqManager || !m_seqManager->getSequencerMapper()) + return ; + + SequencerMapper *mapper = m_seqManager->getSequencerMapper(); + + MappedEvent ev; + bool haveEvent = mapper->getVisual(ev); + if (haveEvent) + getTransport()->setMidiOutLabel(&ev); + + RealTime position = mapper->getPositionPointer(); + + // std::cerr << "RosegardenGUIApp::slotUpdatePlaybackPosition: mapper pos = " << position << std::endl; + + Composition &comp = m_doc->getComposition(); + timeT elapsedTime = comp.getElapsedTimeForRealTime(position); + + // std::cerr << "RosegardenGUIApp::slotUpdatePlaybackPosition: mapper timeT = " << elapsedTime << std::endl; + + if (m_seqManager->getTransportStatus() == RECORDING) { + + MappedComposition mC; + if (mapper->getRecordedEvents(mC) > 0) { + m_seqManager->processAsynchronousMidi(mC, 0); + m_doc->insertRecordedMidi(mC); + } + + m_doc->updateRecordingMIDISegment(); + m_doc->updateRecordingAudioSegments(); + } + + m_originatingJump = true; + m_doc->slotSetPointerPosition(elapsedTime); + m_originatingJump = false; + + if (m_audioMixer && m_audioMixer->isVisible()) + m_audioMixer->updateMeters(mapper); + + if (m_midiMixer && m_midiMixer->isVisible()) + m_midiMixer->updateMeters(mapper); + + m_view->updateMeters(mapper); + + if (++callbackCount == 60) { + slotUpdateCPUMeter(true); + callbackCount = 0; + } + + // if (elapsedTime >= comp.getEndMarker()) + // slotStop(); +} + +void +RosegardenGUIApp::slotUpdateCPUMeter(bool playing) +{ + static std::ifstream *statstream = 0; + static bool modified = false; + static unsigned long lastBusy = 0, lastIdle = 0; + + if (playing) { + + if (!statstream) { + statstream = new std::ifstream("/proc/stat", std::ios::in); + } + + if (!statstream || !*statstream) + return ; + statstream->seekg(0, std::ios::beg); + + std::string cpu; + unsigned long user, nice, sys, idle; + *statstream >> cpu; + *statstream >> user; + *statstream >> nice; + *statstream >> sys; + *statstream >> idle; + + unsigned long busy = user + nice + sys; + unsigned long count = 0; + + if (lastBusy > 0) { + unsigned long bd = busy - lastBusy; + unsigned long id = idle - lastIdle; + if (bd + id > 0) + count = bd * 100 / (bd + id); + if (count > 100) + count = 100; + } + + lastBusy = busy; + lastIdle = idle; + + if (m_progressBar) { + if (!modified) { + m_progressBar->setTextEnabled(true); + m_progressBar->setFormat("CPU"); + } + m_progressBar->setProgress(count); + } + + modified = true; + + } else if (modified) { + if (m_progressBar) { + m_progressBar->setTextEnabled(false); + m_progressBar->setFormat("%p%"); + m_progressBar->setProgress(0); + } + modified = false; + } +} + +void +RosegardenGUIApp::slotUpdateMonitoring() +{ + // Either sequencer mappper or the sequence manager could be missing at + // this point. + // + if (!m_seqManager || !m_seqManager->getSequencerMapper()) + return ; + + SequencerMapper *mapper = m_seqManager->getSequencerMapper(); + + if (m_audioMixer && m_audioMixer->isVisible()) + m_audioMixer->updateMonitorMeters(mapper); + + if (m_midiMixer && m_midiMixer->isVisible()) + m_midiMixer->updateMonitorMeter(mapper); + + m_view->updateMonitorMeters(mapper); + + slotUpdateCPUMeter(false); +} + +void RosegardenGUIApp::slotSetPointerPosition(timeT t) +{ + Composition &comp = m_doc->getComposition(); + + // std::cerr << "RosegardenGUIApp::slotSetPointerPosition: t = " << t << std::endl; + + if (m_seqManager) { + if ( m_seqManager->getTransportStatus() == PLAYING || + m_seqManager->getTransportStatus() == RECORDING ) { + if (t > comp.getEndMarker()) { + if (m_seqManager->getTransportStatus() == PLAYING) { + + slotStop(); + t = comp.getEndMarker(); + m_doc->slotSetPointerPosition(t); //causes this method to be re-invoked + return ; + + } else { // if recording, increase composition duration + std::pair timeRange = comp.getBarRangeForTime(t); + timeT barDuration = timeRange.second - timeRange.first; + timeT newEndMarker = t + 10 * barDuration; + comp.setEndMarker(newEndMarker); + getView()->getTrackEditor()->slotReadjustCanvasSize(); + getView()->getTrackEditor()->updateRulers(); + } + } + } + + // cc 20050520 - jump at the sequencer even if we're not playing, + // because we might be a transport master of some kind + try { + if (!m_originatingJump) { + m_seqManager->sendSequencerJump(comp.getElapsedRealTime(t)); + } + } catch (QString s) { + KMessageBox::error(this, s); + } + } + + // set the time sig + getTransport()->setTimeSignature(comp.getTimeSignatureAt(t)); + + // and the tempo + getTransport()->setTempo(comp.getTempoAtTime(t)); + + // and the time + // + TransportDialog::TimeDisplayMode mode = + getTransport()->getCurrentMode(); + + if (mode == TransportDialog::BarMode || + mode == TransportDialog::BarMetronomeMode) { + + slotDisplayBarTime(t); + + } else { + + RealTime rT(comp.getElapsedRealTime(t)); + + if (getTransport()->isShowingTimeToEnd()) { + rT = rT - comp.getElapsedRealTime(comp.getDuration()); + } + + if (mode == TransportDialog::RealMode) { + + getTransport()->displayRealTime(rT); + + } else if (mode == TransportDialog::SMPTEMode) { + + getTransport()->displaySMPTETime(rT); + + } else { + + getTransport()->displayFrameTime(rT); + } + } + + // handle transport mode configuration changes + std::string modeAsString = getTransport()->getCurrentModeAsString(); + + if (m_doc->getConfiguration().get + (DocumentConfiguration::TransportMode) != modeAsString) { + + m_doc->getConfiguration().set + (DocumentConfiguration::TransportMode, modeAsString); + + //m_doc->slotDocumentModified(); to avoid being prompted for a file change when merely changing the transport display + } + + // Update position on the marker editor if it's available + // + if (m_markerEditor) + m_markerEditor->updatePosition(); +} + +void RosegardenGUIApp::slotDisplayBarTime(timeT t) +{ + Composition &comp = m_doc->getComposition(); + + int barNo = comp.getBarNumber(t); + timeT barStart = comp.getBarStart(barNo); + + TimeSignature timeSig = comp.getTimeSignatureAt(t); + timeT beatDuration = timeSig.getBeatDuration(); + + int beatNo = (t - barStart) / beatDuration; + int unitNo = (t - barStart) - (beatNo * beatDuration); + + if (getTransport()->isShowingTimeToEnd()) { + barNo = barNo + 1 - comp.getNbBars(); + beatNo = timeSig.getBeatsPerBar() - 1 - beatNo; + unitNo = timeSig.getBeatDuration() - 1 - unitNo; + } else { + // convert to 1-based display bar numbers + barNo += 1; + beatNo += 1; + } + + // show units in hemidemis (or whatever), not in raw time ticks + unitNo /= Note(Note::Shortest).getDuration(); + + getTransport()->displayBarTime(barNo, beatNo, unitNo); +} + +void RosegardenGUIApp::slotRefreshTimeDisplay() +{ + if ( m_seqManager->getTransportStatus() == PLAYING || + m_seqManager->getTransportStatus() == RECORDING ) { + return ; // it'll be refreshed in a moment anyway + } + slotSetPointerPosition(m_doc->getComposition().getPosition()); +} + +bool +RosegardenGUIApp::isTrackEditorPlayTracking() const +{ + return m_view->getTrackEditor()->isTracking(); +} + +void RosegardenGUIApp::slotToggleTracking() +{ + m_view->getTrackEditor()->slotToggleTracking(); +} + +void RosegardenGUIApp::slotTestStartupTester() +{ + RG_DEBUG << "RosegardenGUIApp::slotTestStartupTester" << endl; + + if (!m_startupTester) { + m_startupTester = new StartupTester(); + connect(m_startupTester, SIGNAL(newerVersionAvailable(QString)), + this, SLOT(slotNewerVersionAvailable(QString))); + m_startupTester->start(); + QTimer::singleShot(100, this, SLOT(slotTestStartupTester())); + return ; + } + + if (!m_startupTester->isReady()) { + QTimer::singleShot(100, this, SLOT(slotTestStartupTester())); + return ; + } + + QStringList missingFeatures; + QStringList allMissing; + + QStringList missing; + bool have = m_startupTester->haveProjectPackager(&missing); + + stateChanged("have_project_packager", + have ? + KXMLGUIClient::StateNoReverse : KXMLGUIClient::StateReverse); + + if (!have) { + missingFeatures.push_back(i18n("Export and import of Rosegarden Project files")); + if (missing.count() == 0) { + allMissing.push_back(i18n("The Rosegarden Project Packager helper script")); + } else { + for (int i = 0; i < missing.count(); ++i) { +// if (missingFeatures.count() > 1) { + allMissing.push_back(i18n("%1 - for project file support").arg(missing[i])); +// } else { +// allMissing.push_back(missing[i]); +// } + } + } + } + + have = m_startupTester->haveLilyPondView(&missing); + + stateChanged("have_lilypondview", + have ? + KXMLGUIClient::StateNoReverse : KXMLGUIClient::StateReverse); + + if (!have) { + missingFeatures.push_back("Notation previews through LilyPond"); + if (missing.count() == 0) { + allMissing.push_back(i18n("The Rosegarden LilyPondView helper script")); + } else { + for (int i = 0; i < missing.count(); ++i) { + if (missingFeatures.count() > 1) { + allMissing.push_back(i18n("%1 - for LilyPond preview support").arg(missing[i])); + } else { + allMissing.push_back(missing[i]); + } + } + } + } + +#ifdef HAVE_LIBJACK + if (m_seqManager && (m_seqManager->getSoundDriverStatus() & AUDIO_OK)) { + + m_haveAudioImporter = m_startupTester->haveAudioFileImporter(&missing); + + if (!m_haveAudioImporter) { + missingFeatures.push_back("General audio file import and conversion"); + if (missing.count() == 0) { + allMissing.push_back(i18n("The Rosegarden Audio File Importer helper script")); + } else { + for (int i = 0; i < missing.count(); ++i) { + if (missingFeatures.count() > 1) { + allMissing.push_back(i18n("%1 - for audio file import").arg(missing[i])); + } else { + allMissing.push_back(missing[i]); + } + } + } + } + } +#endif + + if (missingFeatures.count() > 0) { + QString message = i18n("

Helper programs not found

Rosegarden could not find one or more helper programs which it needs to provide some features. The following features will not be available:

"); + message += i18n("
    "); + for (int i = 0; i < missingFeatures.count(); ++i) { + message += i18n("
  • %1
  • ").arg(missingFeatures[i]); + } + message += i18n("
"); + message += i18n("

To fix this, you should install the following additional programs:

"); + message += i18n("
    "); + for (int i = 0; i < allMissing.count(); ++i) { + message += i18n("
  • %1
  • ").arg(allMissing[i]); + } + message += i18n("
"); + + awaitDialogClearance(); + + KMessageBox::information + (m_view, + message, + i18n("Helper programs not found"), + "startup-helpers-missing"); + } + + delete m_startupTester; + m_startupTester = 0; +} + +void RosegardenGUIApp::slotDebugDump() +{ + Composition &comp = m_doc->getComposition(); + comp.dump(std::cerr); +} + +bool RosegardenGUIApp::launchSequencer(bool useExisting) +{ + if (!isUsingSequencer()) { + RG_DEBUG << "RosegardenGUIApp::launchSequencer() - not using seq. - returning\n"; + return false; // no need to launch anything + } + + if (isSequencerRunning()) { + RG_DEBUG << "RosegardenGUIApp::launchSequencer() - sequencer already running - returning\n"; + if (m_seqManager) m_seqManager->checkSoundDriverStatus(false); + return true; + } + + // Check to see if we're clearing down sequencer processes - + // if we're not we check DCOP for an existing sequencer and + // try to talk to use that (that's the "developer" mode). + // + // User mode should clear down sequencer processes. + // + if (kapp->dcopClient()->isApplicationRegistered( + QCString(ROSEGARDEN_SEQUENCER_APP_NAME))) { + RG_DEBUG << "RosegardenGUIApp::launchSequencer() - " + << "existing DCOP registered sequencer found\n"; + + if (useExisting) { + if (m_seqManager) m_seqManager->checkSoundDriverStatus(false); + m_sequencerProcess = (KProcess*)SequencerExternal; + return true; + } + + KProcess *proc = new KProcess; + *proc << "/usr/bin/killall"; + *proc << "rosegardensequencer"; + *proc << "lt-rosegardensequencer"; + + proc->start(KProcess::Block, KProcess::All); + + if (!proc->normalExit() || proc->exitStatus()) { + RG_DEBUG << "couldn't kill any sequencer processes" << endl; + } + delete proc; + + sleep(1); + + if (kapp->dcopClient()->isApplicationRegistered( + QCString(ROSEGARDEN_SEQUENCER_APP_NAME))) { + RG_DEBUG << "RosegardenGUIApp::launchSequencer() - " + << "failed to kill existing sequencer\n"; + + KProcess *proc = new KProcess; + *proc << "/usr/bin/killall"; + *proc << "-9"; + *proc << "rosegardensequencer"; + *proc << "lt-rosegardensequencer"; + + proc->start(KProcess::Block, KProcess::All); + + if (proc->exitStatus()) { + RG_DEBUG << "couldn't kill any sequencer processes" << endl; + } + delete proc; + + sleep(1); + } + } + + // + // No sequencer is running, so start one + // + KTmpStatusMsg msg(i18n("Starting the sequencer..."), this); + + if (!m_sequencerProcess) { + m_sequencerProcess = new KProcess; + + (*m_sequencerProcess) << "rosegardensequencer"; + + // Command line arguments + // + KConfig *config = kapp->config(); + config->setGroup(SequencerOptionsConfigGroup); + QString options = config->readEntry("commandlineoptions"); + if (!options.isEmpty()) { + (*m_sequencerProcess) << options; + RG_DEBUG << "sequencer options \"" << options << "\"" << endl; + } + + } else { + RG_DEBUG << "RosegardenGUIApp::launchSequencer() - sequencer KProcess already created\n"; + m_sequencerProcess->disconnect(); // disconnect processExit signal + // it will be reconnected later on + } + + bool res = m_sequencerProcess->start(); + + if (!res) { + KMessageBox::error(0, i18n("Couldn't start the sequencer")); + RG_DEBUG << "Couldn't start the sequencer\n"; + m_sequencerProcess = 0; + // If starting it didn't even work, fall back to no sequencer mode + m_useSequencer = false; + } else { + // connect processExited only after start, otherwise + // a failed startup will call slotSequencerExited() + // right away and we don't get to check the result + // of m_sequencerProcess->start() and thus make the distinction + // between the case where the sequencer was successfully launched + // but crashed right away, or the case where the process couldn't + // be launched at all (missing executable, etc...) + // + // We also re-check that the process is still running at this + // point in case it crashed between the moment we check res above + // and now. + // + //usleep(1000 * 1000); // even wait half a sec. to make sure it's actually running + if (m_sequencerProcess->isRunning()) { + + try { + // if (m_seqManager) { + // RG_DEBUG << "RosegardenGUIApp::launchSequencer : checking sound driver status\n"; + // m_seqManager->checkSoundDriverStatus(); + // } + + stateChanged("sequencer_running"); + slotEnableTransport(true); + + connect(m_sequencerProcess, SIGNAL(processExited(KProcess*)), + this, SLOT(slotSequencerExited(KProcess*))); + + } catch (Exception e) { + m_sequencerProcess = 0; + m_useSequencer = false; + stateChanged("sequencer_running", KXMLGUIClient::StateReverse); + slotEnableTransport(false); + } + + } else { // if it crashed so fast, it's probably pointless + // to try restarting it later, so also fall back to no + // sequencer mode + m_sequencerProcess = 0; + m_useSequencer = false; + stateChanged("sequencer_running", KXMLGUIClient::StateReverse); + slotEnableTransport(false); + } + + } + + // Sync current devices with the sequencer + // + if (m_doc) + m_doc->syncDevices(); + + if (m_doc && m_doc->getStudio().haveMidiDevices()) { + stateChanged("got_midi_devices"); + } else { + stateChanged("got_midi_devices", KXMLGUIClient::StateReverse); + } + + return res; +} + +#ifdef HAVE_LIBJACK +bool RosegardenGUIApp::launchJack() +{ + KConfig* config = kapp->config(); + config->setGroup(SequencerOptionsConfigGroup); + + bool startJack = config->readBoolEntry("jackstart", false); + if (!startJack) + return true; // we don't do anything + + QString jackPath = config->readEntry("jackcommand", ""); + + emit startupStatusMessage(i18n("Clearing down jackd...")); + + KProcess *proc = new KProcess; // TODO: do it in a less clumsy way + *proc << "/usr/bin/killall"; + *proc << "-9"; + *proc << "jackd"; + + proc->start(KProcess::Block, KProcess::All); + + if (proc->exitStatus()) + RG_DEBUG << "couldn't kill any jackd processes" << endl; + else + RG_DEBUG << "killed old jackd processes" << endl; + + emit startupStatusMessage(i18n("Starting jackd...")); + + if (jackPath != "") { + + RG_DEBUG << "starting jack \"" << jackPath << "\"" << endl; + + QStringList splitCommand; + splitCommand = QStringList::split(" ", jackPath); + + RG_DEBUG << "RosegardenGUIApp::launchJack() : splitCommand length : " + << splitCommand.size() << endl; + + // start jack process + m_jackProcess = new KProcess; + + *m_jackProcess << splitCommand; + + m_jackProcess->start(); + } + + + return m_jackProcess != 0 ? m_jackProcess->isRunning() : true; +} +#endif + +void RosegardenGUIApp::slotDocumentDevicesResyncd() +{ + m_sequencerCheckedIn = true; + m_trackParameterBox->populateDeviceLists(); +} + +void RosegardenGUIApp::slotSequencerExited(KProcess*) +{ + RG_DEBUG << "RosegardenGUIApp::slotSequencerExited Sequencer exited\n"; + + KStartupLogo::hideIfStillThere(); + + if (m_sequencerCheckedIn) { + + KMessageBox::error(0, i18n("The Rosegarden sequencer process has exited unexpectedly. Sound and recording will no longer be available for this session.\nPlease exit and restart Rosegarden to restore sound capability.")); + + } else { + + KMessageBox::error(0, i18n("The Rosegarden sequencer could not be started, so sound and recording will be unavailable for this session.\nFor assistance with correct audio and MIDI configuration, go to http://rosegardenmusic.com.")); + } + + m_sequencerProcess = 0; // isSequencerRunning() will return false + // but isUsingSequencer() will keep returning true + // so pressing the play button may attempt to restart the sequencer +} + +void RosegardenGUIApp::slotExportProject() +{ + KTmpStatusMsg msg(i18n("Exporting Rosegarden Project file..."), this); + + QString fileName = getValidWriteFile + ("*.rgp|" + i18n("Rosegarden Project files\n") + + "\n*|" + i18n("All files"), + i18n("Export as...")); + + if (fileName.isEmpty()) + return ; + + QString rgFile = fileName; + rgFile.replace(QRegExp(".rg.rgp$"), ".rg"); + rgFile.replace(QRegExp(".rgp$"), ".rg"); + + CurrentProgressDialog::freeze(); + + QString errMsg; + if (!m_doc->saveDocument(rgFile, errMsg, + true)) { // pretend it's autosave + KMessageBox::sorry(this, i18n("Saving Rosegarden file to package failed: %1").arg(errMsg)); + CurrentProgressDialog::thaw(); + return ; + } + + KProcess *proc = new KProcess; + *proc << "rosegarden-project-package"; + *proc << "--pack"; + *proc << rgFile; + *proc << fileName; + + proc->start(KProcess::Block, KProcess::All); + + if (!proc->normalExit() || proc->exitStatus()) { + KMessageBox::sorry(this, i18n("Failed to export to project file \"%1\"").arg(fileName)); + CurrentProgressDialog::thaw(); + delete proc; + return ; + } + + delete proc; +} + +void RosegardenGUIApp::slotExportMIDI() +{ + KTmpStatusMsg msg(i18n("Exporting MIDI file..."), this); + + QString fileName = getValidWriteFile + ("*.mid *.midi|" + i18n("Standard MIDI files\n") + + "\n*|" + i18n("All files"), + i18n("Export as...")); + + if (fileName.isEmpty()) + return ; + + exportMIDIFile(fileName); +} + +void RosegardenGUIApp::exportMIDIFile(QString file) +{ + ProgressDialog progressDlg(i18n("Exporting MIDI file..."), + 100, + this); + + std::string fname(QFile::encodeName(file)); + + MidiFile midiFile(fname, + &m_doc->getStudio()); + + connect(&midiFile, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + + connect(&midiFile, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + midiFile.convertToMidi(m_doc->getComposition()); + + if (!midiFile.write()) { + CurrentProgressDialog::freeze(); + KMessageBox::sorry(this, i18n("Export failed. The file could not be opened for writing.")); + } +} + +void RosegardenGUIApp::slotExportCsound() +{ + KTmpStatusMsg msg(i18n("Exporting Csound score file..."), this); + + QString fileName = getValidWriteFile(QString("*|") + i18n("All files"), + i18n("Export as...")); + if (fileName.isEmpty()) + return ; + + exportCsoundFile(fileName); +} + +void RosegardenGUIApp::exportCsoundFile(QString file) +{ + ProgressDialog progressDlg(i18n("Exporting Csound score file..."), + 100, + this); + + CsoundExporter e(this, &m_doc->getComposition(), 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.")); + } +} + +void RosegardenGUIApp::slotExportMup() +{ + KTmpStatusMsg msg(i18n("Exporting Mup file..."), this); + + QString fileName = getValidWriteFile + ("*.mup|" + i18n("Mup files\n") + "\n*|" + i18n("All files"), + i18n("Export as...")); + if (fileName.isEmpty()) + return ; + + exportMupFile(fileName); +} + +void RosegardenGUIApp::exportMupFile(QString file) +{ + ProgressDialog progressDlg(i18n("Exporting Mup file..."), + 100, + this); + + MupExporter e(this, &m_doc->getComposition(), 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.")); + } +} + +void RosegardenGUIApp::slotExportLilyPond() +{ + KTmpStatusMsg msg(i18n("Exporting LilyPond file..."), this); + + QString fileName = getValidWriteFile + (QString("*.ly|") + i18n("LilyPond files") + + "\n*|" + i18n("All files"), + i18n("Export as...")); + + if (fileName.isEmpty()) + return ; + + exportLilyPondFile(fileName); +} + +std::map RosegardenGUIApp::m_lilyTempFileMap; + + +void RosegardenGUIApp::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 RosegardenGUIApp::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 RosegardenGUIApp::slotLilyPondViewProcessExited(KProcess *p) +{ + delete m_lilyTempFileMap[p]; + m_lilyTempFileMap.erase(p); + delete p; +} + +bool RosegardenGUIApp::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 RosegardenGUIApp::slotExportMusicXml() +{ + KTmpStatusMsg msg(i18n("Exporting MusicXML file..."), this); + + QString fileName = getValidWriteFile + (QString("*.xml|") + i18n("XML files") + + "\n*|" + i18n("All files"), i18n("Export as...")); + + if (fileName.isEmpty()) + return ; + + exportMusicXmlFile(fileName); +} + +void RosegardenGUIApp::exportMusicXmlFile(QString file) +{ + ProgressDialog progressDlg(i18n("Exporting MusicXML file..."), + 100, + this); + + MusicXmlExporter 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.")); + } +} + +void +RosegardenGUIApp::slotCloseTransport() +{ + m_viewTransport->setChecked(false); + slotToggleTransport(); // hides the transport +} + +void +RosegardenGUIApp::slotDeleteTransport() +{ + delete m_transport; + m_transport = 0; +} + +void +RosegardenGUIApp::slotActivateTool(QString toolName) +{ + if (toolName == SegmentSelector::ToolName) { + actionCollection()->action("select")->activate(); + } +} + +void +RosegardenGUIApp::slotToggleMetronome() +{ + Composition &comp = m_doc->getComposition(); + + if (m_seqManager->getTransportStatus() == STARTING_TO_RECORD || + m_seqManager->getTransportStatus() == RECORDING || + m_seqManager->getTransportStatus() == RECORDING_ARMED) { + if (comp.useRecordMetronome()) + comp.setRecordMetronome(false); + else + comp.setRecordMetronome(true); + + getTransport()->MetronomeButton()->setOn(comp.useRecordMetronome()); + } else { + if (comp.usePlayMetronome()) + comp.setPlayMetronome(false); + else + comp.setPlayMetronome(true); + + getTransport()->MetronomeButton()->setOn(comp.usePlayMetronome()); + } +} + +void +RosegardenGUIApp::slotRewindToBeginning() +{ + // ignore requests if recording + // + if (m_seqManager->getTransportStatus() == RECORDING) + return ; + + m_seqManager->rewindToBeginning(); +} + +void +RosegardenGUIApp::slotFastForwardToEnd() +{ + // ignore requests if recording + // + if (m_seqManager->getTransportStatus() == RECORDING) + return ; + + m_seqManager->fastForwardToEnd(); +} + +void +RosegardenGUIApp::slotSetPlayPosition(timeT time) +{ + RG_DEBUG << "RosegardenGUIApp::slotSetPlayPosition(" << time << ")" << endl; + if (m_seqManager->getTransportStatus() == RECORDING) + return ; + + m_doc->slotSetPointerPosition(time); + + if (m_seqManager->getTransportStatus() == PLAYING) + return ; + + slotPlay(); +} + +void RosegardenGUIApp::notifySequencerStatus(int status) +{ + stateChanged("not_playing", + (status == PLAYING || + status == RECORDING) ? + KXMLGUIClient::StateReverse : KXMLGUIClient::StateNoReverse); + + if (m_seqManager) + m_seqManager->setTransportStatus((TransportStatus) status); +} + +void RosegardenGUIApp::processAsynchronousMidi(const MappedComposition &mC) +{ + if (!m_seqManager) { + return ; // probably getting this from a not-yet-killed runaway sequencer + } + + m_seqManager->processAsynchronousMidi(mC, 0); + SequencerMapper *mapper = m_seqManager->getSequencerMapper(); + if (mapper) + m_view->updateMeters(mapper); +} + +void +RosegardenGUIApp::slotRecord() +{ + if (!isUsingSequencer()) + return ; + + if (!isSequencerRunning()) { + + // Try to launch sequencer and return if we fail + // + if (!launchSequencer(false)) + return ; + } + + if (m_seqManager->getTransportStatus() == RECORDING) { + slotStop(); + return ; + } else if (m_seqManager->getTransportStatus() == PLAYING) { + slotToggleRecord(); + return ; + } + + // Attempt to start recording + // + try { + m_seqManager->record(false); + } catch (QString s) { + // We should already be stopped by this point so just unset + // the buttons after clicking the dialog. + // + KMessageBox::error(this, s); + + getTransport()->MetronomeButton()->setOn(false); + getTransport()->RecordButton()->setOn(false); + getTransport()->PlayButton()->setOn(false); + return ; + } 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) { + slotOpenAudioPathSettings(); + } + getTransport()->MetronomeButton()->setOn(false); + getTransport()->RecordButton()->setOn(false); + getTransport()->PlayButton()->setOn(false); + return ; + } catch (Exception e) { + KMessageBox::error(this, strtoqstr(e.getMessage())); + + getTransport()->MetronomeButton()->setOn(false); + getTransport()->RecordButton()->setOn(false); + getTransport()->PlayButton()->setOn(false); + return ; + } + + // plugin the keyboard accelerators for focus on this dialog + plugAccelerators(m_seqManager->getCountdownDialog(), + m_seqManager->getCountdownDialog()->getAccelerators()); + + connect(m_seqManager->getCountdownDialog(), SIGNAL(stopped()), + this, SLOT(slotStop())); + + // Start the playback timer - this fetches the current sequencer position &c + // + m_stopTimer->stop(); + m_playTimer->start(23); // avoid multiples of 10 just so as + // to avoid always having the same digit + // in one place on the transport. How + // shallow.) +} + +void +RosegardenGUIApp::slotToggleRecord() +{ + if (!isUsingSequencer() || + (!isSequencerRunning() && !launchSequencer(false))) + return ; + + try { + m_seqManager->record(true); + } catch (QString s) { + KMessageBox::error(this, s); + } 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 you start to record audio.\nWould you like to set it now?"), + i18n("Error"), + i18n("Set audio file path")) == KMessageBox::Continue) { + slotOpenAudioPathSettings(); + } + } catch (Exception e) { + KMessageBox::error(this, strtoqstr(e.getMessage())); + } + +} + +void +RosegardenGUIApp::slotSetLoop(timeT lhs, timeT rhs) +{ + try { + m_doc->slotDocumentModified(); + + m_seqManager->setLoop(lhs, rhs); + + // toggle the loop button + if (lhs != rhs) { + getTransport()->LoopButton()->setOn(true); + stateChanged("have_range", KXMLGUIClient::StateNoReverse); + } else { + getTransport()->LoopButton()->setOn(false); + stateChanged("have_range", KXMLGUIClient::StateReverse); + } + } catch (QString s) { + KMessageBox::error(this, s); + } +} + +void RosegardenGUIApp::alive() +{ + if (m_doc) + m_doc->syncDevices(); + + if (m_doc && m_doc->getStudio().haveMidiDevices()) { + stateChanged("got_midi_devices"); + } else { + stateChanged("got_midi_devices", KXMLGUIClient::StateReverse); + } +} + +void RosegardenGUIApp::slotPlay() +{ + if (!isUsingSequencer()) + return ; + + if (!isSequencerRunning()) { + + // Try to launch sequencer and return if it fails + // + if (!launchSequencer(false)) + return ; + } + + if (!m_seqManager) + return ; + + // If we're armed and ready to record then do this instead (calling + // slotRecord ensures we don't toggle the recording state in + // SequenceManager) + // + if (m_seqManager->getTransportStatus() == RECORDING_ARMED) { + slotRecord(); + return ; + } + + // Send the controllers at start of playback if required + // + KConfig *config = kapp->config(); + config->setGroup(SequencerOptionsConfigGroup); + bool sendControllers = config->readBoolEntry("alwayssendcontrollers", false); + + if (sendControllers) + m_doc->initialiseControllers(); + + bool pausedPlayback = false; + + try { + pausedPlayback = m_seqManager->play(); // this will stop playback (pause) if it's already running + // Check the new state of the transport and start or stop timer + // accordingly + // + if (!pausedPlayback) { + + // Start the playback timer - this fetches the current sequencer position &c + // + m_stopTimer->stop(); + m_playTimer->start(23); + } else { + m_playTimer->stop(); + m_stopTimer->start(100); + } + } catch (QString s) { + KMessageBox::error(this, s); + m_playTimer->stop(); + m_stopTimer->start(100); + } catch (Exception e) { + KMessageBox::error(this, e.getMessage()); + m_playTimer->stop(); + m_stopTimer->start(100); + } + +} + +void RosegardenGUIApp::slotJumpToTime(int sec, int usec) +{ + Composition *comp = &m_doc->getComposition(); + timeT t = comp->getElapsedTimeForRealTime + (RealTime(sec, usec * 1000)); + m_doc->slotSetPointerPosition(t); +} + +void RosegardenGUIApp::slotStartAtTime(int sec, int usec) +{ + slotJumpToTime(sec, usec); + slotPlay(); +} + +void RosegardenGUIApp::slotStop() +{ + if (m_seqManager && + m_seqManager->getCountdownDialog()) { + disconnect(m_seqManager->getCountdownDialog(), SIGNAL(stopped()), + this, SLOT(slotStop())); + disconnect(m_seqManager->getCountdownDialog(), SIGNAL(completed()), + this, SLOT(slotStop())); + } + + try { + if (m_seqManager) + m_seqManager->stopping(); + } catch (Exception e) { + KMessageBox::error(this, strtoqstr(e.getMessage())); + } + + // stop the playback timer + m_playTimer->stop(); + m_stopTimer->start(100); +} + +void RosegardenGUIApp::slotRewind() +{ + // ignore requests if recording + // + if (m_seqManager->getTransportStatus() == RECORDING) + return ; + if (m_seqManager) + m_seqManager->rewind(); +} + +void RosegardenGUIApp::slotFastforward() +{ + // ignore requests if recording + // + if (m_seqManager->getTransportStatus() == RECORDING) + return ; + + if (m_seqManager) + m_seqManager->fastforward(); +} + +void +RosegardenGUIApp::slotSetLoop() +{ + // restore loop + m_doc->setLoop(m_storedLoopStart, m_storedLoopEnd); +} + +void +RosegardenGUIApp::slotUnsetLoop() +{ + Composition &comp = m_doc->getComposition(); + + // store the loop + m_storedLoopStart = comp.getLoopStart(); + m_storedLoopEnd = comp.getLoopEnd(); + + // clear the loop at the composition and propagate to the rest + // of the display items + m_doc->setLoop(0, 0); +} + +void +RosegardenGUIApp::slotSetLoopStart() +{ + // Check so that start time is before endtime, othervise move upp the + // endtime to that same pos. + if ( m_doc->getComposition().getPosition() < m_doc->getComposition().getLoopEnd() ) { + m_doc->setLoop(m_doc->getComposition().getPosition(), m_doc->getComposition().getLoopEnd()); + } else { + m_doc->setLoop(m_doc->getComposition().getPosition(), m_doc->getComposition().getPosition()); + } +} + +void +RosegardenGUIApp::slotSetLoopStop() +{ + // Check so that end time is after start time, othervise move upp the + // start time to that same pos. + if ( m_doc->getComposition().getLoopStart() < m_doc->getComposition().getPosition() ) { + m_doc->setLoop(m_doc->getComposition().getLoopStart(), m_doc->getComposition().getPosition()); + } else { + m_doc->setLoop(m_doc->getComposition().getPosition(), m_doc->getComposition().getPosition()); + } +} + +void RosegardenGUIApp::slotToggleSolo(bool value) +{ + RG_DEBUG << "RosegardenGUIApp::slotToggleSolo value = " << value << endl; + + m_doc->getComposition().setSolo(value); + getTransport()->SoloButton()->setOn(value); + + m_doc->slotDocumentModified(); + + emit compositionStateUpdate(); +} + +void RosegardenGUIApp::slotTrackUp() +{ + Composition &comp = m_doc->getComposition(); + + TrackId tid = comp.getSelectedTrack(); + TrackId pos = comp.getTrackById(tid)->getPosition(); + + // If at top already + if (pos == 0) + return ; + + Track *track = comp.getTrackByPosition(pos - 1); + + // If the track exists + if (track) { + comp.setSelectedTrack(track->getId()); + m_view->slotSelectTrackSegments(comp.getSelectedTrack()); + } + +} + +void RosegardenGUIApp::slotTrackDown() +{ + Composition &comp = m_doc->getComposition(); + + TrackId tid = comp.getSelectedTrack(); + TrackId pos = comp.getTrackById(tid)->getPosition(); + + Track *track = comp.getTrackByPosition(pos + 1); + + // If the track exists + if (track) { + comp.setSelectedTrack(track->getId()); + m_view->slotSelectTrackSegments(comp.getSelectedTrack()); + } + +} + +void RosegardenGUIApp::slotMuteAllTracks() +{ + RG_DEBUG << "RosegardenGUIApp::slotMuteAllTracks" << endl; + + Composition &comp = m_doc->getComposition(); + + Composition::trackcontainer tracks = comp.getTracks(); + Composition::trackiterator tit; + for (tit = tracks.begin(); tit != tracks.end(); ++tit) + m_view->slotSetMute((*tit).second->getInstrument(), true); +} + +void RosegardenGUIApp::slotUnmuteAllTracks() +{ + RG_DEBUG << "RosegardenGUIApp::slotUnmuteAllTracks" << endl; + + Composition &comp = m_doc->getComposition(); + + Composition::trackcontainer tracks = comp.getTracks(); + Composition::trackiterator tit; + for (tit = tracks.begin(); tit != tracks.end(); ++tit) + m_view->slotSetMute((*tit).second->getInstrument(), false); +} + +void RosegardenGUIApp::slotToggleMutedCurrentTrack() +{ + Composition &comp = m_doc->getComposition(); + TrackId tid = comp.getSelectedTrack(); + Track *track = comp.getTrackById(tid); + // If the track exists + if (track) { + bool isMuted = track->isMuted(); + m_view->slotSetMuteButton(tid, !isMuted); + } +} + +void RosegardenGUIApp::slotToggleRecordCurrentTrack() +{ + Composition &comp = m_doc->getComposition(); + TrackId tid = comp.getSelectedTrack(); + int pos = comp.getTrackPositionById(tid); + m_view->getTrackEditor()->getTrackButtons()->slotToggleRecordTrack(pos); +} + + +void RosegardenGUIApp::slotConfigure() +{ + RG_DEBUG << "RosegardenGUIApp::slotConfigure\n"; + + ConfigureDialog *configDlg = + new ConfigureDialog(m_doc, kapp->config(), this); + + connect(configDlg, SIGNAL(updateAutoSaveInterval(unsigned int)), + this, SLOT(slotUpdateAutoSaveInterval(unsigned int))); + connect(configDlg, SIGNAL(updateSidebarStyle(unsigned int)), + this, SLOT(slotUpdateSidebarStyle(unsigned int))); + + configDlg->show(); +} + +void RosegardenGUIApp::slotEditDocumentProperties() +{ + RG_DEBUG << "RosegardenGUIApp::slotEditDocumentProperties\n"; + + DocumentConfigureDialog *configDlg = + new DocumentConfigureDialog(m_doc, this); + + configDlg->show(); +} + +void RosegardenGUIApp::slotOpenAudioPathSettings() +{ + RG_DEBUG << "RosegardenGUIApp::slotOpenAudioPathSettings\n"; + + DocumentConfigureDialog *configDlg = + new DocumentConfigureDialog(m_doc, this); + + configDlg->showAudioPage(); + configDlg->show(); +} + +void RosegardenGUIApp::slotEditKeys() +{ + KKeyDialog::configure(actionCollection()); +} + +void RosegardenGUIApp::slotEditToolbars() +{ + KEditToolbar dlg(actionCollection(), "rosegardenui.rc"); + + connect(&dlg, SIGNAL(newToolbarConfig()), + SLOT(slotUpdateToolbars())); + + dlg.exec(); +} + +void RosegardenGUIApp::slotUpdateToolbars() +{ + createGUI("rosegardenui.rc"); + m_viewToolBar->setChecked(!toolBar()->isHidden()); +} + +void RosegardenGUIApp::slotEditTempo() +{ + slotEditTempo(this); +} + +void RosegardenGUIApp::slotEditTempo(timeT atTime) +{ + slotEditTempo(this, atTime); +} + +void RosegardenGUIApp::slotEditTempo(QWidget *parent) +{ + slotEditTempo(parent, m_doc->getComposition().getPosition()); +} + +void RosegardenGUIApp::slotEditTempo(QWidget *parent, timeT atTime) +{ + RG_DEBUG << "RosegardenGUIApp::slotEditTempo\n"; + + TempoDialog tempoDialog(parent, m_doc); + + connect(&tempoDialog, + SIGNAL(changeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction)), + SLOT(slotChangeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction))); + + tempoDialog.setTempoPosition(atTime); + tempoDialog.exec(); +} + +void RosegardenGUIApp::slotEditTimeSignature() +{ + slotEditTimeSignature(this); +} + +void RosegardenGUIApp::slotEditTimeSignature(timeT atTime) +{ + slotEditTimeSignature(this, atTime); +} + +void RosegardenGUIApp::slotEditTimeSignature(QWidget *parent) +{ + slotEditTimeSignature(parent, m_doc->getComposition().getPosition()); +} + +void RosegardenGUIApp::slotEditTimeSignature(QWidget *parent, + timeT time) +{ + Composition &composition(m_doc->getComposition()); + + TimeSignature sig = composition.getTimeSignatureAt(time); + + TimeSignatureDialog dialog(parent, &composition, time, sig); + + if (dialog.exec() == QDialog::Accepted) { + + time = dialog.getTime(); + + if (dialog.shouldNormalizeRests()) { + m_doc->getCommandHistory()->addCommand + (new AddTimeSignatureAndNormalizeCommand + (&composition, time, dialog.getTimeSignature())); + } else { + m_doc->getCommandHistory()->addCommand + (new AddTimeSignatureCommand + (&composition, time, dialog.getTimeSignature())); + } + } +} + +void RosegardenGUIApp::slotEditTransportTime() +{ + slotEditTransportTime(this); +} + +void RosegardenGUIApp::slotEditTransportTime(QWidget *parent) +{ + TimeDialog dialog(parent, i18n("Move playback pointer to time"), + &m_doc->getComposition(), + m_doc->getComposition().getPosition(), + true); + if (dialog.exec() == QDialog::Accepted) { + m_doc->slotSetPointerPosition(dialog.getTime()); + } +} + +void RosegardenGUIApp::slotChangeZoom(int) +{ + double duration44 = TimeSignature(4, 4).getBarDuration(); + double value = double(m_zoomSlider->getCurrentSize()); + m_zoomLabel->setText(i18n("%1%").arg(duration44 / value)); + + RG_DEBUG << "RosegardenGUIApp::slotChangeZoom : zoom size = " + << m_zoomSlider->getCurrentSize() << endl; + + // initZoomToolbar sets the zoom value. With some old versions of + // Qt3.0, this can cause slotChangeZoom() to be called while the + // view hasn't been initialized yet, so we need to check it's not + // null + // + if (m_view) + m_view->setZoomSize(m_zoomSlider->getCurrentSize()); + + long newZoom = int(m_zoomSlider->getCurrentSize() * 1000.0); + + if (m_doc->getConfiguration().get + (DocumentConfiguration::ZoomLevel) != newZoom) { + + m_doc->getConfiguration().set + (DocumentConfiguration::ZoomLevel, newZoom); + + m_doc->slotDocumentModified(); + } +} + +void +RosegardenGUIApp::slotZoomIn() +{ + m_zoomSlider->increment(); +} + +void +RosegardenGUIApp::slotZoomOut() +{ + m_zoomSlider->decrement(); +} + +void +RosegardenGUIApp::slotChangeTempo(timeT time, + tempoT value, + tempoT target, + TempoDialog::TempoDialogAction action) +{ + //!!! handle target + + Composition &comp = m_doc->getComposition(); + + // We define a macro command here and build up the command + // label as we add commands on. + // + if (action == TempoDialog::AddTempo) { + m_doc->getCommandHistory()->addCommand + (new AddTempoChangeCommand(&comp, time, value, target)); + } else if (action == TempoDialog::ReplaceTempo) { + int index = comp.getTempoChangeNumberAt(time); + + // if there's no previous tempo change then just set globally + // + if (index == -1) { + m_doc->getCommandHistory()->addCommand + (new AddTempoChangeCommand(&comp, 0, value, target)); + return ; + } + + // get time of previous tempo change + timeT prevTime = comp.getTempoChange(index).first; + + KMacroCommand *macro = + new KMacroCommand(i18n("Replace Tempo Change at %1").arg(time)); + + macro->addCommand(new RemoveTempoChangeCommand(&comp, index)); + macro->addCommand(new AddTempoChangeCommand(&comp, prevTime, value, + target)); + + m_doc->getCommandHistory()->addCommand(macro); + + } else if (action == TempoDialog::AddTempoAtBarStart) { + m_doc->getCommandHistory()->addCommand(new + AddTempoChangeCommand(&comp, comp.getBarStartForTime(time), + value, target)); + } else if (action == TempoDialog::GlobalTempo || + action == TempoDialog::GlobalTempoWithDefault) { + KMacroCommand *macro = new KMacroCommand(i18n("Set Global Tempo")); + + // Remove all tempo changes in reverse order so as the index numbers + // don't becoming meaningless as the command gets unwound. + // + for (int i = 0; i < comp.getTempoChangeCount(); i++) + macro->addCommand(new RemoveTempoChangeCommand(&comp, + (comp.getTempoChangeCount() - 1 - i))); + + // add tempo change at time zero + // + macro->addCommand(new AddTempoChangeCommand(&comp, 0, value, target)); + + // are we setting default too? + // + if (action == TempoDialog::GlobalTempoWithDefault) { + macro->setName(i18n("Set Global and Default Tempo")); + macro->addCommand(new ModifyDefaultTempoCommand(&comp, value)); + } + + m_doc->getCommandHistory()->addCommand(macro); + + } else { + RG_DEBUG << "RosegardenGUIApp::slotChangeTempo() - " + << "unrecognised tempo command" << endl; + } +} + +void +RosegardenGUIApp::slotMoveTempo(timeT oldTime, + timeT newTime) +{ + Composition &comp = m_doc->getComposition(); + int index = comp.getTempoChangeNumberAt(oldTime); + + if (index < 0) + return ; + + KMacroCommand *macro = + new KMacroCommand(i18n("Move Tempo Change")); + + std::pair tc = + comp.getTempoChange(index); + std::pair tr = + comp.getTempoRamping(index, false); + + macro->addCommand(new RemoveTempoChangeCommand(&comp, index)); + macro->addCommand(new AddTempoChangeCommand(&comp, + newTime, + tc.second, + tr.first ? tr.second : -1)); + + m_doc->getCommandHistory()->addCommand(macro); +} + +void +RosegardenGUIApp::slotDeleteTempo(timeT t) +{ + Composition &comp = m_doc->getComposition(); + int index = comp.getTempoChangeNumberAt(t); + + if (index < 0) + return ; + + m_doc->getCommandHistory()->addCommand(new RemoveTempoChangeCommand + (&comp, index)); +} + +void +RosegardenGUIApp::slotAddMarker(timeT time) +{ + AddMarkerCommand *command = + new AddMarkerCommand(&m_doc->getComposition(), + time, + i18n("new marker"), + i18n("no description")); + + m_doc->getCommandHistory()->addCommand(command); +} + +void +RosegardenGUIApp::slotDeleteMarker(int id, timeT time, QString name, QString description) +{ + RemoveMarkerCommand *command = + new RemoveMarkerCommand(&m_doc->getComposition(), + id, + time, + qstrtostr(name), + qstrtostr(description)); + + m_doc->getCommandHistory()->addCommand(command); +} + +void +RosegardenGUIApp::slotDocumentModified(bool m) +{ + RG_DEBUG << "RosegardenGUIApp::slotDocumentModified(" << m << ") - doc path = " + << m_doc->getAbsFilePath() << endl; + + if (!m_doc->getAbsFilePath().isEmpty()) { + slotStateChanged("saved_file_modified", m); + } else { + slotStateChanged("new_file_modified", m); + } + +} + +void +RosegardenGUIApp::slotStateChanged(QString s, + bool noReverse) +{ + // RG_DEBUG << "RosegardenGUIApp::slotStateChanged " << s << "," << noReverse << endl; + + stateChanged(s, noReverse ? KXMLGUIClient::StateNoReverse : KXMLGUIClient::StateReverse); +} + +void +RosegardenGUIApp::slotTestClipboard() +{ + if (m_clipboard->isEmpty()) { + stateChanged("have_clipboard", KXMLGUIClient::StateReverse); + stateChanged("have_clipboard_single_segment", + KXMLGUIClient::StateReverse); + } else { + stateChanged("have_clipboard", KXMLGUIClient::StateNoReverse); + stateChanged("have_clipboard_single_segment", + (m_clipboard->isSingleSegment() ? + KXMLGUIClient::StateNoReverse : + KXMLGUIClient::StateReverse)); + } +} + +void +RosegardenGUIApp::plugAccelerators(QWidget *widget, QAccel *acc) +{ + + acc->connectItem(acc->insertItem(Key_Enter), + this, + SLOT(slotPlay())); + // Alternative shortcut for Play + acc->connectItem(acc->insertItem(Key_Return + CTRL), + this, + SLOT(slotPlay())); + + acc->connectItem(acc->insertItem(Key_Insert), + this, + SLOT(slotStop())); + + acc->connectItem(acc->insertItem(Key_PageDown), + this, + SLOT(slotFastforward())); + + acc->connectItem(acc->insertItem(Key_End), + this, + SLOT(slotRewind())); + + acc->connectItem(acc->insertItem(Key_Space), + this, + SLOT(slotToggleRecord())); + + TransportDialog *transport = + dynamic_cast(widget); + + if (transport) { + acc->connectItem(acc->insertItem(m_jumpToQuickMarkerAction->shortcut()), + this, + SLOT(slotJumpToQuickMarker())); + + acc->connectItem(acc->insertItem(m_setQuickMarkerAction->shortcut()), + this, + SLOT(slotSetQuickMarker())); + + connect(transport->PlayButton(), + SIGNAL(clicked()), + this, + SLOT(slotPlay())); + + connect(transport->StopButton(), + SIGNAL(clicked()), + this, + SLOT(slotStop())); + + connect(transport->FfwdButton(), + SIGNAL(clicked()), + SLOT(slotFastforward())); + + connect(transport->RewindButton(), + SIGNAL(clicked()), + this, + SLOT(slotRewind())); + + connect(transport->RecordButton(), + SIGNAL(clicked()), + this, + SLOT(slotRecord())); + + connect(transport->RewindEndButton(), + SIGNAL(clicked()), + this, + SLOT(slotRewindToBeginning())); + + connect(transport->FfwdEndButton(), + SIGNAL(clicked()), + this, + SLOT(slotFastForwardToEnd())); + + connect(transport->MetronomeButton(), + SIGNAL(clicked()), + this, + SLOT(slotToggleMetronome())); + + connect(transport->SoloButton(), + SIGNAL(toggled(bool)), + this, + SLOT(slotToggleSolo(bool))); + + connect(transport->TimeDisplayButton(), + SIGNAL(clicked()), + this, + SLOT(slotRefreshTimeDisplay())); + + connect(transport->ToEndButton(), + SIGNAL(clicked()), + SLOT(slotRefreshTimeDisplay())); + } +} + +void +RosegardenGUIApp::setCursor(const QCursor& cursor) +{ + KDockMainWindow::setCursor(cursor); + + // play it safe, so we can use this class at anytime even very early in the app init + if ((getView() && + getView()->getTrackEditor() && + getView()->getTrackEditor()->getSegmentCanvas() && + getView()->getTrackEditor()->getSegmentCanvas()->viewport())) { + + getView()->getTrackEditor()->getSegmentCanvas()->viewport()->setCursor(cursor); + } + + // view, main window... + // + getView()->setCursor(cursor); + + // toolbars... + // + QPtrListIterator tbIter = toolBarIterator(); + KToolBar* tb = 0; + while ((tb = tbIter.current()) != 0) { + tb->setCursor(cursor); + ++tbIter; + } + + m_dockLeft->setCursor(cursor); +} + +QString +RosegardenGUIApp::createNewAudioFile() +{ + AudioFile *aF = 0; + try { + aF = m_doc->getAudioFileManager().createRecordingAudioFile(); + if (!aF) { + // createRecordingAudioFile doesn't actually write to the disk, + // and in principle it shouldn't fail + std::cerr << "ERROR: RosegardenGUIApp::createNewAudioFile: Failed to create recording audio file" << std::endl; + return ""; + } else { + return aF->getFilename().c_str(); + } + } catch (AudioFileManager::BadAudioPathException e) { + delete aF; + std::cerr << "ERROR: RosegardenGUIApp::createNewAudioFile: Failed to create recording audio file: " << e.getMessage() << std::endl; + return ""; + } +} + +QValueVector +RosegardenGUIApp::createRecordAudioFiles(const QValueVector &recordInstruments) +{ + QValueVector qv; + for (unsigned int i = 0; i < recordInstruments.size(); ++i) { + AudioFile *aF = 0; + try { + aF = m_doc->getAudioFileManager().createRecordingAudioFile(); + if (aF) { + // createRecordingAudioFile doesn't actually write to the disk, + // and in principle it shouldn't fail + qv.push_back(aF->getFilename().c_str()); + m_doc->addRecordAudioSegment(recordInstruments[i], + aF->getId()); + } else { + std::cerr << "ERROR: RosegardenGUIApp::createRecordAudioFiles: Failed to create recording audio file" << std::endl; + return qv; + } + } catch (AudioFileManager::BadAudioPathException e) { + delete aF; + std::cerr << "ERROR: RosegardenGUIApp::createRecordAudioFiles: Failed to create recording audio file: " << e.getMessage() << std::endl; + return qv; + } + } + return qv; +} + +QString +RosegardenGUIApp::getAudioFilePath() +{ + return QString(m_doc->getAudioFileManager().getAudioPath().c_str()); +} + +QValueVector +RosegardenGUIApp::getArmedInstruments() +{ + std::set + iid; + + const Composition::recordtrackcontainer &tr = + m_doc->getComposition().getRecordTracks(); + + for (Composition::recordtrackcontainer::const_iterator i = + tr.begin(); i != tr.end(); ++i) { + TrackId tid = (*i); + Track *track = m_doc->getComposition().getTrackById(tid); + if (track) { + iid.insert(track->getInstrument()); + } else { + std::cerr << "Warning: RosegardenGUIApp::getArmedInstruments: Armed track " << tid << " not found in Composition" << std::endl; + } + } + + QValueVector iv; + for (std::set + ::iterator ii = iid.begin(); + ii != iid.end(); ++ii) { + iv.push_back(*ii); + } + return iv; +} + +void +RosegardenGUIApp::showError(QString error) +{ + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + + // This is principally used for return values from DSSI plugin + // configure() calls. It seems some plugins return a string + // telling you when everything's OK, as well as error strings, but + // dssi.h does make it reasonably clear that configure() should + // only return a string when there is actually a problem, so we're + // going to stick with a sorry dialog here rather than an + // information one + + KMessageBox::sorry(0, error); + + CurrentProgressDialog::thaw(); +} + +void +RosegardenGUIApp::slotAudioManager() +{ + if (m_audioManagerDialog) { + m_audioManagerDialog->show(); + m_audioManagerDialog->raise(); + m_audioManagerDialog->setActiveWindow(); + return ; + } + + m_audioManagerDialog = + new AudioManagerDialog(this, m_doc); + + connect(m_audioManagerDialog, + SIGNAL(playAudioFile(AudioFileId, + const RealTime &, + const RealTime&)), + SLOT(slotPlayAudioFile(AudioFileId, + const RealTime &, + const RealTime &))); + + connect(m_audioManagerDialog, + SIGNAL(addAudioFile(AudioFileId)), + SLOT(slotAddAudioFile(AudioFileId))); + + connect(m_audioManagerDialog, + SIGNAL(deleteAudioFile(AudioFileId)), + SLOT(slotDeleteAudioFile(AudioFileId))); + + // + // Sync segment selection between audio man. dialog and main window + // + + // from dialog to us... + connect(m_audioManagerDialog, + SIGNAL(segmentsSelected(const SegmentSelection&)), + m_view, + SLOT(slotPropagateSegmentSelection(const SegmentSelection&))); + + // and from us to dialog + connect(this, SIGNAL(segmentsSelected(const SegmentSelection&)), + m_audioManagerDialog, + SLOT(slotSegmentSelection(const SegmentSelection&))); + + + connect(m_audioManagerDialog, + SIGNAL(deleteSegments(const SegmentSelection&)), + SLOT(slotDeleteSegments(const SegmentSelection&))); + + connect(m_audioManagerDialog, + SIGNAL(insertAudioSegment(AudioFileId, + const RealTime&, + const RealTime&)), + m_view, + SLOT(slotAddAudioSegmentDefaultPosition(AudioFileId, + const RealTime&, + const RealTime&))); + connect(m_audioManagerDialog, + SIGNAL(cancelPlayingAudioFile(AudioFileId)), + SLOT(slotCancelAudioPlayingFile(AudioFileId))); + + connect(m_audioManagerDialog, + SIGNAL(deleteAllAudioFiles()), + SLOT(slotDeleteAllAudioFiles())); + + // Make sure we know when the audio man. dialog is closing + // + connect(m_audioManagerDialog, + SIGNAL(closing()), + SLOT(slotAudioManagerClosed())); + + // And that it goes away when the current document is changing + // + connect(this, SIGNAL(documentAboutToChange()), + m_audioManagerDialog, SLOT(close())); + + m_audioManagerDialog->setAudioSubsystemStatus( + m_seqManager->getSoundDriverStatus() & AUDIO_OK); + + plugAccelerators(m_audioManagerDialog, + m_audioManagerDialog->getAccelerators()); + + m_audioManagerDialog->show(); +} + +void +RosegardenGUIApp::slotPlayAudioFile(unsigned int id, + const RealTime &startTime, + const RealTime &duration) +{ + AudioFile *aF = m_doc->getAudioFileManager().getAudioFile(id); + + if (aF == 0) + return ; + + MappedEvent mE(m_doc->getStudio(). + getAudioPreviewInstrument(), + id, + RealTime( -120, 0), + duration, // duration + startTime); // start index + + StudioControl::sendMappedEvent(mE); + +} + +void +RosegardenGUIApp::slotAddAudioFile(unsigned int id) +{ + AudioFile *aF = m_doc->getAudioFileManager().getAudioFile(id); + + if (aF == 0) + return ; + + QCString replyType; + QByteArray replyData; + QByteArray data; + QDataStream streamOut(data, IO_WriteOnly); + + // We have to pass the filename as a QString + // + streamOut << QString(strtoqstr(aF->getFilename())); + streamOut << (int)aF->getId(); + + if (rgapp->sequencerCall("addAudioFile(QString, int)", replyType, replyData, data)) { + QDataStream streamIn(replyData, IO_ReadOnly); + int result; + streamIn >> result; + if (!result) { + KMessageBox::error(this, i18n("Sequencer failed to add audio file %1").arg(aF->getFilename().c_str())); + } + } +} + +void +RosegardenGUIApp::slotDeleteAudioFile(unsigned int id) +{ + if (m_doc->getAudioFileManager().removeFile(id) == false) + return ; + + QCString replyType; + QByteArray replyData; + QByteArray data; + QDataStream streamOut(data, IO_WriteOnly); + + // file id + // + streamOut << (int)id; + + if (rgapp->sequencerCall("removeAudioFile(int)", replyType, replyData, data)) { + QDataStream streamIn(replyData, IO_ReadOnly); + int result; + streamIn >> result; + if (!result) { + KMessageBox::error(this, i18n("Sequencer failed to remove audio file id %1").arg(id)); + } + } +} + +void +RosegardenGUIApp::slotDeleteSegments(const SegmentSelection &selection) +{ + m_view->slotPropagateSegmentSelection(selection); + slotDeleteSelectedSegments(); +} + +void +RosegardenGUIApp::slotCancelAudioPlayingFile(AudioFileId id) +{ + AudioFile *aF = m_doc->getAudioFileManager().getAudioFile(id); + + if (aF == 0) + return ; + + MappedEvent mE(m_doc->getStudio(). + getAudioPreviewInstrument(), + MappedEvent::AudioCancel, + id); + + StudioControl::sendMappedEvent(mE); +} + +void +RosegardenGUIApp::slotDeleteAllAudioFiles() +{ + m_doc->getAudioFileManager().clear(); + + // Clear at the sequencer + // + QCString replyType; + QByteArray replyData; + QByteArray data; + + rgapp->sequencerCall("clearAllAudioFiles()", replyType, replyData, data); +} + +void +RosegardenGUIApp::slotRepeatingSegments() +{ + m_view->getTrackEditor()->slotTurnRepeatingSegmentToRealCopies(); +} + +void +RosegardenGUIApp::slotRelabelSegments() +{ + if (!m_view->haveSelection()) + return ; + + SegmentSelection selection(m_view->getSelection()); + QString editLabel; + + if (selection.size() == 0) + return ; + else if (selection.size() == 1) + editLabel = i18n("Modify Segment label"); + else + editLabel = i18n("Modify Segments label"); + + KTmpStatusMsg msg(i18n("Relabelling selection..."), this); + + // Generate label + QString label = strtoqstr((*selection.begin())->getLabel()); + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + if (strtoqstr((*i)->getLabel()) != label) + label = ""; + } + + bool ok = false; + + QString newLabel = KInputDialog::getText(editLabel, + i18n("Enter new label"), + label, + &ok, + this); + + if (ok) { + m_doc->getCommandHistory()->addCommand + (new SegmentLabelCommand(selection, newLabel)); + m_view->getTrackEditor()->getSegmentCanvas()->slotUpdateSegmentsDrawBuffer(); + } +} + +void +RosegardenGUIApp::slotTransposeSegments() +{ + if (!m_view->haveSelection()) + return ; + + IntervalDialog intervalDialog(this, true, true); + int ok = intervalDialog.exec(); + + int semitones = intervalDialog.getChromaticDistance(); + int steps = intervalDialog.getDiatonicDistance(); + + if (!ok || (semitones == 0 && steps == 0)) return; + + m_doc->getCommandHistory()->addCommand + (new SegmentTransposeCommand(m_view->getSelection(), intervalDialog.getChangeKey(), steps, semitones, intervalDialog.getTransposeSegmentBack())); +} + +void +RosegardenGUIApp::slotChangeCompositionLength() +{ + CompositionLengthDialog dialog(this, &m_doc->getComposition()); + + if (dialog.exec() == QDialog::Accepted) { + ChangeCompositionLengthCommand *command + = new ChangeCompositionLengthCommand( + &m_doc->getComposition(), + dialog.getStartMarker(), + dialog.getEndMarker()); + + m_view->getTrackEditor()->getSegmentCanvas()->clearSegmentRectsCache(true); + m_doc->getCommandHistory()->addCommand(command); + } +} + +void +RosegardenGUIApp::slotManageMIDIDevices() +{ + if (m_deviceManager) { + m_deviceManager->show(); + m_deviceManager->raise(); + m_deviceManager->setActiveWindow(); + return ; + } + + m_deviceManager = new DeviceManagerDialog(this, m_doc); + + connect(m_deviceManager, SIGNAL(closing()), + this, SLOT(slotDeviceManagerClosed())); + + connect(this, SIGNAL(documentAboutToChange()), + m_deviceManager, SLOT(close())); + + // Cheating way of updating the track/instrument list + // + connect(m_deviceManager, SIGNAL(deviceNamesChanged()), + m_view, SLOT(slotSynchroniseWithComposition())); + + connect(m_deviceManager, SIGNAL(editBanks(DeviceId)), + this, SLOT(slotEditBanks(DeviceId))); + + connect(m_deviceManager, SIGNAL(editControllers(DeviceId)), + this, SLOT(slotEditControlParameters(DeviceId))); + + if (m_midiMixer) { + connect(m_deviceManager, SIGNAL(deviceNamesChanged()), + m_midiMixer, SLOT(slotSynchronise())); + + } + + + m_deviceManager->show(); +} + +void +RosegardenGUIApp::slotManageSynths() +{ + if (m_synthManager) { + m_synthManager->show(); + m_synthManager->raise(); + m_synthManager->setActiveWindow(); + return ; + } + + m_synthManager = new SynthPluginManagerDialog(this, m_doc +#ifdef HAVE_LIBLO + , m_pluginGUIManager +#endif + ); + + connect(m_synthManager, SIGNAL(closing()), + this, SLOT(slotSynthPluginManagerClosed())); + + connect(this, SIGNAL(documentAboutToChange()), + m_synthManager, SLOT(close())); + + connect(m_synthManager, + SIGNAL(pluginSelected(InstrumentId, int, int)), + this, + SLOT(slotPluginSelected(InstrumentId, int, int))); + + connect(m_synthManager, + SIGNAL(showPluginDialog(QWidget *, InstrumentId, int)), + this, + SLOT(slotShowPluginDialog(QWidget *, InstrumentId, int))); + + connect(m_synthManager, + SIGNAL(showPluginGUI(InstrumentId, int)), + this, + SLOT(slotShowPluginGUI(InstrumentId, int))); + + m_synthManager->show(); +} + +void +RosegardenGUIApp::slotOpenAudioMixer() +{ + if (m_audioMixer) { + m_audioMixer->show(); + m_audioMixer->raise(); + m_audioMixer->setActiveWindow(); + return ; + } + + m_audioMixer = new AudioMixerWindow(this, m_doc); + + connect(m_audioMixer, SIGNAL(windowActivated()), + m_view, SLOT(slotActiveMainWindowChanged())); + + connect(m_view, SIGNAL(controllerDeviceEventReceived(MappedEvent *, const void *)), + m_audioMixer, SLOT(slotControllerDeviceEventReceived(MappedEvent *, const void *))); + + connect(m_audioMixer, SIGNAL(closing()), + this, SLOT(slotAudioMixerClosed())); + + connect(m_audioMixer, SIGNAL(selectPlugin(QWidget *, InstrumentId, int)), + this, SLOT(slotShowPluginDialog(QWidget *, InstrumentId, int))); + + connect(this, + SIGNAL(pluginSelected(InstrumentId, int, int)), + m_audioMixer, + SLOT(slotPluginSelected(InstrumentId, int, int))); + + connect(this, + SIGNAL(pluginBypassed(InstrumentId, int, bool)), + m_audioMixer, + SLOT(slotPluginBypassed(InstrumentId, int, bool))); + + connect(this, SIGNAL(documentAboutToChange()), + m_audioMixer, SLOT(close())); + + connect(m_view, SIGNAL(checkTrackAssignments()), + m_audioMixer, SLOT(slotTrackAssignmentsChanged())); + + connect(m_audioMixer, SIGNAL(play()), + this, SLOT(slotPlay())); + connect(m_audioMixer, SIGNAL(stop()), + this, SLOT(slotStop())); + connect(m_audioMixer, SIGNAL(fastForwardPlayback()), + this, SLOT(slotFastforward())); + connect(m_audioMixer, SIGNAL(rewindPlayback()), + this, SLOT(slotRewind())); + connect(m_audioMixer, SIGNAL(fastForwardPlaybackToEnd()), + this, SLOT(slotFastForwardToEnd())); + connect(m_audioMixer, SIGNAL(rewindPlaybackToBeginning()), + this, SLOT(slotRewindToBeginning())); + connect(m_audioMixer, SIGNAL(record()), + this, SLOT(slotRecord())); + connect(m_audioMixer, SIGNAL(panic()), + this, SLOT(slotPanic())); + + connect(m_audioMixer, + SIGNAL(instrumentParametersChanged(InstrumentId)), + this, + SIGNAL(instrumentParametersChanged(InstrumentId))); + + connect(this, + SIGNAL(instrumentParametersChanged(InstrumentId)), + m_audioMixer, + SLOT(slotUpdateInstrument(InstrumentId))); + + if (m_synthManager) { + connect(m_synthManager, + SIGNAL(pluginSelected(InstrumentId, int, int)), + m_audioMixer, + SLOT(slotPluginSelected(InstrumentId, int, int))); + } + + plugAccelerators(m_audioMixer, m_audioMixer->getAccelerators()); + + m_audioMixer->show(); +} + +void +RosegardenGUIApp::slotOpenMidiMixer() +{ + if (m_midiMixer) { + m_midiMixer->show(); + m_midiMixer->raise(); + m_midiMixer->setActiveWindow(); + return ; + } + + m_midiMixer = new MidiMixerWindow(this, m_doc); + + connect(m_midiMixer, SIGNAL(windowActivated()), + m_view, SLOT(slotActiveMainWindowChanged())); + + connect(m_view, SIGNAL(controllerDeviceEventReceived(MappedEvent *, const void *)), + m_midiMixer, SLOT(slotControllerDeviceEventReceived(MappedEvent *, const void *))); + + connect(m_midiMixer, SIGNAL(closing()), + this, SLOT(slotMidiMixerClosed())); + + connect(this, SIGNAL(documentAboutToChange()), + m_midiMixer, SLOT(close())); + + connect(m_midiMixer, SIGNAL(play()), + this, SLOT(slotPlay())); + connect(m_midiMixer, SIGNAL(stop()), + this, SLOT(slotStop())); + connect(m_midiMixer, SIGNAL(fastForwardPlayback()), + this, SLOT(slotFastforward())); + connect(m_midiMixer, SIGNAL(rewindPlayback()), + this, SLOT(slotRewind())); + connect(m_midiMixer, SIGNAL(fastForwardPlaybackToEnd()), + this, SLOT(slotFastForwardToEnd())); + connect(m_midiMixer, SIGNAL(rewindPlaybackToBeginning()), + this, SLOT(slotRewindToBeginning())); + connect(m_midiMixer, SIGNAL(record()), + this, SLOT(slotRecord())); + connect(m_midiMixer, SIGNAL(panic()), + this, SLOT(slotPanic())); + + connect(m_midiMixer, + SIGNAL(instrumentParametersChanged(InstrumentId)), + this, + SIGNAL(instrumentParametersChanged(InstrumentId))); + + connect(this, + SIGNAL(instrumentParametersChanged(InstrumentId)), + m_midiMixer, + SLOT(slotUpdateInstrument(InstrumentId))); + + plugAccelerators(m_midiMixer, m_midiMixer->getAccelerators()); + + m_midiMixer->show(); +} + +void +RosegardenGUIApp::slotEditControlParameters(DeviceId device) +{ + for (std::set + ::iterator i = m_controlEditors.begin(); + i != m_controlEditors.end(); ++i) { + if ((*i)->getDevice() == device) { + (*i)->show(); + (*i)->raise(); + (*i)->setActiveWindow(); + return ; + } + } + + ControlEditorDialog *controlEditor = new ControlEditorDialog(this, m_doc, + device); + m_controlEditors.insert(controlEditor); + + RG_DEBUG << "inserting control editor dialog, have " << m_controlEditors.size() << " now" << endl; + + connect(controlEditor, SIGNAL(closing()), + SLOT(slotControlEditorClosed())); + + connect(this, SIGNAL(documentAboutToChange()), + controlEditor, SLOT(close())); + + connect(m_doc, SIGNAL(devicesResyncd()), + controlEditor, SLOT(slotUpdate())); + + controlEditor->show(); +} + +void +RosegardenGUIApp::slotEditBanks() +{ + slotEditBanks(Device::NO_DEVICE); +} + +void +RosegardenGUIApp::slotEditBanks(DeviceId device) +{ + if (m_bankEditor) { + if (device != Device::NO_DEVICE) + m_bankEditor->setCurrentDevice(device); + m_bankEditor->show(); + m_bankEditor->raise(); + m_bankEditor->setActiveWindow(); + return ; + } + + m_bankEditor = new BankEditorDialog(this, m_doc, device); + + connect(m_bankEditor, SIGNAL(closing()), + this, SLOT(slotBankEditorClosed())); + + connect(this, SIGNAL(documentAboutToChange()), + m_bankEditor, SLOT(slotFileClose())); + + // Cheating way of updating the track/instrument list + // + connect(m_bankEditor, SIGNAL(deviceNamesChanged()), + m_view, SLOT(slotSynchroniseWithComposition())); + + m_bankEditor->show(); +} + +void +RosegardenGUIApp::slotManageTriggerSegments() +{ + if (m_triggerSegmentManager) { + m_triggerSegmentManager->show(); + m_triggerSegmentManager->raise(); + m_triggerSegmentManager->setActiveWindow(); + return ; + } + + m_triggerSegmentManager = new TriggerSegmentManager(this, m_doc); + + connect(m_triggerSegmentManager, SIGNAL(closing()), + SLOT(slotTriggerManagerClosed())); + + connect(m_triggerSegmentManager, SIGNAL(editTriggerSegment(int)), + m_view, SLOT(slotEditTriggerSegment(int))); + + m_triggerSegmentManager->show(); +} + +void +RosegardenGUIApp::slotTriggerManagerClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotTriggerManagerClosed" << endl; + + m_triggerSegmentManager = 0; +} + +void +RosegardenGUIApp::slotEditMarkers() +{ + if (m_markerEditor) { + m_markerEditor->show(); + m_markerEditor->raise(); + m_markerEditor->setActiveWindow(); + return ; + } + + m_markerEditor = new MarkerEditor(this, m_doc); + + connect(m_markerEditor, SIGNAL(closing()), + SLOT(slotMarkerEditorClosed())); + + connect(m_markerEditor, SIGNAL(jumpToMarker(timeT)), + m_doc, SLOT(slotSetPointerPosition(timeT))); + + plugAccelerators(m_markerEditor, m_markerEditor->getAccelerators()); + + m_markerEditor->show(); +} + +void +RosegardenGUIApp::slotMarkerEditorClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotMarkerEditorClosed" << endl; + + m_markerEditor = 0; +} + +void +RosegardenGUIApp::slotEditTempos(timeT t) +{ + if (m_tempoView) { + m_tempoView->show(); + m_tempoView->raise(); + m_tempoView->setActiveWindow(); + return ; + } + + m_tempoView = new TempoView(m_doc, getView(), t); + + connect(m_tempoView, SIGNAL(closing()), + SLOT(slotTempoViewClosed())); + + connect(m_tempoView, SIGNAL(windowActivated()), + getView(), SLOT(slotActiveMainWindowChanged())); + + connect(m_tempoView, + SIGNAL(changeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction)), + this, + SLOT(slotChangeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction))); + + connect(m_tempoView, SIGNAL(saveFile()), this, SLOT(slotFileSave())); + + plugAccelerators(m_tempoView, m_tempoView->getAccelerators()); + + m_tempoView->show(); +} + +void +RosegardenGUIApp::slotTempoViewClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotTempoViewClosed" << endl; + + m_tempoView = 0; +} + +void +RosegardenGUIApp::slotControlEditorClosed() +{ + const QObject *s = sender(); + + RG_DEBUG << "RosegardenGUIApp::slotControlEditorClosed" << endl; + + for (std::set + ::iterator i = m_controlEditors.begin(); + i != m_controlEditors.end(); ++i) { + if (*i == s) { + m_controlEditors.erase(i); + RG_DEBUG << "removed control editor dialog, have " << m_controlEditors.size() << " left" << endl; + return ; + } + } + + std::cerr << "WARNING: control editor " << s << " closed, but couldn't find it in our control editor list (we have " << m_controlEditors.size() << " editors)" << std::endl; +} + +void +RosegardenGUIApp::slotShowPluginDialog(QWidget *parent, + InstrumentId instrumentId, + int index) +{ + if (!parent) + parent = this; + + int key = (index << 16) + instrumentId; + + if (m_pluginDialogs[key]) { + m_pluginDialogs[key]->show(); + m_pluginDialogs[key]->raise(); + m_pluginDialogs[key]->setActiveWindow(); + return ; + } + + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotShowPluginDialog - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + // only create a dialog if we've got a plugin instance + AudioPluginInstance *inst = + container->getPlugin(index); + + if (!inst) { + RG_DEBUG << "RosegardenGUIApp::slotShowPluginDialog - " + << "no AudioPluginInstance found for index " + << index << endl; + return ; + } + + // Create the plugin dialog + // + AudioPluginDialog *dialog = + new AudioPluginDialog(parent, + m_doc->getPluginManager(), +#ifdef HAVE_LIBLO + m_pluginGUIManager, +#endif + container, + index); + + connect(dialog, SIGNAL(windowActivated()), + m_view, SLOT(slotActiveMainWindowChanged())); + +/* This feature isn't provided by the plugin dialog + connect(m_view, SIGNAL(controllerDeviceEventReceived(MappedEvent *, const void *)), + dialog, SLOT(slotControllerDeviceEventReceived(MappedEvent *, const void *))); +*/ + + // Plug the new dialog into the standard keyboard accelerators so + // that we can use them still while the plugin has focus. + // + plugAccelerators(dialog, dialog->getAccelerators()); + + connect(dialog, + SIGNAL(pluginSelected(InstrumentId, int, int)), + this, + SLOT(slotPluginSelected(InstrumentId, int, int))); + + connect(dialog, + SIGNAL(pluginPortChanged(InstrumentId, int, int)), + this, + SLOT(slotPluginPortChanged(InstrumentId, int, int))); + + connect(dialog, + SIGNAL(pluginProgramChanged(InstrumentId, int)), + this, + SLOT(slotPluginProgramChanged(InstrumentId, int))); + + connect(dialog, + SIGNAL(changePluginConfiguration(InstrumentId, int, bool, QString, QString)), + this, + SLOT(slotChangePluginConfiguration(InstrumentId, int, bool, QString, QString))); + + connect(dialog, + SIGNAL(showPluginGUI(InstrumentId, int)), + this, + SLOT(slotShowPluginGUI(InstrumentId, int))); + + connect(dialog, + SIGNAL(stopPluginGUI(InstrumentId, int)), + this, + SLOT(slotStopPluginGUI(InstrumentId, int))); + + connect(dialog, + SIGNAL(bypassed(InstrumentId, int, bool)), + this, + SLOT(slotPluginBypassed(InstrumentId, int, bool))); + + connect(dialog, + SIGNAL(destroyed(InstrumentId, int)), + this, + SLOT(slotPluginDialogDestroyed(InstrumentId, int))); + + connect(this, SIGNAL(documentAboutToChange()), dialog, SLOT(close())); + + m_pluginDialogs[key] = dialog; + m_pluginDialogs[key]->show(); + + // Set modified + m_doc->slotDocumentModified(); +} + +void +RosegardenGUIApp::slotPluginSelected(InstrumentId instrumentId, + int index, int plugin) +{ + const QObject *s = sender(); + + bool fromSynthMgr = (s == m_synthManager); + + // It's assumed that ports etc will already have been set up on + // the AudioPluginInstance before this is invoked. + + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotPluginSelected - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = + container->getPlugin(index); + + if (!inst) { + RG_DEBUG << "RosegardenGUIApp::slotPluginSelected - " + << "got index of unknown plugin!" << endl; + return ; + } + + if (plugin == -1) { + // Destroy plugin instance + //!!! seems iffy -- why can't we just unassign it? + + if (StudioControl:: + destroyStudioObject(inst->getMappedId())) { + RG_DEBUG << "RosegardenGUIApp::slotPluginSelected - " + << "cannot destroy Studio object " + << inst->getMappedId() << endl; + } + + inst->setAssigned(false); + } else { + // If unassigned then create a sequencer instance of this + // AudioPluginInstance. + // + if (inst->isAssigned()) { + RG_DEBUG << "RosegardenGUIApp::slotPluginSelected - " + << " setting identifier for mapper id " << inst->getMappedId() + << " to " << inst->getIdentifier() << endl; + + StudioControl::setStudioObjectProperty + (inst->getMappedId(), + MappedPluginSlot::Identifier, + strtoqstr(inst->getIdentifier())); + } else { + // create a studio object at the sequencer + MappedObjectId newId = + StudioControl::createStudioObject + (MappedObject::PluginSlot); + + RG_DEBUG << "RosegardenGUIApp::slotPluginSelected - " + << " new MappedObjectId = " << newId << endl; + + // set the new Mapped ID and that this instance + // is assigned + inst->setMappedId(newId); + inst->setAssigned(true); + + // set the instrument id + StudioControl::setStudioObjectProperty + (newId, + MappedObject::Instrument, + MappedObjectValue(instrumentId)); + + // set the position + StudioControl::setStudioObjectProperty + (newId, + MappedObject::Position, + MappedObjectValue(index)); + + // set the plugin id + StudioControl::setStudioObjectProperty + (newId, + MappedPluginSlot::Identifier, + strtoqstr(inst->getIdentifier())); + } + } + + int pluginMappedId = inst->getMappedId(); + + //!!! much code duplicated here from RosegardenGUIDoc::initialiseStudio + + inst->setConfigurationValue + (qstrtostr(PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY), + m_doc->getAudioFileManager().getAudioPath()); + + // Set opaque string configuration data (e.g. for DSSI plugin) + // + MappedObjectPropertyList config; + for (AudioPluginInstance::ConfigMap::const_iterator + i = inst->getConfiguration().begin(); + i != inst->getConfiguration().end(); ++i) { + config.push_back(strtoqstr(i->first)); + config.push_back(strtoqstr(i->second)); + } + StudioControl::setStudioObjectPropertyList + (pluginMappedId, + MappedPluginSlot::Configuration, + config); + + // Set the bypass + // + StudioControl::setStudioObjectProperty + (pluginMappedId, + MappedPluginSlot::Bypassed, + MappedObjectValue(inst->isBypassed())); + + // Set the program + // + if (inst->getProgram() != "") { + StudioControl::setStudioObjectProperty + (pluginMappedId, + MappedPluginSlot::Program, + strtoqstr(inst->getProgram())); + } + + // Set all the port values + // + PortInstanceIterator portIt; + + for (portIt = inst->begin(); + portIt != inst->end(); ++portIt) { + StudioControl::setStudioPluginPort + (pluginMappedId, + (*portIt)->number, + (*portIt)->value); + } + + if (fromSynthMgr) { + int key = (index << 16) + instrumentId; + if (m_pluginDialogs[key]) { + m_pluginDialogs[key]->updatePlugin(plugin); + } + } else if (m_synthManager) { + m_synthManager->updatePlugin(instrumentId, plugin); + } + + emit pluginSelected(instrumentId, index, plugin); + + // Set modified + m_doc->slotDocumentModified(); +} + +void +RosegardenGUIApp::slotChangePluginPort(InstrumentId instrumentId, + int pluginIndex, + int portIndex, + float value) +{ + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotChangePluginPort - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = container->getPlugin(pluginIndex); + if (!inst) { + RG_DEBUG << "RosegardenGUIApp::slotChangePluginPort - " + << "no plugin at index " << pluginIndex << " on " << instrumentId << endl; + return ; + } + + PluginPortInstance *port = inst->getPort(portIndex); + if (!port) { + RG_DEBUG << "RosegardenGUIApp::slotChangePluginPort - no port " + << portIndex << endl; + return ; + } + + RG_DEBUG << "RosegardenGUIApp::slotPluginPortChanged - " + << "setting plugin port (" << inst->getMappedId() + << ", " << portIndex << ") from " << port->value + << " to " << value << endl; + + port->setValue(value); + + StudioControl::setStudioPluginPort(inst->getMappedId(), + portIndex, port->value); + + m_doc->slotDocumentModified(); + + // This modification came from The Outside! + int key = (pluginIndex << 16) + instrumentId; + if (m_pluginDialogs[key]) { + m_pluginDialogs[key]->updatePluginPortControl(portIndex); + } +} + +void +RosegardenGUIApp::slotPluginPortChanged(InstrumentId instrumentId, + int pluginIndex, + int portIndex) +{ + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotPluginPortChanged - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = container->getPlugin(pluginIndex); + if (!inst) { + RG_DEBUG << "RosegardenGUIApp::slotPluginPortChanged - " + << "no plugin at index " << pluginIndex << " on " << instrumentId << endl; + return ; + } + + PluginPortInstance *port = inst->getPort(portIndex); + if (!port) { + RG_DEBUG << "RosegardenGUIApp::slotPluginPortChanged - no port " + << portIndex << endl; + return ; + } + + RG_DEBUG << "RosegardenGUIApp::slotPluginPortChanged - " + << "setting plugin port (" << inst->getMappedId() + << ", " << portIndex << ") to " << port->value << endl; + + StudioControl::setStudioPluginPort(inst->getMappedId(), + portIndex, port->value); + + m_doc->slotDocumentModified(); + +#ifdef HAVE_LIBLO + // This modification came from our own plugin dialog, so update + // any external GUIs + if (m_pluginGUIManager) { + m_pluginGUIManager->updatePort(instrumentId, + pluginIndex, + portIndex); + } +#endif +} + +void +RosegardenGUIApp::slotChangePluginProgram(InstrumentId instrumentId, + int pluginIndex, + QString program) +{ + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotChangePluginProgram - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = container->getPlugin(pluginIndex); + if (!inst) { + RG_DEBUG << "RosegardenGUIApp::slotChangePluginProgram - " + << "no plugin at index " << pluginIndex << " on " << instrumentId << endl; + return ; + } + + RG_DEBUG << "RosegardenGUIApp::slotChangePluginProgram - " + << "setting plugin program (" + << inst->getMappedId() << ") from " << inst->getProgram() + << " to " << program << endl; + + inst->setProgram(qstrtostr(program)); + + StudioControl:: + setStudioObjectProperty(inst->getMappedId(), + MappedPluginSlot::Program, + program); + + PortInstanceIterator portIt; + + for (portIt = inst->begin(); + portIt != inst->end(); ++portIt) { + float value = StudioControl::getStudioPluginPort + (inst->getMappedId(), + (*portIt)->number); + (*portIt)->value = value; + } + + // Set modified + m_doc->slotDocumentModified(); + + int key = (pluginIndex << 16) + instrumentId; + if (m_pluginDialogs[key]) { + m_pluginDialogs[key]->updatePluginProgramControl(); + } +} + +void +RosegardenGUIApp::slotPluginProgramChanged(InstrumentId instrumentId, + int pluginIndex) +{ + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotPluginProgramChanged - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = container->getPlugin(pluginIndex); + if (!inst) { + RG_DEBUG << "RosegardenGUIApp::slotPluginProgramChanged - " + << "no plugin at index " << pluginIndex << " on " << instrumentId << endl; + return ; + } + + QString program = strtoqstr(inst->getProgram()); + + RG_DEBUG << "RosegardenGUIApp::slotPluginProgramChanged - " + << "setting plugin program (" + << inst->getMappedId() << ") to " << program << endl; + + StudioControl:: + setStudioObjectProperty(inst->getMappedId(), + MappedPluginSlot::Program, + program); + + PortInstanceIterator portIt; + + for (portIt = inst->begin(); + portIt != inst->end(); ++portIt) { + float value = StudioControl::getStudioPluginPort + (inst->getMappedId(), + (*portIt)->number); + (*portIt)->value = value; + } + + // Set modified + m_doc->slotDocumentModified(); + +#ifdef HAVE_LIBLO + + if (m_pluginGUIManager) + m_pluginGUIManager->updateProgram(instrumentId, + pluginIndex); +#endif +} + +void +RosegardenGUIApp::slotChangePluginConfiguration(InstrumentId instrumentId, + int index, + bool global, + QString key, + QString value) +{ + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotChangePluginConfiguration - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = container->getPlugin(index); + + if (global && inst) { + + // Set the same configuration on other plugins in the same + // instance group + + AudioPlugin *pl = + m_pluginManager->getPluginByIdentifier(strtoqstr(inst->getIdentifier())); + + if (pl && pl->isGrouped()) { + + InstrumentList il = + m_doc->getStudio().getAllInstruments(); + + for (InstrumentList::iterator i = il.begin(); + i != il.end(); ++i) { + + for (PluginInstanceIterator pli = + (*i)->beginPlugins(); + pli != (*i)->endPlugins(); ++pli) { + + if (*pli && (*pli)->isAssigned() && + (*pli)->getIdentifier() == inst->getIdentifier() && + (*pli) != inst) { + + slotChangePluginConfiguration + ((*i)->getId(), (*pli)->getPosition(), + false, key, value); + +#ifdef HAVE_LIBLO + + m_pluginGUIManager->updateConfiguration + ((*i)->getId(), (*pli)->getPosition(), key); +#endif + + } + } + } + } + } + + if (inst) { + + inst->setConfigurationValue(qstrtostr(key), qstrtostr(value)); + + MappedObjectPropertyList config; + for (AudioPluginInstance::ConfigMap::const_iterator + i = inst->getConfiguration().begin(); + i != inst->getConfiguration().end(); ++i) { + config.push_back(strtoqstr(i->first)); + config.push_back(strtoqstr(i->second)); + } + + RG_DEBUG << "RosegardenGUIApp::slotChangePluginConfiguration: setting new config on mapped id " << inst->getMappedId() << endl; + + StudioControl::setStudioObjectPropertyList + (inst->getMappedId(), + MappedPluginSlot::Configuration, + config); + + // Set modified + m_doc->slotDocumentModified(); + + int key = (index << 16) + instrumentId; + if (m_pluginDialogs[key]) { + m_pluginDialogs[key]->updatePluginProgramList(); + } + } +} + +void +RosegardenGUIApp::slotPluginDialogDestroyed(InstrumentId instrumentId, + int index) +{ + int key = (index << 16) + instrumentId; + m_pluginDialogs[key] = 0; +} + +void +RosegardenGUIApp::slotPluginBypassed(InstrumentId instrumentId, + int pluginIndex, bool bp) +{ + PluginContainer *container = 0; + + container = m_doc->getStudio().getContainerById(instrumentId); + if (!container) { + RG_DEBUG << "RosegardenGUIApp::slotPluginBypassed - " + << "no instrument or buss of id " << instrumentId << endl; + return ; + } + + AudioPluginInstance *inst = container->getPlugin(pluginIndex); + + if (inst) { + StudioControl::setStudioObjectProperty + (inst->getMappedId(), + MappedPluginSlot::Bypassed, + MappedObjectValue(bp)); + + // Set the bypass on the instance + // + inst->setBypass(bp); + + // Set modified + m_doc->slotDocumentModified(); + } + + emit pluginBypassed(instrumentId, pluginIndex, bp); +} + +void +RosegardenGUIApp::slotShowPluginGUI(InstrumentId instrument, + int index) +{ +#ifdef HAVE_LIBLO + m_pluginGUIManager->showGUI(instrument, index); +#endif +} + +void +RosegardenGUIApp::slotStopPluginGUI(InstrumentId instrument, + int index) +{ +#ifdef HAVE_LIBLO + m_pluginGUIManager->stopGUI(instrument, index); +#endif +} + +void +RosegardenGUIApp::slotPluginGUIExited(InstrumentId instrument, + int index) +{ + int key = (index << 16) + instrument; + if (m_pluginDialogs[key]) { + m_pluginDialogs[key]->guiExited(); + } +} + +void +RosegardenGUIApp::slotPlayList() +{ + if (!m_playList) { + m_playList = new PlayListDialog(i18n("Play List"), this); + connect(m_playList, SIGNAL(closing()), + SLOT(slotPlayListClosed())); + connect(m_playList->getPlayList(), SIGNAL(play(QString)), + SLOT(slotPlayListPlay(QString))); + } + + m_playList->show(); +} + +void +RosegardenGUIApp::slotPlayListPlay(QString url) +{ + slotStop(); + openURL(url); + slotPlay(); +} + +void +RosegardenGUIApp::slotPlayListClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotPlayListClosed()\n"; + m_playList = 0; +} + +void +RosegardenGUIApp::slotTutorial() +{ + QString tutorialURL = i18n("http://rosegarden.sourceforge.net/tutorial/en/chapter-0.html"); + kapp->invokeBrowser(tutorialURL); +} + +void +RosegardenGUIApp::slotBugGuidelines() +{ + QString glURL = i18n("http://rosegarden.sourceforge.net/tutorial/bug-guidelines.html"); + kapp->invokeBrowser(glURL); +} + +void +RosegardenGUIApp::slotBankEditorClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotBankEditorClosed()\n"; + + if (m_doc->isModified()) { + if (m_view) + m_view->slotSelectTrackSegments(m_doc->getComposition().getSelectedTrack()); + } + + m_bankEditor = 0; +} + +void +RosegardenGUIApp::slotDeviceManagerClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotDeviceManagerClosed()\n"; + + if (m_doc->isModified()) { + if (m_view) + m_view->slotSelectTrackSegments(m_doc->getComposition().getSelectedTrack()); + } + + m_deviceManager = 0; +} + +void +RosegardenGUIApp::slotSynthPluginManagerClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotSynthPluginManagerClosed()\n"; + + m_synthManager = 0; +} + +void +RosegardenGUIApp::slotAudioMixerClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotAudioMixerClosed()\n"; + + m_audioMixer = 0; +} + +void +RosegardenGUIApp::slotMidiMixerClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotMidiMixerClosed()\n"; + + m_midiMixer = 0; +} + +void +RosegardenGUIApp::slotAudioManagerClosed() +{ + RG_DEBUG << "RosegardenGUIApp::slotAudioManagerClosed()\n"; + + if (m_doc->isModified()) { + if (m_view) + m_view->slotSelectTrackSegments(m_doc->getComposition().getSelectedTrack()); + } + + m_audioManagerDialog = 0; +} + +void +RosegardenGUIApp::slotPanic() +{ + if (m_seqManager) { + // Stop the transport before we send a panic as the + // playback goes all to hell anyway. + // + slotStop(); + + ProgressDialog progressDlg(i18n("Queueing MIDI panic events for tranmission..."), + 100, + this); + CurrentProgressDialog::set + (&progressDlg); + ProgressDialog::processEvents(); + + connect(m_seqManager, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + connect(m_seqManager, SIGNAL(incrementProgress(int)), + progressDlg.progressBar(), SLOT(advance(int))); + + m_seqManager->panic(); + + } +} + +void +RosegardenGUIApp::slotPopulateTrackInstrumentPopup() +{ + RG_DEBUG << "RosegardenGUIApp::slotSetTrackInstrument\n"; + Composition &comp = m_doc->getComposition(); + Track *track = comp.getTrackById(comp.getSelectedTrack()); + + if (!track) { + RG_DEBUG << "Weird: no track available for instrument popup!" << endl; + return ; + } + + Instrument* instrument = m_doc->getStudio().getInstrumentById(track->getInstrument()); + + QPopupMenu* popup = dynamic_cast(factory()->container("set_track_instrument", this)); + + m_view->getTrackEditor()->getTrackButtons()->populateInstrumentPopup(instrument, popup); +} + +void +RosegardenGUIApp::slotRemapInstruments() +{ + RG_DEBUG << "RosegardenGUIApp::slotRemapInstruments\n"; + RemapInstrumentDialog dialog(this, m_doc); + + connect(&dialog, SIGNAL(applyClicked()), + m_view->getTrackEditor()->getTrackButtons(), + SLOT(slotSynchroniseWithComposition())); + + if (dialog.exec() == QDialog::Accepted) { + RG_DEBUG << "slotRemapInstruments - accepted\n"; + } + +} + +void +RosegardenGUIApp::slotSaveDefaultStudio() +{ + RG_DEBUG << "RosegardenGUIApp::slotSaveDefaultStudio\n"; + + int reply = KMessageBox::warningYesNo + (this, i18n("Are you sure you want to save this as your default studio?")); + + if (reply != KMessageBox::Yes) + return ; + + KTmpStatusMsg msg(i18n("Saving current document as default studio..."), this); + + QString autoloadFile = ::locateLocal("appdata", "autoload.rg"); + + RG_DEBUG << "RosegardenGUIApp::slotSaveDefaultStudio : saving studio in " + << autoloadFile << endl; + + SetWaitCursor waitCursor; + QString errMsg; + bool res = m_doc->saveDocument(autoloadFile, errMsg); + if (!res) { + if (errMsg) + KMessageBox::error(this, i18n(QString("Could not auto-save document at %1\nError was : %2") + .arg(autoloadFile).arg(errMsg))); + else + KMessageBox::error(this, i18n(QString("Could not auto-save document at %1") + .arg(autoloadFile))); + + } +} + +void +RosegardenGUIApp::slotImportDefaultStudio() +{ + int reply = KMessageBox::warningYesNo + (this, i18n("Are you sure you want to import your default studio and lose the current one?")); + + if (reply != KMessageBox::Yes) + return ; + + QString autoloadFile = + KGlobal::dirs()->findResource("appdata", "autoload.rg"); + + QFileInfo autoloadFileInfo(autoloadFile); + + if (!autoloadFileInfo.isReadable()) { + RG_DEBUG << "RosegardenGUIDoc::slotImportDefaultStudio - " + << "can't find autoload file - defaulting" << endl; + return ; + } + + slotImportStudioFromFile(autoloadFile); +} + +void +RosegardenGUIApp::slotImportStudio() +{ + RG_DEBUG << "RosegardenGUIApp::slotImportStudio()\n"; + + QString studioDir = KGlobal::dirs()->findResource("appdata", "library/"); + QDir dir(studioDir); + if (!dir.exists()) { + studioDir = ":ROSEGARDENDEVICE"; + } else { + studioDir = "file://" + studioDir; + } + + KURL url = KFileDialog::getOpenURL + (studioDir, + "audio/x-rosegarden-device audio/x-rosegarden", + this, i18n("Import Studio from File")); + + if (url.isEmpty()) + return ; + + QString target; + if (KIO::NetAccess::download(url, target, this) == false) { + KMessageBox::error(this, i18n("Cannot download file %1") + .arg(url.prettyURL())); + return ; + } + + slotImportStudioFromFile(target); +} + +void +RosegardenGUIApp::slotImportStudioFromFile(const QString &file) +{ + RosegardenGUIDoc *doc = new RosegardenGUIDoc(this, 0, true); // skipAutoload + + Studio &oldStudio = m_doc->getStudio(); + Studio &newStudio = doc->getStudio(); + + // Add some dummy devices for when we open the document. We guess + // that the file won't have more than 32 devices. + // + // for (unsigned int i = 0; i < 32; i++) { + // newStudio.addDevice("", i, Device::Midi); + // } + + if (doc->openDocument(file, true)) { // true because we actually + // do want to create devices + // on the sequencer here + + KMacroCommand *command = new KMacroCommand(i18n("Import Studio")); + doc->syncDevices(); + + // We actually only copy across MIDI play devices... for now + std::vector midiPlayDevices; + + for (DeviceList::const_iterator i = + oldStudio.begin(); i != oldStudio.end(); ++i) { + + MidiDevice *md = + dynamic_cast(*i); + + if (md && (md->getDirection() == MidiDevice::Play)) { + midiPlayDevices.push_back((*i)->getId()); + } + } + + std::vector::iterator di(midiPlayDevices.begin()); + + for (DeviceList::const_iterator i = + newStudio.begin(); i != newStudio.end(); ++i) { + + MidiDevice *md = + dynamic_cast(*i); + + if (md && (md->getDirection() == MidiDevice::Play)) { + if (di != midiPlayDevices.end()) { + MidiDevice::VariationType variation + (md->getVariationType()); + BankList bl(md->getBanks()); + ProgramList pl(md->getPrograms()); + ControlList cl(md->getControlParameters()); + + ModifyDeviceCommand* mdCommand = new ModifyDeviceCommand(&oldStudio, + *di, + md->getName(), + md->getLibrarianName(), + md->getLibrarianEmail()); + mdCommand->setVariation(variation); + mdCommand->setBankList(bl); + mdCommand->setProgramList(pl); + mdCommand->setControlList(cl); + mdCommand->setOverwrite(true); + mdCommand->setRename(md->getName() != ""); + + command->addCommand(mdCommand); + ++di; + } + } + } + + while (di != midiPlayDevices.end()) { + command->addCommand(new CreateOrDeleteDeviceCommand + (&oldStudio, + *di)); + } + + oldStudio.setMIDIThruFilter(newStudio.getMIDIThruFilter()); + oldStudio.setMIDIRecordFilter(newStudio.getMIDIRecordFilter()); + + m_doc->getCommandHistory()->addCommand(command); + m_doc->syncDevices(); + m_doc->initialiseStudio(); // The other document will have reset it + } + + delete doc; +} + +void +RosegardenGUIApp::slotResetMidiNetwork() +{ + if (m_seqManager) { + + m_seqManager->preparePlayback(true); + + m_seqManager->resetMidiNetwork(); + } + +} + +void +RosegardenGUIApp::slotModifyMIDIFilters() +{ + RG_DEBUG << "RosegardenGUIApp::slotModifyMIDIFilters" << endl; + + MidiFilterDialog dialog(this, m_doc); + + if (dialog.exec() == QDialog::Accepted) { + RG_DEBUG << "slotModifyMIDIFilters - accepted" << endl; + } +} + +void +RosegardenGUIApp::slotManageMetronome() +{ + RG_DEBUG << "RosegardenGUIApp::slotManageMetronome" << endl; + + ManageMetronomeDialog dialog(this, m_doc); + + if (dialog.exec() == QDialog::Accepted) { + RG_DEBUG << "slotManageMetronome - accepted" << endl; + } +} + +void +RosegardenGUIApp::slotAutoSave() +{ + if (!m_seqManager || + m_seqManager->getTransportStatus() == PLAYING || + m_seqManager->getTransportStatus() == RECORDING) + return ; + + KConfig* config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + if (!config->readBoolEntry("autosave", true)) + return ; + + m_doc->slotAutoSave(); +} + +void +RosegardenGUIApp::slotUpdateAutoSaveInterval(unsigned int interval) +{ + RG_DEBUG << "RosegardenGUIApp::slotUpdateAutoSaveInterval - " + << "changed interval to " << interval << endl; + m_autoSaveTimer->changeInterval(int(interval) * 1000); +} + +void +RosegardenGUIApp::slotUpdateSidebarStyle(unsigned int style) +{ + RG_DEBUG << "RosegardenGUIApp::slotUpdateSidebarStyle - " + << "changed style to " << style << endl; + m_parameterArea->setArrangement((RosegardenParameterArea::Arrangement) style); +} + +void +RosegardenGUIApp::slotShowTip() +{ + RG_DEBUG << "RosegardenGUIApp::slotShowTip" << endl; + KTipDialog::showTip(this, locate("data", "rosegarden/tips"), true); +} + +void RosegardenGUIApp::slotShowToolHelp(const QString &s) +{ + QString msg = s; + if (msg != "") msg = " " + msg; + slotStatusMsg(msg); +} + +void +RosegardenGUIApp::slotEnableMIDIThruRouting() +{ + m_seqManager->enableMIDIThruRouting(m_enableMIDIrouting->isChecked()); +} + +TransportDialog* RosegardenGUIApp::getTransport() +{ + if (m_transport == 0) + createAndSetupTransport(); + + return m_transport; +} + +RosegardenGUIDoc *RosegardenGUIApp::getDocument() const +{ + return m_doc; +} + +void +RosegardenGUIApp::awaitDialogClearance() +{ + bool haveDialog = true; + + std::cerr << "RosegardenGUIApp::awaitDialogClearance: entering" << std::endl; + + while (haveDialog) { + + const QObjectList *c = children(); + if (!c) return; + + haveDialog = false; + for (QObjectList::const_iterator i = c->begin(); i != c->end(); ++i) { + QDialog *dialog = dynamic_cast(*i); + if (dialog && dialog->isVisible()) { + haveDialog = true; + break; + } + } + +// std::cerr << "RosegardenGUIApp::awaitDialogClearance: have dialog = " +// << haveDialog << std::endl; + + if (haveDialog) kapp->processEvents(); + } + + std::cerr << "RosegardenGUIApp::awaitDialogClearance: exiting" << std::endl; +} + +void +RosegardenGUIApp::slotNewerVersionAvailable(QString v) +{ + if (m_firstRun) return; + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + awaitDialogClearance(); + KMessageBox::information + (this, + i18n("

Newer version available

A newer version of Rosegarden may be available.
Please consult the Rosegarden website for more information.

"), + i18n("Newer version available"), + QString("version-%1-available-show").arg(v), + KMessageBox::AllowLink); + CurrentProgressDialog::thaw(); +} + +void +RosegardenGUIApp::slotSetQuickMarker() +{ + RG_DEBUG << "RosegardenGUIApp::slotSetQuickMarker" << endl; + + m_doc->setQuickMarker(); + getView()->getTrackEditor()->updateRulers(); +} + +void +RosegardenGUIApp::slotJumpToQuickMarker() +{ + RG_DEBUG << "RosegardenGUIApp::slotJumpToQuickMarker" << endl; + + m_doc->jumpToQuickMarker(); +} + +const void* RosegardenGUIApp::SequencerExternal = (void*)-1; +RosegardenGUIApp *RosegardenGUIApp::m_myself = 0; + +} +#include "RosegardenGUIApp.moc" diff --git a/src/gui/application/RosegardenGUIApp.h b/src/gui/application/RosegardenGUIApp.h new file mode 100644 index 0000000..502d195 --- /dev/null +++ b/src/gui/application/RosegardenGUIApp.h @@ -0,0 +1,1691 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ROSEGARDENGUIAPP_H_ +#define _RG_ROSEGARDENGUIAPP_H_ + +#include +#include +#include "base/MidiProgram.h" +#include "gui/dialogs/TempoDialog.h" +#include "gui/widgets/ZoomSlider.h" +#include "RosegardenIface.h" +#include "base/Event.h" +#include "sound/AudioFile.h" +#include "sound/Midi.h" +#include +#include +#include + + +class QWidget; +class QTimer; +class QTextCodec; +class QShowEvent; +class QObject; +class QLabel; +class QCursor; +class QAccel; +class KURL; +class KTempFile; +class KToggleAction; +class KRecentFilesAction; +class KProcess; +class KConfig; +class KAction; + + +namespace Rosegarden +{ + +class TriggerSegmentManager; +class TransportDialog; +class TrackParameterBox; +class TempoView; +class SynthPluginManagerDialog; +class StartupTester; +class SequenceManager; +class SegmentSelection; +class SegmentParameterBox; +class RosegardenParameterArea; +class RosegardenGUIView; +class RosegardenGUIDoc; +class RealTime; +class ProgressBar; +class PlayListDialog; +class MidiMixerWindow; +class MarkerEditor; +class MappedComposition; +class LircCommander; +class LircClient; +class InstrumentParameterBox; +class DeviceManagerDialog; +class ControlEditorDialog; +class Composition; +class Clipboard; +class BankEditorDialog; +class AudioPluginOSCGUIManager; +class AudioPluginManager; +class AudioPluginDialog; +class AudioMixerWindow; +class AudioManagerDialog; + +/** + * The base class for RosegardenGUI application windows. It sets up the main + * window and reads the config file as well as providing a menubar, toolbar + * and statusbar. An instance of RosegardenGUIView creates your center view, which is connected + * to the window's Doc object. + * RosegardenGUIApp reimplements the methods that KTMainWindow provides for main window handling and supports + * full session management as well as keyboard accelerator configuration by using KAccel. + * @see KTMainWindow + * @see KApplication + * @see KConfig + * @see KAccel + * + * @author Source Framework Automatically Generated by KDevelop, (c) The KDevelop Team. + * @version KDevelop version 0.4 code generation + */ +class RosegardenGUIApp : public KDockMainWindow, virtual public RosegardenIface +{ + Q_OBJECT + + friend class RosegardenGUIView; + +public: + + /** + * construtor of RosegardenGUIApp, calls all init functions to + * create the application. + * \arg useSequencer : if true, the sequencer is launched + * @see initMenuBar initToolBar + */ + RosegardenGUIApp(bool useSequencer = true, + bool useExistingSequencer = false, + QObject *startupStatusMessageReceiver = 0); + + virtual ~RosegardenGUIApp(); + + /* + * Get the current copy of the app object + */ + static RosegardenGUIApp *self() { return m_myself; } + + /** + * returns a pointer to the current document connected to the + * KTMainWindow instance and is used by * the View class to access + * the document object's methods + */ + RosegardenGUIDoc *getDocument() const; + + RosegardenGUIView* getView() { return m_view; } + + TransportDialog* getTransport(); + + enum ImportType { ImportRG4, ImportMIDI, ImportRG21, ImportHydrogen, ImportCheckType }; + + /** + * open a Rosegarden file + */ + virtual void openFile(QString filePath) { openFile(filePath, ImportCheckType); } + + /** + * open a file, explicitly specifying its type + */ + void openFile(QString filePath, ImportType type); + + /** + * decode and open a project file + */ + void importProject(QString filePath); + + /** + * open a URL + */ + virtual void openURL(QString url); + + /** + * merge a file with the existing document + */ + virtual void mergeFile(QString filePath) { mergeFile(filePath, ImportCheckType); } + + /** + * merge a file, explicitly specifying its type + */ + void mergeFile(QString filePath, ImportType type); + + /** + * open a URL + */ + void openURL(const KURL &url); + + /** + * export a MIDI file + */ + void exportMIDIFile(QString url); + + /** + * export a Csound scorefile + */ + void exportCsoundFile(QString url); + + /** + * export a Mup file + */ + void exportMupFile(QString url); + + /** + * export a LilyPond file + */ + bool exportLilyPondFile(QString url, bool forPreview = false); + + /** + * export a MusicXml file + */ + void exportMusicXmlFile(QString url); + + /** + * Get the sequence manager object + */ + SequenceManager* getSequenceManager() { return m_seqManager; } + + /** + * Get a progress bar + */ + ProgressBar *getProgressBar() { return m_progressBar; } + + /** + * Equivalents of the GUI slots, for DCOP use + */ + virtual void fileNew() { slotFileNew(); } + virtual void fileSave() { slotFileSave(); } + virtual void fileClose() { slotFileClose(); } + virtual void quit() { slotQuit(); } + + virtual void play() { slotPlay(); } + virtual void stop() { slotStop(); } + virtual void rewind() { slotRewind(); } + virtual void fastForward() { slotFastforward(); } + virtual void record() { slotRecord(); } + virtual void rewindToBeginning() { slotRewindToBeginning(); } + virtual void fastForwardToEnd() { slotFastForwardToEnd(); } + virtual void jumpToTime(int sec, int usec) { slotJumpToTime(sec, usec); } + virtual void startAtTime(int sec, int usec) { slotStartAtTime(sec, usec); } + + virtual void trackUp() { slotTrackUp(); } + virtual void trackDown() { slotTrackDown(); } + virtual void toggleMutedCurrentTrack() { slotToggleMutedCurrentTrack(); } + virtual void toggleRecordCurrentTrack() { slotToggleRecordCurrentTrack(); } + + /** + * Start the sequencer auxiliary process + * (built in the 'sequencer' directory) + * + * @see slotSequencerExited() + */ + bool launchSequencer(bool useExistingSequencer); + +#ifdef HAVE_LIBJACK + /* + * Launch and control JACK if required to by configuration + */ + bool launchJack(); + +#endif // HAVE_LIBJACK + + + /** + * Returns whether we're using a sequencer. + * false if the '--nosequencer' option was given + * true otherwise. + * This doesn't give the state of the sequencer + * @see #isSequencerRunning + */ + bool isUsingSequencer() { return m_useSequencer; } + + /** + * Returns whether there's a sequencer running. + * The result is dynamically updated depending on the sequencer's + * status. + */ + bool isSequencerRunning() { return m_useSequencer && (m_sequencerProcess != 0); } + + /** + * Returns true if the sequencer wasn't started by us + */ + bool isSequencerExternal() { return m_useSequencer && (m_sequencerProcess == SequencerExternal); } + + /** + * Set the sequencer status - pass through DCOP as an int + */ + virtual void notifySequencerStatus(int status); + + /** + * Handle some random incoming MIDI events. + */ + virtual void processAsynchronousMidi(const MappedComposition &); + + /* + * The sequencer calls this method when it's running to + * allow us to sync data with it. + * + */ + virtual void alive(); + + /* + * Tell the application whether this is the first time this + * version of RG has been run + */ + void setIsFirstRun(bool first) { m_firstRun = first; } + + /* + * Wait in a sub-event-loop until all modal dialogs from the main + * window have been cleared + */ + void awaitDialogClearance(); + + /* + * Return the clipboard + */ + Clipboard* getClipboard() { return m_clipboard; } + +#ifdef HAVE_LIBLO + /** + * Return the plugin native GUI manager, if we have one + */ + AudioPluginOSCGUIManager *getPluginGUIManager() { return m_pluginGUIManager; } +#endif + + /** + * Plug a widget into our common accelerators + */ + void plugAccelerators(QWidget *widget, QAccel *accel); + + /** + * Override from QWidget + * Toolbars and docks need special treatment + */ + virtual void setCursor(const QCursor&); + + bool isTrackEditorPlayTracking() const; + + bool testAudioPath(QString op); // and open the dialog to set it if unset + bool haveAudioImporter() const { return m_haveAudioImporter; } + +protected: + + /**** File handling code that we don't want the outside world to use ****/ + /**/ + /**/ + + /** + * Create document from a file + */ + RosegardenGUIDoc* createDocument(QString filePath, ImportType type = ImportRG4); + + /** + * Create a document from RG file + */ + RosegardenGUIDoc* createDocumentFromRGFile(QString filePath); + + /** + * Create document from MIDI file + */ + RosegardenGUIDoc* createDocumentFromMIDIFile(QString filePath); + + /** + * Create document from RG21 file + */ + RosegardenGUIDoc* createDocumentFromRG21File(QString filePath); + + /** + * Create document from Hydrogen drum machine file + */ + RosegardenGUIDoc* createDocumentFromHydrogenFile(QString filePath); + + /**/ + /**/ + /***********************************************************************/ + + /** + * Set the 'Rewind' and 'Fast Forward' buttons in the transport + * toolbar to AutoRepeat + */ + void setRewFFwdToAutoRepeat(); + + static const void* SequencerExternal; + + /// Raise the transport along + virtual void showEvent(QShowEvent*); + + /** + * read general Options again and initialize all variables like + * the recent file list + */ + void readOptions(); + + /** + * add an item pointing to the example files in the KFileDialog speedbar + */ + void setupFileDialogSpeedbar(); + + /** + * create menus and toolbars + */ + void setupActions(); + + /** + * sets up the zoom toolbar + */ + void initZoomToolbar(); + + /** + * sets up the statusbar for the main window by initialzing a + * statuslabel. + */ + void initStatusBar(); + + /** + * creates the centerwidget of the KTMainWindow instance and sets + * it as the view + */ + void initView(); + + /** + * queryClose is called by KTMainWindow on each closeEvent of a + * window. Against the default implementation (only returns true), + * this calls saveModified() on the document object to ask if the + * document shall be saved if Modified; on cancel the closeEvent + * is rejected. + * + * @see KTMainWindow#queryClose + * @see KTMainWindow#closeEvent + */ + virtual bool queryClose(); + + /** + * queryExit is called by KTMainWindow when the last window of the + * application is going to be closed during the closeEvent(). + * Against the default implementation that just returns true, this + * calls saveOptions() to save the settings of the last window's + * properties. + * + * @see KTMainWindow#queryExit + * @see KTMainWindow#closeEvent + */ + virtual bool queryExit(); + + /** + * saves the window properties for each open window during session + * end to the session config file, including saving the currently + * opened file by a temporary filename provided by KApplication. + * + * @see KTMainWindow#saveProperties + */ + virtual void saveGlobalProperties(KConfig *_cfg); + + /** + * reads the session config file and restores the application's + * state including the last opened files and documents by reading + * the temporary files saved by saveProperties() + * + * @see KTMainWindow#readProperties + */ + virtual void readGlobalProperties(KConfig *_cfg); + + /** + * Create a new audio file for the sequencer and return the + * path to it as a QString. + */ + QString createNewAudioFile(); + QValueVector createRecordAudioFiles(const QValueVector &); + + QString getAudioFilePath(); + + //!!!mtr + QValueVector getArmedInstruments(); + + /** + * Show a sequencer error to the user. This is for errors from + * the framework code; the playback code uses mapped compositions + * to send these things back asynchronously. + */ + void showError(QString error); + + /* + * Return AudioManagerDialog + */ + AudioManagerDialog* getAudioManagerDialog() { return m_audioManagerDialog; } + + /** + * Ask the user for a file to save to, and check that it's + * good and that (if it exists) the user agrees to overwrite. + * Return a null string if the write should not go ahead. + */ + QString getValidWriteFile(QString extension, QString label); + + /** + * Find any non-ASCII strings in a composition that has been + * generated by MIDI import or any other procedure that produces + * events with unknown text encoding, and ask the user what + * encoding to translate them from. This assumes all text strings + * in the composition are of the same encoding, and that it is not + * (known to be) utf8 (in which case no transcoding would be + * necessary). + */ + void fixTextEncodings(Composition *); + QTextCodec *guessTextCodec(std::string); + + /** + * Set the current document + * + * Do all the needed housework when the current document changes + * (like closing edit views, emitting documentChanged signal, etc...) + */ + void setDocument(RosegardenGUIDoc*); + + /** + * Jog a selection of segments by an amount + */ + void jogSelection(timeT amount); + + void createAndSetupTransport(); + +signals: + void startupStatusMessage(QString message); + + /// emitted just before the document is changed + void documentAboutToChange(); + + /// emitted when the current document changes + void documentChanged(RosegardenGUIDoc*); + + /// emitted when the set of selected segments changes (relayed from RosegardenGUIView) + void segmentsSelected(const SegmentSelection &); + + /// emitted when the composition state (selected track, solo, etc...) changes + void compositionStateUpdate(); + + /// emitted when instrument parameters change (relayed from InstrumentParameterBox) + void instrumentParametersChanged(InstrumentId); + + /// emitted when a plugin dialog selects a plugin + void pluginSelected(InstrumentId, int, int); + + /// emitted when a plugin dialog (un)bypasses a plugin + void pluginBypassed(InstrumentId, int, bool); + +public slots: + + /** + * open a URL - used for Dn'D + * + * @param url : a string containing a url (protocol://foo/bar/file.rg) + */ + virtual void slotOpenDroppedURL(QString url); + + /** + * Open the document properties dialog on the Audio page + */ + virtual void slotOpenAudioPathSettings(); + + /** + * open a new application window by creating a new instance of + * RosegardenGUIApp + */ + void slotFileNewWindow(); + + /** + * clears the document in the actual view to reuse it as the new + * document + */ + void slotFileNew(); + + /** + * open a file and load it into the document + */ + void slotFileOpen(); + + /** + * opens a file from the recent files menu + */ + void slotFileOpenRecent(const KURL&); + + /** + * save a document + */ + void slotFileSave(); + + /** + * save a document by a new filename + */ + bool slotFileSaveAs(); + + /** + * asks for saving if the file is modified, then closes the actual + * file and window + */ + void slotFileClose(); + + /** + * print the actual file + */ + void slotFilePrint(); + + /** + * print preview + */ + void slotFilePrintPreview(); + + /** + * Let the user select a Rosegarden Project file for import + */ + void slotImportProject(); + + /** + * Let the user select a MIDI file for import + */ + void slotImportMIDI(); + + /** + * Revert to last loaded file + */ + void slotRevertToSaved(); + + /** + * Let the user select a Rosegarden 2.1 file for import + */ + void slotImportRG21(); + + /** + * Select a Hydrogen drum machine file for import + */ + void slotImportHydrogen(); + + /** + * Let the user select a MIDI file for merge + */ + void slotMerge(); + + /** + * Let the user select a MIDI file for merge + */ + void slotMergeMIDI(); + + /** + * Let the user select a MIDI file for merge + */ + void slotMergeRG21(); + + /** + * Select a Hydrogen drum machine file for merge + */ + void slotMergeHydrogen(); + + /** + * Let the user export a Rosegarden Project file + */ + void slotExportProject(); + + /** + * Let the user enter a MIDI file to export to + */ + void slotExportMIDI(); + + /** + * Let the user enter a Csound scorefile to export to + */ + void slotExportCsound(); + + /** + * Let the user enter a Mup file to export to + */ + void slotExportMup(); + + /** + * Let the user enter a LilyPond file to export to + */ + void slotExportLilyPond(); + + /** + * Export to a temporary file and process + */ + void slotPrintLilyPond(); + void slotPreviewLilyPond(); + void slotLilyPondViewProcessExited(KProcess *); + + /** + * Let the user enter a MusicXml file to export to + */ + void slotExportMusicXml(); + + /** + * closes all open windows by calling close() on each memberList + * item until the list is empty, then quits the application. If + * queryClose() returns false because the user canceled the + * saveModified() dialog, the closing breaks. + */ + void slotQuit(); + + /** + * 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 a time range (sections of segments, tempo, and time + * signature events within that range). + */ + void slotCutRange(); + + /** + * Copy a time range. + */ + void slotCopyRange(); + + /** + * Paste the clipboard at the current pointer position, moving all + * subsequent material along to make space. + */ + void slotPasteRange(); + + /** + * Delete a time range. + */ + void slotDeleteRange(); + + /** + * Insert a time range (asking the user for a duration). + */ + void slotInsertRange(); + + /** + * select all segments on all tracks + */ + void slotSelectAll(); + + /** + * delete selected segments, duh + */ + void slotDeleteSelectedSegments(); + + /** + * Quantize the selected segments (after asking the user how) + */ + void slotQuantizeSelection(); + + /** + * Quantize the selected segments by repeating the last iterative quantize + */ + void slotRepeatQuantizeSelection(); + + /** + * Calculate timing/tempo info based on selected segment + */ + void slotGrooveQuantize(); + + /** + * Rescale the selected segments by a factor requested from + * the user + */ + void slotRescaleSelection(); + + /** + * Split the selected segments on silences (or new timesig, etc) + */ + void slotAutoSplitSelection(); + + /** + * Jog a selection left or right by an amount + */ + void slotJogRight(); + void slotJogLeft(); + + /** + * Split the selected segments by pitch + */ + void slotSplitSelectionByPitch(); + + /** + * Split the selected segments by recorded source + */ + void slotSplitSelectionByRecordedSrc(); + + /** + * Split the selected segments at some time + */ + void slotSplitSelectionAtTime(); + + /** + * Produce a harmony segment from the selected segments + */ + void slotHarmonizeSelection(); + + /** + * Set the start times of the selected segments + */ + void slotSetSegmentStartTimes(); + + /** + * Set the durations of the selected segments + */ + void slotSetSegmentDurations(); + + /** + * Merge the selected segments + */ + void slotJoinSegments(); + + /** + * Tempo to Segment length + */ + void slotTempoToSegmentLength(); + void slotTempoToSegmentLength(QWidget* parent); + + /** + * toggle segment labels + */ + void slotToggleSegmentLabels(); + + /** + * open the default editor for each of the currently-selected segments + */ + void slotEdit(); + + /** + * open an event list view for each of the currently-selected segments + */ + void slotEditInEventList(); + + /** + * open a matrix view for each of the currently-selected segments + */ + void slotEditInMatrix(); + + /** + * open a percussion matrix view for each of the currently-selected segments + */ + void slotEditInPercussionMatrix(); + + /** + * open a notation view with all currently-selected segments in it + */ + void slotEditAsNotation(); + + /** + * open a tempo/timesig edit view + */ + void slotEditTempos(); + void slotEditTempos(timeT openAtTime); + + /** + * Edit the tempo - called from a Transport signal + */ + void slotEditTempo(); + void slotEditTempo(timeT atTime); + void slotEditTempo(QWidget *parent); + void slotEditTempo(QWidget *parent, timeT atTime); + + /** + * Edit the time signature - called from a Transport signal + */ + void slotEditTimeSignature(); + void slotEditTimeSignature(timeT atTime); + void slotEditTimeSignature(QWidget *parent); + void slotEditTimeSignature(QWidget *parent, timeT atTime); + + /** + * Edit the playback pointer position - called from a Transport signal + */ + void slotEditTransportTime(); + void slotEditTransportTime(QWidget *parent); + + /** + * Change the length of the composition + */ + void slotChangeCompositionLength(); + + /** + * open a dialog for document properties + */ + void slotEditDocumentProperties(); + + /** + * Manage MIDI Devices + */ + void slotManageMIDIDevices(); + + /** + * Manage plugin synths + */ + void slotManageSynths(); + + /** + * Show the mixers + */ + void slotOpenAudioMixer(); + void slotOpenMidiMixer(); + + /** + * Edit Banks/Programs + */ + void slotEditBanks(); + + /** + * Edit Banks/Programs for a particular device + */ + void slotEditBanks(DeviceId); + + /** + * Edit Control Parameters for a particular device + */ + void slotEditControlParameters(DeviceId); + + /** + * Edit Document Markers + */ + void slotEditMarkers(); + + /** + * Not an actual action slot : populates the set_track_instrument sub menu + */ + void slotPopulateTrackInstrumentPopup(); + + /** + * Remap instruments + */ + void slotRemapInstruments(); + + /** + * Modify MIDI filters + */ + void slotModifyMIDIFilters(); + + /** + * Manage Metronome + */ + void slotManageMetronome(); + + /** + * Save Studio as Default + */ + void slotSaveDefaultStudio(); + + /** + * Import Studio from File + */ + void slotImportStudio(); + + /** + * Import Studio from Autoload + */ + void slotImportDefaultStudio(); + + /** + * Import Studio from File + */ + void slotImportStudioFromFile(const QString &file); + + /** + * Send MIDI_RESET to all MIDI devices + */ + void slotResetMidiNetwork(); + + /** + * toggles the toolbar + */ + void slotToggleToolBar(); + + /** + * toggles the transport window + */ + void slotToggleTransport(); + + /** + * hides the transport window + */ + void slotHideTransport(); + + /** + * toggles the tools toolbar + */ + void slotToggleToolsToolBar(); + + /** + * toggles the tracks toolbar + */ + void slotToggleTracksToolBar(); + + /** + * toggles the editors toolbar + */ + void slotToggleEditorsToolBar(); + + /** + * toggles the transport toolbar + */ + void slotToggleTransportToolBar(); + + /** + * toggles the zoom toolbar + */ + void slotToggleZoomToolBar(); + + /** + * toggles the statusbar + */ + void slotToggleStatusBar(); + + /** + * changes the statusbar contents for the standard label + * permanently, used to indicate current actions. + * + * @param text the text that is displayed in the statusbar + */ + void slotStatusMsg(QString text); + + /** + * changes the status message of the whole statusbar for two + * seconds, then restores the last status. This is used to display + * statusbar messages that give information about actions for + * toolbar icons and menuentries. + * + * @param text the text that is displayed in the statusbar + */ + void slotStatusHelpMsg(QString text); + + /** + * enables/disables the transport window + */ + void slotEnableTransport(bool); + + /** + * segment select tool + */ + void slotPointerSelected(); + + /** + * segment eraser tool is selected + */ + void slotEraseSelected(); + + /** + * segment draw tool is selected + */ + void slotDrawSelected(); + + /** + * segment move tool is selected + */ + void slotMoveSelected(); + + /** + * segment resize tool is selected + */ + void slotResizeSelected(); + + /* + * Segment join tool + * + */ + void slotJoinSelected(); + + /* + * Segment split tool + * + */ + void slotSplitSelected(); + + /** + * Add one new track + */ + void slotAddTrack(); + + /** + * Add new tracks + */ + void slotAddTracks(); + + /* + * Delete Tracks + */ + void slotDeleteTrack(); + + /* + * Modify track position + */ + void slotMoveTrackUp(); + void slotMoveTrackDown(); + + /** + * timeT version of the same + */ + void slotSetPointerPosition(timeT t); + + /** + * Set the pointer position and start playing (from LoopRuler) + */ + void slotSetPlayPosition(timeT position); + + /** + * Set a loop + */ + void slotSetLoop(timeT lhs, timeT rhs); + + + /** + * Update the transport with the bar, beat and unit times for + * a given timeT + */ + void slotDisplayBarTime(timeT t); + + + /** + * Transport controls + */ + void slotPlay(); + void slotStop(); + void slotRewind(); + void slotFastforward(); + void slotRecord(); + void slotToggleRecord(); + void slotRewindToBeginning(); + void slotFastForwardToEnd(); + void slotJumpToTime(int sec, int usec); + void slotStartAtTime(int sec, int usec); + void slotRefreshTimeDisplay(); + void slotToggleTracking(); + + /** + * Called when the sequencer auxiliary process exits + */ + void slotSequencerExited(KProcess*); + + /// When the transport closes + void slotCloseTransport(); + + /** + * called by RosegardenApplication when session management tells + * it to save its state. This is to avoid saving the transport as + * a 2nd main window + */ + void slotDeleteTransport(); + + /** + * Put the GUI into a given Tool edit mode + */ + void slotActivateTool(QString toolName); + + /** + * Toggles either the play or record metronome according + * to Transport status + */ + void slotToggleMetronome(); + + /* + * Toggle the solo mode + */ + void slotToggleSolo(bool); + + /** + * Set and unset the loop from the transport loop button with + * these slots. + */ + void slotSetLoop(); + void slotUnsetLoop(); + + /** + * Set and unset the loop start/end time from the transport loop start/stop buttons with + * these slots. + */ + void slotSetLoopStart(); + void slotSetLoopStop(); + + /** + * Toggle the track labels on the TrackEditor + */ + void slotToggleTrackLabels(); + + /** + * Toggle the rulers on the TrackEditor + * (aka bar buttons) + */ + void slotToggleRulers(); + + /** + * Toggle the tempo ruler on the TrackEditor + */ + void slotToggleTempoRuler(); + + /** + * Toggle the chord-name ruler on the TrackEditor + */ + void slotToggleChordNameRuler(); + + /** + * Toggle the segment canvas previews + */ + void slotTogglePreviews(); + + /** + * 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); + + /** + * Display tip-of-day dialog on demand + */ + void slotShowTip(); + + /* + * Select Track up or down + */ + void slotTrackUp(); + void slotTrackDown(); + + /** + * Mute/Unmute + */ + void slotMuteAllTracks(); + void slotUnmuteAllTracks(); + void slotToggleMutedCurrentTrack(); + + /** + * Toggle arm (record) current track + */ + void slotToggleRecordCurrentTrack(); + + /** + * save general Options like all bar positions and status as well + * as the geometry and the recent file list to the configuration + * file + */ + void slotSaveOptions(); + + /** + * Show the configure dialog + */ + void slotConfigure(); + + /** + * Show the key mappings + * + */ + void slotEditKeys(); + + /** + * Edit toolbars + */ + void slotEditToolbars(); + + /** + * Update the toolbars after edition + */ + void slotUpdateToolbars(); + + /** + * Zoom slider moved + */ + void slotChangeZoom(int index); + + void slotZoomIn(); + void slotZoomOut(); + + /** + * Modify tempo + */ + void slotChangeTempo(timeT time, + tempoT value, + tempoT target, + TempoDialog::TempoDialogAction action); + + /** + * Move a tempo change + */ + void slotMoveTempo(timeT oldTime, + timeT newTime); + + /** + * Remove a tempo change + */ + void slotDeleteTempo(timeT time); + + /** + * Add marker + */ + void slotAddMarker(timeT time); + + /** + * Remove a marker + */ + void slotDeleteMarker(int id, + timeT time, + QString name, + QString description); + + /** + * Document modified + */ + void slotDocumentModified(bool modified = true); + + + /** + * This slot is here to be connected to RosegardenGUIView's + * stateChange signal. We use a bool for the 2nd arg rather than a + * KXMLGUIClient::ReverseStateChange to spare the include of + * kxmlguiclient.h just for one typedef. + * + * Hopefully we'll be able to get rid of this eventually, + * I should slip this in KMainWindow for KDE 4. + */ + void slotStateChanged(QString, bool noReverse); + + /** + * A command has happened; check the clipboard in case we + * need to change state + */ + void slotTestClipboard(); + + /** + * Show a 'play list' dialog + */ + void slotPlayList(); + + /** + * Play the requested URL + * + * Stop current playback, close current document, + * open specified document and play it. + */ + void slotPlayListPlay(QString url); + + /** + * Call up the online tutorial + */ + void slotTutorial(); + + /** + * Surf to the bug reporting guidelines + */ + void slotBugGuidelines(); + + /** + * View the trigger segments manager + */ + void slotManageTriggerSegments(); + + /** + * View the audio file manager - and some associated actions + */ + void slotAudioManager(); + + void slotAddAudioFile(AudioFileId); + void slotDeleteAudioFile(AudioFileId); + void slotPlayAudioFile(AudioFileId, + const RealTime &, + const RealTime &); + void slotCancelAudioPlayingFile(AudioFileId); + void slotDeleteAllAudioFiles(); + + /** + * Reflect segment deletion from the audio manager + */ + void slotDeleteSegments(const SegmentSelection&); + + void slotRepeatingSegments(); + void slotRelabelSegments(); + void slotTransposeSegments(); + + /// Panic button pressed + void slotPanic(); + + // Auto-save + // + void slotAutoSave(); + + // Auto-save update interval changes + // + void slotUpdateAutoSaveInterval(unsigned int interval); + + // Update the side-bar when the configuration page changes its style. + // + void slotUpdateSidebarStyle(unsigned int style); + + /** + * called when the PlayList is being closed + */ + void slotPlayListClosed(); + + /** + * called when the BankEditor is being closed + */ + void slotBankEditorClosed(); + + /** + * called when the Device Manager is being closed + */ + void slotDeviceManagerClosed(); + + /** + * called when the synth manager is being closed + */ + void slotSynthPluginManagerClosed(); + + /** + * called when the Mixer is being closed + */ + void slotAudioMixerClosed(); + void slotMidiMixerClosed(); + + /** + * when ControlEditor is being closed + */ + void slotControlEditorClosed(); + + /** + * when MarkerEditor is being closed + */ + void slotMarkerEditorClosed(); + + /** + * when TempoView is being closed + */ + void slotTempoViewClosed(); + + /** + * when TriggerManager is being closed + */ + void slotTriggerManagerClosed(); + + /** + * when AudioManagerDialog is being closed + */ + void slotAudioManagerClosed(); + + /** + * Update the pointer position from the sequencer mmapped file when playing + */ + void slotUpdatePlaybackPosition(); + + /** + * Update the CPU level meter + */ + void slotUpdateCPUMeter(bool playing); + + /** + * Update the monitor levels from the sequencer mmapped file when not playing + * (slotUpdatePlaybackPosition does this among other things when playing) + */ + void slotUpdateMonitoring(); + + /** + * Create a plugin dialog for a given instrument and slot, or + * raise an exising one. + */ + void slotShowPluginDialog(QWidget *parent, + InstrumentId instrument, + int index); + + void slotPluginSelected(InstrumentId instrument, + int index, int plugin); + + /** + * An external GUI has requested a port change. + */ + void slotChangePluginPort(InstrumentId instrument, + int index, int portIndex, float value); + + /** + * Our internal GUI has made a port change -- the + * PluginPortInstance already contains the new value, but we need + * to inform the sequencer and update external GUIs. + */ + void slotPluginPortChanged(InstrumentId instrument, + int index, int portIndex); + + /** + * An external GUI has requested a program change. + */ + void slotChangePluginProgram(InstrumentId instrument, + int index, QString program); + + /** + * Our internal GUI has made a program change -- the + * AudioPluginInstance already contains the new program, but we + * need to inform the sequencer, update external GUIs, and update + * the port values for the new program. + */ + void slotPluginProgramChanged(InstrumentId instrument, + int index); + + /** + * An external GUI has requested a configure call. (This can only + * happen from an external GUI, we have no way to manage these + * internally.) + */ + void slotChangePluginConfiguration(InstrumentId, int index, + bool global, QString key, QString value); + void slotPluginDialogDestroyed(InstrumentId instrument, + int index); + void slotPluginBypassed(InstrumentId, + int index, bool bypassed); + + void slotShowPluginGUI(InstrumentId, int index); + void slotStopPluginGUI(InstrumentId, int index); + void slotPluginGUIExited(InstrumentId, int index); + + void slotDocumentDevicesResyncd(); + + void slotTestStartupTester(); + + void slotDebugDump(); + + /** + * Enable or disable the internal MIDI Thru routing. + * + * This policy is implemented at the sequencer side, controlled + * by this flag and also by the MIDI Thru filters. + * + * @see ControlBlock::isMidiRoutingEnabled() + * @see RosegardenSequencerApp::processAsynchronousEvents() + * @see RosegardenSequencerApp::processRecordedEvents() + */ + void slotEnableMIDIThruRouting(); + + void slotShowToolHelp(const QString &); + + void slotNewerVersionAvailable(QString); + + void slotSetQuickMarker(); + + void slotJumpToQuickMarker(); + +private: + + + //--------------- Data members --------------------------------- + + bool m_actionsSetup; + + KRecentFilesAction* m_fileRecent; + + /** + * view is the main widget which represents your working area. The + * View class should handle all events of the view widget. It is + * kept empty so you can create your view according to your + * application's needs by changing the view class. + */ + RosegardenGUIView* m_view; + RosegardenGUIView* m_swapView; + + KDockWidget* m_mainDockWidget; + KDockWidget* m_dockLeft; + + /** + * doc represents your actual document and is created only + * once. It keeps information such as filename and does the + * serialization of your files. + */ + RosegardenGUIDoc* m_doc; + + /** + * KAction pointers to enable/disable actions + */ + KRecentFilesAction* m_fileOpenRecent; + + KToggleAction* m_viewToolBar; + KToggleAction* m_viewToolsToolBar; + KToggleAction* m_viewTracksToolBar; + KToggleAction* m_viewEditorsToolBar; + KToggleAction* m_viewZoomToolBar; + KToggleAction* m_viewStatusBar; + KToggleAction* m_viewTransport; + KToggleAction* m_viewTransportToolBar; + KToggleAction* m_viewTrackLabels; + KToggleAction* m_viewRulers; + KToggleAction* m_viewTempoRuler; + KToggleAction* m_viewChordNameRuler; + KToggleAction* m_viewPreviews; + KToggleAction* m_viewSegmentLabels; + KToggleAction* m_enableMIDIrouting; + + KAction *m_playTransport; + KAction *m_stopTransport; + KAction *m_rewindTransport; + KAction *m_ffwdTransport; + KAction *m_recordTransport; + KAction *m_rewindEndTransport; + KAction *m_ffwdEndTransport; + + KProcess* m_sequencerProcess; + bool m_sequencerCheckedIn; + +#ifdef HAVE_LIBJACK + KProcess* m_jackProcess; +#endif // HAVE_LIBJACK + + ZoomSlider *m_zoomSlider; + QLabel *m_zoomLabel; + + ProgressBar *m_progressBar; + + // SequenceManager + // + SequenceManager *m_seqManager; + + // Transport dialog pointer + // + TransportDialog *m_transport; + + // Dialogs which depend on the document + + // Audio file manager + // + AudioManagerDialog *m_audioManagerDialog; + + bool m_originatingJump; + + // Use these in conjucntion with the loop button to + // remember where a loop was if we've ever set one. + timeT m_storedLoopStart; + timeT m_storedLoopEnd; + + bool m_useSequencer; + bool m_dockVisible; + + AudioPluginManager *m_pluginManager; + + QTimer* m_autoSaveTimer; + + Clipboard *m_clipboard; + + SegmentParameterBox *m_segmentParameterBox; + InstrumentParameterBox *m_instrumentParameterBox; + TrackParameterBox *m_trackParameterBox; + + PlayListDialog *m_playList; + DeviceManagerDialog *m_deviceManager; + SynthPluginManagerDialog *m_synthManager; + AudioMixerWindow *m_audioMixer; + MidiMixerWindow *m_midiMixer; + BankEditorDialog *m_bankEditor; + MarkerEditor *m_markerEditor; + TempoView *m_tempoView; + TriggerSegmentManager *m_triggerSegmentManager; + std::set m_controlEditors; + std::map m_pluginDialogs; +#ifdef HAVE_LIBLO + AudioPluginOSCGUIManager *m_pluginGUIManager; +#endif + + static RosegardenGUIApp *m_myself; + + static std::map m_lilyTempFileMap; + + // Used to fetch the current sequencer position from the mmapped sequencer information file + // + QTimer *m_playTimer; + QTimer *m_stopTimer; + + StartupTester *m_startupTester; + + bool m_firstRun; + bool m_haveAudioImporter; + + RosegardenParameterArea *m_parameterArea; + + KAction *m_setQuickMarkerAction; + KAction *m_jumpToQuickMarkerAction; + +#ifdef HAVE_LIRC + LircClient *m_lircClient; + LircCommander *m_lircCommander; +#endif +}; + + +} + +#endif diff --git a/src/gui/application/RosegardenGUIView.cpp b/src/gui/application/RosegardenGUIView.cpp new file mode 100644 index 0000000..c61b51e --- /dev/null +++ b/src/gui/application/RosegardenGUIView.cpp @@ -0,0 +1,2041 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RosegardenGUIView.h" +#include + +#include "sound/Midi.h" +#include "gui/editors/segment/TrackButtons.h" +#include +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "gui/application/RosegardenDCOP.h" +#include "base/AudioLevel.h" +#include "base/Composition.h" +#include "base/Instrument.h" +#include "base/MidiDevice.h" +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "base/RealTime.h" +#include "base/RulerScale.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "commands/segment/AudioSegmentAutoSplitCommand.h" +#include "commands/segment/AudioSegmentInsertCommand.h" +#include "commands/segment/SegmentSingleRepeatToCopyCommand.h" +#include "document/MultiViewCommandHistory.h" +#include "document/RosegardenGUIDoc.h" +#include "RosegardenApplication.h" +#include "gui/configuration/GeneralConfigurationPage.h" +#include "gui/configuration/AudioConfigurationPage.h" +#include "gui/dialogs/AudioSplitDialog.h" +#include "gui/dialogs/AudioManagerDialog.h" +#include "gui/dialogs/DocumentConfigureDialog.h" +#include "gui/dialogs/TempoDialog.h" +#include "gui/editors/eventlist/EventView.h" +#include "gui/editors/matrix/MatrixView.h" +#include "gui/editors/notation/NotationView.h" +#include "gui/editors/parameters/InstrumentParameterBox.h" +#include "gui/editors/parameters/SegmentParameterBox.h" +#include "gui/editors/parameters/TrackParameterBox.h" +#include "gui/editors/segment/segmentcanvas/CompositionView.h" +#include "gui/editors/segment/segmentcanvas/SegmentSelector.h" +#include "gui/editors/segment/TrackEditor.h" +#include "gui/seqmanager/SequenceManager.h" +#include "gui/seqmanager/SequencerMapper.h" +#include "gui/rulers/ChordNameRuler.h" +#include "gui/rulers/LoopRuler.h" +#include "gui/rulers/TempoRuler.h" +#include "gui/rulers/StandardRuler.h" +#include "gui/widgets/ProgressDialog.h" +#include "gui/widgets/CurrentProgressDialog.h" +#include "RosegardenGUIApp.h" +#include "SetWaitCursor.h" +#include "sound/AudioFile.h" +#include "sound/AudioFileManager.h" +#include "sound/MappedEvent.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +// Use this to define the basic unit of the main QCanvas size. +// +// This apparently arbitrary figure is what we think is an +// appropriate width in pixels for a 4/4 bar. Beware of making it +// too narrow, as shorter bars will be proportionally smaller -- +// the visual difference between 2/4 and 4/4 is perhaps greater +// than it sounds. +// +static double barWidth44 = 100.0; + +const QWidget *RosegardenGUIView::m_lastActiveMainWindow = 0; + +// This is the maximum number of matrix, event view or percussion +// matrix editors to open in a single operation (not the maximum that +// can be open at a time -- there isn't one) +// +static int maxEditorsToOpen = 8; + +RosegardenGUIView::RosegardenGUIView(bool showTrackLabels, + SegmentParameterBox* segmentParameterBox, + InstrumentParameterBox* instrumentParameterBox, + TrackParameterBox* trackParameterBox, + QWidget *parent, + const char* /*name*/) + : QVBox(parent), + m_rulerScale(0), + m_trackEditor(0), + m_segmentParameterBox(segmentParameterBox), + m_instrumentParameterBox(instrumentParameterBox), + m_trackParameterBox(trackParameterBox) +{ + RosegardenGUIDoc* doc = getDocument(); + Composition *comp = &doc->getComposition(); + + double unitsPerPixel = + TimeSignature(4, 4).getBarDuration() / barWidth44; + m_rulerScale = new SimpleRulerScale(comp, 0, unitsPerPixel); + + // Construct the trackEditor first so we can then + // query it for placement information + // + m_trackEditor = new TrackEditor(doc, this, + m_rulerScale, showTrackLabels, unitsPerPixel, this /*hbox*/); + + connect(m_trackEditor->getSegmentCanvas(), + SIGNAL(editSegment(Segment*)), + SLOT(slotEditSegment(Segment*))); + + connect(m_trackEditor->getSegmentCanvas(), + SIGNAL(editSegmentNotation(Segment*)), + SLOT(slotEditSegmentNotation(Segment*))); + + connect(m_trackEditor->getSegmentCanvas(), + SIGNAL(editSegmentMatrix(Segment*)), + SLOT(slotEditSegmentMatrix(Segment*))); + + connect(m_trackEditor->getSegmentCanvas(), + SIGNAL(editSegmentAudio(Segment*)), + SLOT(slotEditSegmentAudio(Segment*))); + + connect(m_trackEditor->getSegmentCanvas(), + SIGNAL(audioSegmentAutoSplit(Segment*)), + SLOT(slotSegmentAutoSplit(Segment*))); + + connect(m_trackEditor->getSegmentCanvas(), + SIGNAL(editSegmentEventList(Segment*)), + SLOT(slotEditSegmentEventList(Segment*))); + + connect(m_trackEditor->getSegmentCanvas(), + SIGNAL(editRepeat(Segment*, timeT)), + SLOT(slotEditRepeat(Segment*, timeT))); + + connect(m_trackEditor->getSegmentCanvas(), + SIGNAL(setPointerPosition(timeT)), + doc, SLOT(slotSetPointerPosition(timeT))); + + connect(m_trackEditor, + SIGNAL(droppedDocument(QString)), + parent, + SLOT(slotOpenDroppedURL(QString))); + + connect(m_trackEditor, + SIGNAL(droppedAudio(QString)), + this, + SLOT(slotDroppedAudio(QString))); + + connect(m_trackEditor, + SIGNAL(droppedNewAudio(QString)), + this, + SLOT(slotDroppedNewAudio(QString))); + + connect(m_instrumentParameterBox, + SIGNAL(changeInstrumentLabel(InstrumentId, QString)), + this, + SLOT(slotChangeInstrumentLabel(InstrumentId, QString))); + + connect(m_instrumentParameterBox, + SIGNAL(changeInstrumentLabel(InstrumentId, QString)), + m_trackParameterBox, + SLOT(slotInstrumentLabelChanged(InstrumentId, QString))); + + connect(m_trackEditor->getTrackButtons(), + SIGNAL(nameChanged()), + m_trackParameterBox, + SLOT(slotSelectedTrackNameChanged())); + + connect(m_trackEditor->getTrackButtons(), + SIGNAL(instrumentSelected(int)), + m_trackParameterBox, + SLOT(slotUpdateControls(int))); + + connect(m_trackParameterBox, + SIGNAL(instrumentSelected(TrackId, int)), + m_trackEditor->getTrackButtons(), + SLOT(slotTrackInstrumentSelection(TrackId, int))); + + connect(this, SIGNAL(controllerDeviceEventReceived(MappedEvent *, const void *)), + this, SLOT(slotControllerDeviceEventReceived(MappedEvent *, const void *))); + + if (doc) { + /* signal no longer exists + connect(doc, SIGNAL(recordingSegmentUpdated(Segment *, + timeT)), + this, SLOT(slotUpdateRecordingSegment(Segment *, + timeT))); + */ + + QObject::connect + (getCommandHistory(), SIGNAL(commandExecuted()), + m_trackEditor->getSegmentCanvas(), SLOT(slotUpdateSegmentsDrawBuffer())); + } +} + +RosegardenGUIView::~RosegardenGUIView() +{ + RG_DEBUG << "~RosegardenGUIView()" << endl; + delete m_rulerScale; +} + +RosegardenGUIDoc* +RosegardenGUIView::getDocument() const +{ + return RosegardenGUIApp::self()->getDocument(); +} + +void RosegardenGUIView::print(Composition* p, bool previewOnly) +{ + SetWaitCursor waitCursor; + + std::vector segmentsToEdit; + + for (Composition::iterator i = p->begin(); i != p->end(); ++i) { + if ((*i)->getType() != Segment::Audio) { + segmentsToEdit.push_back(*i); + } + } + + if (segmentsToEdit.empty()) { + KMessageBox::sorry(this, i18n("No non-audio segments in composition")); + return ; + } + + NotationView *notationView = new NotationView(getDocument(), + segmentsToEdit, + this, + (NotationView *)0); + + if (!notationView->isOK()) { + RG_DEBUG << "RosegardenGUIView::print : operation cancelled" << endl; + delete notationView; + return ; + } + + notationView->print(previewOnly); + + delete notationView; +} + +void RosegardenGUIView::selectTool(const QString toolName) +{ + m_trackEditor->getSegmentCanvas()->slotSetTool(toolName); +} + +bool +RosegardenGUIView::haveSelection() +{ + return m_trackEditor->getSegmentCanvas()->haveSelection(); +} + +SegmentSelection +RosegardenGUIView::getSelection() +{ + return m_trackEditor->getSegmentCanvas()->getSelectedSegments(); +} + +void RosegardenGUIView::updateSelectionContents() +{ + m_trackEditor->getSegmentCanvas()->updateSelectionContents(); +} + +/* hjj: WHAT DO DO WITH THIS ? +void +RosegardenGUIView::slotEditMetadata(QString name) +{ + const QWidget *ww = dynamic_cast(sender()); + QWidget *w = const_cast(ww); + + DocumentConfigureDialog *configDlg = + new DocumentConfigureDialog(getDocument(), w ? w : this); + + configDlg->selectMetadata(name); + + configDlg->show(); +} +*/ + +void RosegardenGUIView::slotEditSegment(Segment* segment) +{ + Segment::SegmentType type = Segment::Internal; + + if (segment) { + type = segment->getType(); + } else { + if (haveSelection()) { + + bool haveType = false; + + SegmentSelection selection = getSelection(); + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + + Segment::SegmentType myType = (*i)->getType(); + if (haveType) { + if (myType != type) { + KMessageBox::sorry(this, i18n("Selection must contain only audio or non-audio segments")); + return ; + } + } else { + type = myType; + haveType = true; + segment = *i; + } + } + } else + return ; + } + + if (type == Segment::Audio) { + slotEditSegmentAudio(segment); + } else { + + KConfig* config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + GeneralConfigurationPage::DoubleClickClient + client = + (GeneralConfigurationPage::DoubleClickClient) + (config->readUnsignedNumEntry("doubleclickclient", + (unsigned int)GeneralConfigurationPage::NotationView)); + + if (client == GeneralConfigurationPage::MatrixView) { + + bool isPercussion = false; + Track *track = getDocument()->getComposition().getTrackById + (segment->getTrack()); + if (track) { + InstrumentId iid = track->getInstrument(); + Instrument *instrument = + getDocument()->getStudio().getInstrumentById(iid); + if (instrument && instrument->isPercussion()) isPercussion = true; + } + + if (isPercussion) { + slotEditSegmentPercussionMatrix(segment); + } else { + slotEditSegmentMatrix(segment); + } + + } else if (client == GeneralConfigurationPage::EventView) { + slotEditSegmentEventList(segment); + } else { + slotEditSegmentNotation(segment); + } + } +} + +void RosegardenGUIView::slotEditRepeat(Segment *segment, + timeT time) +{ + SegmentSingleRepeatToCopyCommand *command = + new SegmentSingleRepeatToCopyCommand(segment, time); + slotAddCommandToHistory(command); +} + +void RosegardenGUIView::slotEditSegmentNotation(Segment* p) +{ + SetWaitCursor waitCursor; + std::vector segmentsToEdit; + + RG_DEBUG << "\n\n\n\nRosegardenGUIView::slotEditSegmentNotation: p is " << p << endl; + + // The logic here is: If we're calling for this operation to + // happen on a particular segment, then open that segment and if + // it's part of a selection open all other selected segments too. + // If we're not calling for any particular segment, then open all + // selected segments if there are any. + + if (haveSelection()) { + + SegmentSelection selection = getSelection(); + + if (!p || (selection.find(p) != selection.end())) { + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + if ((*i)->getType() != Segment::Audio) { + segmentsToEdit.push_back(*i); + } + } + } else { + if (p->getType() != Segment::Audio) { + segmentsToEdit.push_back(p); + } + } + + } else if (p) { + if (p->getType() != Segment::Audio) { + segmentsToEdit.push_back(p); + } + } else { + return ; + } + + if (segmentsToEdit.empty()) { + KMessageBox::sorry(this, i18n("No non-audio segments selected")); + return ; + } + + slotEditSegmentsNotation(segmentsToEdit); +} + +void RosegardenGUIView::slotEditSegmentsNotation(std::vector segmentsToEdit) +{ + NotationView *view = createNotationView(segmentsToEdit); + if (view) + view->show(); +} + +NotationView * +RosegardenGUIView::createNotationView(std::vector segmentsToEdit) +{ + NotationView *notationView = + new NotationView(getDocument(), segmentsToEdit, this, true); + + if (!notationView->isOK()) { + RG_DEBUG << "slotEditSegmentNotation : operation cancelled" << endl; + delete notationView; + return 0; + } + + // For tempo changes (ugh -- it'd be nicer to make a tempo change + // command that could interpret all this stuff from the dialog) + // + connect(notationView, SIGNAL(changeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction)), + RosegardenGUIApp::self(), SLOT(slotChangeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction))); + + + connect(notationView, SIGNAL(windowActivated()), + this, SLOT(slotActiveMainWindowChanged())); + + connect(notationView, SIGNAL(selectTrack(int)), + this, SLOT(slotSelectTrackSegments(int))); + + connect(notationView, SIGNAL(play()), + RosegardenGUIApp::self(), SLOT(slotPlay())); + connect(notationView, SIGNAL(stop()), + RosegardenGUIApp::self(), SLOT(slotStop())); + connect(notationView, SIGNAL(fastForwardPlayback()), + RosegardenGUIApp::self(), SLOT(slotFastforward())); + connect(notationView, SIGNAL(rewindPlayback()), + RosegardenGUIApp::self(), SLOT(slotRewind())); + connect(notationView, SIGNAL(fastForwardPlaybackToEnd()), + RosegardenGUIApp::self(), SLOT(slotFastForwardToEnd())); + connect(notationView, SIGNAL(rewindPlaybackToBeginning()), + RosegardenGUIApp::self(), SLOT(slotRewindToBeginning())); + connect(notationView, SIGNAL(panic()), + RosegardenGUIApp::self(), SLOT(slotPanic())); + + connect(notationView, SIGNAL(saveFile()), + RosegardenGUIApp::self(), SLOT(slotFileSave())); + connect(notationView, SIGNAL(jumpPlaybackTo(timeT)), + getDocument(), SLOT(slotSetPointerPosition(timeT))); + connect(notationView, SIGNAL(openInNotation(std::vector)), + this, SLOT(slotEditSegmentsNotation(std::vector))); + connect(notationView, SIGNAL(openInMatrix(std::vector)), + this, SLOT(slotEditSegmentsMatrix(std::vector))); + connect(notationView, SIGNAL(openInPercussionMatrix(std::vector)), + this, SLOT(slotEditSegmentsPercussionMatrix(std::vector))); + connect(notationView, SIGNAL(openInEventList(std::vector)), + this, SLOT(slotEditSegmentsEventList(std::vector))); +/* hjj: WHAT DO DO WITH THIS ? + connect(notationView, SIGNAL(editMetadata(QString)), + this, SLOT(slotEditMetadata(QString))); +*/ + connect(notationView, SIGNAL(editTriggerSegment(int)), + this, SLOT(slotEditTriggerSegment(int))); + connect(notationView, SIGNAL(staffLabelChanged(TrackId, QString)), + this, SLOT(slotChangeTrackLabel(TrackId, QString))); + connect(notationView, SIGNAL(toggleSolo(bool)), + RosegardenGUIApp::self(), SLOT(slotToggleSolo(bool))); + connect(notationView, SIGNAL(editTimeSignature(timeT)), + RosegardenGUIApp::self(), SLOT(slotEditTempos(timeT))); + + SequenceManager *sM = getDocument()->getSequenceManager(); + + connect(sM, SIGNAL(insertableNoteOnReceived(int, int)), + notationView, SLOT(slotInsertableNoteOnReceived(int, int))); + connect(sM, SIGNAL(insertableNoteOffReceived(int, int)), + notationView, SLOT(slotInsertableNoteOffReceived(int, int))); + + connect(notationView, SIGNAL(stepByStepTargetRequested(QObject *)), + this, SIGNAL(stepByStepTargetRequested(QObject *))); + connect(this, SIGNAL(stepByStepTargetRequested(QObject *)), + notationView, SLOT(slotStepByStepTargetRequested(QObject *))); + connect(RosegardenGUIApp::self(), SIGNAL(compositionStateUpdate()), + notationView, SLOT(slotCompositionStateUpdate())); + connect(this, SIGNAL(compositionStateUpdate()), + notationView, SLOT(slotCompositionStateUpdate())); + + // Encourage the notation view window to open to the same + // interval as the current segment view + if (m_trackEditor->getSegmentCanvas()->horizontalScrollBar()->value() > 1) { // don't scroll unless we need to + // first find the time at the center of the visible segment canvas + int centerX = (int)(m_trackEditor->getSegmentCanvas()->contentsX() + + m_trackEditor->getSegmentCanvas()->visibleWidth() / 2); + timeT centerSegmentView = m_trackEditor->getRulerScale()->getTimeForX(centerX); + // then scroll the notation view to that time, "localized" for the current segment + notationView->scrollToTime(centerSegmentView); + notationView->updateView(); + } + + return notationView; +} + +void RosegardenGUIView::slotEditSegmentMatrix(Segment* p) +{ + SetWaitCursor waitCursor; + + std::vector segmentsToEdit; + + // unlike notation, if we're calling for this on a particular + // segment we don't open all the other selected segments as well + // (fine in notation because they're in a single window) + + if (p) { + if (p->getType() != Segment::Audio) { + segmentsToEdit.push_back(p); + } + } else { + int count = 0; + SegmentSelection selection = getSelection(); + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + if ((*i)->getType() != Segment::Audio) { + slotEditSegmentMatrix(*i); + if (++count == maxEditorsToOpen) + break; + } + } + return ; + } + + if (segmentsToEdit.empty()) { + KMessageBox::sorry(this, i18n("No non-audio segments selected")); + return ; + } + + slotEditSegmentsMatrix(segmentsToEdit); +} + +void RosegardenGUIView::slotEditSegmentPercussionMatrix(Segment* p) +{ + SetWaitCursor waitCursor; + + std::vector segmentsToEdit; + + // unlike notation, if we're calling for this on a particular + // segment we don't open all the other selected segments as well + // (fine in notation because they're in a single window) + + if (p) { + if (p->getType() != Segment::Audio) { + segmentsToEdit.push_back(p); + } + } else { + int count = 0; + SegmentSelection selection = getSelection(); + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + if ((*i)->getType() != Segment::Audio) { + slotEditSegmentPercussionMatrix(*i); + if (++count == maxEditorsToOpen) + break; + } + } + return ; + } + + if (segmentsToEdit.empty()) { + KMessageBox::sorry(this, i18n("No non-audio segments selected")); + return ; + } + + slotEditSegmentsPercussionMatrix(segmentsToEdit); +} + +void RosegardenGUIView::slotEditSegmentsMatrix(std::vector segmentsToEdit) +{ + int count = 0; + for (std::vector::iterator i = segmentsToEdit.begin(); + i != segmentsToEdit.end(); ++i) { + std::vector tmpvec; + tmpvec.push_back(*i); + MatrixView *view = createMatrixView(tmpvec, false); + if (view) { + view->show(); + if (++count == maxEditorsToOpen) + break; + } + } +} + +void RosegardenGUIView::slotEditSegmentsPercussionMatrix(std::vector segmentsToEdit) +{ + int count = 0; + for (std::vector::iterator i = segmentsToEdit.begin(); + i != segmentsToEdit.end(); ++i) { + std::vector tmpvec; + tmpvec.push_back(*i); + MatrixView *view = createMatrixView(tmpvec, true); + if (view) { + view->show(); + if (++count == maxEditorsToOpen) + break; + } + } +} + +MatrixView * +RosegardenGUIView::createMatrixView(std::vector segmentsToEdit, bool drumMode) +{ + MatrixView *matrixView = new MatrixView(getDocument(), + segmentsToEdit, + this, + drumMode); + + // For tempo changes (ugh -- it'd be nicer to make a tempo change + // command that could interpret all this stuff from the dialog) + // + connect(matrixView, SIGNAL(changeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction)), + RosegardenGUIApp::self(), SLOT(slotChangeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction))); + + connect(matrixView, SIGNAL(windowActivated()), + this, SLOT(slotActiveMainWindowChanged())); + + connect(matrixView, SIGNAL(selectTrack(int)), + this, SLOT(slotSelectTrackSegments(int))); + + connect(matrixView, SIGNAL(play()), + RosegardenGUIApp::self(), SLOT(slotPlay())); + connect(matrixView, SIGNAL(stop()), + RosegardenGUIApp::self(), SLOT(slotStop())); + connect(matrixView, SIGNAL(fastForwardPlayback()), + RosegardenGUIApp::self(), SLOT(slotFastforward())); + connect(matrixView, SIGNAL(rewindPlayback()), + RosegardenGUIApp::self(), SLOT(slotRewind())); + connect(matrixView, SIGNAL(fastForwardPlaybackToEnd()), + RosegardenGUIApp::self(), SLOT(slotFastForwardToEnd())); + connect(matrixView, SIGNAL(rewindPlaybackToBeginning()), + RosegardenGUIApp::self(), SLOT(slotRewindToBeginning())); + connect(matrixView, SIGNAL(panic()), + RosegardenGUIApp::self(), SLOT(slotPanic())); + + connect(matrixView, SIGNAL(saveFile()), + RosegardenGUIApp::self(), SLOT(slotFileSave())); + connect(matrixView, SIGNAL(jumpPlaybackTo(timeT)), + getDocument(), SLOT(slotSetPointerPosition(timeT))); + connect(matrixView, SIGNAL(openInNotation(std::vector)), + this, SLOT(slotEditSegmentsNotation(std::vector))); + connect(matrixView, SIGNAL(openInMatrix(std::vector)), + this, SLOT(slotEditSegmentsMatrix(std::vector))); + connect(matrixView, SIGNAL(openInEventList(std::vector)), + this, SLOT(slotEditSegmentsEventList(std::vector))); + connect(matrixView, SIGNAL(editTriggerSegment(int)), + this, SLOT(slotEditTriggerSegment(int))); + connect(matrixView, SIGNAL(toggleSolo(bool)), + RosegardenGUIApp::self(), SLOT(slotToggleSolo(bool))); + connect(matrixView, SIGNAL(editTimeSignature(timeT)), + RosegardenGUIApp::self(), SLOT(slotEditTempos(timeT))); + + SequenceManager *sM = getDocument()->getSequenceManager(); + + connect(sM, SIGNAL(insertableNoteOnReceived(int, int)), + matrixView, SLOT(slotInsertableNoteOnReceived(int, int))); + connect(sM, SIGNAL(insertableNoteOffReceived(int, int)), + matrixView, SLOT(slotInsertableNoteOffReceived(int, int))); + + connect(matrixView, SIGNAL(stepByStepTargetRequested(QObject *)), + this, SIGNAL(stepByStepTargetRequested(QObject *))); + connect(this, SIGNAL(stepByStepTargetRequested(QObject *)), + matrixView, SLOT(slotStepByStepTargetRequested(QObject *))); + connect(RosegardenGUIApp::self(), SIGNAL(compositionStateUpdate()), + matrixView, SLOT(slotCompositionStateUpdate())); + connect(this, SIGNAL(compositionStateUpdate()), + matrixView, SLOT(slotCompositionStateUpdate())); + connect(this, + SIGNAL(instrumentLevelsChanged(InstrumentId, + const LevelInfo &)), + matrixView, + SLOT(slotInstrumentLevelsChanged(InstrumentId, + const LevelInfo &))); + + // Encourage the matrix view window to open to the same + // interval as the current segment view + if (m_trackEditor->getSegmentCanvas()->horizontalScrollBar()->value() > 1) { // don't scroll unless we need to + // first find the time at the center of the visible segment canvas + int centerX = (int)(m_trackEditor->getSegmentCanvas()->contentsX()); + // Seems to work better for matrix view to scroll to left side + // + m_trackEditor->getSegmentCanvas()->visibleWidth() / 2); + timeT centerSegmentView = m_trackEditor->getRulerScale()->getTimeForX(centerX); + // then scroll the notation view to that time, "localized" for the current segment + matrixView->scrollToTime(centerSegmentView); + matrixView->updateView(); + } + + return matrixView; +} + +void RosegardenGUIView::slotEditSegmentEventList(Segment *p) +{ + SetWaitCursor waitCursor; + + std::vector segmentsToEdit; + + // unlike notation, if we're calling for this on a particular + // segment we don't open all the other selected segments as well + // (fine in notation because they're in a single window) + + if (p) { + if (p->getType() != Segment::Audio) { + segmentsToEdit.push_back(p); + } + } else { + int count = 0; + SegmentSelection selection = getSelection(); + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + if ((*i)->getType() != Segment::Audio) { + slotEditSegmentEventList(*i); + if (++count == maxEditorsToOpen) + break; + } + } + return ; + } + + if (segmentsToEdit.empty()) { + KMessageBox::sorry(this, i18n("No non-audio segments selected")); + return ; + } + + slotEditSegmentsEventList(segmentsToEdit); +} + +void RosegardenGUIView::slotEditSegmentsEventList(std::vector segmentsToEdit) +{ + int count = 0; + for (std::vector::iterator i = segmentsToEdit.begin(); + i != segmentsToEdit.end(); ++i) { + std::vector tmpvec; + tmpvec.push_back(*i); + EventView *view = createEventView(tmpvec); + if (view) { + view->show(); + if (++count == maxEditorsToOpen) + break; + } + } +} + +void RosegardenGUIView::slotEditTriggerSegment(int id) +{ + SetWaitCursor waitCursor; + + std::vector segmentsToEdit; + + Segment *s = getDocument()->getComposition().getTriggerSegment(id); + + if (s) { + segmentsToEdit.push_back(s); + } else { + return ; + } + + slotEditSegmentsEventList(segmentsToEdit); +} + +void RosegardenGUIView::slotSegmentAutoSplit(Segment *segment) +{ + AudioSplitDialog aSD(this, segment, getDocument()); + + if (aSD.exec() == QDialog::Accepted) { + KCommand *command = + new AudioSegmentAutoSplitCommand(getDocument(), + segment, aSD.getThreshold()); + slotAddCommandToHistory(command); + } +} + +void RosegardenGUIView::slotEditSegmentAudio(Segment *segment) +{ + std::cout << "RosegardenGUIView::slotEditSegmentAudio() - " + << "starting external audio editor" << std::endl; + + KConfig* config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + + QString application = config->readEntry("externalaudioeditor", ""); + + if (application == "") { + application = AudioConfigurationPage::getBestAvailableAudioEditor(); + } + + QStringList splitCommand = QStringList::split(" ", application); + + if (splitCommand.size() == 0) { + + std::cerr << "RosegardenGUIView::slotEditSegmentAudio() - " + << "external editor \"" << application.data() + << "\" not found" << std::endl; + + KMessageBox::sorry(this, + i18n("You've not yet defined an audio editor for Rosegarden to use.\nSee Settings -> Configure Rosegarden -> Audio.")); + + return ; + } + + QFileInfo *appInfo = new QFileInfo(splitCommand[0]); + if (appInfo->exists() == false || appInfo->isExecutable() == false) { + std::cerr << "RosegardenGUIView::slotEditSegmentAudio() - " + << "can't execute \"" << splitCommand[0] << "\"" + << std::endl; + return; + } + + AudioFile *aF = getDocument()->getAudioFileManager(). + getAudioFile(segment->getAudioFileId()); + if (aF == 0) { + std::cerr << "RosegardenGUIView::slotEditSegmentAudio() - " + << "can't find audio file" << std::endl; + return ; + } + + // wait cursor + QApplication::setOverrideCursor(QCursor(Qt::waitCursor)); + + // Prepare the process + // + KProcess *process = new KProcess(); + (*process) << splitCommand; + (*process) << QString(aF->getFilename().c_str()); + + // Start it + // + if (!process->start()) { + std::cerr << "RosegardenGUIView::slotEditSegmentAudio() - " + << "can't start external editor" << std::endl; + } + + // restore cursor + QApplication::restoreOverrideCursor(); + +} + +void RosegardenGUIView::setZoomSize(double size) +{ + m_rulerScale->setUnitsPerPixel(size); + + double duration44 = TimeSignature(4, 4).getBarDuration(); + + double xScale = duration44 / (size * barWidth44); + RG_DEBUG << "RosegardenGUIView::setZoomSize - xScale = " << xScale << endl; + + m_trackEditor->slotSetPointerPosition + (getDocument()->getComposition().getPosition()); + + m_trackEditor->getSegmentCanvas()->clearSegmentRectsCache(true); + m_trackEditor->getSegmentCanvas()->slotUpdateSize(); + m_trackEditor->getSegmentCanvas()->slotUpdateSegmentsDrawBuffer(); + + if (m_trackEditor->getTempoRuler()) { + m_trackEditor->getTempoRuler()->repaint(); + } + + if (m_trackEditor->getChordNameRuler()) { + m_trackEditor->getChordNameRuler()->repaint(); + } + + if (m_trackEditor->getTopStandardRuler()) { + m_trackEditor->getTopStandardRuler()->repaint(); + } + + if (m_trackEditor->getBottomStandardRuler()) { + m_trackEditor->getBottomStandardRuler()->repaint(); + } +} + +void RosegardenGUIView::slotSelectTrackSegments(int trackId) +{ + // update the instrument parameter box + Composition &comp = getDocument()->getComposition(); + Track *track = comp.getTrackById(trackId); + + if (track == 0) + return ; + + // Show the selection on the track buttons. Find the position. + // + m_trackEditor->getTrackButtons()->selectLabel(track->getPosition()); + m_trackEditor->slotScrollToTrack(track->getPosition()); + + SegmentSelection segments; + + for (Composition::iterator i = + getDocument()->getComposition().begin(); + i != getDocument()->getComposition().end(); i++) { + if (((int)(*i)->getTrack()) == trackId) + segments.insert(*i); + } + + // Store the selected Track in the Composition + // + comp.setSelectedTrack(trackId); + + m_trackParameterBox->slotSelectedTrackChanged(); + + slotUpdateInstrumentParameterBox(comp.getTrackById(trackId)-> + getInstrument()); + + + slotPropagateSegmentSelection(segments); + + // inform + emit segmentsSelected(segments); + emit compositionStateUpdate(); +} + +void RosegardenGUIView::slotPropagateSegmentSelection(const SegmentSelection &segments) +{ + // Send this signal to the GUI to activate the correct tool + // on the toolbar so that we have a SegmentSelector object + // to write the Segments into + // + if (!segments.empty()) { + emit activateTool(SegmentSelector::ToolName); + } + + // Send the segment list even if it's empty as we + // use that to clear any current selection + // + m_trackEditor->getSegmentCanvas()->slotSelectSegments(segments); + + // update the segment parameter box + m_segmentParameterBox->useSegments(segments); + + if (!segments.empty()) { + emit stateChange("have_selection", true); + if (!segments.hasNonAudioSegment()) { + emit stateChange("audio_segment_selected", true); + } + } else { + emit stateChange("have_selection", false); + } +} + +void RosegardenGUIView::slotSelectAllSegments() +{ + SegmentSelection segments; + + InstrumentId instrument = 0; + bool haveInstrument = false; + bool multipleInstruments = false; + + Composition &comp = getDocument()->getComposition(); + + for (Composition::iterator i = comp.begin(); i != comp.end(); ++i) { + + InstrumentId myInstrument = + comp.getTrackById((*i)->getTrack())->getInstrument(); + + if (haveInstrument) { + if (myInstrument != instrument) { + multipleInstruments = true; + } + } else { + instrument = myInstrument; + haveInstrument = true; + } + + segments.insert(*i); + } + + // Send this signal to the GUI to activate the correct tool + // on the toolbar so that we have a SegmentSelector object + // to write the Segments into + // + if (!segments.empty()) { + emit activateTool(SegmentSelector::ToolName); + } + + // Send the segment list even if it's empty as we + // use that to clear any current selection + // + m_trackEditor->getSegmentCanvas()->slotSelectSegments(segments); + + // update the segment parameter box + m_segmentParameterBox->useSegments(segments); + + // update the instrument parameter box + if (haveInstrument && !multipleInstruments) { + slotUpdateInstrumentParameterBox(instrument); + } else { + m_instrumentParameterBox->useInstrument(0); + } + + //!!! similarly, how to set no selected track? + //comp.setSelectedTrack(trackId); + + if (!segments.empty()) { + emit stateChange("have_selection", true); + if (!segments.hasNonAudioSegment()) { + emit stateChange("audio_segment_selected", true); + } + } else { + emit stateChange("have_selection", false); + } + + // inform + //!!! inform what? is this signal actually used? + emit segmentsSelected(segments); +} + +void RosegardenGUIView::slotUpdateInstrumentParameterBox(int id) +{ + Studio &studio = getDocument()->getStudio(); + Instrument *instrument = studio.getInstrumentById(id); + Composition &comp = getDocument()->getComposition(); + + Track *track = comp.getTrackById(comp.getSelectedTrack()); + + // Reset the instrument + // + m_instrumentParameterBox->useInstrument(instrument); + + // Then do this instrument/track fiddling + // + /* + if (track && instrument && + instrument->getType() == Instrument::Audio) + { + // Set the mute status + m_instrumentParameterBox->setMute(track->isMuted()); + + // Set the record track + m_instrumentParameterBox->setRecord( + track->getId() == comp.getRecordTrack()); + + // Set solo + m_instrumentParameterBox->setSolo( + comp.isSolo() && (track->getId() == comp.getSelectedTrack())); + } + */ + emit checkTrackAssignments(); +} + +void RosegardenGUIView::showVisuals(const MappedEvent *mE) +{ + double valueLeft = ((double)mE->getData1()) / 127.0; + double valueRight = ((double)mE->getData2()) / 127.0; + + if (mE->getType() == MappedEvent::AudioLevel) { + + // Send to the high sensitivity instrument parameter box + // (if any) + // + if (m_instrumentParameterBox->getSelectedInstrument() && + mE->getInstrument() == + m_instrumentParameterBox->getSelectedInstrument()->getId()) { + float dBleft = AudioLevel::fader_to_dB + (mE->getData1(), 127, AudioLevel::LongFader); + float dBright = AudioLevel::fader_to_dB + (mE->getData2(), 127, AudioLevel::LongFader); + + m_instrumentParameterBox->setAudioMeter(dBleft, dBright, + AudioLevel::DB_FLOOR, + AudioLevel::DB_FLOOR); + } + + // Don't always send all audio levels so we don't + // get vu meter flickering on track meters + // + if (valueLeft < 0.05 && valueRight < 0.05) + return ; + + } else if (mE->getType() != MappedEvent::MidiNote) + return ; + + m_trackEditor->getTrackButtons()-> + slotSetMetersByInstrument((valueLeft + valueRight) / 2, + mE->getInstrument()); + +} + +void +RosegardenGUIView::updateMeters(SequencerMapper *mapper) +{ + const int unknownState = 0, oldState = 1, newState = 2; + + typedef std::map StateMap; + static StateMap states; + static StateMap recStates; + + typedef std::map LevelMap; + static LevelMap levels; + static LevelMap recLevels; + + for (StateMap::iterator i = states.begin(); i != states.end(); ++i) { + i->second = unknownState; + } + for (StateMap::iterator i = recStates.begin(); i != recStates.end(); ++i) { + i->second = unknownState; + } + + for (Composition::trackcontainer::iterator i = + getDocument()->getComposition().getTracks().begin(); + i != getDocument()->getComposition().getTracks().end(); ++i) { + + Track *track = i->second; + if (!track) + continue; + + InstrumentId instrumentId = track->getInstrument(); + + if (states[instrumentId] == unknownState) { + bool isNew = mapper->getInstrumentLevel(instrumentId, + levels[instrumentId]); + states[instrumentId] = (isNew ? newState : oldState); + } + + if (recStates[instrumentId] == unknownState) { + bool isNew = mapper->getInstrumentRecordLevel(instrumentId, + recLevels[instrumentId]); + recStates[instrumentId] = (isNew ? newState : oldState); + } + + if (states[instrumentId] == oldState && + recStates[instrumentId] == oldState) + continue; + + Instrument *instrument = + getDocument()->getStudio().getInstrumentById(instrumentId); + if (!instrument) + continue; + + // This records the level of this instrument, not neccessarily + // caused by notes on this particular track. + LevelInfo &info = levels[instrumentId]; + LevelInfo &recInfo = recLevels[instrumentId]; + + if (instrument->getType() == Instrument::Audio || + instrument->getType() == Instrument::SoftSynth) { + + float dBleft = AudioLevel::DB_FLOOR; + float dBright = AudioLevel::DB_FLOOR; + float recDBleft = AudioLevel::DB_FLOOR; + float recDBright = AudioLevel::DB_FLOOR; + + bool toSet = false; + + if (states[instrumentId] == newState && + (getDocument()->getSequenceManager()->getTransportStatus() + != STOPPED)) { + + if (info.level != 0 || info.levelRight != 0) { + dBleft = AudioLevel::fader_to_dB + (info.level, 127, AudioLevel::LongFader); + dBright = AudioLevel::fader_to_dB + (info.levelRight, 127, AudioLevel::LongFader); + } + toSet = true; + m_trackEditor->getTrackButtons()->slotSetTrackMeter + ((info.level + info.levelRight) / 254.0, track->getPosition()); + } + + if (recStates[instrumentId] == newState && + instrument->getType() == Instrument::Audio && + (getDocument()->getSequenceManager()->getTransportStatus() + != PLAYING)) { + + if (recInfo.level != 0 || recInfo.levelRight != 0) { + recDBleft = AudioLevel::fader_to_dB + (recInfo.level, 127, AudioLevel::LongFader); + recDBright = AudioLevel::fader_to_dB + (recInfo.levelRight, 127, AudioLevel::LongFader); + } + toSet = true; + } + + if (toSet && + m_instrumentParameterBox->getSelectedInstrument() && + instrument->getId() == + m_instrumentParameterBox->getSelectedInstrument()->getId()) { + + m_instrumentParameterBox->setAudioMeter(dBleft, dBright, + recDBleft, recDBright); + } + + } else { + // Not audio or softsynth + if (info.level == 0) + continue; + + if (getDocument()->getSequenceManager()->getTransportStatus() + != STOPPED) { + + // The information in 'info' is specific for this instrument, not + // for this track. + //m_trackEditor->getTrackButtons()->slotSetTrackMeter + // (info.level / 127.0, track->getPosition()); + m_trackEditor->getTrackButtons()->slotSetMetersByInstrument + (info.level / 127.0, instrumentId); + } + } + } + + for (StateMap::iterator i = states.begin(); i != states.end(); ++i) { + if (i->second == newState) { + emit instrumentLevelsChanged(i->first, levels[i->first]); + } + } +} + +void +RosegardenGUIView::updateMonitorMeters(SequencerMapper *mapper) +{ + Instrument *instrument = + m_instrumentParameterBox->getSelectedInstrument(); + if (!instrument || + (instrument->getType() != Instrument::Audio)) + return ; + + LevelInfo level; + if (!mapper->getInstrumentRecordLevel(instrument->getId(), level)) + return ; + + float dBleft = AudioLevel::fader_to_dB + (level.level, 127, AudioLevel::LongFader); + float dBright = AudioLevel::fader_to_dB + (level.levelRight, 127, AudioLevel::LongFader); + + m_instrumentParameterBox->setAudioMeter + (AudioLevel::DB_FLOOR, AudioLevel::DB_FLOOR, + dBleft, dBright); +} + +void +RosegardenGUIView::slotSelectedSegments(const SegmentSelection &segments) +{ + // update the segment parameter box + m_segmentParameterBox->useSegments(segments); + + if (!segments.empty()) { + emit stateChange("have_selection", true); + if (!segments.hasNonAudioSegment()) + emit stateChange("audio_segment_selected", true); + } else { + emit stateChange("have_selection", false); + } + + emit segmentsSelected(segments); +} + +void RosegardenGUIView::slotShowRulers(bool v) +{ + if (v) { + m_trackEditor->getTopStandardRuler()->getLoopRuler()->show(); + m_trackEditor->getBottomStandardRuler()->getLoopRuler()->show(); + } else { + m_trackEditor->getTopStandardRuler()->getLoopRuler()->hide(); + m_trackEditor->getBottomStandardRuler()->getLoopRuler()->hide(); + } +} + +void RosegardenGUIView::slotShowTempoRuler(bool v) +{ + if (v) { + m_trackEditor->getTempoRuler()->show(); + } else { + m_trackEditor->getTempoRuler()->hide(); + } +} + +void RosegardenGUIView::slotShowChordNameRuler(bool v) +{ + if (v) { + m_trackEditor->getChordNameRuler()->setStudio(&getDocument()->getStudio()); + m_trackEditor->getChordNameRuler()->show(); + } else { + m_trackEditor->getChordNameRuler()->hide(); + } +} + +void RosegardenGUIView::slotShowPreviews(bool v) +{ + m_trackEditor->getSegmentCanvas()->setShowPreviews(v); + m_trackEditor->getSegmentCanvas()->slotUpdateSegmentsDrawBuffer(); +} + +void RosegardenGUIView::slotShowSegmentLabels(bool v) +{ + m_trackEditor->getSegmentCanvas()->setShowSegmentLabels(v); + m_trackEditor->getSegmentCanvas()->slotUpdateSegmentsDrawBuffer(); +} + +void RosegardenGUIView::slotAddTracks(unsigned int nbTracks, + InstrumentId id, int pos) +{ + RG_DEBUG << "RosegardenGUIView::slotAddTracks(" << nbTracks << ", " << pos << ")" << endl; + m_trackEditor->slotAddTracks(nbTracks, id, pos); +} + +void RosegardenGUIView::slotDeleteTracks( + std::vector tracks) +{ + RG_DEBUG << "RosegardenGUIView::slotDeleteTracks - " + << "deleting " << tracks.size() << " tracks" + << endl; + + m_trackEditor->slotDeleteTracks(tracks); +} + +MultiViewCommandHistory* +RosegardenGUIView::getCommandHistory() +{ + return getDocument()->getCommandHistory(); +} + +void +RosegardenGUIView::slotAddCommandToHistory(KCommand *command) +{ + getCommandHistory()->addCommand(command); +} + +void +RosegardenGUIView::slotChangeInstrumentLabel(InstrumentId id, + QString label) +{ + m_trackEditor->getTrackButtons()->changeInstrumentLabel(id, label); +} + +void +RosegardenGUIView::slotChangeTrackLabel(TrackId id, + QString label) +{ + m_trackEditor->getTrackButtons()->changeTrackLabel(id, label); +} + +void +RosegardenGUIView::slotAddAudioSegment(AudioFileId audioId, + TrackId trackId, + timeT position, + const RealTime &startTime, + const RealTime &endTime) +{ + AudioSegmentInsertCommand *command = + new AudioSegmentInsertCommand(getDocument(), + trackId, + position, + audioId, + startTime, + endTime); + slotAddCommandToHistory(command); + + Segment *newSegment = command->getNewSegment(); + if (newSegment) { + SegmentSelection selection; + selection.insert(newSegment); + slotPropagateSegmentSelection(selection); + emit segmentsSelected(selection); + } +} + +void +RosegardenGUIView::slotAddAudioSegmentCurrentPosition(AudioFileId audioFileId, + const RealTime &startTime, + const RealTime &endTime) +{ + Composition &comp = getDocument()->getComposition(); + + AudioSegmentInsertCommand *command = + new AudioSegmentInsertCommand(getDocument(), + comp.getSelectedTrack(), + comp.getPosition(), + audioFileId, + startTime, + endTime); + slotAddCommandToHistory(command); + + Segment *newSegment = command->getNewSegment(); + if (newSegment) { + SegmentSelection selection; + selection.insert(newSegment); + slotPropagateSegmentSelection(selection); + emit segmentsSelected(selection); + } +} + +void +RosegardenGUIView::slotAddAudioSegmentDefaultPosition(AudioFileId audioFileId, + const RealTime &startTime, + const RealTime &endTime) +{ + // Add at current track if it's an audio track, otherwise at first + // empty audio track if there is one, otherwise at first audio track. + // This behaviour should be of no interest to proficient users (who + // should have selected the right track already, or be using drag- + // and-drop) but it should save beginners from inserting an audio + // segment and being quite unable to work out why it won't play + + Composition &comp = getDocument()->getComposition(); + Studio &studio = getDocument()->getStudio(); + + TrackId currentTrackId = comp.getSelectedTrack(); + Track *track = comp.getTrackById(currentTrackId); + + if (track) { + InstrumentId ii = track->getInstrument(); + Instrument *instrument = studio.getInstrumentById(ii); + + if (instrument && + instrument->getType() == Instrument::Audio) { + slotAddAudioSegment(audioFileId, currentTrackId, + comp.getPosition(), startTime, endTime); + return ; + } + } + + // current track is not an audio track, find a more suitable one + + TrackId bestSoFar = currentTrackId; + + for (Composition::trackcontainer::const_iterator + ti = comp.getTracks().begin(); + ti != comp.getTracks().end(); ++ti) { + + InstrumentId ii = ti->second->getInstrument(); + Instrument *instrument = studio.getInstrumentById(ii); + + if (instrument && + instrument->getType() == Instrument::Audio) { + + if (bestSoFar == currentTrackId) + bestSoFar = ti->first; + bool haveSegment = false; + + for (Composition::segmentcontainer::const_iterator si = + comp.getSegments().begin(); + si != comp.getSegments().end(); ++si) { + if ((*si)->getTrack() == ti->first) { + // there's a segment on this track + haveSegment = true; + break; + } + } + + if (!haveSegment) { // perfect + slotAddAudioSegment(audioFileId, ti->first, + comp.getPosition(), startTime, endTime); + return ; + } + } + } + + slotAddAudioSegment(audioFileId, bestSoFar, + comp.getPosition(), startTime, endTime); + return ; +} + +void +RosegardenGUIView::slotDroppedNewAudio(QString audioDesc) +{ + QTextIStream s(&audioDesc); + + QString url; + int trackId; + timeT time; + url = s.readLine(); + s >> trackId; + s >> time; + + std::cerr << "RosegardenGUIView::slotDroppedNewAudio: url " << url << ", trackId " << trackId << ", time " << time << std::endl; + + RosegardenGUIApp *app = RosegardenGUIApp::self(); + AudioFileManager &aFM = getDocument()->getAudioFileManager(); + + AudioFileId audioFileId = 0; + + int sampleRate = 0; + if (getDocument()->getSequenceManager()) { + sampleRate = getDocument()->getSequenceManager()->getSampleRate(); + } + + KURL kurl(url); + if (!kurl.isLocalFile()) { + if (!RosegardenGUIApp::self()->testAudioPath("importing a remote audio file")) return; + } else if (aFM.fileNeedsConversion(qstrtostr(kurl.path()), sampleRate)) { + if (!RosegardenGUIApp::self()->testAudioPath("importing an audio file that needs to be converted or resampled")) return; + } + + ProgressDialog progressDlg(i18n("Adding audio file..."), + 100, + this); + + CurrentProgressDialog::set(&progressDlg); + progressDlg.progressBar()->hide(); + progressDlg.show(); + + // Connect the progress dialog + // + connect(&aFM, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + connect(&aFM, SIGNAL(setOperationName(QString)), + &progressDlg, SLOT(slotSetOperationName(QString))); + connect(&progressDlg, SIGNAL(cancelClicked()), + &aFM, SLOT(slotStopImport())); + + try { + audioFileId = aFM.importURL(kurl, sampleRate); + } catch (AudioFileManager::BadAudioPathException e) { + CurrentProgressDialog::freeze(); + QString errorString = i18n("Can't add dropped file. ") + strtoqstr(e.getMessage()); + KMessageBox::sorry(this, errorString); + return ; + } catch (SoundFile::BadSoundFileException e) { + CurrentProgressDialog::freeze(); + QString errorString = i18n("Can't add dropped file. ") + strtoqstr(e.getMessage()); + KMessageBox::sorry(this, errorString); + return ; + } + + disconnect(&progressDlg, SIGNAL(cancelClicked()), + &aFM, SLOT(slotStopImport())); + connect(&progressDlg, SIGNAL(cancelClicked()), + &aFM, SLOT(slotStopPreview())); + progressDlg.progressBar()->show(); + progressDlg.slotSetOperationName(i18n("Generating audio preview...")); + + try { + aFM.generatePreview(audioFileId); + } catch (Exception e) { + CurrentProgressDialog::freeze(); + QString message = strtoqstr(e.getMessage()) + "\n\n" + + i18n("Try copying this file to a directory where you have write permission and re-add it"); + KMessageBox::information(this, message); + //return false; + } + + disconnect(&progressDlg, SIGNAL(cancelClicked()), + &aFM, SLOT(slotStopPreview())); + + // add the file at the sequencer + emit addAudioFile(audioFileId); + + // Now fetch file details + // + AudioFile *aF = aFM.getAudioFile(audioFileId); + + if (aF) { + slotAddAudioSegment(audioFileId, trackId, time, + RealTime(0, 0), aF->getLength()); + + RG_DEBUG << "RosegardenGUIView::slotDroppedNewAudio(" + << "file = " << url + << ", trackid = " << trackId + << ", time = " << time << endl; + } +} + +void +RosegardenGUIView::slotDroppedAudio(QString audioDesc) +{ + QTextIStream s(&audioDesc); + + AudioFileId audioFileId; + TrackId trackId; + timeT position; + RealTime startTime, endTime; + + // read the audio info + s >> audioFileId; + s >> trackId; + s >> position; + s >> startTime.sec; + s >> startTime.nsec; + s >> endTime.sec; + s >> endTime.nsec; + + RG_DEBUG << "RosegardenGUIView::slotDroppedAudio(" + //<< audioDesc + << ") : audioFileId = " << audioFileId + << " - trackId = " << trackId + << " - position = " << position + << " - startTime.sec = " << startTime.sec + << " - startTime.nsec = " << startTime.nsec + << " - endTime.sec = " << endTime.sec + << " - endTime.nsec = " << endTime.nsec + << endl; + + slotAddAudioSegment(audioFileId, trackId, position, startTime, endTime); +} + +void +RosegardenGUIView::slotSetMuteButton(TrackId track, bool value) +{ + RG_DEBUG << "RosegardenGUIView::slotSetMuteButton - track id = " << track + << ", value = " << value << endl; + + m_trackEditor->getTrackButtons()->setMuteButton(track, value); + Track *trackObj = getDocument()-> + getComposition().getTrackById(track); + /* + // to fix 739544 + if (m_instrumentParameterBox->getSelectedInstrument() && + m_instrumentParameterBox->getSelectedInstrument()->getId() == + trackObj->getInstrument()) + { + m_instrumentParameterBox->setMute(value); + } + */ + // set the value in the composition + trackObj->setMuted(value); + + getDocument()->slotDocumentModified(); // set the modification flag + +} + +void +RosegardenGUIView::slotSetMute(InstrumentId id, bool value) +{ + RG_DEBUG << "RosegardenGUIView::slotSetMute - " + << "id = " << id + << ",value = " << value << endl; + + Composition &comp = getDocument()->getComposition(); + Composition::trackcontainer &tracks = comp.getTracks(); + Composition::trackiterator it; + + for (it = tracks.begin(); it != tracks.end(); ++it) { + if ((*it).second->getInstrument() == id) + slotSetMuteButton((*it).second->getId(), value); + } + +} + +void +RosegardenGUIView::slotSetRecord(InstrumentId id, bool value) +{ + RG_DEBUG << "RosegardenGUIView::slotSetRecord - " + << "id = " << id + << ",value = " << value << endl; + /* + // IPB + // + m_instrumentParameterBox->setRecord(value); + */ + Composition &comp = getDocument()->getComposition(); + Composition::trackcontainer &tracks = comp.getTracks(); + Composition::trackiterator it; +#ifdef NOT_DEFINED + + for (it = tracks.begin(); it != tracks.end(); ++it) { + if (comp.getSelectedTrack() == (*it).second->getId()) { + //!!! MTR m_trackEditor->getTrackButtons()-> + // setRecordTrack((*it).second->getPosition()); + //!!! MTR is this needed? I think probably not + slotUpdateInstrumentParameterBox((*it).second->getInstrument()); + } + } +#endif + Studio &studio = getDocument()->getStudio(); + Instrument *instr = studio.getInstrumentById(id); +} + +void +RosegardenGUIView::slotSetSolo(InstrumentId id, bool value) +{ + RG_DEBUG << "RosegardenGUIView::slotSetSolo - " + << "id = " << id + << ",value = " << value << endl; + + emit toggleSolo(value); +} + +void +RosegardenGUIView::slotUpdateRecordingSegment(Segment *segment, + timeT ) +{ + // We're only interested in this on the first call per recording segment, + // when we possibly create a view for it + static Segment *lastRecordingSegment = 0; + + if (segment == lastRecordingSegment) + return ; + lastRecordingSegment = segment; + + KConfig* config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + + int tracking = config->readUnsignedNumEntry("recordtracking", 0); + if (tracking != 1) + return ; + + RG_DEBUG << "RosegardenGUIView::slotUpdateRecordingSegment: segment is " << segment << ", lastRecordingSegment is " << lastRecordingSegment << ", opening a new view" << endl; + + std::vector segments; + segments.push_back(segment); + + NotationView *view = createNotationView(segments); + if (!view) + return ; + + /* signal no longer exists + QObject::connect + (getDocument(), SIGNAL(recordingSegmentUpdated(Segment *, timeT)), + view, SLOT(slotUpdateRecordingSegment(Segment *, timeT))); + */ + + view->show(); +} + +void +RosegardenGUIView::slotSynchroniseWithComposition() +{ + // Track buttons + // + m_trackEditor->getTrackButtons()->slotSynchroniseWithComposition(); + + // Update all IPBs + // + Composition &comp = getDocument()->getComposition(); + Track *track = comp.getTrackById(comp.getSelectedTrack()); + slotUpdateInstrumentParameterBox(track->getInstrument()); + + m_instrumentParameterBox->slotUpdateAllBoxes(); +} + +void +RosegardenGUIView::windowActivationChange(bool) +{ + if (isActiveWindow()) { + slotActiveMainWindowChanged(this); + } +} + +void +RosegardenGUIView::slotActiveMainWindowChanged(const QWidget *w) +{ + m_lastActiveMainWindow = w; +} + +void +RosegardenGUIView::slotActiveMainWindowChanged() +{ + const QWidget *w = dynamic_cast(sender()); + if (w) + slotActiveMainWindowChanged(w); +} + +void +RosegardenGUIView::slotControllerDeviceEventReceived(MappedEvent *e) +{ + RG_DEBUG << "Controller device event received - send to " << (void *)m_lastActiveMainWindow << " (I am " << this << ")" << endl; + + //!!! So, what _should_ we do with these? + + // -- external controller that sends e.g. volume control for each + // of a number of channels -> if mixer present, use control to adjust + // tracks on mixer + + // -- external controller that sends e.g. separate controllers on + // the same channel for adjusting various parameters -> if IPB + // visible, adjust it. Should we use the channel to select the + // track? maybe as an option + + // do we actually need the last active main window for either of + // these? -- yes, to determine whether to send to mixer or to IPB + // in the first place. Send to audio mixer if active, midi mixer + // if active, plugin dialog if active, otherwise keep it for + // ourselves for the IPB. But, we'll do that by having the edit + // views pass it back to us. + + // -- then we need to send back out to device. + + //!!! special cases: controller 81 received by any window -> + // select window 0->main, 1->audio mix, 2->midi mix + + //!!! controller 82 received by main window -> select track + + //!!! these obviously should be configurable + + if (e->getType() == MappedEvent::MidiController) { + + if (e->getData1() == 81) { + + // select window + int window = e->getData2(); + + if (window < 10) { // me + + show(); + raise(); + setActiveWindow(); + + } else if (window < 20) { + + RosegardenGUIApp::self()->slotOpenAudioMixer(); + + } else if (window < 30) { + + RosegardenGUIApp::self()->slotOpenMidiMixer(); + } + } + } + + emit controllerDeviceEventReceived(e, m_lastActiveMainWindow); +} + +void +RosegardenGUIView::slotControllerDeviceEventReceived(MappedEvent *e, const void *preferredCustomer) +{ + if (preferredCustomer != this) + return ; + RG_DEBUG << "RosegardenGUIView::slotControllerDeviceEventReceived: this one's for me" << endl; + raise(); + + RG_DEBUG << "Event is type: " << int(e->getType()) << ", channel " << int(e->getRecordedChannel()) << ", data1 " << int(e->getData1()) << ", data2 " << int(e->getData2()) << endl; + + Composition &comp = getDocument()->getComposition(); + Studio &studio = getDocument()->getStudio(); + + TrackId currentTrackId = comp.getSelectedTrack(); + Track *track = comp.getTrackById(currentTrackId); + + // If the event is a control change on channel n, then (if + // follow-channel is on) switch to the nth track of the same type + // as the current track -- or the first track of the given + // channel?, and set the control appropriately. Any controls in + // IPB are supported for a MIDI device plus program and bank; only + // volume and pan are supported for audio/synth devices. + //!!! complete this + + if (e->getType() != MappedEvent::MidiController) { + + if (e->getType() == MappedEvent::MidiProgramChange) { + int program = e->getData1(); + if (!track) + return ; + InstrumentId ii = track->getInstrument(); + Instrument *instrument = studio.getInstrumentById(ii); + if (!instrument) + return ; + instrument->setProgramChange(program); + emit instrumentParametersChanged(ii); + } + return ; + } + + unsigned int channel = e->getRecordedChannel(); + MidiByte controller = e->getData1(); + MidiByte value = e->getData2(); + + if (controller == 82) { //!!! magic select-track controller + int tracks = comp.getNbTracks(); + Track *track = comp.getTrackByPosition(value * tracks / 127); + if (track) { + slotSelectTrackSegments(track->getId()); + } + return ; + } + + if (!track) + return ; + + InstrumentId ii = track->getInstrument(); + Instrument *instrument = studio.getInstrumentById(ii); + + if (!instrument) + return ; + + switch (instrument->getType()) { + + case Instrument::Midi: { + MidiDevice *md = dynamic_cast + (instrument->getDevice()); + if (!md) { + std::cerr << "WARNING: MIDI instrument has no MIDI device in slotControllerDeviceEventReceived" << std::endl; + return ; + } + + //!!! we need a central clearing house for these changes, + // for a proper mvc structure. reqd for automation post-1.2. + // in the mean time this duplicates much of + // MIDIInstrumentParameterPanel::slotControllerChanged etc + + switch (controller) { + + case MIDI_CONTROLLER_VOLUME: + RG_DEBUG << "Setting volume for instrument " << instrument->getId() << " to " << value << endl; + instrument->setVolume(value); + break; + + case MIDI_CONTROLLER_PAN: + RG_DEBUG << "Setting pan for instrument " << instrument->getId() << " to " << value << endl; + instrument->setPan(value); + break; + + default: { + ControlList cl = md->getIPBControlParameters(); + for (ControlList::const_iterator i = cl.begin(); + i != cl.end(); ++i) { + if ((*i).getControllerValue() == controller) { + RG_DEBUG << "Setting controller " << controller << " for instrument " << instrument->getId() << " to " << value << endl; + instrument->setControllerValue(controller, value); + break; + } + } + break; + } + } + + break; + } + + case Instrument::SoftSynth: + case Instrument::Audio: + + switch (controller) { + + case MIDI_CONTROLLER_VOLUME: + RG_DEBUG << "Setting volume for instrument " << instrument->getId() << " to " << value << endl; + instrument->setLevel(AudioLevel::fader_to_dB + (value, 127, AudioLevel::ShortFader)); + break; + + case MIDI_CONTROLLER_PAN: + RG_DEBUG << "Setting pan for instrument " << instrument->getId() << " to " << value << endl; + instrument->setPan(MidiByte((value / 64.0) * 100.0 + 0.01)); + break; + + default: + break; + } + + break; + } + + emit instrumentParametersChanged(instrument->getId()); + + //!!! send out updates via MIDI +} + +void +RosegardenGUIView::initChordNameRuler() +{ + getTrackEditor()->getChordNameRuler()->setReady(); +} + +EventView * +RosegardenGUIView::createEventView(std::vector segmentsToEdit) +{ + EventView *eventView = new EventView(getDocument(), + segmentsToEdit, + this); + + connect(eventView, SIGNAL(windowActivated()), + this, SLOT(slotActiveMainWindowChanged())); + + connect(eventView, SIGNAL(selectTrack(int)), + this, SLOT(slotSelectTrackSegments(int))); + + connect(eventView, SIGNAL(saveFile()), + RosegardenGUIApp::self(), SLOT(slotFileSave())); + + connect(eventView, SIGNAL(openInNotation(std::vector)), + this, SLOT(slotEditSegmentsNotation(std::vector))); + connect(eventView, SIGNAL(openInMatrix(std::vector)), + this, SLOT(slotEditSegmentsMatrix(std::vector))); + connect(eventView, SIGNAL(openInPercussionMatrix(std::vector)), + this, SLOT(slotEditSegmentsPercussionMatrix(std::vector))); + connect(eventView, SIGNAL(openInEventList(std::vector)), + this, SLOT(slotEditSegmentsEventList(std::vector))); + connect(eventView, SIGNAL(editTriggerSegment(int)), + this, SLOT(slotEditTriggerSegment(int))); + connect(this, SIGNAL(compositionStateUpdate()), + eventView, SLOT(slotCompositionStateUpdate())); + connect(RosegardenGUIApp::self(), SIGNAL(compositionStateUpdate()), + eventView, SLOT(slotCompositionStateUpdate())); + connect(eventView, SIGNAL(toggleSolo(bool)), + RosegardenGUIApp::self(), SLOT(slotToggleSolo(bool))); + + // create keyboard accelerators on view + // + RosegardenGUIApp *par = dynamic_cast(parent()); + + if (par) { + par->plugAccelerators(eventView, eventView->getAccelerators()); + } + + return eventView; +} + +} +#include "RosegardenGUIView.moc" diff --git a/src/gui/application/RosegardenGUIView.h b/src/gui/application/RosegardenGUIView.h new file mode 100644 index 0000000..b3727f3 --- /dev/null +++ b/src/gui/application/RosegardenGUIView.h @@ -0,0 +1,347 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ROSEGARDENGUIVIEW_H_ +#define _RG_ROSEGARDENGUIVIEW_H_ + +#include "base/Event.h" +#include "base/MidiProgram.h" +#include "base/Selection.h" +#include "base/Track.h" +#include "sound/AudioFile.h" +#include "gui/editors/segment/TrackEditor.h" +#include +#include + + +class QWidget; +class QObject; +class LevelInfo; +class KCommand; + + +namespace Rosegarden +{ + +class TrackParameterBox; +class TrackEditor; +class SimpleRulerScale; +class SequencerMapper; +class SegmentParameterBox; +class Segment; +class RosegardenGUIDoc; +class RealTime; +class NotationView; +class MultiViewCommandHistory; +class MatrixView; +class MappedEvent; +class InstrumentParameterBox; +class EventView; +class Composition; +class LevelInfo; + +/** + * The RosegardenGUIView class provides the view widget for the + * RosegardenGUIApp instance. The View instance inherits QWidget as a + * base class and represents the view object of a KTMainWindow. As + * RosegardenGUIView is part of the docuement-view model, it needs a + * reference to the document object connected with it by the + * RosegardenGUIApp class to manipulate and display the document + * structure provided by the RosegardenGUIDoc class. + * + * @author Source Framework Automatically Generated by KDevelop, (c) The KDevelop Team. + * @version KDevelop version 0.4 code generation + */ +class RosegardenGUIView : public QVBox +{ + Q_OBJECT +public: + + /**p + * Constructor for the main view + */ + RosegardenGUIView(bool showTrackLabels, + SegmentParameterBox*, + InstrumentParameterBox*, + TrackParameterBox*, + QWidget *parent = 0, + const char *name=0); + + /** + * Destructor for the main view + */ + ~RosegardenGUIView(); + + /** + * returns a pointer to the document connected to the view + * instance. Mind that this method requires a RosegardenGUIApp + * instance as a parent widget to get to the window document + * pointer by calling the RosegardenGUIApp::getDocument() method. + * + * @see RosegardenGUIApp#getDocument + */ + RosegardenGUIDoc* getDocument() const; + + /** + * Command history + */ + MultiViewCommandHistory* getCommandHistory(); + + TrackEditor* getTrackEditor() { return m_trackEditor; } + + /** + * contains the implementation for printing functionality + */ + void print(Composition*, bool previewOnly = false); + + // the following aren't slots because they're called from + // RosegardenGUIApp + + /** + * Select a tool at the SegmentCanvas + */ + void selectTool(QString toolName); + + /** + * Show output levels + */ + void showVisuals(const MappedEvent *mE); + + void updateMeters(SequencerMapper *mapper); + void updateMonitorMeters(SequencerMapper *mapper); + + /** + * Change zoom size -- set the RulerScale's units-per-pixel to size + */ + void setZoomSize(double size); + + void initChordNameRuler(); + + bool haveSelection(); + SegmentSelection getSelection(); + void updateSelectionContents(); + + static bool isMainWindowLastActive(const QWidget *w) { + return w == m_lastActiveMainWindow; + } + +public slots: + void slotEditSegment(Segment*); + void slotEditSegmentNotation(Segment*); + void slotEditSegmentsNotation(std::vector); + void slotEditSegmentMatrix(Segment*); + void slotEditSegmentsMatrix(std::vector); + void slotEditSegmentPercussionMatrix(Segment*); + void slotEditSegmentsPercussionMatrix(std::vector); + void slotEditSegmentEventList(Segment*); + void slotEditSegmentsEventList(std::vector); + void slotEditTriggerSegment(int); + void slotEditSegmentAudio(Segment*); + void slotSegmentAutoSplit(Segment*); + void slotEditRepeat(Segment*, timeT); +/* hjj: WHAT DO DO WITH THIS ? + void slotEditMetadata(QString); +*/ + + /** + * Highlight all the Segments on a Track because the Track has + * been selected * We have to ensure we create a Selector object + * before we can highlight * these tracks. + * + * Called by signal from Track selection routine to highlight + * all available Segments on a Track + */ + void slotSelectTrackSegments(int); + + void slotSelectAllSegments(); + + void slotUpdateInstrumentParameterBox(int id); + + // This is called from the canvas (actually the selector tool) moving out + // + void slotSelectedSegments(const SegmentSelection &segments); + + // And this one from the user interface going down + // + void slotPropagateSegmentSelection(const SegmentSelection &segments); + + void slotShowRulers(bool); + + void slotShowTempoRuler(bool); + + void slotShowChordNameRuler(bool); + + void slotShowPreviews(bool); + + void slotShowSegmentLabels(bool); + + void slotAddTracks(unsigned int count, InstrumentId instrument, int position); + + void slotDeleteTracks(std::vector tracks); + + void slotAddAudioSegmentCurrentPosition(AudioFileId, + const RealTime &startTime, + const RealTime &endTime); + + void slotAddAudioSegmentDefaultPosition(AudioFileId, + const RealTime &startTime, + const RealTime &endTime); + + void slotAddAudioSegment(AudioFileId audioId, + TrackId trackId, + timeT position, + const RealTime &startTime, + const RealTime &endTime); + + void slotDroppedAudio(QString audioDesc); + void slotDroppedNewAudio(QString audioDesc); + + /* + * Commands + * + */ + void slotAddCommandToHistory(KCommand *command); + + /* + * Change the Instrument Label + */ + void slotChangeInstrumentLabel(InstrumentId id, QString label); + + /* + * Change the Track Label + */ + void slotChangeTrackLabel(TrackId id, QString label); + + /* + * Set the mute button on the track buttons and on the instrument + * parameter box + */ + void slotSetMuteButton(TrackId track, bool value); + + /* + * Set mute, record and solo by instrument id + */ + void slotSetMute(InstrumentId, bool); + void slotSetRecord(InstrumentId, bool); + void slotSetSolo(InstrumentId, bool); + + /** + * To indicate that we should track the recording segment (despite + * no commands being issued on it) + */ + void slotUpdateRecordingSegment(Segment *segment, + timeT updatedFrom); + + /** + * A manual fudgy way of creating a view update for certain + * semi-static data (devices/instrument labels mainly) + */ + void slotSynchroniseWithComposition(); + + /** + * To indicate that an edit view, mixer, etc (something that might + * want to receive MIDI input) has become active. We only send + * inputs such as MIDI to a single one of these, in most cases, + * and it's whichever was most recently made active. (It doesn't + * have to still _be_ active -- we want to allow moving focus to + * another application entirely but still receiving MIDI etc in + * Rosegarden.) + */ + void slotActiveMainWindowChanged(const QWidget *); + void slotActiveMainWindowChanged(); // uses sender() + + /** + * An event has been received from a device connected to the + * external controller port. + */ + void slotControllerDeviceEventReceived(MappedEvent *); + void slotControllerDeviceEventReceived(MappedEvent *, const void *); + +signals: + void activateTool(QString toolName); + + void stateChange(QString, bool); + + // Inform that we've got a SegmentSelection + // + void segmentsSelected(const SegmentSelection&); + + void toggleSolo(bool); + + /** + * Current used to dispatch things like track select changes, solo, etc... + * to edit views + */ + void compositionStateUpdate(); + + + /** + * This signal is used to dispatch a notification for a request to + * set the step-by-step-editing target window to all candidate targets, + * so that they can either know that their request has been granted + * (if they match the QObject passed) or else deactivate any step-by- + * step editing currently active in their own window (otherwise). + */ + void stepByStepTargetRequested(QObject *); + + /* + * Add an audio file at the sequencer - when we drop a new file + * on the segment canvas. + */ + void addAudioFile(AudioFileId); + + void checkTrackAssignments(); + + void instrumentLevelsChanged(InstrumentId, + const LevelInfo &); + + void controllerDeviceEventReceived(MappedEvent *, + const void *); + + void instrumentParametersChanged(InstrumentId); + +protected: + NotationView *createNotationView(std::vector); + MatrixView *createMatrixView (std::vector, bool drumMode); + EventView *createEventView (std::vector); + + virtual void windowActivationChange(bool); + + //--------------- Data members --------------------------------- + + SimpleRulerScale *m_rulerScale; + TrackEditor *m_trackEditor; + + SegmentParameterBox *m_segmentParameterBox; + InstrumentParameterBox *m_instrumentParameterBox; + TrackParameterBox *m_trackParameterBox; + + static const QWidget *m_lastActiveMainWindow; +}; + + +} + +#endif diff --git a/src/gui/application/RosegardenIface.cpp b/src/gui/application/RosegardenIface.cpp new file mode 100644 index 0000000..7e07f14 --- /dev/null +++ b/src/gui/application/RosegardenIface.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Parts of this file are from KDE Konqueror : KonqMainWindowIface + Copyright (C) 2000 Simon Hausmann + Copyright (C) 2000 David Faure + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RosegardenIface.h" + +#include "sound/MappedComposition.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +RosegardenIface::RosegardenIface(KMainWindow* mainWindow) + : DCOPObject(mainWindow->name()), + m_dcopActionProxy(0) +{} + +void RosegardenIface::iFaceDelayedInit(KMainWindow* mainWindow) +{ + m_dcopActionProxy = new KDCOPActionProxy(mainWindow->actionCollection(), + this); +} + +DCOPRef RosegardenIface::action(const QCString &name) +{ + return DCOPRef(kapp->dcopClient()->appId(), + m_dcopActionProxy->actionObjectId(name)); +} + +QCStringList RosegardenIface::actions() +{ + QCStringList res; + QValueList lst = m_dcopActionProxy->actions(); + QValueList::ConstIterator it = lst.begin(); + QValueList::ConstIterator end = lst.end(); + for (; it != end; ++it ) + res.append( (*it)->name() ); + + return res; +} + +QMap RosegardenIface::actionMap() +{ + return m_dcopActionProxy->actionMap(); +} + +} diff --git a/src/gui/application/RosegardenIface.h b/src/gui/application/RosegardenIface.h new file mode 100644 index 0000000..baa8b4e --- /dev/null +++ b/src/gui/application/RosegardenIface.h @@ -0,0 +1,130 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Parts of this file are from KDE Konqueror : KonqMainWindowIface + Copyright (C) 2000 Simon Hausmann + Copyright (C) 2000 David Faure + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ROSEGARDENIFACE_H_ +#define _RG_ROSEGARDENIFACE_H_ + +#include +#include +#include +#include +#include + +#include "base/Instrument.h" +#include "sound/MappedComposition.h" + +class QCString; +class KMainWindow; +class KDCOPActionProxy; + + +namespace Rosegarden +{ + + +/** + * RosegardenGUI DCOP Interface + */ +class RosegardenIface : virtual public DCOPObject +{ + K_DCOP + +public: + RosegardenIface(KMainWindow*); + void iFaceDelayedInit(KMainWindow*); + +k_dcop: + virtual void openFile(QString file) = 0; + virtual void openURL(QString url) = 0; + virtual void mergeFile(QString file) = 0; + virtual void fileNew() = 0; + virtual void fileSave() = 0; + virtual void fileClose() = 0; + virtual void quit() = 0; + + virtual void play() = 0; + virtual void stop() = 0; + virtual void rewind() = 0; + virtual void fastForward() = 0; + virtual void record() = 0; + virtual void rewindToBeginning() = 0; + virtual void fastForwardToEnd() = 0; + virtual void jumpToTime(int sec, int usec) = 0; + virtual void startAtTime(int sec, int usec) = 0; + + // Extra functions used by Infrared Remotes + virtual void trackDown() = 0; + virtual void trackUp() = 0; + virtual void toggleMutedCurrentTrack() = 0; + virtual void toggleRecordCurrentTrack() = 0; + + // Sequencer updates GUI with status + // + virtual void notifySequencerStatus(int status) = 0; + + // Used to map unexpected (async) MIDI events to the user interface. + // We can show these on the Transport or on a MIDI Mixer. + // + virtual void processAsynchronousMidi(const MappedComposition &mC) = 0; + + // The sequencer tries to call this action until it can - then + // we can go on and retrive device information + // + virtual void alive() = 0; + + // The sequencer requests that a new audio file is created - the + // gui does so and returns the path of the new file so that the + // sequencer can use it. + // + virtual QString createNewAudioFile() = 0; + virtual QValueVector createRecordAudioFiles + (const QValueVector &recordInstruments) = 0; + virtual QString getAudioFilePath() = 0; + + virtual QValueVector getArmedInstruments() = 0; + + virtual void showError(QString error) = 0; + + // Actions proxy + // + DCOPRef action( const QCString &name ); + QCStringList actions(); + QMap actionMap(); + +protected: + //--------------- Data members --------------------------------- + + KDCOPActionProxy *m_dcopActionProxy; + +}; + + +} + +#endif diff --git a/src/gui/application/SetWaitCursor.cpp b/src/gui/application/SetWaitCursor.cpp new file mode 100644 index 0000000..7fc0053 --- /dev/null +++ b/src/gui/application/SetWaitCursor.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SetWaitCursor.h" + +#include "gui/editors/segment/TrackEditor.h" +#include "gui/editors/segment/segmentcanvas/CompositionView.h" +#include "misc/Debug.h" +#include "RosegardenGUIApp.h" +#include "RosegardenGUIView.h" +#include +#include +#include +#include + +namespace Rosegarden +{ + +SetWaitCursor::SetWaitCursor() + : m_guiApp(dynamic_cast(kapp->mainWidget())) +{ + if (m_guiApp) { + + // play it safe, so we can use this class at anytime even very early in the app init + if ((m_guiApp->getView() && + m_guiApp->getView()->getTrackEditor() && + m_guiApp->getView()->getTrackEditor()->getSegmentCanvas() && + m_guiApp->getView()->getTrackEditor()->getSegmentCanvas()->viewport())) { + + m_saveSegmentCanvasCursor = m_guiApp->getView()->getTrackEditor()->getSegmentCanvas()->viewport()->cursor(); + + } + + RG_DEBUG << "SetWaitCursor::SetWaitCursor() : setting waitCursor\n"; + m_saveCursor = m_guiApp->cursor(); + + m_guiApp->setCursor(KCursor::waitCursor()); + } +} + +SetWaitCursor::~SetWaitCursor() +{ + if (m_guiApp) { + + RG_DEBUG << "SetWaitCursor::SetWaitCursor() : restoring normal cursor\n"; + QWidget* viewport = 0; + QCursor currentSegmentCanvasCursor; + + if ((m_guiApp->getView() && + m_guiApp->getView()->getTrackEditor() && + m_guiApp->getView()->getTrackEditor()->getSegmentCanvas() && + m_guiApp->getView()->getTrackEditor()->getSegmentCanvas()->viewport())) { + viewport = m_guiApp->getView()->getTrackEditor()->getSegmentCanvas()->viewport(); + currentSegmentCanvasCursor = viewport->cursor(); + } + + m_guiApp->setCursor(m_saveCursor); + + if (viewport) { + if (currentSegmentCanvasCursor.shape() == KCursor::waitCursor().shape()) { + viewport->setCursor(m_saveSegmentCanvasCursor); + } else { + viewport->setCursor(currentSegmentCanvasCursor); // because m_guiApp->setCursor() has replaced it + } + } + + // otherwise, it's been modified elsewhere, so leave it as is + + } + +} + +} diff --git a/src/gui/application/SetWaitCursor.h b/src/gui/application/SetWaitCursor.h new file mode 100644 index 0000000..38687c5 --- /dev/null +++ b/src/gui/application/SetWaitCursor.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SETWAITCURSOR_H_ +#define _RG_SETWAITCURSOR_H_ + +#include + + + + +namespace Rosegarden +{ + +class RosegardenGUIApp; + + +/** + * Temporarily change the global cursor to waitCursor + */ +class SetWaitCursor +{ +public: + SetWaitCursor(); + ~SetWaitCursor(); + +protected: + RosegardenGUIApp* m_guiApp; + QCursor m_saveCursor; + QCursor m_saveSegmentCanvasCursor; +}; + + +} + +#endif diff --git a/src/gui/application/StartupTester.cpp b/src/gui/application/StartupTester.cpp new file mode 100644 index 0000000..15940b6 --- /dev/null +++ b/src/gui/application/StartupTester.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "StartupTester.h" + +#include "misc/Debug.h" +#include "gui/dialogs/LilyPondOptionsDialog.h" + +#include +#include +#include +#include + + +namespace Rosegarden +{ + +StartupTester::StartupTester() : + m_ready(false), + m_haveProjectPackager(false), + m_haveLilyPondView(false), + m_haveAudioFileImporter(false) +{ + QHttp *http = new QHttp(); + connect(http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)), + this, SLOT(slotHttpResponseHeaderReceived(const QHttpResponseHeader &))); + connect(http, SIGNAL(done(bool)), + this, SLOT(slotHttpDone(bool))); + m_versionHttpFailed = false; + http->setHost("www.rosegardenmusic.com"); + http->get("/latest-version.txt"); +} + +StartupTester::~StartupTester() +{ +} + +void +StartupTester::run() +{ + m_projectPackagerMutex.lock(); + m_lilyPondViewMutex.lock(); + m_audioFileImporterMutex.lock(); + m_ready = true; + + KProcess *proc = new KProcess(); + m_stdoutBuffer = ""; + QObject::connect(proc, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(stdoutReceived(KProcess *, char *, int))); + *proc << "rosegarden-audiofile-importer"; + *proc << "--conftest"; + proc->start(KProcess::Block, KProcess::All); + if (!proc->normalExit() || proc->exitStatus()) { + RG_DEBUG << "StartupTester - No audio file importer available" << endl; + m_haveAudioFileImporter = false; + parseStdoutBuffer(m_audioFileImporterMissing); + } else { + RG_DEBUG << "StartupTester - Audio file importer OK" << endl; + m_haveAudioFileImporter = true; + } + delete proc; + m_audioFileImporterMutex.unlock(); + + proc = new KProcess; + m_stdoutBuffer = ""; + QObject::connect(proc, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(stdoutReceived(KProcess *, char *, int))); + *proc << "rosegarden-project-package"; + *proc << "--conftest"; + proc->start(KProcess::Block, KProcess::All); + if (!proc->normalExit() || proc->exitStatus()) { + m_haveProjectPackager = false; + // rosegarden-project-package ran but exited with an error code + RG_DEBUG << "StartupTester - No project packager available" << endl; + m_haveProjectPackager = false; + parseStdoutBuffer(m_projectPackagerMissing); + } else { + RG_DEBUG << "StartupTester - Project packager OK" << endl; + m_haveProjectPackager = true; + } + delete proc; + m_projectPackagerMutex.unlock(); + + proc = new KProcess(); + m_stdoutBuffer = ""; + QObject::connect(proc, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(stdoutReceived(KProcess *, char *, int))); + *proc << "rosegarden-lilypondview"; + *proc << "--conftest"; + proc->start(KProcess::Block, KProcess::All); + if (!proc->normalExit() || proc->exitStatus()) { + RG_DEBUG << "StartupTester - No lilypondview available" << endl; + m_haveLilyPondView = false; + parseStdoutBuffer(m_lilyPondViewMissing); + } else { + RG_DEBUG << "StartupTester - lilypondview OK" << endl; + m_haveLilyPondView = true; + QRegExp re("LilyPond version: ([^\n]*)"); + if (re.search(m_stdoutBuffer) != -1) { + LilyPondOptionsDialog::setDefaultLilyPondVersion(re.cap(1)); + } + } + delete proc; + m_lilyPondViewMutex.unlock(); +} + +bool +StartupTester::isReady() +{ + while (!m_ready) + usleep(10000); + if (m_projectPackagerMutex.tryLock()) { + m_projectPackagerMutex.unlock(); + } else { + return false; + } + if (m_lilyPondViewMutex.tryLock()) { + m_lilyPondViewMutex.unlock(); + } else { + return false; + } + return true; +} + +void +StartupTester::stdoutReceived(KProcess *, char *buffer, int len) +{ + m_stdoutBuffer += QString::fromLatin1(buffer, len); +} + +void +StartupTester::parseStdoutBuffer(QStringList &target) +{ + QRegExp re("Required: ([^\n]*)"); + if (re.search(m_stdoutBuffer) != -1) { + target = QStringList::split(", ", re.cap(1)); + } +} + +bool +StartupTester::haveProjectPackager(QStringList *missing) +{ + while (!m_ready) + usleep(10000); + QMutexLocker locker(&m_projectPackagerMutex); + if (missing) *missing = m_projectPackagerMissing; + return m_haveProjectPackager; +} + +bool +StartupTester::haveLilyPondView(QStringList *missing) +{ + while (!m_ready) + usleep(10000); + QMutexLocker locker(&m_lilyPondViewMutex); + if (missing) *missing = m_lilyPondViewMissing; + return m_haveLilyPondView; +} + +bool +StartupTester::haveAudioFileImporter(QStringList *missing) +{ + while (!m_ready) + usleep(10000); + QMutexLocker locker(&m_audioFileImporterMutex); + if (missing) *missing = m_audioFileImporterMissing; + return m_haveAudioFileImporter; +} + +bool +StartupTester::isVersionNewerThan(QString a, QString b) +{ + QRegExp re("[._-]"); + QStringList alist = QStringList::split(re, a); + QStringList blist = QStringList::split(re, b); + int ae = alist.size(); + int be = blist.size(); + int e = std::max(ae, be); + for (int i = 0; i < e; ++i) { + int an = 0, bn = 0; + if (i < ae) { + an = alist[i].toInt(); + if (an == 0) an = -1; // non-numeric field -> "-pre1" etc + } + if (i < be) { + bn = blist[i].toInt(); + if (bn == 0) bn = -1; + } + if (an < bn) return false; + if (an > bn) return true; + } + return false; +} + +void +StartupTester::slotHttpResponseHeaderReceived(const QHttpResponseHeader &h) +{ + if (h.statusCode() / 100 != 2) m_versionHttpFailed = true; +} + +void +StartupTester::slotHttpDone(bool error) +{ + QHttp *http = const_cast(dynamic_cast(sender())); + if (!http) return; + http->deleteLater(); + if (error) return; + + QByteArray responseData = http->readAll(); + QString str = QString::fromUtf8(responseData.data()); + QStringList lines = QStringList::split('\n', str); + if (lines.empty()) return; + + QString latestVersion = lines[0]; + std::cerr << "Comparing current version \"" << VERSION + << "\" with latest version \"" << latestVersion << "\"" + << std::endl; + if (isVersionNewerThan(latestVersion, VERSION)) { + emit newerVersionAvailable(latestVersion); + } +} + +} + +#include "StartupTester.moc" + diff --git a/src/gui/application/StartupTester.h b/src/gui/application/StartupTester.h new file mode 100644 index 0000000..9c82e07 --- /dev/null +++ b/src/gui/application/StartupTester.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_STARTUPTESTER_H_ +#define _RG_STARTUPTESTER_H_ + +#include +#include +#include +#include +#include + +class KProcess; + +namespace Rosegarden +{ + +class StartupTester : public QObject, public QThread +{ + Q_OBJECT + +public: + StartupTester(); + virtual ~StartupTester(); + + virtual void run(); + + bool isReady(); + + // If you call one of these methods before the startup test has + // completed in the background, then it will block. + + bool haveProjectPackager(QStringList *missingApplications); + bool haveLilyPondView(QStringList *missingApplications); + bool haveAudioFileImporter(QStringList *missingApplications); + +signals: + void newerVersionAvailable(QString); + +protected slots: + void stdoutReceived(KProcess *, char *, int); + + void slotHttpResponseHeaderReceived(const QHttpResponseHeader &); + void slotHttpDone(bool); + +protected: + bool m_ready; + QMutex m_projectPackagerMutex; + QMutex m_lilyPondViewMutex; + QMutex m_audioFileImporterMutex; + bool m_haveProjectPackager; + QStringList m_projectPackagerMissing; + bool m_haveLilyPondView; + QStringList m_lilyPondViewMissing; + bool m_haveAudioFileImporter; + QStringList m_audioFileImporterMissing; + QString m_stdoutBuffer; + bool m_versionHttpFailed; + void parseStdoutBuffer(QStringList &target); + bool isVersionNewerThan(QString, QString); +}; + + +} + +#endif diff --git a/src/gui/application/main.cpp b/src/gui/application/main.cpp new file mode 100644 index 0000000..d7b5779 --- /dev/null +++ b/src/gui/application/main.cpp @@ -0,0 +1,741 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 +#include +#include +#include "base/RealTime.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "document/ConfigGroups.h" +#include "misc/Strings.h" +#include "misc/Debug.h" +#include "gui/application/RosegardenGUIApp.h" +#include "gui/widgets/CurrentProgressDialog.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/kdeext/KStartupLogo.h" + +#include "gui/application/RosegardenApplication.h" +#include "gui/application/RosegardenDCOP.h" + +#include "gui/kdeext/klearlook.h" + +using namespace Rosegarden; + +/*! \mainpage Rosegarden global design + +Rosegarden is split into 3 main parts: + +\section base Base + +The base library holds all of the fundamental "music handling" +structures, of which the primary ones are Event, Segment, Track, +Instrument and Composition. It also contains a selection of utility +and helper classes of a kind that is not specific to any particular +GUI. Everything here is part of the Rosegarden namespace, and there +are no dependencies on KDE or Qt (although it uses the STL heavily). + +The keyword for the basic structures in use is "flexibility". Our +Event objects can be extended arbitrarily for the convenience of GUI +or performance code without having to change their declaration or +modify anything in the base library. And most of our assumptions +about the use of the container classes can be violated without +disastrous side-effects. + +\subsection musicstructs Music Structures + + - \link Event Event\endlink is the basic musical element. It's more or less a + generalization of the MIDI event. Each note or rest, each key + change or tempo change, is an event: there's no "note class" or + "rest class" as such, they are simply represented by events whose + type happens to be "note" or "rest". + Each Event has a type code, absolute time (the moment at which the + Event starts, relative only to the start of the Composition) and + duration (usually non-zero only for notes and rests), together + with an arbitrary set of named and typed properties that can be + assigned and queried dynamically by other parts of the + application. So, for example, a note event is likely to have an + integer property called "pitch", and probably a "velocity", as + well as potentially many others -- but this is not fixed anywhere, + and there's no definition of what exactly a note is: client code + is simply expected to ignore any unrecognised events or properties + and to cope if properties that should be there are not. + + - \link Segment Segment\endlink is a series of consecutive Events found on the same Track, + automatically ordered by their absolute time. It's the usual + container for Events. A Segment has a starting time that can be + changed, and a duration that is based solely on the end time of + the last Event it contains. Note that in order to facilitate + musical notation editing, we explicitly store silences as series + of rest Events; thus a Segment really should contain no gaps + between its Events. (This isn't checked anywhere and nothing will + break very badly if there are gaps, but notation won't quite work + correctly.) + + - \link Track Track \endlink is much the same thing as on a mixing table, usually + assigned to an instrument, a voice, etc. Although a Track is not + a container of Events and is not strictly a container of Segments + either, it is referred to by a set of Segments that are therefore + mutually associated with the same instruments and parameters. In + GUI terms, the Track is a horizontal row on the main Rosegarden + window, whereas a Segment is a single blue box within that row, of + which there may be any number. + + - \link Instrument Instrument \endlink corresponds broadly to a MIDI or Audio channel, and is + the destination for a performed Event. Each Track is mapped to a + single Instrument (although many Tracks may have the same + Instrument), and the Instrument is indicated in the header at the + left of the Track's row in the GUI. + + - \link Composition Composition\endlink is the container for the entire piece of music. It + consists of a set of Segments, together with a set of Tracks that + the Segments may or may not be associated with, a set of + Instruments, and some information about time signature and tempo + changes. (The latter are not stored in Segments; they are only + stored in the top-level Composition. You can't have differing + time signatures or tempos in different Segments.) Any code that + wants to know about the locations of bar lines, or request + real-time calculations based on tempo changes, talks to the + Composition. + + +See also docs/data_struct/units.txt for an explanation of the units we +use for time and pitch values. See docs/discussion/names.txt for some +name-related discussion. See docs/code/creating_events.txt for an +explanation of how to create new Events and add properties to them. + +The base directory also contains various music-related helper classes: + + - The NotationTypes.[Ch] files contain classes that help with + creating and manipulating events. It's very important to realise + that these classes are not the events themselves: although there + is a Note class in this file, and a TimeSignature class, and Clef + and Key classes, instances of these are rarely stored anywhere. + Instead they're created on-the-fly in order to do calculation + related to note durations or time signatures or whatever, and they + contain getAsEvent() methods that may be used when an event for + storage is required. But the class of a stored event is always + simply Event. + + The NotationTypes classes also define important constants for the + names of common properties in Events. For example, the Note class + contains Note::EventType, which is the type of a note Event, and + Note::EventRestType, the type of a rest Event; and Key contains + Key::EventType, the type of a key change Event, KeyPropertyName, + the name of the property that defines the key change, and a set + of the valid strings for key changes. + + - BaseProperties.[Ch] contains a set of "standard"-ish Event + property names that are not basic enough to go in NotationTypes. + + - \link SegmentNotationHelper SegmentNotationHelper\endlink + and \link SegmentPerformanceHelper SegmentPerformanceHelper\endlink + do tasks that + may be useful to notation-type code and performer code + respectively. For example, SegmentNotationHelper is used to + manage rests when inserting and deleting notes in a score editor, + and to create beamed groups and suchlike; SegmentPerformanceHelper + generally does calculations involving real performance time of + notes (taking into account tied notes, tuplets and tempo changes). + These two lightweight helper classes are also usually constructed + on-the-fly for use on the events in a given Segment and then + discarded after use. + + - \link Quantizer Quantizer\endlink is used to quantize event timings and set quantized + timing properties on those events. Note that quantization is + non-destructive, as it takes advantage of the ability to set new + Event properties to simply assign the quantized values as separate + properties from the original absolute time and duration. + + +\section gui GUI + +The GUI directory builds into a KDE/Qt application. Like most KDE +applications, it follows a document/view model. The document (class +RosegardenGUIDoc, which wraps a Composition) can have several views +(class RosegardenGUIView), although at the moment only a single one is +used. + +This view is the TrackEditor, which shows all the Composition's +Segments organized in Tracks. Each Segment can be edited in two ways: +notation (score) or matrix (piano roll). + +All editor views are derived from EditView. An EditView is the class +dealing with the edition per se of the events. It uses several +components: + + - Layout classes, horizontal and vertical: these are the classes + which determine the x and y coordinates of the graphic items + representing the events (notes or piano-roll rectangles). They + are derived from the LayoutEngine base-class in the base library. + + - Tools, which implement each editing function at the GUI (such as + insert, erase, cut and paste). These are the tools which appear on + the EditView's toolbar. + + - Toolbox, which is a simple string => tool map. + + - Commands, which are the fundamental implementations of editing + operations (both menu functions and tool operations) subclassed + from KDE's Command and used for undo and redo. + + - a canvas view. Although this isn't a part of the EditView's + definition, both of the existing edit views (notation and matrix) + use one, because they both use a QCanvas to represent data. + + - LinedStaff, a staff with lines. Like the canvas view, this isn't + part of the EditView definition, but both views use one. + + +There are currently two editor views: + + - NotationView, with accompanying classes NotationHLayout, + NotationVLayout, NotationStaff, and all the classes in the + notationtool and notationcommands files. These are also closely + associated with the NotePixmapFactory and NoteFont classes, which + are used to generate notes from component pixmap files. + + - MatrixView, with accompanying classes MatrixHLayout, + MatrixVLayout, MatrixStaff and other classes in the matrixview + files. + +The editing process works as follows: + +[NOTE : in the following, we're talking both about events as UI events +or user events (mouse button clicks, mouse move, keystrokes, etc...) +and Events (our basic music element). To help lift the ambiguity, +"events" is for UI events, Events is for Event.] + + -# The canvas view gets the user events (see + NotationCanvasView::contentsMousePressEvent(QMouseEvent*) for an + example). It locates where the event occured in terms of musical + element: which note or staff line the user clicked on, which pitch + and time this corresponds to, that kind of stuff. (In the + Notation and Matrix views, the LinedStaff calculates mappings + between coordinates and staff lines: the former is especially + complicated because of its support for page layout.)\n + -# The canvas view transmits this kind of info as a signal, which is + connected to a slot in the parent EditView. + -# The EditView delegates action to the current tool.\n + -# The tool performs the actual job (inserting or deleting a note, + etc...). + +Since this action is usually complex (merely inserting a note requires +dealing with the surrounding Events, rests or notes), it does it +through a SegmentHelper (for instance, base/SegmentNotationHelper) +which "wraps" the complexity into simple calls and performs all the +hidden tasks. + +The EditView also maintains (obviously) its visual appearance with the +layout classes, applying them when appropriate. + +\section sequencer Sequencer + +The sequencer directory also builds into a KDE/Qt application, but one +which doesn't have a gui. The Sequencer can be started automatically +by the main Rosegarden GUI or manually if testing - it's sometimes +more convenient to do the latter as the Sequencer needs to be connected +up to the underlying sound system every time it is started. + +The Sequencer interfaces directly with \link AlsaDriver ALSA\endlink +and provides MIDI "play" and "record" ports which can be connected to +other MIDI clients (MIDI IN and OUT hardware ports or ALSA synth devices) +using any ALSA MIDI Connection Manager. The Sequencer also supports +playing and recording of Audio sample files using \link JackDriver Jack\endlink + +The GUI and Sequencer communicate using the KDE DCOP communication framework. +Look in: + - \link rosegardenguiiface.h gui/rosegardenguiiface.h\endlink + - \link rosegardensequenceriface.h sequencer/rosegardensequenceriface.h\endlink + +for definitions of the DCOP interfaces pertinent to the Sequencer +and GUI. The main DCOP operations from the GUI involve starting and +stopping the Sequencer, playing and recording, fast forwarding and +rewinding. Once a play or record cycle is enabled it's the Sequencer +that does most of the hard work. Events are read from (or written to, when recording) +a set of mmapped files. + +The Sequencer makes use of two libraries libRosegardenSequencer +and libRosegardenSound: + + - libRosegardenSequencer holds everything pertinent to sequencing + for Rosegarden including the + Sequencer class itself. This library is only linked into the + Rosegarden Sequencer. + + - libRosegardenSound holds the MidiFile class (writing and reading + MIDI files) and the MappedEvent and MappedComposition classes (the + communication class for transferring events back and forth across + DCOP). This library is needed by the GUI as well as the Sequencer. + +The main Sequencer state machine is a good starting point and clearly +visible at the bottom of rosegarden/sequencer/main.cpp. + + +*/ + +static const char *description = + I18N_NOOP("Rosegarden - A sequencer and musical notation editor"); + +static KCmdLineOptions options[] = + { + { "nosequencer", I18N_NOOP("Don't use the sequencer (support editing only)"), 0 }, + { "nosplash", I18N_NOOP("Don't show the splash screen"), 0 }, + { "nofork", I18N_NOOP("Don't automatically run in the background"), 0 }, + { "existingsequencer", I18N_NOOP("Attach to a running sequencer process, if found"), 0 }, + { "ignoreversion", I18N_NOOP("Ignore installed version - for devs only"), 0 }, + { "+[File]", I18N_NOOP("file to open"), 0 }, + { 0, 0, 0 } + }; + + +// ----------------------------------------------------------------- + +#ifdef Q_WS_X11 +#include +#include +#include +#include + +static int _x_errhandler( Display *dpy, XErrorEvent *err ) +{ + char errstr[256]; + XGetErrorText( dpy, err->error_code, errstr, 256 ); + if ( err->error_code != BadWindow ) + kdWarning() << "Rosegarden: detected X Error: " << errstr << " " << err->error_code + << "\n Major opcode: " << err->request_code << endl; + return 0; +} +#endif + +// NOTE: to get a dump of the stack trace from KDE during program execution: +// std::cerr << kdBacktrace() << std::endl +// (see kdebug.h) + +void testInstalledVersion() +{ + QString versionLocation = locate("appdata", "version.txt"); + QString installedVersion; + + if (versionLocation) { + QFile versionFile(versionLocation); + if (versionFile.open(IO_ReadOnly)) { + QTextStream text(&versionFile); + QString s = text.readLine().stripWhiteSpace(); + versionFile.close(); + if (s) { + if (s == VERSION) return; + installedVersion = s; + } + } + } + + if (installedVersion) { + + KMessageBox::detailedError + (0, + i18n("Installation contains the wrong version of Rosegarden."), + i18n(" The wrong versions of Rosegarden's data files were\n" + " found in the standard KDE installation directories.\n" + " (I am %1, but the installed files are for version %2.)\n\n" + " This may mean one of the following:\n\n" + " 1. This is a new upgrade of Rosegarden, and it has not yet been\n" + " installed. If you compiled it yourself, check that you have\n" + " run \"make install\" and that the procedure completed\n" + " successfully.\n\n" + " 2. The upgrade was installed in a non-standard directory,\n" + " and an old version was found in a standard directory. If so,\n" + " you will need to add the correct directory to your KDEDIRS\n" + " environment variable before you can run it.").arg(VERSION).arg(installedVersion), + i18n("Installation problem")); + + } else { + + KMessageBox::detailedError + (0, + i18n("Rosegarden does not appear to have been installed."), + i18n(" One or more of Rosegarden's data files could not be\n" + " found in the standard KDE installation directories.\n\n" + " This may mean one of the following:\n\n" + " 1. Rosegarden has not been correctly installed. If you compiled\n" + " it yourself, check that you have run \"make install\" and that\n" + " the procedure completed successfully.\n\n" + " 2. Rosegarden has been installed in a non-standard directory,\n" + " and you need to add this directory to your KDEDIRS environment\n" + " variable before you can run it. This may be the case if you\n" + " installed into $HOME or a local third-party package directory\n" + " like /usr/local or /opt."), + i18n("Installation problem")); + } + + exit(1); +} + + +int main(int argc, char *argv[]) +{ + setsid(); // acquire shiny new process group + + srandom((unsigned int)time(0) * (unsigned int)getpid()); + + KAboutData aboutData( "rosegarden", I18N_NOOP("Rosegarden"), + VERSION, description, KAboutData::License_GPL, + I18N_NOOP("Copyright 2000 - 2008 Guillaume Laurent, Chris Cannam, Richard Bown\nParts copyright 1994 - 2004 Chris Cannam, Andy Green, Richard Bown, Guillaume Laurent\nLilyPond fonts copyright 1997 - 2005 Han-Wen Nienhuys and Jan Nieuwenhuizen"), + 0, + "http://www.rosegardenmusic.com/", + "rosegarden-devel@lists.sourceforge.net"); + + aboutData.addAuthor("Guillaume Laurent (lead)", 0, "glaurent@telegraph-road.org", "http://telegraph-road.org"); + aboutData.addAuthor("Chris Cannam (lead)", 0, "cannam@all-day-breakfast.com", "http://all-day-breakfast.com"); + aboutData.addAuthor("Richard Bown (lead)", 0, "richard.bown@ferventsoftware.com"); + aboutData.addAuthor("D. Michael McIntyre", 0, "dmmcintyr@users.sourceforge.net"); + aboutData.addAuthor("Pedro Lopez-Cabanillas", 0, "plcl@users.sourceforge.net"); + aboutData.addAuthor("Heikki Johannes Junes", 0, "hjunes@users.sourceforge.net"); + + aboutData.addCredit("Randall Farmer", I18N_NOOP("Chord labelling code"), " rfarme@simons-rock.edu"); + aboutData.addCredit("Hans Kieserman", I18N_NOOP("LilyPond output\nassorted other patches\ni18n-ization"), "hkieserman@mail.com"); + aboutData.addCredit("Levi Burton", I18N_NOOP("UI improvements\nbug fixes"), "donburton@sbcglobal.net"); + aboutData.addCredit("Mark Hymers", I18N_NOOP("Segment colours\nOther UI and bug fixes"), ""); + aboutData.addCredit("Alexandre Prokoudine", I18N_NOOP("Russian translation\ni18n-ization"), "avp@altlinux.ru"); + aboutData.addCredit("Jörg Schumann", I18N_NOOP("German translation"), "jrschumann@gmx.de"); + aboutData.addCredit("Eckhard Jokisch", I18N_NOOP("German translation"), "e.jokisch@u-code.de"); + aboutData.addCredit("Kevin Donnelly", I18N_NOOP("Welsh translation")); + aboutData.addCredit("Didier Burli", I18N_NOOP("French translation"), "didierburli@bluewin.ch"); + aboutData.addCredit("Yves Guillemot", I18N_NOOP("French translation\nBug fixes"), "yc.guillemot@wanadoo.fr"); + aboutData.addCredit("Daniele Medri", I18N_NOOP("Italian translation"), "madrid@linuxmeeting.net"); + aboutData.addCredit("Alessandro Musesti", I18N_NOOP("Italian translation"), "a.musesti@dmf.unicatt.it"); + aboutData.addCredit("Stefan Asserhäll", I18N_NOOP("Swedish translation"), "stefan.asserhall@comhem.se"); + aboutData.addCredit("Erik Magnus Johansson", I18N_NOOP("Swedish translation"), "erik.magnus.johansson@telia.com"); + aboutData.addCredit("Hasso Tepper", I18N_NOOP("Estonian translation"), "hasso@estpak.ee"); + aboutData.addCredit("Jelmer Vernooij", I18N_NOOP("Dutch translation"), "jelmer@samba.org"); + aboutData.addCredit("Jasper Stein", I18N_NOOP("Dutch translation"), "jasper.stein@12move.nl"); + aboutData.addCredit("Kevin Liang", I18N_NOOP("HSpinBox class"), "xkliang@rhpcs.mcmaster.ca"); + aboutData.addCredit("Arnout Engelen", I18N_NOOP("Transposition by interval")); + aboutData.addCredit("Thorsten Wilms", I18N_NOOP("Original designs for rotary controllers"), "t_w_@freenet.de"); + aboutData.addCredit("Oota Toshiya", I18N_NOOP("Japanese translation"), "ribbon@users.sourceforge.net"); + aboutData.addCredit("William", I18N_NOOP("Auto-scroll deceleration\nRests outside staves and other bug fixes"), "rosegarden4p AT orthoset.com"); + aboutData.addCredit("Liu Songhe", I18N_NOOP("Simplified Chinese translation"), "jackliu9999@msn.com"); + aboutData.addCredit("Toni Arnold", I18N_NOOP("LIRC infrared remote-controller support"), ""); + aboutData.addCredit("Vince Negri", I18N_NOOP("MTC slave timing implementation"), "vince.negri@gmail.com"); + aboutData.addCredit("Jan Bína", I18N_NOOP("Czech translation"), "jbina@sky.cz"); + aboutData.addCredit("Thomas Nagy", I18N_NOOP("SCons/bksys building system"), "tnagy256@yahoo.fr"); + aboutData.addCredit("Vladimir Savic", I18N_NOOP("icons, icons, icons"), "vladimir@vladimirsavic.net"); + aboutData.addCredit("Marcos Germán Guglielmetti", I18N_NOOP("Spanish translation"), "marcospcmusica@yahoo.com.ar"); + aboutData.addCredit("Lisandro Damián Nicanor Pérez Meyer", I18N_NOOP("Spanish translation"), "perezmeyer@infovia.com.ar"); + aboutData.addCredit("Javier Castrillo", I18N_NOOP("Spanish translation"), "riverplatense@gmail.com"); + aboutData.addCredit("Lucas Godoy", I18N_NOOP("Spanish translation"), "godoy.lucas@gmail.com"); + aboutData.addCredit("Feliu Ferrer", I18N_NOOP("Catalan translation"), "mverge2@pie.xtec.es"); + aboutData.addCredit("Quim Perez i Noguer", I18N_NOOP("Catalan translation"), "noguer@osona.com"); + aboutData.addCredit("Carolyn McIntyre", I18N_NOOP("1.2.3 splash screen photo\nGave birth to D. Michael McIntyre, bought him a good flute once\nupon a time, and always humored him when he came over to play her\nsome new instrument, even though she really hated his playing.\nBorn October 19, 1951, died September 21, 2007, R. I. P."), "DECEASED"); + aboutData.addCredit("Stephen Torri", I18N_NOOP("Initial guitar chord editing code"), "storri@torri.org"); + aboutData.addCredit("Piotr Sawicki", I18N_NOOP("Polish translation"), "pelle@plusnet.pl"); + aboutData.addCredit("David García-Abad", I18N_NOOP("Basque translation"), "davidgarciabad@telefonica.net"); + aboutData.addCredit("Joerg C. Koenig, Craig Drummond, Bernhard Rosenkränzer, Preston Brown, Than Ngo", I18N_NOOP("Klearlook theme"), "jck@gmx.org"); + + aboutData.setTranslator(I18N_NOOP("_: NAME OF TRANSLATORS\nYour names") , I18N_NOOP("_: EMAIL OF TRANSLATORS\nYour emails")); + + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineArgs::addCmdLineOptions( options ); // Add our own options. + KUniqueApplication::addCmdLineOptions(); // Add KUniqueApplication options. + + if (!RosegardenApplication::start()) + return 0; + + RosegardenApplication app; + + // + // Ensure quit on last window close + // Register main DCOP interface + // + QObject::connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit())); + app.dcopClient()->registerAs(app.name(), false); + app.dcopClient()->setDefaultObject(ROSEGARDEN_GUI_IFACE_NAME); + + // Parse cmd line args + // + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + if (!args->isSet("ignoreversion")) { + // Give up immediately if we haven't been installed or if the + // installation is out of date + // + testInstalledVersion(); + } + + KConfig *config = kapp->config(); + + config->setGroup(GeneralOptionsConfigGroup); + QString lastVersion = config->readEntry("lastversion", ""); + bool newVersion = (lastVersion != VERSION); + if (newVersion) { + std::cerr << "*** This is the first time running this Rosegarden version" << std::endl; + config->writeEntry("lastversion", VERSION); + } + + // If there is no config setting for the startup window size, set + // one now. But base the default on the appropriate desktop size + // (i.e. not the entire desktop, if Xinerama is in use). This is + // obtained from KGlobalSettings::desktopGeometry(), but we can't + // give it a meaningful point to measure from at this stage so we + // always use the "leftmost" display (point 0,0). + + // The config keys are "Height X" and "Width Y" where X and Y are + // the sizes of the available desktop (i.e. the whole shebang if + // under Xinerama). These are obtained from QDesktopWidget. + + config->setGroup("MainView"); + int windowWidth = 0, windowHeight = 0; + + QDesktopWidget *desktop = KApplication::desktop(); + if (desktop) { + QRect totalRect(desktop->screenGeometry()); + QRect desktopRect = KGlobalSettings::desktopGeometry(QPoint(0, 0)); + QSize startupSize; + if (desktopRect.height() <= 800) { + startupSize = QSize((desktopRect.width() * 6) / 7, + (desktopRect.height() * 6) / 7); + } else { + startupSize = QSize((desktopRect.width() * 4) / 5, + (desktopRect.height() * 4) / 5); + } + QString widthKey = QString("Width %1").arg(totalRect.width()); + QString heightKey = QString("Height %1").arg(totalRect.height()); + windowWidth = config->readUnsignedNumEntry + (widthKey, startupSize.width()); + windowHeight = config->readUnsignedNumEntry + (heightKey, startupSize.height()); + } + + config->setGroup("KDE Action Restrictions"); + config->writeEntry("action/help_report_bug", false); + + config->setGroup(GeneralOptionsConfigGroup); + int install = config->readNumEntry("Install Own Theme", 1); + if (install == 2 || (install == 1 && !getenv("KDE_FULL_SESSION"))) { + kapp->setStyle(new KlearlookStyle); + } + + // Show Startup logo + // (this code borrowed from KDevelop 2.0, + // (c) The KDevelop Development Team + // + config->setGroup(GeneralOptionsConfigGroup); + KStartupLogo* startLogo = 0L; + + // See if the config wants us to control JACK + // + if (config->readBoolEntry("Logo", true) && (!kapp->isRestored() && args->isSet("splash")) ) { + RG_DEBUG << k_funcinfo << "Showing startup logo\n"; + startLogo = KStartupLogo::getInstance(); + startLogo->setShowTip(!newVersion); + startLogo->show(); + } + + struct timeval logoShowTime; + gettimeofday(&logoShowTime, 0); + + // + // Start application + // + RosegardenGUIApp *rosegardengui = 0; + + if (app.isRestored()) { + RG_DEBUG << "Restoring from session\n"; + + // RESTORE(RosegardenGUIApp); + int n = 1; + while (KMainWindow::canBeRestored(n)) { + // memory leak if more than one can be restored? + RG_DEBUG << "Restoring from session - restoring app #" << n << endl; + (rosegardengui = new RosegardenGUIApp)->restore(n); + n++; + } + + } else { + +#ifndef NO_SOUND + app.setNoSequencerMode(!args->isSet("sequencer")); +#else + + app.setNoSequencerMode(true); +#endif // NO_SOUND + + rosegardengui = new RosegardenGUIApp(!app.noSequencerMode(), + args->isSet("existingsequencer"), + startLogo); + + rosegardengui->setIsFirstRun(newVersion); + + app.setMainWidget(rosegardengui); + + if (windowWidth != 0 && windowHeight != 0) { + rosegardengui->resize(windowWidth, windowHeight); + } + + rosegardengui->show(); + + // raise start logo + // + if (startLogo) { + startLogo->raise(); + startLogo->setHideEnabled(true); + QApplication::flushX(); + } + + if (args->count()) { + rosegardengui->openFile(QFile::decodeName(args->arg(0)), RosegardenGUIApp::ImportCheckType); + } else { + // rosegardengui->openDocumentFile(); + } + + args->clear(); + + } + + QObject::connect(&app, SIGNAL(aboutToSaveState()), + rosegardengui, SLOT(slotDeleteTransport())); + + // Now that we've started up, raise start logo + // + if (startLogo) { + startLogo->raise(); + startLogo->setHideEnabled(true); + QApplication::flushX(); + } + + // Check for sequencer and launch if needed + // + try { + rosegardengui->launchSequencer(args->isSet("existingsequencer")); + } catch (std::string e) { + RG_DEBUG << "RosegardenGUI - " << e << endl; + } catch (QString e) { + RG_DEBUG << "RosegardenGUI - " << e << endl; + } catch (Exception e) { + RG_DEBUG << "RosegardenGUI - " << e.getMessage() << endl; + } + + + config->setGroup(SequencerOptionsConfigGroup); + + // See if the config wants us to load a soundfont + // + if (config->readBoolEntry("sfxloadenabled", false)) { + QString sfxLoadPath = config->readEntry("sfxloadpath", "/bin/sfxload"); + QString soundFontPath = config->readEntry("soundfontpath", ""); + QFileInfo sfxLoadInfo(sfxLoadPath), soundFontInfo(soundFontPath); + if (sfxLoadInfo.isExecutable() && soundFontInfo.isReadable()) { + KProcess* sfxLoadProcess = new KProcess; + (*sfxLoadProcess) << sfxLoadPath << soundFontPath; + RG_DEBUG << "Starting sfxload : " << sfxLoadPath << " " << soundFontPath << endl; + + QObject::connect(sfxLoadProcess, SIGNAL(processExited(KProcess*)), + &app, SLOT(sfxLoadExited(KProcess*))); + + sfxLoadProcess->start(); + } else { + RG_DEBUG << "sfxload not executable or soundfont not readable : " + << sfxLoadPath << " " << soundFontPath << endl; + } + + } else { + RG_DEBUG << "sfxload disabled\n"; + } + + +#ifdef Q_WS_X11 + XSetErrorHandler( _x_errhandler ); +#endif + + if (startLogo) { + + // pause to ensure the logo has been visible for a reasonable + // length of time, just 'cos it looks a bit silly to show it + // and remove it immediately + + struct timeval now; + gettimeofday(&now, 0); + + RealTime visibleFor = + RealTime(now.tv_sec, now.tv_usec * 1000) - + RealTime(logoShowTime.tv_sec, logoShowTime.tv_usec * 1000); + + if (visibleFor < RealTime(2, 0)) { + int waitTime = visibleFor.sec * 1000 + visibleFor.msec(); + QTimer::singleShot(2500 - waitTime, startLogo, SLOT(close())); + } else { + startLogo->close(); + } + + } else { + + // if the start logo is there, it's responsible for showing this; + // otherwise we have to + + if (!newVersion) { + RosegardenGUIApp::self()->awaitDialogClearance(); + KTipDialog::showTip(locate("data", "rosegarden/tips")); + } + } + + if (newVersion) { + KStartupLogo::hideIfStillThere(); + CurrentProgressDialog::freeze(); + + KDialogBase *dialog = new KDialogBase(rosegardengui, "welcome", + true, i18n("Welcome!"), + KDialogBase::Ok, + KDialogBase::Ok, false); + QVBox *mw = dialog->makeVBoxMainWidget(); + QHBox *hb = new QHBox(mw); + QLabel *image = new QLabel(hb); + image->setAlignment(Qt::AlignTop); + QString iconFile = locate("appdata", "pixmaps/misc/welcome-icon.png"); + if (iconFile) { + image->setPixmap(QPixmap(iconFile)); + } + QLabel *label = new QLabel(hb); + label->setText(i18n("

Welcome to Rosegarden!

Welcome to the Rosegarden audio and MIDI sequencer and musical notation editor.

  • If you have not already done so, you may wish to install some DSSI synth plugins, or a separate synth program such as QSynth. Rosegarden does not synthesize sounds from MIDI on its own, so without these you will hear nothing.


  • Rosegarden uses the JACK audio server for recording and playback of audio, and for playback from DSSI synth plugins. These features will only be available if the JACK server is running.


  • Rosegarden has comprehensive documentation: see the Help menu for the handbook, tutorials, and other information!

Rosegarden was brought to you by a team of volunteers across the world. To learn more, go to http://www.rosegardenmusic.com/.

")); + dialog->showButtonOK(true); + rosegardengui->awaitDialogClearance(); + dialog->exec(); + + CurrentProgressDialog::thaw(); + } + + return kapp->exec(); +} + diff --git a/src/gui/configuration/AudioConfigurationPage.cpp b/src/gui/configuration/AudioConfigurationPage.cpp new file mode 100644 index 0000000..28aff71 --- /dev/null +++ b/src/gui/configuration/AudioConfigurationPage.cpp @@ -0,0 +1,323 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AudioConfigurationPage.h" + +#include "sound/Midi.h" +#include "sound/SoundDriver.h" +#include "document/ConfigGroups.h" +#include "base/MidiProgram.h" +#include "base/Studio.h" +#include "ConfigurationPage.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/dialogs/ShowSequencerStatusDialog.h" +#include "gui/seqmanager/SequenceManager.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/studio/StudioControl.h" +#include "sound/MappedEvent.h" +#include "TabbedConfigurationPage.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +AudioConfigurationPage::AudioConfigurationPage( + RosegardenGUIDoc *doc, + KConfig *cfg, + QWidget *parent, + const char *name): + TabbedConfigurationPage(cfg, parent, name), + m_externalAudioEditorPath(0) +{ + // set the document in the super class + m_doc = doc; + + m_cfg->setGroup(SequencerOptionsConfigGroup); + + QFrame *frame = new QFrame(m_tabWidget); + QGridLayout *layout = new QGridLayout(frame, 7, 2, 10, 5); + + QLabel *label = 0; + + int row = 0; + + m_cfg->setGroup(GeneralOptionsConfigGroup); + + layout->setRowSpacing(row, 15); + ++row; + + layout->addWidget(new QLabel(i18n("Audio preview scale"), + frame), row, 0); + + m_previewStyle = new KComboBox(frame); + m_previewStyle->insertItem(i18n("Linear - easier to see loud peaks")); + m_previewStyle->insertItem(i18n("Meter scaling - easier to see quiet activity")); + m_previewStyle->setCurrentItem(m_cfg->readUnsignedNumEntry("audiopreviewstyle", 1)); + layout->addMultiCellWidget(m_previewStyle, row, row, 1, 2); + ++row; + +#ifdef HAVE_LIBJACK + m_cfg->setGroup(SequencerOptionsConfigGroup); + + label = new QLabel(i18n("Record audio files as"), frame); + m_audioRecFormat = new KComboBox(frame); + m_audioRecFormat->insertItem(i18n("16-bit PCM WAV format (smaller files)")); + m_audioRecFormat->insertItem(i18n("32-bit float WAV format (higher quality)")); + m_audioRecFormat->setCurrentItem(m_cfg->readUnsignedNumEntry("audiorecordfileformat", 1)); + layout->addWidget(label, row, 0); + layout->addMultiCellWidget(m_audioRecFormat, row, row, 1, 2); + ++row; +#endif + + m_cfg->setGroup(GeneralOptionsConfigGroup); + + layout->addWidget(new QLabel(i18n("External audio editor"), frame), + row, 0); + + QString defaultAudioEditor = getBestAvailableAudioEditor(); + + std::cerr << "defaultAudioEditor = " << defaultAudioEditor << std::endl; + + QString externalAudioEditor = m_cfg->readEntry("externalaudioeditor", + defaultAudioEditor); + + if (externalAudioEditor == "") { + externalAudioEditor = defaultAudioEditor; + m_cfg->writeEntry("externalaudioeditor", externalAudioEditor); + } + + m_externalAudioEditorPath = new QLineEdit(externalAudioEditor, frame); +// m_externalAudioEditorPath->setMinimumWidth(150); + layout->addWidget(m_externalAudioEditorPath, row, 1); + + QPushButton *changePathButton = + new QPushButton(i18n("Choose..."), frame); + + layout->addWidget(changePathButton, row, 2); + connect(changePathButton, SIGNAL(clicked()), SLOT(slotFileDialog())); + ++row; + + m_cfg->setGroup(SequencerOptionsConfigGroup); + + layout->addWidget(new QLabel(i18n("Create JACK outputs"), frame), + row, 0); +// ++row; + +#ifdef HAVE_LIBJACK + m_createFaderOuts = new QCheckBox(i18n("for individual audio instruments"), frame); + m_createFaderOuts->setChecked(m_cfg->readBoolEntry("audiofaderouts", false)); + +// layout->addWidget(label, row, 0, Qt::AlignRight); + layout->addWidget(m_createFaderOuts, row, 1); + ++row; + + m_createSubmasterOuts = new QCheckBox(i18n("for submasters"), frame); + m_createSubmasterOuts->setChecked(m_cfg->readBoolEntry("audiosubmasterouts", + false)); + +// layout->addWidget(label, row, 0, Qt::AlignRight); + layout->addWidget(m_createSubmasterOuts, row, 1); + ++row; +#endif + + layout->setRowStretch(row, 10); + + addTab(frame, i18n("General")); + + // --------------------- Startup control ---------------------- + // +#ifdef HAVE_LIBJACK +#define OFFER_JACK_START_OPTION 1 +#ifdef OFFER_JACK_START_OPTION + + frame = new QFrame(m_tabWidget); + layout = new QGridLayout(frame, 8, 4, 10, 5); + + row = 0; + + layout->setRowSpacing(row, 15); + ++row; + + label = new QLabel(i18n("Rosegarden can start the JACK audio daemon (jackd) for you automatically if it isn't already running when Rosegarden starts.\n\nThis is recommended for beginners and those who use Rosegarden as their main audio application, but it might not be to the liking of advanced users.\n\nIf you want to start JACK automatically, make sure the command includes a full path where necessary as well as any command-line arguments you want to use.\n\nFor example: /usr/local/bin/jackd -d alsa -d hw -r44100 -p 2048 -n 2\n\n"), frame); + label->setAlignment(Qt::WordBreak); + + layout->addMultiCellWidget(label, row, row, 0, 3); + ++row; + + // JACK control things + // + bool startJack = m_cfg->readBoolEntry("jackstart", false); + m_startJack = new QCheckBox(frame); + m_startJack->setChecked(startJack); + + layout->addWidget(new QLabel(i18n("Start JACK when Rosegarden starts"), frame), 2, 0); + + layout->addWidget(m_startJack, row, 1); + ++row; + + layout->addWidget(new QLabel(i18n("JACK command"), frame), + row, 0); + + QString jackPath = m_cfg->readEntry("jackcommand", + // "/usr/local/bin/jackd -d alsa -d hw -r 44100 -p 2048 -n 2"); + "/usr/bin/qjackctl -s"); + m_jackPath = new QLineEdit(jackPath, frame); + + layout->addMultiCellWidget(m_jackPath, row, row, 1, 3); + ++row; + + layout->setRowStretch(row, 10); + + addTab(frame, i18n("JACK Startup")); + +#endif // OFFER_JACK_START_OPTION +#endif // HAVE_LIBJACK + +} + +void +AudioConfigurationPage::slotFileDialog() +{ + QString path = KFileDialog::getOpenFileName(QString::null, QString::null, this, i18n("External audio editor path")); + m_externalAudioEditorPath->setText(path); +} + +void +AudioConfigurationPage::apply() +{ + m_cfg->setGroup(SequencerOptionsConfigGroup); + +#ifdef HAVE_LIBJACK +#ifdef OFFER_JACK_START_OPTION + // Jack control + // + m_cfg->writeEntry("jackstart", m_startJack->isChecked()); + m_cfg->writeEntry("jackcommand", m_jackPath->text()); +#endif // OFFER_JACK_START_OPTION + + // Jack audio inputs + // + m_cfg->writeEntry("audiofaderouts", m_createFaderOuts->isChecked()); + m_cfg->writeEntry("audiosubmasterouts", m_createSubmasterOuts->isChecked()); + m_cfg->writeEntry("audiorecordfileformat", m_audioRecFormat->currentItem()); +#endif + + m_cfg->setGroup(GeneralOptionsConfigGroup); + + int previewstyle = m_previewStyle->currentItem(); + m_cfg->writeEntry("audiopreviewstyle", previewstyle); + + QString externalAudioEditor = getExternalAudioEditor(); + + QStringList extlist = QStringList::split(" ", externalAudioEditor); + QString extpath = ""; + if (extlist.size() > 0) extpath = extlist[0]; + + if (extpath != "") { + QFileInfo info(extpath); + if (!info.exists() || !info.isExecutable()) { + KMessageBox::error(0, i18n("External audio editor \"%1\" not found or not executable").arg(extpath)); + m_cfg->writeEntry("externalaudioeditor", ""); + } else { + m_cfg->writeEntry("externalaudioeditor", externalAudioEditor); + } + } else { + m_cfg->writeEntry("externalaudioeditor", ""); + } +} + +QString +AudioConfigurationPage::getBestAvailableAudioEditor() +{ + static QString result = ""; + static bool haveResult = false; + + if (haveResult) return result; + + QString path; + const char *cpath = getenv("PATH"); + if (cpath) path = cpath; + else path = "/usr/bin:/bin"; + + QStringList pathList = QStringList::split(":", path); + + const char *candidates[] = { + "mhwaveedit", + "rezound", + "audacity" + }; + + for (int i = 0; + i < sizeof(candidates)/sizeof(candidates[0]) && result == ""; + i++) { + + QString n(candidates[i]); + + for (int j = 0; + j < pathList.size() && result == ""; + j++) { + + QDir dir(pathList[j]); + QString fp(dir.filePath(n)); + QFileInfo fi(fp); + + if (fi.exists() && fi.isExecutable()) { + if (n == "rezound") { + result = QString("%1 --audio-method=jack").arg(fp); + } else { + result = fp; + } + } + } + } + + haveResult = true; + return result; +} + +} +#include "AudioConfigurationPage.moc" + diff --git a/src/gui/configuration/AudioConfigurationPage.h b/src/gui/configuration/AudioConfigurationPage.h new file mode 100644 index 0000000..bd71df6 --- /dev/null +++ b/src/gui/configuration/AudioConfigurationPage.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_AUDIOCONFIGURATIONPAGE_H_ +#define _RG_AUDIOCONFIGURATIONPAGE_H_ + +#include "TabbedConfigurationPage.h" +#include +#include +#include + +class QWidget; +class QSpinBox; +class QSlider; +class QPushButton; +class QLabel; +class QComboBox; +class QCheckBox; +class KConfig; +class KComboBox; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +class AudioConfigurationPage : public TabbedConfigurationPage +{ + Q_OBJECT +public: + AudioConfigurationPage(RosegardenGUIDoc *doc, + KConfig *cfg, + QWidget *parent=0, + const char *name=0); + + virtual void apply(); + + static QString iconLabel() { return i18n("Audio"); } + static QString title() { return i18n("Audio Settings"); } + static QString iconName() { return "configure-audio"; } + +#ifdef HAVE_LIBJACK + QString getJackPath() { return m_jackPath->text(); } +#endif // HAVE_LIBJACK + + static QString getBestAvailableAudioEditor(); + +protected slots: + void slotFileDialog(); + +protected: + QString getExternalAudioEditor() { return m_externalAudioEditorPath->text(); } + + + //--------------- Data members --------------------------------- + +#ifdef HAVE_LIBJACK + QCheckBox *m_startJack; + QLineEdit *m_jackPath; +#endif // HAVE_LIBJACK + + +#ifdef HAVE_LIBJACK + // Number of JACK input ports our RG client creates - + // this decides how many audio input destinations + // we have. + // + QCheckBox *m_createFaderOuts; + QCheckBox *m_createSubmasterOuts; + + QComboBox *m_audioRecFormat; + +#endif // HAVE_LIBJACK + + QLineEdit* m_externalAudioEditorPath; + QComboBox* m_previewStyle; + +}; + + + +} + +#endif diff --git a/src/gui/configuration/AudioPropertiesPage.cpp b/src/gui/configuration/AudioPropertiesPage.cpp new file mode 100644 index 0000000..65d574e --- /dev/null +++ b/src/gui/configuration/AudioPropertiesPage.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AudioPropertiesPage.h" + +#include "misc/Strings.h" +#include "ConfigurationPage.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/studio/AudioPluginManager.h" +#include "sound/AudioFileManager.h" +#include "TabbedConfigurationPage.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +AudioPropertiesPage::AudioPropertiesPage(RosegardenGUIDoc *doc, + QWidget *parent, + const char *name) + : TabbedConfigurationPage(doc, parent, name) +{ + AudioFileManager &afm = doc->getAudioFileManager(); + + QFrame *frame = new QFrame(m_tabWidget); + QGridLayout *layout = new QGridLayout(frame, 4, 3, 10, 5); + layout->addWidget(new QLabel(i18n("Audio file path:"), frame), 0, 0); + m_path = new QLabel(QString(afm.getAudioPath().c_str()), frame); + layout->addWidget(m_path, 0, 1); + + m_changePathButton = + new QPushButton(i18n("Choose..."), frame); + + layout->addWidget(m_changePathButton, 0, 2); + + m_diskSpace = new QLabel(frame); + layout->addWidget(new QLabel(i18n("Disk space remaining:"), frame), 1, 0); + layout->addWidget(m_diskSpace, 1, 1); + + m_minutesAtStereo = new QLabel(frame); + layout->addWidget( + new QLabel(i18n("Equivalent minutes of 16-bit stereo:"), + frame), 2, 0); + + layout->addWidget(m_minutesAtStereo, 2, 1, AlignCenter); + + layout->setRowStretch(3, 2); + + calculateStats(); + + connect(m_changePathButton, SIGNAL(released()), + SLOT(slotFileDialog())); + + addTab(frame, i18n("Modify audio path")); +} + +void +AudioPropertiesPage::calculateStats() +{ + // This stolen from KDE libs kfile/kpropertiesdialog.cpp + // + QString mountPoint = KIO::findPathMountPoint(m_path->text()); + KDiskFreeSp * job = new KDiskFreeSp; + connect(job, SIGNAL(foundMountPoint(const QString&, unsigned long, unsigned long, + unsigned long)), + this, SLOT(slotFoundMountPoint(const QString&, unsigned long, unsigned long, + unsigned long))); + job->readDF(mountPoint); +} + +void +AudioPropertiesPage::slotFoundMountPoint(const QString&, + unsigned long kBSize, + unsigned long /*kBUsed*/, + unsigned long kBAvail ) +{ + m_diskSpace->setText(i18n("%1 out of %2 (%3% used)") + .arg(KIO::convertSizeFromKB(kBAvail)) + .arg(KIO::convertSizeFromKB(kBSize)) + .arg( 100 - (int)(100.0 * kBAvail / kBSize) )); + + + AudioPluginManager *apm = m_doc->getPluginManager(); + + int sampleRate = 48000; + QCString replyType; + QByteArray replyData; + + if (rgapp->sequencerCall("getSampleRate()", replyType, replyData)) { + + QDataStream streamIn(replyData, IO_ReadOnly); + unsigned int result; + streamIn >> result; + sampleRate = result; + } + + // Work out total bytes and divide this by the sample rate times the + // number of channels (2) times the number of bytes per sample (2) + // times 60 seconds. + // + float stereoMins = ( float(kBAvail) * 1024.0 ) / + ( float(sampleRate) * 2.0 * 2.0 * 60.0 ); + QString minsStr; + minsStr.sprintf("%8.1f", stereoMins); + + m_minutesAtStereo-> + setText(QString("%1 %2 %3Hz").arg(minsStr) + .arg(i18n("minutes at")) + .arg(sampleRate)); +} + +void +AudioPropertiesPage::slotFileDialog() +{ + AudioFileManager &afm = m_doc->getAudioFileManager(); + + KFileDialog *fileDialog = new KFileDialog(QString(afm.getAudioPath().c_str()), + QString::null, + this, "file dialog", true); + fileDialog->setMode(KFile::Directory); + + connect(fileDialog, SIGNAL(fileSelected(const QString&)), + SLOT(slotFileSelected(const QString&))); + + connect(fileDialog, SIGNAL(destroyed()), + SLOT(slotDirectoryDialogClosed())); + + if (fileDialog->exec() == QDialog::Accepted) { + m_path->setText(fileDialog->selectedFile()); + calculateStats(); + } + delete fileDialog; +} + +void +AudioPropertiesPage::apply() +{ + AudioFileManager &afm = m_doc->getAudioFileManager(); + QString newDir = m_path->text(); + + if (!newDir.isNull()) { + afm.setAudioPath(qstrtostr(newDir)); + m_doc->slotDocumentModified(); + } +} + +} +#include "AudioPropertiesPage.moc" diff --git a/src/gui/configuration/AudioPropertiesPage.h b/src/gui/configuration/AudioPropertiesPage.h new file mode 100644 index 0000000..f21fecc --- /dev/null +++ b/src/gui/configuration/AudioPropertiesPage.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_AUDIOPROPERTIESPAGE_H_ +#define _RG_AUDIOPROPERTIESPAGE_H_ + +#include "TabbedConfigurationPage.h" +#include +#include + + +class QWidget; +class QPushButton; +class QLabel; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +/** + * Audio Properties page + * + * (document-wide settings) + */ +class AudioPropertiesPage : public TabbedConfigurationPage +{ + Q_OBJECT +public: + AudioPropertiesPage(RosegardenGUIDoc *doc, + QWidget *parent=0, const char *name=0); + virtual void apply(); + + static QString iconLabel() { return i18n("Audio"); } + static QString title() { return i18n("Audio Settings"); } + static QString iconName() { return "configure-audio"; } + +protected slots: + void slotFileDialog(); + + // Work out and display remaining disk space and time left + // at current path. + // + void calculateStats(); + + void slotFoundMountPoint(const QString&, + unsigned long kBSize, + unsigned long kBUsed, + unsigned long kBAvail); + +protected: + + //--------------- Data members --------------------------------- + + QLabel *m_path; + QLabel *m_diskSpace; + QLabel *m_minutesAtStereo; + + QPushButton *m_changePathButton; +}; + + +} + +#endif diff --git a/src/gui/configuration/ColourConfigurationPage.cpp b/src/gui/configuration/ColourConfigurationPage.cpp new file mode 100644 index 0000000..f87cf20 --- /dev/null +++ b/src/gui/configuration/ColourConfigurationPage.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ColourConfigurationPage.h" + +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Colour.h" +#include "base/ColourMap.h" +#include "commands/segment/SegmentColourMapCommand.h" +#include "ConfigurationPage.h" +#include "document/RosegardenGUIDoc.h" +#include "document/MultiViewCommandHistory.h" +#include "gui/general/GUIPalette.h" +#include "gui/widgets/ColourTable.h" +#include "TabbedConfigurationPage.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +ColourConfigurationPage::ColourConfigurationPage(RosegardenGUIDoc *doc, + QWidget *parent, + const char *name) + : TabbedConfigurationPage(doc, parent, name) +{ + QFrame *frame = new QFrame(m_tabWidget); + QGridLayout *layout = new QGridLayout(frame, 2, 2, + 10, 5); + + m_map = m_doc->getComposition().getSegmentColourMap(); + + m_colourtable = new ColourTable(frame, m_map, m_listmap); + m_colourtable->setFixedHeight(280); + + layout->addMultiCellWidget(m_colourtable, 0, 0, 0, 1); + + QPushButton* addColourButton = new QPushButton(i18n("Add New Color"), + frame); + layout->addWidget(addColourButton, 1, 0, Qt::AlignHCenter); + + QPushButton* deleteColourButton = new QPushButton(i18n("Delete Color"), + frame); + layout->addWidget(deleteColourButton, 1, 1, Qt::AlignHCenter); + + connect(addColourButton, SIGNAL(clicked()), + this, SLOT(slotAddNew())); + + connect(deleteColourButton, SIGNAL(clicked()), + this, SLOT(slotDelete())); + + connect(this, SIGNAL(docColoursChanged()), + m_doc, SLOT(slotDocColoursChanged())); + + connect(m_colourtable, SIGNAL(entryTextChanged(unsigned int, QString)), + this, SLOT(slotTextChanged(unsigned int, QString))); + + connect(m_colourtable, SIGNAL(entryColourChanged(unsigned int, QColor)), + this, SLOT(slotColourChanged(unsigned int, QColor))); + + addTab(frame, i18n("Color Map")); + +} + +void +ColourConfigurationPage::slotTextChanged(unsigned int index, QString string) +{ + m_map.modifyNameByIndex(m_listmap[index], string.ascii()); + m_colourtable->populate_table(m_map, m_listmap); +} + +void +ColourConfigurationPage::slotColourChanged(unsigned int index, QColor color) +{ + m_map.modifyColourByIndex(m_listmap[index], GUIPalette::convertColour(color)); + m_colourtable->populate_table(m_map, m_listmap); +} + +void +ColourConfigurationPage::apply() +{ + SegmentColourMapCommand *command = new SegmentColourMapCommand(m_doc, m_map); + m_doc->getCommandHistory()->addCommand(command); + + RG_DEBUG << "ColourConfigurationPage::apply() emitting docColoursChanged()" << endl; + emit docColoursChanged(); +} + +void +ColourConfigurationPage::slotAddNew() +{ + QColor temp; + + bool ok = false; + + QString newName = KInputDialog::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( temp ); + + if (result == KColorDialog::Accepted) { + Colour temp2 = GUIPalette::convertColour(temp); + m_map.addItem(temp2, qstrtostr(newName)); + m_colourtable->populate_table(m_map, m_listmap); + } + // Else we don't do anything as they either didn't give a name + // or didn't give a colour + } + +} + +void +ColourConfigurationPage::slotDelete() +{ + QTableSelection temp = m_colourtable->selection(0); + + if ((!temp.isActive()) || (temp.topRow() == 0)) + return ; + + unsigned int toDel = temp.topRow(); + + m_map.deleteItemByIndex(m_listmap[toDel]); + m_colourtable->populate_table(m_map, m_listmap); + +} + +} +#include "ColourConfigurationPage.moc" diff --git a/src/gui/configuration/ColourConfigurationPage.h b/src/gui/configuration/ColourConfigurationPage.h new file mode 100644 index 0000000..9ef4ae0 --- /dev/null +++ b/src/gui/configuration/ColourConfigurationPage.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_COLOURCONFIGURATIONPAGE_H_ +#define _RG_COLOURCONFIGURATIONPAGE_H_ + +#include "base/ColourMap.h" +#include "gui/widgets/ColourTable.h" +#include "TabbedConfigurationPage.h" +#include +#include + + +class QWidget; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +/** + * Colour Configuration Page + * + * (document-wide settings) + */ +class ColourConfigurationPage : public TabbedConfigurationPage +{ + Q_OBJECT +public: + ColourConfigurationPage(RosegardenGUIDoc *doc, + QWidget *parent=0, const char *name=0); + virtual void apply(); + + void populate_table(); + + static QString iconLabel() { return i18n("Color"); } + static QString title() { return i18n("Color Settings"); } + static QString iconName() { return "colorize"; } + +signals: + void docColoursChanged(); + +protected slots: + void slotAddNew(); + void slotDelete(); + void slotTextChanged(unsigned int, QString); + void slotColourChanged(unsigned int, QColor); + +protected: + ColourTable *m_colourtable; + + ColourMap m_map; + ColourTable::ColourList m_listmap; + +}; + +// ----------- SequencerConfigurationage ----------------- +// + + +} + +#endif diff --git a/src/gui/configuration/ConfigurationPage.cpp b/src/gui/configuration/ConfigurationPage.cpp new file mode 100644 index 0000000..3f3730b --- /dev/null +++ b/src/gui/configuration/ConfigurationPage.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ConfigurationPage.h" + +#include "document/RosegardenGUIDoc.h" +#include +#include +#include + + +namespace Rosegarden +{ +} +#include "ConfigurationPage.moc" diff --git a/src/gui/configuration/ConfigurationPage.h b/src/gui/configuration/ConfigurationPage.h new file mode 100644 index 0000000..4a93195 --- /dev/null +++ b/src/gui/configuration/ConfigurationPage.h @@ -0,0 +1,104 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Parts of the configuration classes are taken from KMail. + Copyright (C) 2000 The KMail Development Team. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CONFIGURATIONPAGE_H_ +#define _RG_CONFIGURATIONPAGE_H_ + +#include + + +class KConfig; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +/** + * This class borrowed from KMail + * (c) 2000 The KMail Development Team + */ +class ConfigurationPage : public QWidget +{ + Q_OBJECT + +public: + ConfigurationPage(RosegardenGUIDoc *doc, + QWidget *parent=0, const char *name=0) + : QWidget(parent, name), m_doc(doc), m_cfg(0), m_pageIndex(0) {} + + ConfigurationPage(KConfig *cfg, + QWidget *parent=0, const char *name=0) + : QWidget(parent, name), m_doc(0), m_cfg(cfg), m_pageIndex(0) {} + + ConfigurationPage(RosegardenGUIDoc *doc, KConfig *cfg, + QWidget *parent=0, const char *name=0) + : QWidget(parent, name), m_doc(doc), m_cfg(cfg), m_pageIndex(0) {} + + virtual ~ConfigurationPage() {}; + + /** + * Should set the page up (ie. read the setting from the @ref + * KConfig object into the widgets) after creating it in the + * constructor. Called from @ref ConfigureDialog. + */ +// virtual void setup() = 0; + + /** + * Should apply the changed settings (ie. read the settings from + * the widgets into the @ref KConfig object). Called from @ref + * ConfigureDialog. + */ + virtual void apply() = 0; + + /** + * Should cleanup any temporaries after cancel. The default + * implementation does nothing. Called from @ref + * ConfigureDialog. + */ + virtual void dismiss() {} + + void setPageIndex( int aPageIndex ) { m_pageIndex = aPageIndex; } + int pageIndex() const { return m_pageIndex; } + +protected: + + //--------------- Data members --------------------------------- + + RosegardenGUIDoc* m_doc; + KConfig* m_cfg; + + int m_pageIndex; +}; + + +} + +#endif diff --git a/src/gui/configuration/DocumentMetaConfigurationPage.cpp b/src/gui/configuration/DocumentMetaConfigurationPage.cpp new file mode 100644 index 0000000..9f5064b --- /dev/null +++ b/src/gui/configuration/DocumentMetaConfigurationPage.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "DocumentMetaConfigurationPage.h" + +#include "base/Event.h" +#include "base/BaseProperties.h" +#include "misc/Strings.h" +#include "base/Colour.h" +#include "base/Composition.h" +#include "base/Configuration.h" +#include "base/NotationTypes.h" +#include "base/PropertyName.h" +#include "base/BasicQuantizer.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "ConfigurationPage.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/editors/notation/NotationStrings.h" +#include "gui/configuration/HeadersConfigurationPage.h" +#include "gui/general/GUIPalette.h" +#include "TabbedConfigurationPage.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +static QString durationToString(Rosegarden::Composition &comp, + Rosegarden::timeT absTime, + Rosegarden::timeT duration, + Rosegarden::RealTime rt) +{ + return i18n("%1 minutes %2.%3%4 seconds (%5 units, %6 measures)") // TODO - PLURAL + .arg(rt.sec / 60).arg(rt.sec % 60) + .arg(rt.msec() / 100).arg((rt.msec() / 10) % 10) + .arg(duration).arg(comp.getBarNumber(absTime + duration) - + comp.getBarNumber(absTime)); +} + +class SegmentDataItem : public QTableItem +{ +public: + SegmentDataItem(QTable *t, QString s) : + QTableItem(t, QTableItem::Never, s) { } + virtual int alignment() const { return Qt::AlignCenter; } + + virtual QString key() const { + + // It doesn't seem to be possible to specify a comparator so + // as to get the right sorting for numeric items (what am I + // missing here?), only to override this function to return a + // string for comparison. So for integer items we'll return a + // string that starts with a single digit corresponding to the + // number of digits in the integer, which should ensure that + // dictionary sorting works correctly. + // + // This relies on the assumption that any item whose text + // starts with a digit will contain nothing other than a + // single non-negative integer of no more than 9 digits. That + // assumption should hold for all current uses of this class, + // but may need checking for future uses... + + QString s(text()); + if (s[0].digitValue() >= 0) { + return QString("%1%2").arg(s.length()).arg(s); + } else { + return s; + } + } +}; + +DocumentMetaConfigurationPage::DocumentMetaConfigurationPage(RosegardenGUIDoc *doc, + QWidget *parent, + const char *name) : + TabbedConfigurationPage(doc, parent, name) +{ + m_headersPage = new HeadersConfigurationPage(this, doc); + addTab(m_headersPage, i18n("Headers")); + + Composition &comp = doc->getComposition(); + std::set + usedTracks; + + int audioSegments = 0, internalSegments = 0; + for (Composition::iterator ci = comp.begin(); + ci != comp.end(); ++ci) { + usedTracks.insert((*ci)->getTrack()); + if ((*ci)->getType() == Segment::Audio) + ++audioSegments; + else + ++internalSegments; + } + + QFrame *frame = new QFrame(m_tabWidget); + QGridLayout *layout = new QGridLayout(frame, + 6, 2, + 10, 5); + + layout->addWidget(new QLabel(i18n("Filename:"), frame), 0, 0); + layout->addWidget(new QLabel(doc->getTitle(), frame), 0, 1); + + layout->addWidget(new QLabel(i18n("Formal duration (to end marker):"), frame), 1, 0); + timeT d = comp.getEndMarker(); + RealTime rtd = comp.getElapsedRealTime(d); + layout->addWidget(new QLabel(durationToString(comp, 0, d, rtd), frame), 1, 1); + + layout->addWidget(new QLabel(i18n("Playing duration:"), frame), 2, 0); + d = comp.getDuration(); + rtd = comp.getElapsedRealTime(d); + layout->addWidget(new QLabel(durationToString(comp, 0, d, rtd), frame), 2, 1); + + layout->addWidget(new QLabel(i18n("Tracks:"), frame), 3, 0); + layout->addWidget(new QLabel(i18n("%1 used, %2 total") + .arg(usedTracks.size()) + .arg(comp.getNbTracks()), + frame), 3, 1); + + layout->addWidget(new QLabel(i18n("Segments:"), frame), 4, 0); + layout->addWidget(new QLabel(i18n("%1 MIDI, %2 audio, %3 total") + .arg(internalSegments) + .arg(audioSegments) + .arg(internalSegments + audioSegments), + frame), 4, 1); + + layout->setRowStretch(5, 2); + + addTab(frame, i18n("Statistics")); + + frame = new QFrame(m_tabWidget); + layout = new QGridLayout(frame, 1, 1, 10, 5); + + QTable *table = new QTable(1, 11, frame, "Segment Table"); + table->setSelectionMode(QTable::NoSelection); + table->setSorting(true); + table->horizontalHeader()->setLabel(0, i18n("Type")); + table->horizontalHeader()->setLabel(1, i18n("Track")); + table->horizontalHeader()->setLabel(2, i18n("Label")); + table->horizontalHeader()->setLabel(3, i18n("Time")); + table->horizontalHeader()->setLabel(4, i18n("Duration")); + table->horizontalHeader()->setLabel(5, i18n("Events")); + table->horizontalHeader()->setLabel(6, i18n("Polyphony")); + table->horizontalHeader()->setLabel(7, i18n("Repeat")); + table->horizontalHeader()->setLabel(8, i18n("Quantize")); + table->horizontalHeader()->setLabel(9, i18n("Transpose")); + table->horizontalHeader()->setLabel(10, i18n("Delay")); + table->setNumRows(audioSegments + internalSegments); + + table->setColumnWidth(0, 50); + table->setColumnWidth(1, 50); + table->setColumnWidth(2, 150); + table->setColumnWidth(3, 80); + table->setColumnWidth(4, 80); + table->setColumnWidth(5, 80); + table->setColumnWidth(6, 80); + table->setColumnWidth(7, 80); + table->setColumnWidth(8, 80); + table->setColumnWidth(9, 80); + table->setColumnWidth(10, 80); + + int i = 0; + + for (Composition::iterator ci = comp.begin(); + ci != comp.end(); ++ci) { + + Segment *s = *ci; + + table->setItem(i, 0, new SegmentDataItem + (table, + s->getType() == Segment::Audio ? + i18n("Audio") : i18n("MIDI"))); + + table->setItem(i, 1, new SegmentDataItem + (table, + QString("%1").arg(s->getTrack() + 1))); + + QPixmap colourPixmap(16, 16); + Colour colour = + comp.getSegmentColourMap().getColourByIndex(s->getColourIndex()); + colourPixmap.fill(GUIPalette::convertColour(colour)); + + table->setItem(i, 2, + new QTableItem(table, QTableItem::Never, + strtoqstr(s->getLabel()), + colourPixmap)); + + table->setItem(i, 3, new SegmentDataItem + (table, + QString("%1").arg(s->getStartTime()))); + + table->setItem(i, 4, new SegmentDataItem + (table, + QString("%1").arg(s->getEndMarkerTime() - + s->getStartTime()))); + + std::set notesOn; + std::multimap noteOffs; + int events = 0, notes = 0, poly = 0, maxPoly = 0; + + for (Segment::iterator si = s->begin(); + s->isBeforeEndMarker(si); ++si) { + ++events; + if ((*si)->isa(Note::EventType)) { + ++notes; + timeT startTime = (*si)->getAbsoluteTime(); + timeT endTime = startTime + (*si)->getDuration(); + if (endTime == startTime) continue; + while (!noteOffs.empty() && + (startTime >= noteOffs.begin()->first)) { + notesOn.erase(noteOffs.begin()->second); + noteOffs.erase(noteOffs.begin()); + } + long pitch = 0; + (*si)->get(BaseProperties::PITCH, pitch); + notesOn.insert(pitch); + noteOffs.insert(std::multimap::value_type(endTime, pitch)); + poly = notesOn.size(); + if (poly > maxPoly) maxPoly = poly; + } + } + + table->setItem(i, 5, new SegmentDataItem + (table, + QString("%1").arg(events))); + + table->setItem(i, 6, new SegmentDataItem + (table, + QString("%1").arg(maxPoly))); + + table->setItem(i, 7, new SegmentDataItem + (table, + s->isRepeating() ? i18n("Yes") : i18n("No"))); + + timeT discard; + + if (s->getQuantizer() && s->hasQuantization()) { + timeT unit = s->getQuantizer()->getUnit(); + table->setItem(i, 8, new SegmentDataItem + (table, + NotationStrings::makeNoteMenuLabel + (unit, true, discard, false))); + } else { + table->setItem(i, 8, new SegmentDataItem + (table, + i18n("Off"))); + } + + table->setItem(i, 9, new SegmentDataItem + (table, + QString("%1").arg(s->getTranspose()))); + + if (s->getDelay() != 0) { + if (s->getRealTimeDelay() != RealTime::zeroTime) { + table->setItem(i, 10, new SegmentDataItem + (table, + QString("%1 + %2 ms") + .arg(NotationStrings::makeNoteMenuLabel + (s->getDelay(), true, discard, false)) + .arg(s->getRealTimeDelay().sec * 1000 + + s->getRealTimeDelay().msec()))); + } else { + table->setItem(i, 10, new SegmentDataItem + (table, + NotationStrings::makeNoteMenuLabel + (s->getDelay(), true, discard, false))); + } + } else if (s->getRealTimeDelay() != RealTime::zeroTime) { + table->setItem(i, 10, new SegmentDataItem + (table, + QString("%2 ms") + .arg(s->getRealTimeDelay().sec * 1000 + + s->getRealTimeDelay().msec()))); + } else { + table->setItem(i, 10, new SegmentDataItem + (table, + i18n("None"))); + } + + ++i; + } + + layout->addWidget(table, 0, 0); + + addTab(frame, i18n("Segment Summary")); + +} + +void +DocumentMetaConfigurationPage::apply() +{ + m_headersPage->apply(); + + m_doc->slotDocumentModified(); +} + +/* hjj: WHAT TO DO WITH THIS ? +void +DocumentMetaConfigurationPage::selectMetadata(QString name) +{ + std::vector fixedKeys = + CompositionMetadataKeys::getFixedKeys(); + std::vector::iterator i = fixedKeys.begin(); + + for (QListViewItem *item = m_fixed->firstChild(); + item != 0; item = item->nextSibling()) { + + if (i == fixedKeys.end()) + break; + + if (name == strtoqstr(i->getName())) { + m_fixed->setSelected(item, true); + m_fixed->setCurrentItem(item); + return ; + } + + ++i; + } + + for (QListViewItem *item = m_metadata->firstChild(); + item != 0; item = item->nextSibling()) { + + if (item->text(0).lower() != name) + continue; + + m_metadata->setSelected(item, true); + m_metadata->setCurrentItem(item); + return ; + } +} +*/ + +} +#include "DocumentMetaConfigurationPage.moc" diff --git a/src/gui/configuration/DocumentMetaConfigurationPage.h b/src/gui/configuration/DocumentMetaConfigurationPage.h new file mode 100644 index 0000000..db26f54 --- /dev/null +++ b/src/gui/configuration/DocumentMetaConfigurationPage.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_DOCUMENTMETACONFIGURATIONPAGE_H_ +#define _RG_DOCUMENTMETACONFIGURATIONPAGE_H_ + +#include "TabbedConfigurationPage.h" +#include +#include + + +class QWidget; +class KListView; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class HeadersConfigurationPage; + +/** + * Document Meta-information page + * + * (document-wide settings) + */ +class DocumentMetaConfigurationPage : public TabbedConfigurationPage +{ + Q_OBJECT +public: + DocumentMetaConfigurationPage(RosegardenGUIDoc *doc, + QWidget *parent = 0, const char *name = 0); + virtual void apply(); + + static QString iconLabel() { return i18n("About"); } + static QString title() { return i18n("About"); } + static QString iconName() { return "contents"; } + +/* hjj: WHAT TO DO WITH THIS ? + void selectMetadata(QString name); +*/ + +protected: + + //--------------- Data members --------------------------------- + + HeadersConfigurationPage *m_headersPage; +}; + + + +} + +#endif diff --git a/src/gui/configuration/GeneralConfigurationPage.cpp b/src/gui/configuration/GeneralConfigurationPage.cpp new file mode 100644 index 0000000..22915ed --- /dev/null +++ b/src/gui/configuration/GeneralConfigurationPage.cpp @@ -0,0 +1,429 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "GeneralConfigurationPage.h" + +#include "document/ConfigGroups.h" +#include "ConfigurationPage.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/editors/eventlist/EventView.h" +#include "gui/editors/parameters/RosegardenParameterArea.h" +#include "gui/studio/StudioControl.h" +#include "gui/dialogs/ShowSequencerStatusDialog.h" +#include "gui/seqmanager/SequenceManager.h" +#include "sound/SoundDriver.h" +#include "TabbedConfigurationPage.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +GeneralConfigurationPage::GeneralConfigurationPage(RosegardenGUIDoc *doc, + KConfig *cfg, + QWidget *parent, const char *name) + : TabbedConfigurationPage(cfg, parent, name), + m_doc(doc), + m_client(0), + m_countIn(0), + m_nameStyle(0) +{ + m_cfg->setGroup(GeneralOptionsConfigGroup); + + QFrame *frame; + QGridLayout *layout; + QLabel *label = 0; + int row = 0; + + // + // "Behavior" tab + // + frame = new QFrame(m_tabWidget); + layout = new QGridLayout(frame, + 6, 2, // nbrow, nbcol + 10, 5); + + layout->setRowSpacing(row, 15); + ++row; + + layout->addWidget(new QLabel(i18n("Double-click opens segment in"), + frame), row, 0); + + m_client = new KComboBox(frame); + m_client->insertItem(i18n("Notation editor")); + m_client->insertItem(i18n("Matrix editor")); + m_client->insertItem(i18n("Event List editor")); + m_client->setCurrentItem(m_cfg->readUnsignedNumEntry("doubleclickclient", NotationView)); + + layout->addMultiCellWidget(m_client, row, row, 1, 2); + ++row; + + layout->addWidget(new QLabel(i18n("Number of count-in measures when recording"), + frame), row, 0); + + m_countIn = new QSpinBox(frame); + m_countIn->setValue(m_cfg->readUnsignedNumEntry("countinbars", 0)); + m_countIn->setMaxValue(10); + m_countIn->setMinValue(0); + layout->addMultiCellWidget(m_countIn, row, row, 1, 2); + ++row; + + layout->addWidget(new QLabel(i18n("Auto-save interval"), frame), row, 0); + + m_autoSave = new KComboBox(frame); + m_autoSave->insertItem(i18n("Every 30 seconds")); + m_autoSave->insertItem(i18n("Every minute")); + m_autoSave->insertItem(i18n("Every five minutes")); + m_autoSave->insertItem(i18n("Every half an hour")); + m_autoSave->insertItem(i18n("Never")); + + bool doAutoSave = m_cfg->readBoolEntry("autosave", true); + int autoSaveInterval = m_cfg->readUnsignedNumEntry("autosaveinterval", 300); + if (!doAutoSave || autoSaveInterval == 0) { + m_autoSave->setCurrentItem(4); // off + } else if (autoSaveInterval < 45) { + m_autoSave->setCurrentItem(0); + } else if (autoSaveInterval < 150) { + m_autoSave->setCurrentItem(1); + } else if (autoSaveInterval < 900) { + m_autoSave->setCurrentItem(2); + } else { + m_autoSave->setCurrentItem(3); + } + + layout->addMultiCellWidget(m_autoSave, row, row, 1, 2); + ++row; + + // JACK Transport + // +#ifdef HAVE_LIBJACK + m_cfg->setGroup(SequencerOptionsConfigGroup); + + label = new QLabel(i18n("Use JACK transport"), frame); + layout->addWidget(label, row, 0); + + m_jackTransport = new QCheckBox(frame); + layout->addMultiCellWidget(m_jackTransport, row, row, 1, 2); + +// m_jackTransport->insertItem(i18n("Ignore JACK transport")); +// m_jackTransport->insertItem(i18n("Sync")); + + /*!!! Removed as not yet implemented + m_jackTransport->insertItem(i18n("Sync, and offer timebase master")); + */ + + bool jackMaster = m_cfg->readBoolEntry("jackmaster", false); + bool jackTransport = m_cfg->readBoolEntry("jacktransport", false); +/* + if (jackTransport) + m_jackTransport->setCurrentItem(1); + else + m_jackTransport->setCurrentItem(0); +*/ + m_jackTransport->setChecked(jackTransport); + + ++row; + + m_cfg->setGroup(GeneralOptionsConfigGroup); +#endif + + layout->setRowSpacing(row, 20); + ++row; + + layout->addWidget(new QLabel(i18n("Sequencer status"), frame), row, 0); + + QString status(i18n("Unknown")); + SequenceManager *mgr = doc->getSequenceManager(); + if (mgr) { + int driverStatus = mgr->getSoundDriverStatus() & (AUDIO_OK | MIDI_OK); + switch (driverStatus) { + case AUDIO_OK: + status = i18n("No MIDI, audio OK"); + break; + case MIDI_OK: + status = i18n("MIDI OK, no audio"); + break; + case AUDIO_OK | MIDI_OK: + status = i18n("MIDI OK, audio OK"); + break; + default: + status = i18n("No driver"); + break; + } + } + + layout->addWidget(new QLabel(status, frame), row, 1); + + QPushButton *showStatusButton = new QPushButton(i18n("Details..."), + frame); + QObject::connect(showStatusButton, SIGNAL(clicked()), + this, SLOT(slotShowStatus())); + layout->addWidget(showStatusButton, row, 2, Qt::AlignRight); + ++row; + + layout->setRowStretch(row, 10); + + addTab(frame, i18n("Behavior")); + + // + // "Appearance" tab + // + frame = new QFrame(m_tabWidget); + layout = new QGridLayout(frame, + 7, 4, // nbrow, nbcol -- one extra row improves layout + 10, 5); + + row = 0; + + layout->setRowSpacing(row, 15); + ++row; + + layout->addWidget(new QLabel(i18n("Side-bar parameter box layout"), + frame), row, 0); + + m_sidebarStyle = new KComboBox(frame); + m_sidebarStyle->insertItem(i18n("Vertically stacked"), + RosegardenParameterArea::CLASSIC_STYLE); + m_sidebarStyle->insertItem(i18n("Tabbed"), + RosegardenParameterArea::TAB_BOX_STYLE); + + m_sidebarStyle->setCurrentItem(m_cfg->readUnsignedNumEntry("sidebarstyle", + 0)); + layout->addMultiCellWidget(m_sidebarStyle, row, row, 1, 3); + ++row; + + layout->addWidget(new QLabel(i18n("Note name style"), + frame), row, 0); + + m_nameStyle = new KComboBox(frame); + m_nameStyle->insertItem(i18n("Always use US names (e.g. quarter, 8th)")); + m_nameStyle->insertItem(i18n("Localized (where available)")); + m_nameStyle->setCurrentItem(m_cfg->readUnsignedNumEntry("notenamestyle", Local)); + layout->addMultiCellWidget(m_nameStyle, row, row, 1, 3); + ++row; +/* + layout->addWidget(new QLabel(i18n("Show tool context help in status bar"), frame), row, 0); + + m_toolContextHelp = new QCheckBox(frame); + layout->addWidget(m_toolContextHelp, row, 1); + m_toolContextHelp->setChecked(m_cfg->readBoolEntry + ("toolcontexthelp", true)); + ++row; +*/ + + layout->addWidget(new QLabel(i18n("Show textured background on"), frame), row, 0); + + m_backgroundTextures = new QCheckBox(i18n("Main window"), frame); + layout->addWidget(m_backgroundTextures, row, 1); + + m_matrixBackgroundTextures = new QCheckBox(i18n("Matrix"), frame); + layout->addWidget(m_matrixBackgroundTextures, row, 2); + + m_notationBackgroundTextures = new QCheckBox(i18n("Notation"), frame); + layout->addWidget(m_notationBackgroundTextures, row, 3); + + m_backgroundTextures->setChecked(m_cfg->readBoolEntry + ("backgroundtextures", true)); + + m_cfg->setGroup(MatrixViewConfigGroup); + m_matrixBackgroundTextures->setChecked(m_cfg->readBoolEntry + ("backgroundtextures-1.6-plus", true)); + m_cfg->setGroup(NotationViewConfigGroup); + m_notationBackgroundTextures->setChecked(m_cfg->readBoolEntry + ("backgroundtextures", true)); + m_cfg->setGroup(GeneralOptionsConfigGroup); + ++row; + + layout->addWidget(new QLabel(i18n("Use bundled Klearlook theme"), frame), row, 0); + m_globalStyle = new KComboBox(frame); + m_globalStyle->insertItem(i18n("Never")); + m_globalStyle->insertItem(i18n("When not running under KDE")); + m_globalStyle->insertItem(i18n("Always")); + m_globalStyle->setCurrentItem(m_cfg->readUnsignedNumEntry("Install Own Theme", 1)); + layout->addMultiCellWidget(m_globalStyle, row, row, 1, 3); + + ++row; + + layout->setRowStretch(row, 10); + + addTab(frame, i18n("Presentation")); + +} + +void +GeneralConfigurationPage::slotShowStatus() +{ + ShowSequencerStatusDialog dialog(this); + dialog.exec(); +} + +void GeneralConfigurationPage::apply() +{ + m_cfg->setGroup(GeneralOptionsConfigGroup); + + int countIn = getCountInSpin(); + m_cfg->writeEntry("countinbars", countIn); + + int client = getDblClickClient(); + m_cfg->writeEntry("doubleclickclient", client); + + int globalstyle = m_globalStyle->currentItem(); + m_cfg->writeEntry("Install Own Theme", globalstyle); + + int namestyle = getNoteNameStyle(); + m_cfg->writeEntry("notenamestyle", namestyle); +/* + m_cfg->writeEntry("toolcontexthelp", m_toolContextHelp->isChecked()); +*/ + bool texturesChanged = false; + bool mainTextureChanged = false; + m_cfg->setGroup(GeneralOptionsConfigGroup); + + if (m_cfg->readBoolEntry("backgroundtextures", true) != + m_backgroundTextures->isChecked()) { + texturesChanged = true; + mainTextureChanged = true; + } else { + m_cfg->setGroup(MatrixViewConfigGroup); + if (m_cfg->readBoolEntry("backgroundtextures-1.6-plus", false) != + m_matrixBackgroundTextures->isChecked()) { + texturesChanged = true; + } else { + m_cfg->setGroup(NotationViewConfigGroup); + if (m_cfg->readBoolEntry("backgroundtextures", true) != + m_notationBackgroundTextures->isChecked()) { + texturesChanged = true; + } + } + } + + m_cfg->setGroup(GeneralOptionsConfigGroup); + m_cfg->writeEntry("backgroundtextures", m_backgroundTextures->isChecked()); + + m_cfg->setGroup(MatrixViewConfigGroup); + m_cfg->writeEntry("backgroundtextures-1.6-plus", m_matrixBackgroundTextures->isChecked()); + + m_cfg->setGroup(NotationViewConfigGroup); + m_cfg->writeEntry("backgroundtextures", m_notationBackgroundTextures->isChecked()); + + m_cfg->setGroup(GeneralOptionsConfigGroup); + + int sidebarStyle = m_sidebarStyle->currentItem(); + m_cfg->writeEntry("sidebarstyle", sidebarStyle); + emit updateSidebarStyle(sidebarStyle); + + unsigned int interval = 0; + + if (m_autoSave->currentItem() == 4) { + m_cfg->writeEntry("autosave", false); + } else { + m_cfg->writeEntry("autosave", true); + if (m_autoSave->currentItem() == 0) { + interval = 30; + } else if (m_autoSave->currentItem() == 1) { + interval = 60; + } else if (m_autoSave->currentItem() == 2) { + interval = 300; + } else { + interval = 1800; + } + m_cfg->writeEntry("autosaveinterval", interval); + emit updateAutoSaveInterval(interval); + } + +#ifdef HAVE_LIBJACK + m_cfg->setGroup(SequencerOptionsConfigGroup); + + // Write the JACK entry + // +/* + int jackValue = m_jackTransport->currentItem(); + bool jackTransport, jackMaster; + + switch (jackValue) { + case 2: + jackTransport = true; + jackMaster = true; + break; + + case 1: + jackTransport = true; + jackMaster = false; + break; + + default: + jackValue = 0; + + case 0: + jackTransport = false; + jackMaster = false; + break; + } +*/ + + bool jackTransport = m_jackTransport->isChecked(); + bool jackMaster = false; + + int jackValue = 0; // 0 -> nothing, 1 -> sync, 2 -> master + if (jackTransport) jackValue = 1; + + // Write the items + // + m_cfg->writeEntry("jacktransport", jackTransport); + m_cfg->writeEntry("jackmaster", jackMaster); + + // Now send it + // + MappedEvent mEjackValue(MidiInstrumentBase, // InstrumentId + MappedEvent::SystemJackTransport, + MidiByte(jackValue)); + + StudioControl::sendMappedEvent(mEjackValue); +#endif // HAVE_LIBJACK + + if (mainTextureChanged) { + KMessageBox::information(this, i18n("Changes to the textured background in the main window will not take effect until you restart Rosegarden.")); + } + +} + +} +#include "GeneralConfigurationPage.moc" diff --git a/src/gui/configuration/GeneralConfigurationPage.h b/src/gui/configuration/GeneralConfigurationPage.h new file mode 100644 index 0000000..7d3203d --- /dev/null +++ b/src/gui/configuration/GeneralConfigurationPage.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_GENERALCONFIGURATIONPAGE_H_ +#define _RG_GENERALCONFIGURATIONPAGE_H_ + +#include "TabbedConfigurationPage.h" +#include "gui/editors/eventlist/EventView.h" +#include +#include +#include +#include +#include +#include +#include + +class QWidget; +class KConfig; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +/** + * General Rosegarden Configuration page + * + * (application-wide settings) + */ +class GeneralConfigurationPage : public TabbedConfigurationPage +{ + Q_OBJECT + +public: + enum DoubleClickClient + { + NotationView, + MatrixView, + EventView + }; + + enum NoteNameStyle + { + American, + Local + }; + + GeneralConfigurationPage(RosegardenGUIDoc *doc, + KConfig *cfg, + QWidget *parent=0, const char *name=0); + + virtual void apply(); + + static QString iconLabel() { return i18n("General"); } + static QString title() { return i18n("General Configuration"); } + static QString iconName() { return "configure-general"; } + +signals: + void updateAutoSaveInterval(unsigned int); + void updateSidebarStyle(unsigned int); + +protected slots: + void slotShowStatus(); + +protected: + int getCountInSpin() { return m_countIn->value(); } + int getDblClickClient() { return m_client->currentItem(); } + int getNoteNameStyle() { return m_nameStyle->currentItem(); } + + //--------------- Data members --------------------------------- + RosegardenGUIDoc* m_doc; + + QComboBox* m_client; + QSpinBox* m_countIn; + QCheckBox* m_toolContextHelp; + QCheckBox* m_backgroundTextures; + QCheckBox* m_notationBackgroundTextures; + QCheckBox* m_matrixBackgroundTextures; + QComboBox *m_autoSave; + QComboBox* m_nameStyle; + QComboBox* m_sidebarStyle; + QComboBox* m_globalStyle; + QCheckBox *m_jackTransport; + +}; + + + + +} + +#endif diff --git a/src/gui/configuration/HeadersConfigurationPage.cpp b/src/gui/configuration/HeadersConfigurationPage.cpp new file mode 100644 index 0000000..0571fb5 --- /dev/null +++ b/src/gui/configuration/HeadersConfigurationPage.cpp @@ -0,0 +1,294 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "HeadersConfigurationPage.h" + +#include "document/ConfigGroups.h" +#include "document/RosegardenGUIDoc.h" +#include "document/io/LilyPondExporter.h" +#include "gui/widgets/CollapsingFrame.h" +#include "misc/Strings.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Rosegarden +{ + +HeadersConfigurationPage::HeadersConfigurationPage(QWidget *parent, + RosegardenGUIDoc *doc) : + QVBox(parent), + m_doc(doc) +{ + // + // LilyPond export: Printable headers + // + + QGroupBox *headersBox = new QGroupBox + (1, Horizontal, + i18n("Printable headers"), this); + QFrame *frameHeaders = new QFrame(headersBox); + QGridLayout *layoutHeaders = new QGridLayout(frameHeaders, 10, 6, 10, 5); + + // grab user headers from metadata + Configuration metadata = (&m_doc->getComposition())->getMetadata(); + std::vector propertyNames = metadata.getPropertyNames(); + std::vector fixedKeys = + CompositionMetadataKeys::getFixedKeys(); + + std::set shown; + + for (unsigned int index = 0; index < fixedKeys.size(); index++) { + std::string key = fixedKeys[index].getName(); + std::string header = ""; + for (unsigned int i = 0; i < propertyNames.size(); ++i) { + std::string property = propertyNames [i]; + if (property == key) { + header = metadata.get(property); + } + } + + unsigned int row = 0, col = 0, width = 1; + QLineEdit *editHeader = new QLineEdit(strtoqstr( header ), frameHeaders); + QString trName; + if (key == headerDedication) { + m_editDedication = editHeader; + row = 0; col = 2; width = 2; + trName = i18n("Dedication"); + } else if (key == headerTitle) { + m_editTitle = editHeader; + row = 1; col = 1; width = 4; + trName = i18n("Title"); + } else if (key == headerSubtitle) { + m_editSubtitle = editHeader; + row = 2; col = 1; width = 4; + trName = i18n("Subtitle"); + } else if (key == headerSubsubtitle) { + m_editSubsubtitle = editHeader; + row = 3; col = 2; width = 2; + trName = i18n("Subsubtitle"); + } else if (key == headerPoet) { + m_editPoet = editHeader; + row = 4; col = 0; width = 2; + trName = i18n("Poet"); + } else if (key == headerInstrument) { + m_editInstrument = editHeader; + row = 4; col = 2; width = 2; + trName = i18n("Instrument"); + } else if (key == headerComposer) { + m_editComposer = editHeader; + row = 4; col = 4; width = 2; + trName = i18n("Composer"); + } else if (key == headerMeter) { + m_editMeter = editHeader; + row = 5; col = 0; width = 3; + trName = i18n("Meter"); + } else if (key == headerArranger) { + m_editArranger = editHeader; + row = 5; col = 3; width = 3; + trName = i18n("Arranger"); + } else if (key == headerPiece) { + m_editPiece = editHeader; + row = 6; col = 0; width = 3; + trName = i18n("Piece"); + } else if (key == headerOpus) { + m_editOpus = editHeader; + row = 6; col = 3; width = 3; + trName = i18n("Opus"); + } else if (key == headerCopyright) { + m_editCopyright = editHeader; + row = 8; col = 1; width = 4; + trName = i18n("Copyright"); + } else if (key == headerTagline) { + m_editTagline = editHeader; + row = 9; col = 1; width = 4; + trName = i18n("Tagline"); + } + + // editHeader->setReadOnly( true ); + editHeader->setAlignment( (col == 0 ? Qt::AlignLeft : (col >= 3 ? Qt::AlignRight : Qt::AlignCenter) )); + + layoutHeaders->addMultiCellWidget(editHeader, row, row, col, col+(width-1) ); + + // + // ToolTips + // + QToolTip::add( editHeader, trName ); + + shown.insert(key); + } + QLabel *separator = new QLabel(i18n("The composition comes here."), frameHeaders); + separator->setAlignment( Qt::AlignCenter ); + layoutHeaders->addMultiCellWidget(separator, 7, 7, 1, 4 ); + + // + // LilyPond export: Non-printable headers + // + + // set default expansion to false for this group -- what a faff + KConfig *config = kapp->config(); + QString groupTemp = config->group(); + config->setGroup("CollapsingFrame"); + bool expanded = config->readBoolEntry("nonprintableheaders", false); + config->writeEntry("nonprintableheaders", expanded); + config->setGroup(groupTemp); + + CollapsingFrame *otherHeadersBox = new CollapsingFrame + (i18n("Non-printable headers"), this, "nonprintableheaders"); + QFrame *frameOtherHeaders = new QFrame(otherHeadersBox); + otherHeadersBox->setWidgetFill(true); + QFont font(otherHeadersBox->font()); + font.setBold(false); + otherHeadersBox->setFont(font); + otherHeadersBox->setWidget(frameOtherHeaders); + + QGridLayout *layoutOtherHeaders = new QGridLayout(frameOtherHeaders, 2, 2, 10, 5); + + m_metadata = new KListView(frameOtherHeaders); + m_metadata->addColumn(i18n("Name")); + m_metadata->addColumn(i18n("Value")); + m_metadata->setFullWidth(true); + m_metadata->setItemsRenameable(true); + m_metadata->setRenameable(0); + m_metadata->setRenameable(1); + m_metadata->setItemMargin(5); + m_metadata->setDefaultRenameAction(QListView::Accept); + m_metadata->setShowSortIndicator(true); + + std::vector names(metadata.getPropertyNames()); + + for (unsigned int i = 0; i < names.size(); ++i) { + + if (shown.find(names[i]) != shown.end()) + continue; + + QString name(strtoqstr(names[i])); + + // property names stored in lower case + name = name.left(1).upper() + name.right(name.length() - 1); + + new KListViewItem(m_metadata, name, + strtoqstr(metadata.get(names[i]))); + + shown.insert(names[i]); + } + + layoutOtherHeaders->addMultiCellWidget(m_metadata, 0, 0, 0, 1); + + QPushButton* addPropButton = new QPushButton(i18n("Add New Property"), + frameOtherHeaders); + layoutOtherHeaders->addWidget(addPropButton, 1, 0, Qt::AlignHCenter); + + QPushButton* deletePropButton = new QPushButton(i18n("Delete Property"), + frameOtherHeaders); + layoutOtherHeaders->addWidget(deletePropButton, 1, 1, Qt::AlignHCenter); + + connect(addPropButton, SIGNAL(clicked()), + this, SLOT(slotAddNewProperty())); + + connect(deletePropButton, SIGNAL(clicked()), + this, SLOT(slotDeleteProperty())); +} + +void +HeadersConfigurationPage::slotAddNewProperty() +{ + QString propertyName; + int i = 0; + + while (1) { + propertyName = + (i > 0 ? i18n("{new property %1}").arg(i) : i18n("{new property}")); + if (!m_doc->getComposition().getMetadata().has(qstrtostr(propertyName)) && + m_metadata->findItem(qstrtostr(propertyName),0) == 0) + break; + ++i; + } + + new KListViewItem(m_metadata, propertyName, i18n("{undefined}")); +} + +void +HeadersConfigurationPage::slotDeleteProperty() +{ + delete m_metadata->currentItem(); +} + +void HeadersConfigurationPage::apply() +{ + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + + // If one of the items still has focus, it won't remember edits. + // Switch between two fields in order to lose the current focus. + m_editTitle->setFocus(); + m_metadata->setFocus(); + + // + // Update header fields + // + + Configuration &metadata = (&m_doc->getComposition())->getMetadata(); + metadata.clear(); + + metadata.set(CompositionMetadataKeys::Dedication, qstrtostr(m_editDedication->text())); + metadata.set(CompositionMetadataKeys::Title, qstrtostr(m_editTitle->text())); + metadata.set(CompositionMetadataKeys::Subtitle, qstrtostr(m_editSubtitle->text())); + metadata.set(CompositionMetadataKeys::Subsubtitle, qstrtostr(m_editSubsubtitle->text())); + metadata.set(CompositionMetadataKeys::Poet, qstrtostr(m_editPoet->text())); + metadata.set(CompositionMetadataKeys::Composer, qstrtostr(m_editComposer->text())); + metadata.set(CompositionMetadataKeys::Meter, qstrtostr(m_editMeter->text())); + metadata.set(CompositionMetadataKeys::Opus, qstrtostr(m_editOpus->text())); + metadata.set(CompositionMetadataKeys::Arranger, qstrtostr(m_editArranger->text())); + metadata.set(CompositionMetadataKeys::Instrument, qstrtostr(m_editInstrument->text())); + metadata.set(CompositionMetadataKeys::Piece, qstrtostr(m_editPiece->text())); + metadata.set(CompositionMetadataKeys::Copyright, qstrtostr(m_editCopyright->text())); + metadata.set(CompositionMetadataKeys::Tagline, qstrtostr(m_editTagline->text())); + + for (QListViewItem *item = m_metadata->firstChild(); + item != 0; item = item->nextSibling()) { + + metadata.set(qstrtostr(item->text(0).lower()), + qstrtostr(item->text(1))); + } + + m_doc->slotDocumentModified(); +} + +} +#include "HeadersConfigurationPage.moc" diff --git a/src/gui/configuration/HeadersConfigurationPage.h b/src/gui/configuration/HeadersConfigurationPage.h new file mode 100644 index 0000000..403d412 --- /dev/null +++ b/src/gui/configuration/HeadersConfigurationPage.h @@ -0,0 +1,80 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_HEADERSCONFIGURATIONPAGE_H_ +#define _RG_HEADERSCONFIGURATIONPAGE_H_ + +#include + +class QVBox; +class QWidget; +class QLineEdit; +class KListView; + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + +class HeadersConfigurationPage : public QVBox +{ + Q_OBJECT + +public: + HeadersConfigurationPage(QWidget *parent = 0, + RosegardenGUIDoc *doc = 0); + +public slots: + void apply(); + +protected slots: + void slotAddNewProperty(); + void slotDeleteProperty(); + +protected: + RosegardenGUIDoc *m_doc; + + // Header fields + QLineEdit *m_editDedication; + QLineEdit *m_editTitle; + QLineEdit *m_editSubtitle; + QLineEdit *m_editSubsubtitle; + QLineEdit *m_editPoet; + QLineEdit *m_editComposer; + QLineEdit *m_editMeter; + QLineEdit *m_editOpus; + QLineEdit *m_editArranger; + QLineEdit *m_editInstrument; + QLineEdit *m_editPiece; + QLineEdit *m_editCopyright; + QLineEdit *m_editTagline; + + KListView *m_metadata; +}; + + +} + +#endif diff --git a/src/gui/configuration/LatencyConfigurationPage.cpp b/src/gui/configuration/LatencyConfigurationPage.cpp new file mode 100644 index 0000000..ff89edb --- /dev/null +++ b/src/gui/configuration/LatencyConfigurationPage.cpp @@ -0,0 +1,157 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "LatencyConfigurationPage.h" +#include + +#include "document/ConfigGroups.h" +#include "ConfigurationPage.h" +#include "document/RosegardenGUIDoc.h" +#include "TabbedConfigurationPage.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +LatencyConfigurationPage::LatencyConfigurationPage(RosegardenGUIDoc *doc, + KConfig *cfg, + QWidget *parent, + const char *name) + : TabbedConfigurationPage(doc, cfg, parent, name) +{ + // Configuration &config = doc->getConfiguration(); + m_cfg->setGroup(LatencyOptionsConfigGroup); + +#ifdef NOT_DEFINED +#ifdef HAVE_LIBJACK + + frame = new QFrame(m_tabWidget, i18n("JACK latency")); + layout = new QGridLayout(frame, 6, 5, 10, 10); + + layout->addMultiCellWidget(new QLabel(i18n("Use the \"Fetch JACK latencies\" button to discover the latency values set at\nthe sequencer. It's recommended that you use the returned values but it's also\npossible to override them manually using the sliders. Note that if you change\nyour JACK server parameters you should always fetch the latency values again.\nThe latency values will be stored by Rosegarden for use next time."), frame), + 0, 0, + 0, 3); + + layout->addWidget(new QLabel(i18n("JACK playback latency (in ms)"), frame), 1, 0); + layout->addWidget(new QLabel(i18n("JACK record latency (in ms)"), frame), 3, 0); + + m_fetchLatencyValues = new QPushButton(i18n("Fetch JACK latencies"), + frame); + + layout->addWidget(m_fetchLatencyValues, 1, 3); + + connect(m_fetchLatencyValues, SIGNAL(released()), + SLOT(slotFetchLatencyValues())); + + int jackPlaybackValue = (m_cfg->readLongNumEntry( + "jackplaybacklatencyusec", 0) / 1000) + + (m_cfg->readLongNumEntry( + "jackplaybacklatencysec", 0) * 1000); + + m_jackPlayback = new QSlider(Horizontal, frame); + m_jackPlayback->setTickmarks(QSlider::Below); + layout->addMultiCellWidget(m_jackPlayback, 3, 3, 2, 3); + + QLabel *jackPlaybackLabel = new QLabel(QString("%1").arg(jackPlaybackValue), + frame); + layout->addWidget(jackPlaybackLabel, 2, 2, Qt::AlignHCenter); + connect(m_jackPlayback, SIGNAL(valueChanged(int)), + jackPlaybackLabel, SLOT(setNum(int))); + + m_jackPlayback->setMinValue(0); + layout->addWidget(new QLabel("0", frame), 3, 1, Qt::AlignRight); + + m_jackPlayback->setMaxValue(500); + layout->addWidget(new QLabel("500", frame), 3, 4, Qt::AlignLeft); + + m_jackPlayback->setValue(jackPlaybackValue); + + int jackRecordValue = (m_cfg->readLongNumEntry( + "jackrecordlatencyusec", 0) / 1000) + + (m_cfg->readLongNumEntry( + "jackrecordlatencysec", 0) * 1000); + + m_jackRecord = new QSlider(Horizontal, frame); + m_jackRecord->setTickmarks(QSlider::Below); + layout->addMultiCellWidget(m_jackRecord, 5, 5, 2, 3); + + QLabel *jackRecordLabel = new QLabel(QString("%1").arg(jackRecordValue), + frame); + layout->addWidget(jackRecordLabel, 4, 2, Qt::AlignHCenter); + connect(m_jackRecord, SIGNAL(valueChanged(int)), + jackRecordLabel, SLOT(setNum(int))); + + m_jackRecord->setMinValue(0); + layout->addWidget(new QLabel("0", frame), 5, 1, Qt::AlignRight); + + m_jackRecord->setMaxValue(500); + m_jackRecord->setValue(jackRecordValue); + layout->addWidget(new QLabel("500", frame), 5, 4, Qt::AlignLeft); + + addTab(frame, i18n("JACK Latency")); +#endif // HAVE_LIBJACK +#endif // NOT_DEFINED + +} + +void LatencyConfigurationPage::apply() +{ + m_cfg->setGroup(LatencyOptionsConfigGroup); + +#ifdef HAVE_LIBJACK + + int jackPlayback = getJACKPlaybackValue(); + m_cfg->writeEntry("jackplaybacklatencysec", jackPlayback / 1000); + m_cfg->writeEntry("jackplaybacklatencyusec", jackPlayback * 1000); + + int jackRecord = getJACKRecordValue(); + m_cfg->writeEntry("jackrecordlatencysec", jackRecord / 1000); + m_cfg->writeEntry("jackrecordlatencyusec", jackRecord * 1000); + +#endif // HAVE_LIBJACK +} + +void LatencyConfigurationPage::slotFetchLatencyValues() +{ + int jackPlaybackValue = m_doc->getAudioPlayLatency().msec() + + m_doc->getAudioPlayLatency().sec * 1000; + + m_jackPlayback->setValue(jackPlaybackValue); + + int jackRecordValue = m_doc->getAudioRecordLatency().msec() + + m_doc->getAudioRecordLatency().sec * 1000; + m_jackRecord->setValue(jackRecordValue); +} + +} +#include "LatencyConfigurationPage.moc" diff --git a/src/gui/configuration/LatencyConfigurationPage.h b/src/gui/configuration/LatencyConfigurationPage.h new file mode 100644 index 0000000..6caba88 --- /dev/null +++ b/src/gui/configuration/LatencyConfigurationPage.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_LATENCYCONFIGURATIONPAGE_H_ +#define _RG_LATENCYCONFIGURATIONPAGE_H_ + +#include "TabbedConfigurationPage.h" +#include +#include +#include + + +class QWidget; +class QPushButton; +class KConfig; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +/** + * Latency Configuration page + * + * (application-wide settings) + */ +class LatencyConfigurationPage : public TabbedConfigurationPage +{ + Q_OBJECT + +public: + LatencyConfigurationPage(RosegardenGUIDoc *doc, + KConfig *cfg, + QWidget *parent=0, const char *name=0); + + virtual void apply(); + + static QString iconLabel() { return i18n("Latency"); } + static QString title() { return i18n("Sequencer Latency"); } + + int getJACKPlaybackValue() { return m_jackPlayback->value(); } + int getJACKRecordValue() { return m_jackRecord->value(); } + +protected slots: + // Get the latest latency values from the sequencer + // + void slotFetchLatencyValues(); + +protected: + + //--------------- Data members --------------------------------- + + QSlider* m_jackPlayback; + QSlider* m_jackRecord; + + QPushButton* m_fetchLatencyValues; +}; + + + +} + +#endif diff --git a/src/gui/configuration/MIDIConfigurationPage.cpp b/src/gui/configuration/MIDIConfigurationPage.cpp new file mode 100644 index 0000000..3d46841 --- /dev/null +++ b/src/gui/configuration/MIDIConfigurationPage.cpp @@ -0,0 +1,400 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MIDIConfigurationPage.h" + +#include "sound/Midi.h" +#include "sound/SoundDriver.h" +#include "document/ConfigGroups.h" +#include "base/MidiProgram.h" +#include "base/Studio.h" +#include "ConfigurationPage.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/dialogs/ShowSequencerStatusDialog.h" +#include "gui/seqmanager/SequenceManager.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/studio/StudioControl.h" +#include "sound/MappedEvent.h" +#include "TabbedConfigurationPage.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Rosegarden +{ + +MIDIConfigurationPage::MIDIConfigurationPage( + RosegardenGUIDoc *doc, + KConfig *cfg, + QWidget *parent, + const char *name): + TabbedConfigurationPage(cfg, parent, name), + m_midiPitchOctave(0) +{ + // set the document in the super class + m_doc = doc; + + // ---------------- General tab ------------------ + // + QFrame *frame = new QFrame(m_tabWidget); + QGridLayout *layout = new QGridLayout(frame, 9, 4, 10, 5); + + int row = 0; + + layout->setRowSpacing(row, 15); + ++row; + + QLabel *label = 0; + + m_cfg->setGroup(GeneralOptionsConfigGroup); + + layout->addMultiCellWidget(new QLabel(i18n("Base octave number for MIDI pitch display"), + frame), row, row, 0, 1); + + m_midiPitchOctave = new QSpinBox(frame); + m_midiPitchOctave->setMaxValue(10); + m_midiPitchOctave->setMinValue( -10); + m_midiPitchOctave->setValue(m_cfg->readNumEntry("midipitchoctave", -2)); + layout->addMultiCellWidget(m_midiPitchOctave, row, row, 2, 3); + ++row; + + layout->setRowSpacing(row, 20); + ++row; + + m_cfg->setGroup(GeneralOptionsConfigGroup); + + layout->addMultiCellWidget(new QLabel(i18n("Always use default studio when loading files"), + frame), row, row, 0, 1); + + m_studio = new QCheckBox(frame); + m_studio->setChecked(m_cfg->readBoolEntry("alwaysusedefaultstudio", false)); + layout->addWidget(m_studio, row, 2); + ++row; + + // Send Controllers + // + m_cfg->setGroup(SequencerOptionsConfigGroup); + + label = new QLabel(i18n("Send all MIDI Controllers at start of each playback"), frame); + + QString controllerTip = i18n("Rosegarden can send all MIDI Controllers (Pan, Reverb etc) to all MIDI devices every\ntime you hit play if you so wish. Please note that this option will usually incur a\ndelay at the start of playback due to the amount of data being transmitted."); + QToolTip::add + (label, controllerTip); + layout->addMultiCellWidget(label, row, row, 0, 1); + + m_sendControllersAtPlay = new QCheckBox(frame); + bool sendControllers = m_cfg->readBoolEntry("alwayssendcontrollers", false); + m_sendControllersAtPlay->setChecked(sendControllers); + QToolTip::add + (m_sendControllersAtPlay, controllerTip); + layout->addWidget(m_sendControllersAtPlay, row, 2); + ++row; + + // Timer selection + // + m_cfg->setGroup(SequencerOptionsConfigGroup); + + label = new QLabel(i18n("Sequencer timing source"), frame); + layout->addMultiCellWidget(label, row, row, 0, 1); + + m_timer = new KComboBox(frame); + layout->addMultiCellWidget(m_timer, row, row, 2, 3); + + QStringList timers = m_doc->getTimers(); + m_origTimer = m_doc->getCurrentTimer(); + QString currentTimer = m_cfg->readEntry("timer", m_origTimer); + + for (unsigned int i = 0; i < timers.size(); ++i) { + m_timer->insertItem(timers[i]); + if (timers[i] == currentTimer) + m_timer->setCurrentItem(i); + } + + ++row; + + layout->setRowSpacing(row, 20); + ++row; + + m_cfg->setGroup(SequencerOptionsConfigGroup); + + // SoundFont loading + // + QLabel* lbl = new QLabel(i18n("Load SoundFont to SoundBlaster card at startup"), frame); + QString tooltip = i18n("Check this box to enable soundfont loading on EMU10K-based cards when Rosegarden is launched"); + QToolTip::add(lbl, tooltip); + layout->addMultiCellWidget(lbl, row, row, 0, 1); + + m_sfxLoadEnabled = new QCheckBox(frame); + layout->addWidget(m_sfxLoadEnabled, row, 2); + QToolTip::add(m_sfxLoadEnabled, tooltip); + ++row; + + layout->addWidget(new QLabel(i18n("Path to 'asfxload' or 'sfxload' command"), frame), row, 0); + m_sfxLoadPath = new QLineEdit(m_cfg->readEntry("sfxloadpath", "/bin/sfxload"), frame); + layout->addMultiCellWidget(m_sfxLoadPath, row, row, 1, 2); + m_sfxLoadChoose = new QPushButton("Choose...", frame); + layout->addWidget(m_sfxLoadChoose, row, 3); + ++row; + + layout->addWidget(new QLabel(i18n("SoundFont"), frame), row, 0); + m_soundFontPath = new QLineEdit(m_cfg->readEntry("soundfontpath", ""), frame); + layout->addMultiCellWidget(m_soundFontPath, row, row, 1, 2); + m_soundFontChoose = new QPushButton("Choose...", frame); + layout->addWidget(m_soundFontChoose, row, 3); + ++row; + + bool sfxLoadEnabled = m_cfg->readBoolEntry("sfxloadenabled", false); + m_sfxLoadEnabled->setChecked(sfxLoadEnabled); + if (!sfxLoadEnabled) { + m_sfxLoadPath->setEnabled(false); + m_sfxLoadChoose->setEnabled(false); + m_soundFontPath->setEnabled(false); + m_soundFontChoose->setEnabled(false); + } + + connect(m_sfxLoadEnabled, SIGNAL(toggled(bool)), + this, SLOT(slotSoundFontToggled(bool))); + + connect(m_sfxLoadChoose, SIGNAL(clicked()), + this, SLOT(slotSfxLoadPathChoose())); + + connect(m_soundFontChoose, SIGNAL(clicked()), + this, SLOT(slotSoundFontChoose())); + + layout->setRowStretch(row, 10); + + addTab(frame, i18n("General")); + + m_cfg->setGroup(SequencerOptionsConfigGroup); + + // -------------- Synchronisation tab ----------------- + // + frame = new QFrame(m_tabWidget); + layout = new QGridLayout(frame, 7, 2, 10, 5); + + row = 0; + + layout->setRowSpacing(row, 15); + ++row; + + // MIDI Clock and System Realtime Messages + // + label = new QLabel(i18n("MIDI Clock and System messages"), frame); + layout->addWidget(label, row, 0); + m_midiSync = new KComboBox(frame); + layout->addWidget(m_midiSync, row, 1); + + m_midiSync->insertItem(i18n("Off")); + m_midiSync->insertItem(i18n("Send MIDI Clock, Start and Stop")); + m_midiSync->insertItem(i18n("Accept Start, Stop and Continue")); + + int midiClock = m_cfg->readNumEntry("midiclock", 0); + if (midiClock < 0 || midiClock > 2) + midiClock = 0; + m_midiSync->setCurrentItem(midiClock); + + ++row; + + // MMC Transport + // + label = new QLabel(i18n("MIDI Machine Control mode"), frame); + layout->addWidget(label, row, 0); + + m_mmcTransport = new KComboBox(frame); + layout->addWidget(m_mmcTransport, row, 1); //, Qt::AlignHCenter); + + m_mmcTransport->insertItem(i18n("Off")); + m_mmcTransport->insertItem(i18n("MMC Master")); + m_mmcTransport->insertItem(i18n("MMC Slave")); + + int mmcMode = m_cfg->readNumEntry("mmcmode", 0); + if (mmcMode < 0 || mmcMode > 2) + mmcMode = 0; + m_mmcTransport->setCurrentItem(mmcMode); + + ++row; + + // MTC transport + // + label = new QLabel(i18n("MIDI Time Code mode"), frame); + layout->addWidget(label, row, 0); + + m_mtcTransport = new KComboBox(frame); + layout->addWidget(m_mtcTransport, row, 1); + + m_mtcTransport->insertItem(i18n("Off")); + m_mtcTransport->insertItem(i18n("MTC Master")); + m_mtcTransport->insertItem(i18n("MTC Slave")); + + int mtcMode = m_cfg->readNumEntry("mtcmode", 0); + if (mtcMode < 0 || mtcMode > 2) + mtcMode = 0; + m_mtcTransport->setCurrentItem(mtcMode); + + ++row; + + QHBox *hbox = new QHBox(frame); + hbox->setSpacing(5); + layout->addMultiCellWidget(hbox, row, row, 0, 1); + + label = new QLabel(i18n("Automatically connect sync output to all devices in use"), hbox); +// layout->addWidget(label, row, 0); + m_midiSyncAuto = new QCheckBox(hbox); +// layout->addWidget(m_midiSyncAuto, row, 1); + + m_midiSyncAuto->setChecked(m_cfg->readBoolEntry("midisyncautoconnect", false)); + + ++row; + + layout->setRowStretch(row, 10); + + addTab(frame, i18n("MIDI Sync")); +} + + +void +MIDIConfigurationPage::slotSoundFontToggled(bool isChecked) +{ + m_sfxLoadPath->setEnabled(isChecked); + m_sfxLoadChoose->setEnabled(isChecked); + m_soundFontPath->setEnabled(isChecked); + m_soundFontChoose->setEnabled(isChecked); +} + +void +MIDIConfigurationPage::slotSfxLoadPathChoose() +{ + QString path = KFileDialog::getOpenFileName(":SFXLOAD", QString::null, this, i18n("sfxload path")); + m_sfxLoadPath->setText(path); +} + +void +MIDIConfigurationPage::slotSoundFontChoose() +{ + QString path = KFileDialog::getOpenFileName(":SOUNDFONTS", "*.sb *.sf2 *.SF2 *.SB", this, i18n("Soundfont path")); + m_soundFontPath->setText(path); +} + +void +MIDIConfigurationPage::apply() +{ + m_cfg->setGroup(SequencerOptionsConfigGroup); + + m_cfg->writeEntry("alwayssendcontrollers", + m_sendControllersAtPlay->isChecked()); + + m_cfg->writeEntry("sfxloadenabled", m_sfxLoadEnabled->isChecked()); + m_cfg->writeEntry("sfxloadpath", m_sfxLoadPath->text()); + m_cfg->writeEntry("soundfontpath", m_soundFontPath->text()); + + m_cfg->writeEntry("timer", m_timer->currentText()); + if (m_timer->currentText() != m_origTimer) { + m_doc->setCurrentTimer(m_timer->currentText()); + } + + // Write the entries + // + m_cfg->writeEntry("mmcmode", m_mmcTransport->currentItem()); + m_cfg->writeEntry("mtcmode", m_mtcTransport->currentItem()); + m_cfg->writeEntry("midisyncautoconnect", m_midiSyncAuto->isChecked()); + + // Now send + // + MappedEvent mEmccValue(MidiInstrumentBase, // InstrumentId + MappedEvent::SystemMMCTransport, + MidiByte(m_mmcTransport->currentItem())); + + StudioControl::sendMappedEvent(mEmccValue); + + MappedEvent mEmtcValue(MidiInstrumentBase, // InstrumentId + MappedEvent::SystemMTCTransport, + MidiByte(m_mtcTransport->currentItem())); + + StudioControl::sendMappedEvent(mEmtcValue); + + MappedEvent mEmsaValue(MidiInstrumentBase, // InstrumentId + MappedEvent::SystemMIDISyncAuto, + MidiByte(m_midiSyncAuto->isChecked() ? 1 : 0)); + + StudioControl::sendMappedEvent(mEmsaValue); + + + // ------------- MIDI Clock and System messages ------------ + // + int midiClock = m_midiSync->currentItem(); + m_cfg->writeEntry("midiclock", midiClock); + + // Now send it (OLD METHOD - to be removed) + //!!! No, don't remove -- this controls SPP as well doesn't it? + // + MappedEvent mEMIDIClock(MidiInstrumentBase, // InstrumentId + MappedEvent::SystemMIDIClock, + MidiByte(midiClock)); + + StudioControl::sendMappedEvent(mEMIDIClock); + + + // Now update the metronome mapped segment with new clock ticks + // if needed. + // + Studio &studio = m_doc->getStudio(); + const MidiMetronome *metronome = studio. + getMetronomeFromDevice(studio.getMetronomeDevice()); + + if (metronome) { + InstrumentId instrument = metronome->getInstrument(); + m_doc->getSequenceManager()->metronomeChanged(instrument, true); + } + + m_cfg->setGroup(GeneralOptionsConfigGroup); + + bool deftstudio = getUseDefaultStudio(); + m_cfg->writeEntry("alwaysusedefaultstudio", deftstudio); + + int octave = m_midiPitchOctave->value(); + m_cfg->writeEntry("midipitchoctave", octave); +} + +} +#include "MIDIConfigurationPage.moc" diff --git a/src/gui/configuration/MIDIConfigurationPage.h b/src/gui/configuration/MIDIConfigurationPage.h new file mode 100644 index 0000000..243f041 --- /dev/null +++ b/src/gui/configuration/MIDIConfigurationPage.h @@ -0,0 +1,104 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MIDICONFIGURATIONPAGE_H_ +#define _RG_MIDICONFIGURATIONPAGE_H_ + +#include "TabbedConfigurationPage.h" +#include +#include +#include +#include + + +class QWidget; +class QSpinBox; +class QSlider; +class QPushButton; +class QLabel; +class QComboBox; +class KConfig; +class KComboBox; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +class MIDIConfigurationPage : public TabbedConfigurationPage +{ + Q_OBJECT +public: + MIDIConfigurationPage(RosegardenGUIDoc *doc, + KConfig *cfg, + QWidget *parent=0, + const char *name=0); + + virtual void apply(); + + static QString iconLabel() { return i18n("MIDI"); } + static QString title() { return i18n("MIDI Settings"); } + static QString iconName() { return "configure-midi"; } + +protected slots: + void slotSoundFontToggled(bool); + void slotSfxLoadPathChoose(); + void slotSoundFontChoose(); + +protected: + bool getUseDefaultStudio() { return m_studio->isChecked(); } + + //--------------- Data members --------------------------------- + + // General + QCheckBox *m_sendControllersAtPlay; + + QCheckBox *m_sfxLoadEnabled; + QLineEdit *m_sfxLoadPath; + QPushButton *m_sfxLoadChoose; + QLineEdit *m_soundFontPath; + QPushButton *m_soundFontChoose; + + // Sync and timing + // + //QCheckBox *m_midiClockEnabled; + QComboBox *m_midiSync; + QString m_origTimer; + QComboBox *m_timer; + QComboBox *m_mmcTransport; + QComboBox *m_mtcTransport; + QCheckBox *m_midiSyncAuto; + + QCheckBox* m_studio; + QSpinBox* m_midiPitchOctave; + +}; + + + +} + +#endif diff --git a/src/gui/configuration/MatrixConfigurationPage.cpp b/src/gui/configuration/MatrixConfigurationPage.cpp new file mode 100644 index 0000000..e21f3fd --- /dev/null +++ b/src/gui/configuration/MatrixConfigurationPage.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MatrixConfigurationPage.h" + +#include "document/ConfigGroups.h" +#include "ConfigurationPage.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "gui/editors/matrix/MatrixView.h" +#include "TabbedConfigurationPage.h" +#include +#include +#include +#include +#include +#include +#include + +namespace Rosegarden +{ + +MatrixConfigurationPage::MatrixConfigurationPage(KConfig *cfg, + QWidget *parent, + const char *name) : + TabbedConfigurationPage(cfg, parent, name) +{ + m_cfg->setGroup(MatrixViewConfigGroup); + + QFrame *frame = new QFrame(m_tabWidget); + QGridLayout *layout = new QGridLayout(frame, + 4, 2, // nbrow, nbcol + 10, 5); + + layout->addWidget(new QLabel("Nothing here yet", frame), 0, 0); + + addTab(frame, i18n("General")); +} + +void MatrixConfigurationPage::apply() +{ + m_cfg->setGroup(MatrixViewConfigGroup); +} + +} +#include "MatrixConfigurationPage.moc" diff --git a/src/gui/configuration/MatrixConfigurationPage.h b/src/gui/configuration/MatrixConfigurationPage.h new file mode 100644 index 0000000..9c4b3fc --- /dev/null +++ b/src/gui/configuration/MatrixConfigurationPage.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MATRIXCONFIGURATIONPAGE_H_ +#define _RG_MATRIXCONFIGURATIONPAGE_H_ + +#include "TabbedConfigurationPage.h" +#include +#include + + +class QWidget; +class KConfig; + + +namespace Rosegarden +{ + + + +/** + * Notation Configuration page + */ +class MatrixConfigurationPage : public TabbedConfigurationPage +{ + Q_OBJECT + +public: + MatrixConfigurationPage(KConfig *cfg, + QWidget *parent = 0, const char *name=0); + + virtual void apply(); + + static QString iconLabel() { return i18n("Matrix"); } + static QString title() { return i18n("Matrix"); } + +protected slots: + +protected: + + //--------------- Data members --------------------------------- +}; + + +} + +#endif diff --git a/src/gui/configuration/NotationConfigurationPage.cpp b/src/gui/configuration/NotationConfigurationPage.cpp new file mode 100644 index 0000000..a828fe7 --- /dev/null +++ b/src/gui/configuration/NotationConfigurationPage.cpp @@ -0,0 +1,741 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "NotationConfigurationPage.h" +#include + +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/Exception.h" +#include "base/NotationTypes.h" +#include "commands/edit/PasteEventsCommand.h" +#include "ConfigurationPage.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/editors/notation/HeadersGroup.h" +#include "gui/editors/notation/NotationHLayout.h" +#include "gui/editors/notation/NoteFontFactory.h" +#include "gui/editors/notation/NoteFont.h" +#include "gui/editors/notation/NoteFontMap.h" +#include "gui/editors/notation/NoteFontViewer.h" +#include "gui/editors/notation/NotePixmapFactory.h" +#include "gui/editors/notation/NoteStyleFactory.h" +#include "gui/widgets/QuantizeParameters.h" +#include "TabbedConfigurationPage.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Rosegarden +{ + +NotationConfigurationPage::NotationConfigurationPage(KConfig *cfg, + QWidget *parent, + const char *name) : + TabbedConfigurationPage(cfg, parent, name) +{ + m_cfg->setGroup(NotationViewConfigGroup); + + QFrame *frame; + QGridLayout *layout; + + frame = new QFrame(m_tabWidget); + layout = new QGridLayout(frame, 9, 3, 10, 5); + + int row = 0; + + layout->setRowSpacing(row, 15); + ++row; + + layout->addWidget(new QLabel(i18n("Default layout mode"), frame), row, 0); + + m_layoutMode = new KComboBox(frame); + m_layoutMode->setEditable(false); + m_layoutMode->insertItem(i18n("Linear layout")); + m_layoutMode->insertItem(i18n("Continuous page layout")); + m_layoutMode->insertItem(i18n("Multiple page layout")); + int defaultLayoutMode = m_cfg->readNumEntry("layoutmode", 0); + if (defaultLayoutMode >= 0 && defaultLayoutMode <= 2) { + m_layoutMode->setCurrentItem(defaultLayoutMode); + } + layout->addMultiCellWidget(m_layoutMode, row, row, 1, 2); + ++row; + + layout->addWidget(new QLabel(i18n("Default spacing"), frame), row, 0); + + m_spacing = new KComboBox(frame); + m_spacing->setEditable(false); + + std::vector s = NotationHLayout::getAvailableSpacings(); + int defaultSpacing = m_cfg->readNumEntry("spacing", 100); + + for (std::vector::iterator i = s.begin(); i != s.end(); ++i) { + + QString text("%1 %"); + if (*i == 100) + text = "%1 % (normal)"; + m_spacing->insertItem(text.arg(*i)); + + if (*i == defaultSpacing) { + m_spacing->setCurrentItem(m_spacing->count() - 1); + } + } + + layout->addMultiCellWidget(m_spacing, row, row, 1, 2); + + ++row; + + layout->addWidget(new QLabel(i18n("Default duration factor"), frame), row, 0); + + m_proportion = new KComboBox(frame); + m_proportion->setEditable(false); + + s = NotationHLayout::getAvailableProportions(); + int defaultProportion = m_cfg->readNumEntry("proportion", 60); + + for (std::vector::iterator i = s.begin(); i != s.end(); ++i) { + + QString text = QString("%1 %").arg(*i); + if (*i == 40) + text = "40 % (normal)"; + else if (*i == 0) + text = i18n("None"); + else if (*i == 100) + text = i18n("Full"); + m_proportion->insertItem(text); + + if (*i == defaultProportion) { + m_proportion->setCurrentItem(m_proportion->count() - 1); + } + } + + layout->addMultiCellWidget(m_proportion, row, row, 1, 2); + ++row; + + layout->addWidget(new QLabel(i18n("Show track headers (linear layout only)"), + frame), row, 0); + + m_showTrackHeaders = new KComboBox(frame); + m_showTrackHeaders->setEditable(false); + m_showTrackHeaders->insertItem(i18n("Never"), HeadersGroup::ShowNever); + m_showTrackHeaders->insertItem(i18n("When needed"), HeadersGroup::ShowWhenNeeded); + m_showTrackHeaders->insertItem(i18n("Always"), HeadersGroup::ShowAlways); + int defaultShowTrackHeaders = m_cfg->readNumEntry("shownotationheader", + HeadersGroup::DefaultShowMode); + if (HeadersGroup::isValidShowMode(defaultShowTrackHeaders)) { + m_showTrackHeaders->setCurrentItem(defaultShowTrackHeaders); + } + QToolTip::add(m_showTrackHeaders, QString(i18n( + "\"Always\" and \"Never\" mean what they usually mean\n" + "\"When needed\" means \"when staves are too many to all fit" + " in the current window\""))); + + layout->addMultiCellWidget(m_showTrackHeaders, row, row, 1, 2); + ++row; + + layout->setRowSpacing(row, 20); + ++row; + + layout->addMultiCellWidget + (new QLabel + (i18n("Show non-notation events as question marks"), frame), + row, row, 0, 1); + m_showUnknowns = new QCheckBox(frame); + bool defaultShowUnknowns = m_cfg->readBoolEntry("showunknowns", false); + m_showUnknowns->setChecked(defaultShowUnknowns); + layout->addWidget(m_showUnknowns, row, 2); + ++row; + + layout->addMultiCellWidget + (new QLabel + (i18n("Show notation-quantized notes in a different color"), frame), + row, row, 0, 1); + m_colourQuantize = new QCheckBox(frame); + bool defaultColourQuantize = m_cfg->readBoolEntry("colourquantize", false); + m_colourQuantize->setChecked(defaultColourQuantize); + layout->addWidget(m_colourQuantize, row, 2); + ++row; + + layout->addMultiCellWidget + (new QLabel + (i18n("Show \"invisible\" events in grey"), frame), + row, row, 0, 1); + m_showInvisibles = new QCheckBox(frame); + bool defaultShowInvisibles = m_cfg->readBoolEntry("showinvisibles", true); + m_showInvisibles->setChecked(defaultShowInvisibles); + layout->addWidget(m_showInvisibles, row, 2); + ++row; + + layout->addMultiCellWidget + (new QLabel + (i18n("Show notes outside suggested playable range in red"), frame), + row, row, 0, 1); + m_showRanges = new QCheckBox(frame); + bool defaultShowRanges = m_cfg->readBoolEntry("showranges", true); + m_showRanges->setChecked(defaultShowRanges); + layout->addWidget(m_showRanges, row, 2); + ++row; + + layout->addMultiCellWidget + (new QLabel + (i18n("Highlight superimposed notes with a halo effect"), frame), + row, row, 0, 1); + m_showCollisions = new QCheckBox(frame); + bool defaultShowCollisions = m_cfg->readBoolEntry("showcollisions", true); + m_showCollisions->setChecked(defaultShowCollisions); + layout->addWidget(m_showCollisions, row, 2); + ++row; + + layout->setRowSpacing(row, 20); + ++row; + + layout->addMultiCellWidget + (new QLabel + (i18n("When recording MIDI, split-and-tie long notes at barlines"), frame), + row, row, 0, 1); + m_splitAndTie = new QCheckBox(frame); + bool defaultSplitAndTie = m_cfg->readBoolEntry("quantizemakeviable", false); + m_splitAndTie->setChecked(defaultSplitAndTie); + layout->addWidget(m_splitAndTie, row, 2); + ++row; + + layout->setRowStretch(row, 10); + + + addTab(frame, i18n("Layout")); + + + + frame = new QFrame(m_tabWidget); + layout = new QGridLayout(frame, 6, 3, 10, 5); + + row = 0; + + layout->setRowSpacing(row, 15); + ++row; + + layout->addMultiCellWidget + (new QLabel(i18n("Default note style for new notes"), frame), + row, row, 0, 1); + + layout->setColStretch(2, 10); + + m_noteStyle = new KComboBox(frame); + m_noteStyle->setEditable(false); + m_untranslatedNoteStyle.clear(); + + QString defaultStyle = + m_cfg->readEntry("style", strtoqstr(NoteStyleFactory::DefaultStyle)); + std::vector styles + (NoteStyleFactory::getAvailableStyleNames()); + + for (std::vector::iterator i = styles.begin(); + i != styles.end(); ++i) { + + QString styleQName(strtoqstr(*i)); + m_untranslatedNoteStyle.append(styleQName); + m_noteStyle->insertItem(i18n(styleQName.utf8())); + if (styleQName == defaultStyle) { + m_noteStyle->setCurrentItem(m_noteStyle->count() - 1); + } + } + + layout->addWidget(m_noteStyle, row, 2); + ++row; + + layout->setRowSpacing(row, 20); + ++row; + + layout->addWidget + (new QLabel(i18n("When inserting notes..."), frame), row, 0); + + int defaultInsertType = m_cfg->readNumEntry("inserttype", 0); + + m_insertType = new KComboBox(frame); + m_insertType->setEditable(false); + m_insertType->insertItem + (i18n("Split notes into ties to make durations match")); + m_insertType->insertItem(i18n("Ignore existing durations")); + m_insertType->setCurrentItem(defaultInsertType); + + layout->addMultiCellWidget(m_insertType, row, row, 1, 2); + ++row; + + bool autoBeam = m_cfg->readBoolEntry("autobeam", true); + + layout->addMultiCellWidget + (new QLabel + (i18n("Auto-beam on insert when appropriate"), frame), + row, row, 0, 1); + m_autoBeam = new QCheckBox(frame); + m_autoBeam->setChecked(autoBeam); + layout->addMultiCellWidget(m_autoBeam, row, row, 2, 2); + + ++row; + + bool collapse = m_cfg->readBoolEntry("collapse", false); + + layout->addMultiCellWidget + (new QLabel + (i18n("Collapse rests after erase"), frame), + row, row, 0, 1); + m_collapseRests = new QCheckBox(frame); + m_collapseRests->setChecked(collapse); + layout->addMultiCellWidget(m_collapseRests, row, row, 2, 2); + ++row; + + layout->setRowSpacing(row, 20); + ++row; + + layout->addWidget + (new QLabel(i18n("Default paste type"), frame), row, 0); + + m_pasteType = new KComboBox(frame); + m_pasteType->setEditable(false); + + unsigned int defaultPasteType = m_cfg->readUnsignedNumEntry + ("pastetype", PasteEventsCommand::Restricted); + + PasteEventsCommand::PasteTypeMap pasteTypes = + PasteEventsCommand::getPasteTypes(); + + for (PasteEventsCommand::PasteTypeMap::iterator i = pasteTypes.begin(); + i != pasteTypes.end(); ++i) { + m_pasteType->insertItem(i->second); + } + + m_pasteType->setCurrentItem(defaultPasteType); + layout->addMultiCellWidget(m_pasteType, row, row, 1, 2); + ++row; + + layout->setRowStretch(row, 10); + + addTab(frame, i18n("Editing")); + + + + frame = new QFrame(m_tabWidget); + layout = new QGridLayout(frame, 4, 2, 10, 5); + + row = 0; + + layout->setRowSpacing(row, 15); + ++row; + + layout->addWidget(new QLabel(i18n("Accidentals in one octave..."), frame), row, 0); + m_accOctavePolicy = new KComboBox(frame); + m_accOctavePolicy->insertItem(i18n("Affect only that octave")); + m_accOctavePolicy->insertItem(i18n("Require cautionaries in other octaves")); + m_accOctavePolicy->insertItem(i18n("Affect all subsequent octaves")); + int accOctaveMode = m_cfg->readNumEntry("accidentaloctavemode", 1); + if (accOctaveMode >= 0 && accOctaveMode < 3) { + m_accOctavePolicy->setCurrentItem(accOctaveMode); + } + layout->addWidget(m_accOctavePolicy, row, 1); + ++row; + + layout->addWidget(new QLabel(i18n("Accidentals in one bar..."), frame), row, 0); + m_accBarPolicy = new KComboBox(frame); + m_accBarPolicy->insertItem(i18n("Affect only that bar")); + m_accBarPolicy->insertItem(i18n("Require cautionary resets in following bar")); + m_accBarPolicy->insertItem(i18n("Require explicit resets in following bar")); + int accBarMode = m_cfg->readNumEntry("accidentalbarmode", 0); + if (accBarMode >= 0 && accBarMode < 3) { + m_accBarPolicy->setCurrentItem(accBarMode); + } + layout->addWidget(m_accBarPolicy, row, 1); + ++row; + + layout->addWidget(new QLabel(i18n("Key signature cancellation style"), frame), row, 0); + m_keySigCancelMode = new KComboBox(frame); + m_keySigCancelMode->insertItem(i18n("Cancel only when entering C major or A minor")); + m_keySigCancelMode->insertItem(i18n("Cancel whenever removing sharps or flats")); + m_keySigCancelMode->insertItem(i18n("Cancel always")); + int cancelMode = m_cfg->readNumEntry("keysigcancelmode", 1); + if (cancelMode >= 0 && cancelMode < 3) { + m_keySigCancelMode->setCurrentItem(cancelMode); + } + layout->addWidget(m_keySigCancelMode, row, 1); + ++row; + + layout->setRowStretch(row, 10); + + addTab(frame, i18n("Accidentals")); + +/* + QString preamble = + (i18n("Rosegarden can apply automatic quantization to recorded " + "or imported MIDI data for notation purposes only. " + "This does not affect playback, and does not affect " + "editing in any of the views except notation.")); + + // force to default of 2 if not used before + int quantizeType = m_cfg->readNumEntry("quantizetype", 2); + m_cfg->writeEntry("quantizetype", quantizeType); + m_cfg->writeEntry("quantizenotationonly", true); + + m_quantizeFrame = new QuantizeParameters + (m_tabWidget, QuantizeParameters::Notation, + false, false, "Notation Options", preamble); + + addTab(m_quantizeFrame, i18n("Quantize")); +*/ + row = 0; + +// QFrame *mainFrame = new QFrame(m_tabWidget); +// QGridLayout *mainLayout = new QGridLayout(mainFrame, 2, 4, 10, 5); + +// QGroupBox *noteFontBox = new QGroupBox(1, Horizontal, i18n("Notation font"), mainFrame); +// QGroupBox *otherFontBox = new QGroupBox(1, Horizontal, i18n("Other fonts"), mainFrame); +// QGroupBox *descriptionBox = new QGroupBox(1, Horizontal, i18n("Description"), mainFrame); + +// mainLayout->addWidget(noteFontBox, 0, 0); +// mainLayout->addWidget(otherFontBox, 1, 0); + +// QFrame *mainFrame = new QFrame(m_tabWidget); + frame = new QFrame(m_tabWidget); + layout = new QGridLayout(frame, 2, 4, 10, 5); + +// frame = new QFrame(noteFontBox); +// layout = new QGridLayout(frame, 5, 2, 10, 5); + + m_viewButton = 0; + + layout->addWidget(new QLabel(i18n("Notation font"), frame), 0, 0); + + m_font = new KComboBox(frame); + +#ifdef HAVE_XFT + m_viewButton = new QPushButton(i18n("View"), frame); + layout->addMultiCellWidget(m_font, row, row, 1, 2); + layout->addWidget(m_viewButton, row, 3); + QObject::connect(m_viewButton, SIGNAL(clicked()), + this, SLOT(slotViewButtonPressed())); +#else + layout->addMultiCellWidget(m_font, row, row, 1, 3); +#endif + m_font->setEditable(false); + QObject::connect(m_font, SIGNAL(activated(int)), + this, SLOT(slotFontComboChanged(int))); + ++row; + + QFrame *subFrame = new QFrame(frame); + QGridLayout *subLayout = new QGridLayout(subFrame, + 4, 2, // nbrow, nbcol + 12, 2); + + QFont font = m_font->font(); + font.setPointSize((font.pointSize() * 9) / 10); + + QLabel *originLabel = new QLabel(i18n("Origin:"), subFrame); + originLabel->setFont(font); + subLayout->addWidget(originLabel, 0, 0); + + QLabel *copyrightLabel = new QLabel(i18n("Copyright:"), subFrame); + copyrightLabel->setFont(font); + subLayout->addWidget(copyrightLabel, 1, 0); + + QLabel *mappedLabel = new QLabel(i18n("Mapped by:"), subFrame); + mappedLabel->setFont(font); + subLayout->addWidget(mappedLabel, 2, 0); + + QLabel *typeLabel = new QLabel(i18n("Type:"), subFrame); + typeLabel->setFont(font); + subLayout->addWidget(typeLabel, 3, 0); + + m_fontOriginLabel = new QLabel(subFrame); + m_fontOriginLabel->setAlignment(Qt::WordBreak); + m_fontOriginLabel->setFont(font); +// m_fontOriginLabel->setFixedWidth(250); + m_fontCopyrightLabel = new QLabel(subFrame); + m_fontCopyrightLabel->setAlignment(Qt::WordBreak); + m_fontCopyrightLabel->setFont(font); +// m_fontCopyrightLabel->setFixedWidth(250); + m_fontMappedByLabel = new QLabel(subFrame); + m_fontMappedByLabel->setFont(font); + m_fontTypeLabel = new QLabel(subFrame); + m_fontTypeLabel->setFont(font); + subLayout->addWidget(m_fontOriginLabel, 0, 1); + subLayout->addWidget(m_fontCopyrightLabel, 1, 1); + subLayout->addWidget(m_fontMappedByLabel, 2, 1); + subLayout->addWidget(m_fontTypeLabel, 3, 1); + + subLayout->setColStretch(1, 10); + + layout->addMultiCellWidget(subFrame, + row, row, + 0, 3); + ++row; + + layout->addMultiCellWidget + (new QLabel(i18n("Font size for single-staff views"), frame), + row, row, 0, 1); + m_singleStaffSize = new KComboBox(frame); + m_singleStaffSize->setEditable(false); + layout->addMultiCellWidget(m_singleStaffSize, row, row, 2, 2); + ++row; + + layout->addMultiCellWidget + (new QLabel(i18n("Font size for multi-staff views"), frame), + row, row, 0, 1); + m_multiStaffSize = new KComboBox(frame); + m_multiStaffSize->setEditable(false); + layout->addMultiCellWidget(m_multiStaffSize, row, row, 2, 2); + ++row; + + layout->addMultiCellWidget + (new QLabel(i18n("Font size for printing (pt)"), frame), + row, row, 0, 1); + m_printingSize = new KComboBox(frame); + m_printingSize->setEditable(false); + layout->addMultiCellWidget(m_printingSize, row, row, 2, 2); + ++row; + + slotPopulateFontCombo(false); + + layout->setRowSpacing(row, 15); + ++row; + + QFont defaultTextFont(NotePixmapFactory::defaultSerifFontFamily), + defaultSansFont(NotePixmapFactory::defaultSansSerifFontFamily), + defaultTimeSigFont(NotePixmapFactory::defaultTimeSigFontFamily); + + layout->addWidget + (new QLabel(i18n("Text font"), frame), row, 0); + m_textFont = new KFontRequester(frame); + QFont textFont = m_cfg->readFontEntry("textfont", &defaultTextFont); + m_textFont->setFont(textFont); + layout->addMultiCellWidget(m_textFont, row, row, 1, 3); + ++row; + + layout->addWidget + (new QLabel(i18n("Sans-serif font"), frame), row, 0); + m_sansFont = new KFontRequester(frame); + QFont sansFont = m_cfg->readFontEntry("sansfont", &defaultSansFont); + m_sansFont->setFont(sansFont); + layout->addMultiCellWidget(m_sansFont, row, row, 1, 3); + ++row; + +/*!!! No -- not much point in having the time sig font here: it's only + * used if the time sig characters are not found in the notation font, + * and our default notation font has all the characters we need + + layout->addWidget + (new QLabel(i18n("Time Signature font"), frame), row, 0); + m_timeSigFont = new KFontRequester(frame); + QFont timeSigFont = m_cfg->readFontEntry("timesigfont", &defaultTimeSigFont); + m_timeSigFont->setFont(timeSigFont); + layout->addMultiCellWidget(m_timeSigFont, row, row, 1, 3); + ++row; +*/ + +// addTab(mainFrame, i18n("Font")); + addTab(frame, i18n("Font")); + + +} + +void +NotationConfigurationPage::slotViewButtonPressed() +{ +#ifdef HAVE_XFT + std::string fontName = qstrtostr(m_untranslatedFont[m_font->currentItem()]); + + try { + NoteFont *noteFont = NoteFontFactory::getFont + (fontName, NoteFontFactory::getDefaultSize(fontName)); + const NoteFontMap &map(noteFont->getNoteFontMap()); + QStringList systemFontNames(map.getSystemFontNames()); + if (systemFontNames.count() == 0) { + m_viewButton->setEnabled(false); // oops + } else { + NoteFontViewer *viewer = + new NoteFontViewer(0, m_untranslatedFont[m_font->currentItem()], + systemFontNames, 24); + (void)viewer->exec(); // no return value + } + } catch (Exception f) { + KMessageBox::error(0, i18n(strtoqstr(f.getMessage()))); + } +#endif +} + +void +NotationConfigurationPage::slotPopulateFontCombo(bool rescan) +{ + QString defaultFont = m_cfg->readEntry + ("notefont", strtoqstr(NoteFontFactory::getDefaultFontName())); + + try { + (void)NoteFontFactory::getFont + (qstrtostr(defaultFont), + NoteFontFactory::getDefaultSize(qstrtostr(defaultFont))); + } catch (Exception e) { + defaultFont = strtoqstr(NoteFontFactory::getDefaultFontName()); + } + + std::set + fs(NoteFontFactory::getFontNames(rescan)); + std::vector f(fs.begin(), fs.end()); + std::sort(f.begin(), f.end()); + + m_untranslatedFont.clear(); + m_font->clear(); + + for (std::vector::iterator i = f.begin(); i != f.end(); ++i) { + QString s(strtoqstr(*i)); + m_untranslatedFont.append(s); + m_font->insertItem(i18n(s.utf8())); + if (s == defaultFont) + m_font->setCurrentItem(m_font->count() - 1); + } + + slotFontComboChanged(m_font->currentItem()); +} + +void +NotationConfigurationPage::slotFontComboChanged(int index) +{ + std::string fontStr = qstrtostr(m_untranslatedFont[index]); + + populateSizeCombo(m_singleStaffSize, fontStr, + m_cfg->readUnsignedNumEntry + ("singlestaffnotesize", + NoteFontFactory::getDefaultSize(fontStr))); + populateSizeCombo(m_multiStaffSize, fontStr, + m_cfg->readUnsignedNumEntry + ("multistaffnotesize", + NoteFontFactory::getDefaultSize(fontStr))); + + int printpt = m_cfg->readUnsignedNumEntry("printingnotesize", 5); + for (int i = 2; i < 16; ++i) { + m_printingSize->insertItem(QString("%1").arg(i)); + if (i == printpt) { + m_printingSize->setCurrentItem(m_printingSize->count() - 1); + } + } + + try { + NoteFont *noteFont = NoteFontFactory::getFont + (fontStr, NoteFontFactory::getDefaultSize(fontStr)); + const NoteFontMap &map(noteFont->getNoteFontMap()); + m_fontOriginLabel->setText(i18n(strtoqstr(map.getOrigin()))); + m_fontCopyrightLabel->setText(i18n(strtoqstr(map.getCopyright()))); + m_fontMappedByLabel->setText(i18n(strtoqstr(map.getMappedBy()))); + if (map.isSmooth()) { + m_fontTypeLabel->setText( + i18n("%1 (smooth)").arg(i18n(strtoqstr(map.getType())))); + } else { + m_fontTypeLabel->setText( + i18n("%1 (jaggy)").arg(i18n(strtoqstr(map.getType())))); + } + if (m_viewButton) { + m_viewButton->setEnabled(map.getSystemFontNames().count() > 0); + } + } catch (Exception f) { + KMessageBox::error(0, i18n(strtoqstr(f.getMessage()))); + } +} + +void +NotationConfigurationPage::populateSizeCombo(QComboBox *combo, + std::string font, + int defaultSize) +{ + std::vector sizes = NoteFontFactory::getScreenSizes(font); + combo->clear(); + + for (std::vector::iterator i = sizes.begin(); i != sizes.end(); ++i) { + combo->insertItem(QString("%1").arg(*i)); + if (*i == defaultSize) + combo->setCurrentItem(combo->count() - 1); + } +} + +void +NotationConfigurationPage::apply() +{ + m_cfg->setGroup(NotationViewConfigGroup); + + m_cfg->writeEntry("notefont", m_untranslatedFont[m_font->currentItem()]); + m_cfg->writeEntry("singlestaffnotesize", + m_singleStaffSize->currentText().toUInt()); + m_cfg->writeEntry("multistaffnotesize", + m_multiStaffSize->currentText().toUInt()); + m_cfg->writeEntry("printingnotesize", + m_printingSize->currentText().toUInt()); + m_cfg->writeEntry("textfont", + m_textFont->font()); + m_cfg->writeEntry("sansfont", + m_sansFont->font()); +/*!!! + m_cfg->writeEntry("timesigfont", + m_timeSigFont->font()); +*/ + std::vector s = NotationHLayout::getAvailableSpacings(); + m_cfg->writeEntry("spacing", s[m_spacing->currentItem()]); + + s = NotationHLayout::getAvailableProportions(); + m_cfg->writeEntry("proportion", s[m_proportion->currentItem()]); + + m_cfg->writeEntry("layoutmode", m_layoutMode->currentItem()); + m_cfg->writeEntry("colourquantize", m_colourQuantize->isChecked()); + m_cfg->writeEntry("showunknowns", m_showUnknowns->isChecked()); + m_cfg->writeEntry("showinvisibles", m_showInvisibles->isChecked()); + m_cfg->writeEntry("showranges", m_showRanges->isChecked()); + m_cfg->writeEntry("showcollisions", m_showCollisions->isChecked()); + m_cfg->writeEntry("shownotationheader", + m_showTrackHeaders->currentItem()); + m_cfg->writeEntry("style", m_untranslatedNoteStyle[m_noteStyle->currentItem()]); + m_cfg->writeEntry("inserttype", m_insertType->currentItem()); + m_cfg->writeEntry("autobeam", m_autoBeam->isChecked()); + m_cfg->writeEntry("collapse", m_collapseRests->isChecked()); + m_cfg->writeEntry("pastetype", m_pasteType->currentItem()); + m_cfg->writeEntry("accidentaloctavemode", m_accOctavePolicy->currentItem()); + m_cfg->writeEntry("accidentalbarmode", m_accBarPolicy->currentItem()); + m_cfg->writeEntry("keysigcancelmode", m_keySigCancelMode->currentItem()); + + m_cfg->writeEntry("quantizemakeviable", m_splitAndTie->isChecked()); + +// (void)m_quantizeFrame->getQuantizer(); // this also writes to the config +} + +} +#include "NotationConfigurationPage.moc" diff --git a/src/gui/configuration/NotationConfigurationPage.h b/src/gui/configuration/NotationConfigurationPage.h new file mode 100644 index 0000000..a3d3dc5 --- /dev/null +++ b/src/gui/configuration/NotationConfigurationPage.h @@ -0,0 +1,117 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_NOTATIONCONFIGURATIONPAGE_H_ +#define _RG_NOTATIONCONFIGURATIONPAGE_H_ + +#include +#include "TabbedConfigurationPage.h" +#include +#include +#include + + +class QWidget; +class QPushButton; +class QLabel; +class QComboBox; +class QCheckBox; +class KFontRequester; +class KConfig; + + +namespace Rosegarden +{ + +class QuantizeParameters; + + +/** + * Notation Configuration page + */ +class NotationConfigurationPage : public TabbedConfigurationPage +{ + Q_OBJECT + +public: + NotationConfigurationPage(KConfig *cfg, + QWidget *parent = 0, const char *name=0); + + virtual void apply(); + + static QString iconLabel() { return i18n("Notation"); } + static QString title() { return i18n("Notation"); } + static QString iconName() { return "configure-notation"; } + +protected slots: + void slotFontComboChanged(int); + void slotPopulateFontCombo(bool rescan); + void slotViewButtonPressed(); + +protected: + + //--------------- Data members --------------------------------- + + QComboBox *m_font; + QComboBox *m_singleStaffSize; + QComboBox *m_multiStaffSize; + QComboBox *m_printingSize; + KFontRequester* m_textFont; + KFontRequester* m_sansFont; + KFontRequester* m_timeSigFont; + QPushButton *m_viewButton; + QLabel *m_fontOriginLabel; + QLabel *m_fontCopyrightLabel; + QLabel *m_fontMappedByLabel; + QLabel *m_fontTypeLabel; + QComboBox *m_layoutMode; + QComboBox *m_spacing; + QComboBox *m_proportion; + QCheckBox *m_colourQuantize; + QCheckBox *m_showUnknowns; + QCheckBox *m_showInvisibles; + QCheckBox *m_showRanges; + QCheckBox *m_showCollisions; + QComboBox *m_showTrackHeaders; + QComboBox *m_noteStyle; + QComboBox *m_insertType; + QCheckBox *m_autoBeam; + QCheckBox *m_collapseRests; + QComboBox *m_pasteType; + QComboBox *m_accOctavePolicy; + QComboBox *m_accBarPolicy; + QComboBox *m_keySigCancelMode; + QCheckBox *m_splitAndTie; + QuantizeParameters *m_quantizeFrame; + QStringList m_untranslatedFont; + QStringList m_untranslatedNoteStyle; + + void populateSizeCombo(QComboBox *combo, std::string font, int dfltSize); +}; + + +} + +#endif diff --git a/src/gui/configuration/TabbedConfigurationPage.cpp b/src/gui/configuration/TabbedConfigurationPage.cpp new file mode 100644 index 0000000..cc808a9 --- /dev/null +++ b/src/gui/configuration/TabbedConfigurationPage.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TabbedConfigurationPage.h" + +#include "ConfigurationPage.h" +#include "document/RosegardenGUIDoc.h" +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +TabbedConfigurationPage::TabbedConfigurationPage(RosegardenGUIDoc *doc, + QWidget *parent, + const char *name) + : ConfigurationPage(doc, parent, name) +{ + init(); +} + +TabbedConfigurationPage::TabbedConfigurationPage(KConfig *cfg, + QWidget *parent, + const char *name) + : ConfigurationPage(cfg, parent, name) +{ + init(); +} + +TabbedConfigurationPage::TabbedConfigurationPage(RosegardenGUIDoc *doc, + KConfig *cfg, + QWidget *parent, + const char *name) + : ConfigurationPage(doc, cfg, parent, name) +{ + init(); +} + +void TabbedConfigurationPage::init() +{ + QVBoxLayout *vlay = new QVBoxLayout(this, 0, KDialog::spacingHint()); + m_tabWidget = new QTabWidget(this); + vlay->addWidget(m_tabWidget); +} + +void TabbedConfigurationPage::addTab(QWidget *tab, const QString &title) +{ + m_tabWidget->addTab(tab, title); +} + +} +#include "TabbedConfigurationPage.moc" diff --git a/src/gui/configuration/TabbedConfigurationPage.h b/src/gui/configuration/TabbedConfigurationPage.h new file mode 100644 index 0000000..8c370d5 --- /dev/null +++ b/src/gui/configuration/TabbedConfigurationPage.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_TABBEDCONFIGURATIONPAGE_H_ +#define _RG_TABBEDCONFIGURATIONPAGE_H_ + +#include "ConfigurationPage.h" +#include + + +class QWidget; +class QTabWidget; +class KConfig; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +/** + * This class borrowed from KMail + * (c) 2000 The KMail Development Team + */ +class TabbedConfigurationPage : public ConfigurationPage +{ + Q_OBJECT + +public: + TabbedConfigurationPage(RosegardenGUIDoc *doc, + QWidget *parent=0, const char *name=0); + + TabbedConfigurationPage(KConfig *cfg, + QWidget *parent=0, const char *name=0); + + TabbedConfigurationPage(RosegardenGUIDoc *doc, + KConfig *cfg, + QWidget *parent=0, const char *name=0); + + static QString iconName() { return "misc"; } + +protected: + void init(); + void addTab(QWidget *tab, const QString &title); + + //--------------- Data members --------------------------------- + + QTabWidget *m_tabWidget; + +}; + + +} + +#endif diff --git a/src/gui/dialogs/AddTracksDialog.cpp b/src/gui/dialogs/AddTracksDialog.cpp new file mode 100644 index 0000000..67e5412 --- /dev/null +++ b/src/gui/dialogs/AddTracksDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AddTracksDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "document/ConfigGroups.h" + + +namespace Rosegarden +{ + +AddTracksDialog::AddTracksDialog(QWidget *parent, int currentTrack) : + KDialogBase(parent, 0, true, i18n("Add Tracks"), + Ok | Cancel), + m_currentTrack(currentTrack) +{ + QVBox *vBox = makeVBoxMainWidget(); + + QHBox *countBox = new QHBox(vBox); + countBox->setSpacing(4); + new QLabel(i18n("How many tracks do you want to add?"), countBox); + m_count = new QSpinBox(countBox); + m_count->setMinValue(1); + m_count->setMaxValue(32); + m_count->setValue(1); + + QHBox *posBox = new QHBox(vBox); + posBox->setSpacing(4); + new QLabel(i18n("Add tracks"), posBox); + m_position = new KComboBox(posBox); + m_position->insertItem(i18n("At the top")); + m_position->insertItem(i18n("Above the current selected track")); + m_position->insertItem(i18n("Below the current selected track")); + m_position->insertItem(i18n("At the bottom")); + + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + m_position->setCurrentItem(config->readUnsignedNumEntry("lastaddtracksposition", 2)); +} + +int +AddTracksDialog::getTracks() +{ + return m_count->value(); +} + +int +AddTracksDialog::getInsertPosition() +{ + int opt = m_position->currentItem(); + + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + config->writeEntry("lastaddtracksposition", opt); + + int pos = 0; + + switch (opt) { + case 0: // at top + pos = 0; + break; + case 1: // above current track + pos = m_currentTrack; + break; + case 2: // below current track + pos = m_currentTrack + 1; + break; + case 3: // at bottom + pos = -1; + break; + } + + return pos; +} + +} +#include "AddTracksDialog.moc" diff --git a/src/gui/dialogs/AddTracksDialog.h b/src/gui/dialogs/AddTracksDialog.h new file mode 100644 index 0000000..9930e46 --- /dev/null +++ b/src/gui/dialogs/AddTracksDialog.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ADDTRACKSDIALOG_H_ +#define _RG_ADDTRACKSDIALOG_H_ + +#include + +class QWidget; +class QSpinBox; +class QComboBox; + +namespace Rosegarden +{ + +class AddTracksDialog : public KDialogBase +{ + Q_OBJECT + +public: + AddTracksDialog(QWidget *parent, int currentTrack = -1); + + int getTracks(); + int getInsertPosition(); + +protected: + int m_currentTrack; + QSpinBox *m_count; + QComboBox *m_position; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/AudioManagerDialog.cpp b/src/gui/dialogs/AudioManagerDialog.cpp new file mode 100644 index 0000000..5982632 --- /dev/null +++ b/src/gui/dialogs/AudioManagerDialog.cpp @@ -0,0 +1,1257 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AudioManagerDialog.h" +#include + +#include "base/Event.h" +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "AudioPlayingDialog.h" +#include "base/Composition.h" +#include "base/Exception.h" +#include "base/Instrument.h" +#include "base/MidiProgram.h" +#include "base/NotationTypes.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "document/MultiViewCommandHistory.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "gui/application/RosegardenGUIView.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/widgets/AudioListItem.h" +#include "gui/widgets/AudioListView.h" +#include "gui/widgets/CurrentProgressDialog.h" +#include "gui/widgets/ProgressDialog.h" +#include "sound/AudioFile.h" +#include "sound/AudioFileManager.h" +#include "sound/WAVAudioFile.h" +#include "UnusedAudioSelectionDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +const int AudioManagerDialog::m_maxPreviewWidth = 100; +const int AudioManagerDialog::m_previewHeight = 30; +const char* const AudioManagerDialog::m_listViewLayoutName = "AudioManagerDialog Layout"; + +AudioManagerDialog::AudioManagerDialog(QWidget *parent, + RosegardenGUIDoc *doc): + KMainWindow(parent, "audioManagerDialog"), + m_doc(doc), + m_playingAudioFile(0), + m_audioPlayingDialog(0), + m_playTimer(new QTimer(this)), + m_audiblePreview(true) +{ + setCaption(i18n("Audio File Manager")); + setWFlags(WDestructiveClose); + + QVBox *box = new QVBox(this); + setCentralWidget(box); + box->setMargin(10); + box->setSpacing(5); + + m_sampleRate = 0; + + QCString replyType; + QByteArray replyData; + if (rgapp->sequencerCall("getSampleRate()", replyType, replyData)) { + QDataStream streamIn(replyData, IO_ReadOnly); + unsigned int result; + streamIn >> result; + m_sampleRate = result; + } + + m_fileList = new AudioListView(box); + + m_wrongSampleRates = new QLabel(i18n("* Some audio files are encoded at a sample rate different from that of the JACK audio server.\nRosegarden will play them at the correct speed, but they will sound terrible.\nPlease consider resampling such files externally, or adjusting the sample rate of the JACK server."), box); + m_wrongSampleRates->hide(); + + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QIconSet icon(QPixmap(pixmapDir + "/toolbar/transport-play.xpm")); + + new KAction(i18n("&Add Audio File..."), "fileopen", 0, this, + SLOT(slotAdd()), actionCollection(), "add_audio"); + + new KAction(i18n("&Unload Audio File"), "editdelete", 0, this, + SLOT(slotRemove()), + actionCollection(), "remove_audio"); + + icon = QIconSet(QPixmap(pixmapDir + "/toolbar/transport-play.xpm")); + new KAction(i18n("&Play Preview"), icon, 0, this, + SLOT(slotPlayPreview()), + actionCollection(), "preview_audio"); + + /*!!! Not actually implemented -- this never worked right! + new KAction(i18n("Re&label"), 0, 0, this, + SLOT(slotRename()), + actionCollection(), "rename_audio"); + */ + + icon = QIconSet(QPixmap(pixmapDir + "/toolbar/insert_audio_into_track.xpm")); + new KAction(i18n("&Insert into Selected Audio Track"), + icon, 0, this, SLOT(slotInsert()), + actionCollection(), "insert_audio"); + + new KAction(i18n("Unload &all Audio Files"), 0, 0, this, + SLOT(slotRemoveAll()), + actionCollection(), "remove_all_audio"); + + new KAction(i18n("Unload all &Unused Audio Files"), 0, 0, this, + SLOT(slotRemoveAllUnused()), + actionCollection(), "remove_all_unused_audio"); + + new KAction(i18n("&Delete Unused Audio Files..."), 0, 0, this, + SLOT(slotDeleteUnused()), + actionCollection(), "delete_unused_audio"); + + new KAction(i18n("&Export Audio File..."), "fileexport", 0, this, + SLOT(slotExportAudio()), + actionCollection(), "export_audio"); +/* + new KAction(i18n("Distribute Audio on &MIDI"), + 0, 0, this, + SLOT(slotDistributeOnMidiSegment()), + actionCollection(), + "distribute_audio"); +*/ + // Set the column names + // + m_fileList->addColumn(i18n("Name")); // 0 + m_fileList->addColumn(i18n("Duration")); // 1 + m_fileList->addColumn(i18n("Envelope")); // 2 + m_fileList->addColumn(i18n("Sample rate")); // 3 + m_fileList->addColumn(i18n("Channels")); // 4 + m_fileList->addColumn(i18n("Resolution")); // 5 + m_fileList->addColumn(i18n("File")); // 6 + + m_fileList->setColumnAlignment(1, Qt::AlignHCenter); + m_fileList->setColumnAlignment(2, Qt::AlignHCenter); + m_fileList->setColumnAlignment(3, Qt::AlignHCenter); + m_fileList->setColumnAlignment(4, Qt::AlignHCenter); + m_fileList->setColumnAlignment(5, Qt::AlignHCenter); + + m_fileList->restoreLayout(kapp->config(), m_listViewLayoutName); + + // a minimum width for the list box + //m_fileList->setMinimumWidth(300); + + // show focus across all columns + m_fileList->setAllColumnsShowFocus(true); + + // show tooltips when columns are partially hidden + m_fileList->setShowToolTips(true); + + // connect selection mechanism + connect(m_fileList, SIGNAL(selectionChanged(QListViewItem*)), + SLOT(slotSelectionChanged(QListViewItem*))); + + connect(m_fileList, SIGNAL(dropped(QDropEvent*, QListViewItem*)), + SLOT(slotDropped(QDropEvent*, QListViewItem*))); + + // setup local accelerators + // + m_accelerators = new QAccel(this); + + // delete + // + m_accelerators->connectItem(m_accelerators->insertItem(Key_Delete), + this, + SLOT(slotRemove())); + + slotPopulateFileList(); + + // Connect command history for updates + // + connect(getCommandHistory(), SIGNAL(commandExecuted(KCommand *)), + this, SLOT(slotCommandExecuted(KCommand *))); + + //setInitialSize(configDialogSize(AudioManagerDialogConfigGroup)); + + connect(m_playTimer, SIGNAL(timeout()), + this, SLOT(slotCancelPlayingAudio())); + + KStdAction::close(this, + SLOT(slotClose()), + actionCollection()); + + createGUI("audiomanager.rc"); + + updateActionState(false); +} + +AudioManagerDialog::~AudioManagerDialog() +{ + RG_DEBUG << "\n*** AudioManagerDialog::~AudioManagerDialog\n" << endl; + m_fileList->saveLayout(kapp->config(), m_listViewLayoutName); + //saveDialogSize(AudioManagerDialogConfigGroup); +} + +void +AudioManagerDialog::slotPopulateFileList() +{ + // create pixmap of given size + QPixmap *audioPixmap = new QPixmap(m_maxPreviewWidth, m_previewHeight); + + // Store last selected item if we have one + // + AudioListItem *selectedItem = + dynamic_cast(m_fileList->selectedItem()); + AudioFileId lastId = 0; + Segment *lastSegment = 0; + bool findSelection = false; + bool foundSelection = false; + + if (selectedItem) { + lastId = selectedItem->getId(); + lastSegment = selectedItem->getSegment(); + findSelection = true; + } + + // We don't want the selection changes to be propagated + // to the main view + // + m_fileList->blockSignals(true); + + // clear file list and disable associated action buttons + m_fileList->clear(); + + if (m_doc->getAudioFileManager().begin() == + m_doc->getAudioFileManager().end()) { + // Turn off selection and report empty list + // + new AudioListItem(m_fileList, i18n(""), 0); + m_fileList->setSelectionMode(QListView::NoSelection); + m_fileList->setRootIsDecorated(false); + + m_fileList->blockSignals(false); + updateActionState(false); + return ; + } + + // show tree hierarchy + m_fileList->setRootIsDecorated(true); + + // enable selection + m_fileList->setSelectionMode(QListView::Single); + + // for the sample file length + QString msecs, sRate; + RealTime length; + + // Create a vector of audio Segments only + // + std::vector segments; + std::vector::const_iterator iit; + + for (Composition::iterator it = m_doc->getComposition().begin(); + it != m_doc->getComposition().end(); ++it) { + if ((*it)->getType() == Segment::Audio) + segments.push_back(*it); + } + + // duration + RealTime segmentDuration; + bool wrongSampleRates = false; + + for (std::vector::const_iterator + it = m_doc->getAudioFileManager().begin(); + it != m_doc->getAudioFileManager().end(); + ++it) { + try { + m_doc->getAudioFileManager(). + drawPreview((*it)->getId(), + RealTime::zeroTime, + (*it)->getLength(), + audioPixmap); + } catch (Exception e) { + audioPixmap->fill(); // white + QPainter p(audioPixmap); + p.setPen(Qt::black); + p.drawText(10, m_previewHeight / 2, QString("")); + } + + //!!! Why isn't the label the label the user assigned to the file? + // Why do we allow the user to assign a label at all, then? + + QString label = QString((*it)->getShortFilename().c_str()); + + // Set the label, duration, envelope pixmap and filename + // + AudioListItem *item = new AudioListItem(m_fileList, label, + (*it)->getId()); + // Duration + // + length = (*it)->getLength(); + msecs.sprintf("%03d", length.nsec / 1000000); + item->setText(1, QString("%1.%2s").arg(length.sec).arg(msecs)); + + // set start time and duration + item->setStartTime(RealTime::zeroTime); + item->setDuration(length); + + // Envelope pixmap + // + item->setPixmap(2, *audioPixmap); + + // File location + // + item->setText(6, QString( + m_doc->getAudioFileManager(). + substituteHomeForTilde((*it)->getFilename()).c_str())); + + // Resolution + // + item->setText(5, QString("%1 bits").arg((*it)->getBitsPerSample())); + + // Channels + // + item->setText(4, QString("%1").arg((*it)->getChannels())); + + // Sample rate + // + if (m_sampleRate != 0 && (*it)->getSampleRate() != m_sampleRate) { + sRate.sprintf("%.1f KHz *", float((*it)->getSampleRate()) / 1000.0); + wrongSampleRates = true; + } else { + sRate.sprintf("%.1f KHz", float((*it)->getSampleRate()) / 1000.0); + } + item->setText(3, sRate); + + // Test audio file element for selection criteria + // + if (findSelection && lastSegment == 0 && lastId == (*it)->getId()) { + m_fileList->setSelected(item, true); + findSelection = false; + } + + // Add children + // + for (iit = segments.begin(); iit != segments.end(); iit++) { + if ((*iit)->getAudioFileId() == (*it)->getId()) { + AudioListItem *childItem = + new AudioListItem(item, + QString((*iit)->getLabel().c_str()), + (*it)->getId()); + segmentDuration = (*iit)->getAudioEndTime() - + (*iit)->getAudioStartTime(); + + // store the start time + // + childItem->setStartTime((*iit)->getAudioStartTime()); + childItem->setDuration(segmentDuration); + + // Write segment duration + // + msecs.sprintf("%03d", segmentDuration.nsec / 1000000); + childItem->setText(1, QString("%1.%2s") + .arg(segmentDuration.sec) + .arg(msecs)); + + try { + m_doc->getAudioFileManager(). + drawHighlightedPreview((*it)->getId(), + RealTime::zeroTime, + (*it)->getLength(), + (*iit)->getAudioStartTime(), + (*iit)->getAudioEndTime(), + audioPixmap); + } catch (Exception e) { + // should already be set to "no file" + } + + // set pixmap + // + childItem->setPixmap(2, *audioPixmap); + + // set segment + // + childItem->setSegment(*iit); + + if (findSelection && lastSegment == (*iit)) { + m_fileList->setSelected(childItem, true); + findSelection = false; + foundSelection = true; + } + + // Add children + } + } + } + + updateActionState(foundSelection); + + if (wrongSampleRates) { + m_wrongSampleRates->show(); + } else { + m_wrongSampleRates->hide(); + } + + m_fileList->blockSignals(false); +} + +AudioFile* +AudioManagerDialog::getCurrentSelection() +{ + // try and get the selected item + AudioListItem *item = + dynamic_cast(m_fileList->selectedItem()); + if (item == 0) + return 0; + + std::vector::const_iterator it; + + for (it = m_doc->getAudioFileManager().begin(); + it != m_doc->getAudioFileManager().end(); + ++it) { + // If we match then return the valid AudioFile + // + if (item->getId() == (*it)->getId()) + return (*it); + } + + return 0; +} + +void +AudioManagerDialog::slotExportAudio() +{ + WAVAudioFile *sourceFile + = dynamic_cast(getCurrentSelection()); + + AudioListItem *item = + dynamic_cast(m_fileList->selectedItem()); + + Segment *segment = item->getSegment(); + + QString saveFile = + KFileDialog::getSaveFileName(":WAVS", + i18n("*.wav|WAV files (*.wav)"), + this, i18n("Choose a name to save this file as")); + + if (sourceFile == 0 || item == 0 || saveFile.isEmpty()) + return ; + + // Check for a dot extension and append ".wav" if not found + // + if (saveFile.contains(".") == 0) + saveFile += ".wav"; + + ProgressDialog progressDlg(i18n("Exporting audio file..."), + 100, + this); + + progressDlg.progressBar()->setProgress(0); + + RealTime clipStartTime = RealTime::zeroTime; + RealTime clipDuration = sourceFile->getLength(); + + if (segment) { + clipStartTime = segment->getAudioStartTime(); + clipDuration = segment->getAudioEndTime() - clipStartTime; + } + + WAVAudioFile *destFile + = new WAVAudioFile(qstrtostr(saveFile), + sourceFile->getChannels(), + sourceFile->getSampleRate(), + sourceFile->getBytesPerSecond(), + sourceFile->getBytesPerFrame(), + sourceFile->getBitsPerSample()); + + if (sourceFile->open() == false) { + delete destFile; + return ; + } + + destFile->write(); + + sourceFile->scanTo(clipStartTime); + destFile->appendSamples(sourceFile->getSampleFrameSlice(clipDuration)); + + destFile->close(); + sourceFile->close(); + delete destFile; + + progressDlg.progressBar()->setProgress(100); +} + +void +AudioManagerDialog::slotRemove() +{ + AudioFile *audioFile = getCurrentSelection(); + AudioListItem *item = + dynamic_cast(m_fileList->selectedItem()); + + if (audioFile == 0 || item == 0) + return ; + + // If we're on a Segment then delete it at the Composition + // and refresh the list. + // + if (item->getSegment()) { + // Get the next item to highlight + // + QListViewItem *newItem = item->itemBelow(); + + // Or try above + // + if (newItem == 0) + newItem = item->itemAbove(); + + // Or the parent + // + if (newItem == 0) + newItem = item->parent(); + + // Get the id and segment of the next item so that we can + // match against it + // + AudioFileId id = 0; + Segment *segment = 0; + AudioListItem *aItem = dynamic_cast(newItem); + + if (aItem) { + segment = aItem->getSegment(); + id = aItem->getId(); + } + + // Jump to new selection + // + if (newItem) + setSelected(id, segment, true); // propagate + + // Do it - will force update + // + SegmentSelection selection; + selection.insert(item->getSegment()); + emit deleteSegments(selection); + + return ; + } + + // remove segments along with audio file + // + AudioFileId id = audioFile->getId(); + SegmentSelection selection; + Composition &comp = m_doc->getComposition(); + + bool haveSegments = false; + for (Composition::iterator it = comp.begin(); it != comp.end(); ++it) { + if ((*it)->getType() == Segment::Audio && + (*it)->getAudioFileId() == id) { + haveSegments = true; + break; + } + } + + if (haveSegments) { + + QString question = i18n("This will unload audio file \"%1\" and remove all associated segments. Are you sure?") + .arg(QString(audioFile->getFilename().c_str())); + + // Ask the question + int reply = KMessageBox::warningContinueCancel(this, question); + + if (reply != KMessageBox::Continue) + return ; + } + + for (Composition::iterator it = comp.begin(); it != comp.end(); ++it) { + if ((*it)->getType() == Segment::Audio && + (*it)->getAudioFileId() == id) + selection.insert(*it); + } + emit deleteSegments(selection); + + m_doc->notifyAudioFileRemoval(id); + + m_doc->getAudioFileManager().removeFile(id); + + // tell the sequencer + emit deleteAudioFile(id); + + // repopulate + slotPopulateFileList(); +} + +void +AudioManagerDialog::slotPlayPreview() +{ + AudioFile *audioFile = getCurrentSelection(); + AudioListItem *item = + dynamic_cast(m_fileList->selectedItem()); + + if (item == 0 || audioFile == 0) + return ; + + // store the audio file we're playing + m_playingAudioFile = audioFile->getId(); + + // tell the sequencer + emit playAudioFile(audioFile->getId(), + item->getStartTime(), + item->getDuration()); + + // now open up the playing dialog + // + m_audioPlayingDialog = + new AudioPlayingDialog(this, QString(audioFile->getFilename().c_str())); + + // Setup timer to pop down dialog after file has completed + // + int msecs = item->getDuration().sec * 1000 + + item->getDuration().nsec / 1000000; + m_playTimer->start(msecs, true); // single shot + + // just execute + // + if (m_audioPlayingDialog->exec() == QDialog::Rejected) + emit cancelPlayingAudioFile(m_playingAudioFile); + + delete m_audioPlayingDialog; + m_audioPlayingDialog = 0; + + m_playTimer->stop(); + +} + +void +AudioManagerDialog::slotCancelPlayingAudio() +{ + //std::cout << "AudioManagerDialog::slotCancelPlayingAudio" << std::endl; + if (m_audioPlayingDialog) { + m_playTimer->stop(); + delete m_audioPlayingDialog; + m_audioPlayingDialog = 0; + } +} + +void +AudioManagerDialog::slotAdd() +{ + QString extensionList = i18n("*.wav|WAV files (*.wav)\n*.*|All files"); + + if (RosegardenGUIApp::self()->haveAudioImporter()) { + //!!! This list really needs to come from the importer helper program + // (which has an option to supply it -- we just haven't recorded it) + extensionList = i18n("*.wav *.flac *.ogg *.mp3|Audio files (*.wav *.flac *.ogg *.mp3)\n*.wav|WAV files (*.wav)\n*.flac|FLAC files (*.flac)\n*.ogg|Ogg files (*.ogg)\n*.mp3|MP3 files (*.mp3)\n*.*|All files"); + } + + KURL::List kurlList = + KFileDialog::getOpenURLs(":WAVS", + extensionList, + // i18n("*.wav|WAV files (*.wav)\n*.mp3|MP3 files (*.mp3)"), + this, i18n("Select one or more audio files")); + + KURL::List::iterator it; + + for (it = kurlList.begin(); it != kurlList.end(); ++it) + addFile(*it); +} + +void +AudioManagerDialog::updateActionState(bool haveSelection) +{ + if (m_doc->getAudioFileManager().begin() == + m_doc->getAudioFileManager().end()) { + stateChanged("have_audio_files", KXMLGUIClient::StateReverse); + } else { + stateChanged("have_audio_files", KXMLGUIClient::StateNoReverse); + } + + if (haveSelection) { + + stateChanged("have_audio_selected", KXMLGUIClient::StateNoReverse); + + if (m_audiblePreview) { + stateChanged("have_audible_preview", KXMLGUIClient::StateNoReverse); + } else { + stateChanged("have_audible_preview", KXMLGUIClient::StateReverse); + } + + if (isSelectedTrackAudio()) { + stateChanged("have_audio_insertable", KXMLGUIClient::StateNoReverse); + } else { + stateChanged("have_audio_insertable", KXMLGUIClient::StateReverse); + } + + } else { + stateChanged("have_audio_selected", KXMLGUIClient::StateReverse); + stateChanged("have_audio_insertable", KXMLGUIClient::StateReverse); + stateChanged("have_audible_preview", KXMLGUIClient::StateReverse); + } +} + +void +AudioManagerDialog::slotInsert() +{ + AudioFile *audioFile = getCurrentSelection(); + if (audioFile == 0) + return ; + + RG_DEBUG << "AudioManagerDialog::slotInsert\n"; + + emit insertAudioSegment(audioFile->getId(), + RealTime::zeroTime, + audioFile->getLength()); +} + +void +AudioManagerDialog::slotRemoveAll() +{ + QString question = + i18n("This will unload all audio files and remove their associated segments.\nThis action cannot be undone, and associations with these files will be lost.\nFiles will not be removed from your disk.\nAre you sure?"); + + int reply = KMessageBox::warningContinueCancel(this, question); + + if (reply != KMessageBox::Continue) + return ; + + SegmentSelection selection; + Composition &comp = m_doc->getComposition(); + + for (Composition::iterator it = comp.begin(); it != comp.end(); ++it) { + if ((*it)->getType() == Segment::Audio) + selection.insert(*it); + } + // delete segments + emit deleteSegments(selection); + + for (std::vector::const_iterator + aIt = m_doc->getAudioFileManager().begin(); + aIt != m_doc->getAudioFileManager().end(); ++aIt) { + m_doc->notifyAudioFileRemoval((*aIt)->getId()); + } + + m_doc->getAudioFileManager().clear(); + + // and now the audio files + emit deleteAllAudioFiles(); + + // clear the file list + m_fileList->clear(); + slotPopulateFileList(); +} + +void +AudioManagerDialog::slotRemoveAllUnused() +{ + QString question = + i18n("This will unload all audio files that are not associated with any segments in this composition.\nThis action cannot be undone, and associations with these files will be lost.\nFiles will not be removed from your disk.\nAre you sure?"); + + int reply = KMessageBox::warningContinueCancel(this, question); + + if (reply != KMessageBox::Continue) + return ; + + std::set + audioFiles; + Composition &comp = m_doc->getComposition(); + + for (Composition::iterator it = comp.begin(); it != comp.end(); ++it) { + if ((*it)->getType() == Segment::Audio) + audioFiles.insert((*it)->getAudioFileId()); + } + + std::vector toDelete; + for (std::vector::const_iterator + aIt = m_doc->getAudioFileManager().begin(); + aIt != m_doc->getAudioFileManager().end(); ++aIt) { + if (audioFiles.find((*aIt)->getId()) == audioFiles.end()) + toDelete.push_back((*aIt)->getId()); + } + + // Delete the audio files from the AFM + // + for (std::vector::iterator dIt = toDelete.begin(); + dIt != toDelete.end(); ++dIt) { + + m_doc->notifyAudioFileRemoval(*dIt); + m_doc->getAudioFileManager().removeFile(*dIt); + emit deleteAudioFile(*dIt); + } + + // clear the file list + m_fileList->clear(); + slotPopulateFileList(); +} + +void +AudioManagerDialog::slotDeleteUnused() +{ + std::set + audioFiles; + Composition &comp = m_doc->getComposition(); + + for (Composition::iterator it = comp.begin(); it != comp.end(); ++it) { + if ((*it)->getType() == Segment::Audio) + audioFiles.insert((*it)->getAudioFileId()); + } + + std::vector toDelete; + std::map nameMap; + + for (std::vector::const_iterator + aIt = m_doc->getAudioFileManager().begin(); + aIt != m_doc->getAudioFileManager().end(); ++aIt) { + if (audioFiles.find((*aIt)->getId()) == audioFiles.end()) { + toDelete.push_back(strtoqstr((*aIt)->getFilename())); + nameMap[strtoqstr((*aIt)->getFilename())] = (*aIt)->getId(); + } + } + + UnusedAudioSelectionDialog *dialog = new UnusedAudioSelectionDialog + (this, + i18n("The following audio files are not used in the current composition.\n\nPlease select the ones you wish to delete permanently from the hard disk.\n"), + toDelete); + + if (dialog->exec() == QDialog::Accepted) { + + std::vector names = dialog->getSelectedAudioFileNames(); + + if (names.size() > 0) { + + QString question = + i18n("About to delete 1 audio file permanently from the hard disk.
This action cannot be undone, and there will be no way to recover this file.
Are you sure?
\n", "About to delete %n audio files permanently from the hard disk.
This action cannot be undone, and there will be no way to recover these files.
Are you sure?
", names.size()); + + int reply = KMessageBox::warningContinueCancel(this, question); + + if (reply != KMessageBox::Continue) { + delete dialog; + return ; + } + + for (int i = 0; i < names.size(); ++i) { + std::cerr << i << ": " << names[i] << std::endl; + QFile file(names[i]); + if (!file.remove()) { + KMessageBox::error(this, i18n("File %1 could not be deleted.").arg(names[i])); + } else { + if (nameMap.find(names[i]) != nameMap.end()) { + m_doc->getAudioFileManager().removeFile(nameMap[names[i]]); + emit deleteAudioFile(nameMap[names[i]]); + } else { + std::cerr << "WARNING: Audio file name " << names[i] << " not in name map" << std::endl; + } + + QFile peakFile(QString("%1.pk").arg(names[i])); + peakFile.remove(); + } + } + } + } + + m_fileList->clear(); + slotPopulateFileList(); + + delete dialog; +} + +void +AudioManagerDialog::slotRename() +{ + AudioFile *audioFile = getCurrentSelection(); + + if (audioFile == 0) + return ; + + bool ok = false; + + QString newText = KLineEditDlg::getText( + i18n("Change Audio File label"), + i18n("Enter new label"), + QString(audioFile->getName().c_str()), + &ok, + this); + + if ( ok && !newText.isEmpty() ) + audioFile->setName(qstrtostr(newText)); + + slotPopulateFileList(); +} + +void +AudioManagerDialog::slotSelectionChanged(QListViewItem *item) +{ + AudioListItem *aItem = dynamic_cast(item); + + // If we're on a segment then send a "select" signal + // and enable appropriate buttons. + // + if (aItem && aItem->getSegment()) { + SegmentSelection selection; + selection.insert(aItem->getSegment()); + emit segmentsSelected(selection); + } + + updateActionState(aItem != 0); +} + +void +AudioManagerDialog::setSelected(AudioFileId id, + const Segment *segment, + bool propagate) +{ + QListViewItem *it = m_fileList->firstChild(); + QListViewItem *chIt = 0; + AudioListItem *aItem; + + while (it) { + // If we're looking for a top level audio file + if (segment == 0) { + aItem = dynamic_cast(it); + + if (aItem->getId() == id) { + selectFileListItemNoSignal(it); + return ; + } + } else // look for a child + { + if (it->childCount() > 0) + chIt = it->firstChild(); + + while (chIt) { + aItem = dynamic_cast(chIt); + + if (aItem) { + if (aItem->getId() == id && aItem->getSegment() == segment) { + selectFileListItemNoSignal(chIt); + + // Only propagate to segmentcanvas if asked to + if (propagate) { + SegmentSelection selection; + selection.insert(aItem->getSegment()); + emit segmentsSelected(selection); + } + + return ; + } + } + chIt = chIt->nextSibling(); + } + } + + it = it->nextSibling(); + } + +} + +void +AudioManagerDialog::selectFileListItemNoSignal(QListViewItem* it) +{ + m_fileList->blockSignals(true); + + if (it) { + m_fileList->ensureItemVisible(it); + m_fileList->setSelected(it, true); + } else { + m_fileList->clearSelection(); + } + + m_fileList->blockSignals(false); +} + +MultiViewCommandHistory* +AudioManagerDialog::getCommandHistory() +{ + return m_doc->getCommandHistory(); +} + +void +AudioManagerDialog::slotCommandExecuted(KCommand*) +{ + slotPopulateFileList(); +} + +void +AudioManagerDialog::slotSegmentSelection( + const SegmentSelection &segments) +{ + const Segment *segment = 0; + + for (SegmentSelection::const_iterator it = segments.begin(); + it != segments.end(); ++it) { + if ((*it)->getType() == Segment::Audio) { + // Only get one audio segment + if (segment == 0) + segment = *it; + else + segment = 0; + } + + } + + if (segment) { + // We don't propagate this segment setting to the canvas + // as we probably got called from there. + // + setSelected(segment->getAudioFileId(), segment, false); + } else { + selectFileListItemNoSignal(0); + } + +} + +void +AudioManagerDialog::slotCancelPlayingAudioFile() +{ + emit cancelPlayingAudioFile(m_playingAudioFile); +} + +void +AudioManagerDialog::closePlayingDialog(AudioFileId id) +{ + //std::cout << "AudioManagerDialog::closePlayingDialog" << std::endl; + if (m_audioPlayingDialog && id == m_playingAudioFile) { + m_playTimer->stop(); + delete m_audioPlayingDialog; + m_audioPlayingDialog = 0; + } + +} + +bool +AudioManagerDialog::addFile(const KURL& kurl) +{ + AudioFileId id = 0; + + AudioFileManager &aFM = m_doc->getAudioFileManager(); + + if (!kurl.isLocalFile()) { + if (!RosegardenGUIApp::self()->testAudioPath("importing a remote audio file")) return false; + } else if (aFM.fileNeedsConversion(qstrtostr(kurl.path()), m_sampleRate)) { + if (!RosegardenGUIApp::self()->testAudioPath("importing an audio file that needs to be converted or resampled")) return false; + } + + ProgressDialog progressDlg(i18n("Adding audio file..."), + 100, + this); + + CurrentProgressDialog::set(&progressDlg); + progressDlg.progressBar()->hide(); + progressDlg.show(); + + // Connect the progress dialog + // + connect(&aFM, SIGNAL(setProgress(int)), + progressDlg.progressBar(), SLOT(setValue(int))); + connect(&aFM, SIGNAL(setOperationName(QString)), + &progressDlg, SLOT(slotSetOperationName(QString))); + connect(&progressDlg, SIGNAL(cancelClicked()), + &aFM, SLOT(slotStopImport())); + + try { + id = aFM.importURL(kurl, m_sampleRate); + } catch (AudioFileManager::BadAudioPathException e) { + CurrentProgressDialog::freeze(); + QString errorString = i18n("Failed to add audio file. ") + strtoqstr(e.getMessage()); + KMessageBox::sorry(this, errorString); + return false; + } catch (SoundFile::BadSoundFileException e) { + CurrentProgressDialog::freeze(); + QString errorString = i18n("Failed to add audio file. ") + strtoqstr(e.getMessage()); + KMessageBox::sorry(this, errorString); + return false; + } + + disconnect(&progressDlg, SIGNAL(cancelClicked()), + &aFM, SLOT(slotStopImport())); + connect(&progressDlg, SIGNAL(cancelClicked()), + &aFM, SLOT(slotStopPreview())); + progressDlg.progressBar()->show(); + progressDlg.slotSetOperationName(i18n("Generating audio preview...")); + + try { + aFM.generatePreview(id); + } catch (Exception e) { + CurrentProgressDialog::freeze(); + + QString message = strtoqstr(e.getMessage()) + "\n\n" + + i18n("Try copying this file to a directory where you have write permission and re-add it"); + KMessageBox::information(this, message); + } + + disconnect(&progressDlg, SIGNAL(cancelClicked()), + &aFM, SLOT(slotStopPreview())); + + slotPopulateFileList(); + + // tell the sequencer + emit addAudioFile(id); + + return true; +} + +void +AudioManagerDialog::slotDropped(QDropEvent *event, QListViewItem*) +{ + 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 + for (QString url = uri.first(); url; url = uri.next()) { + + RG_DEBUG << "AudioManagerDialog::dropEvent() : got " + << url << endl; + + addFile(KURL(url)); + } + + } +} + +void +AudioManagerDialog::closeEvent(QCloseEvent *e) +{ + RG_DEBUG << "AudioManagerDialog::closeEvent()\n"; + emit closing(); + KMainWindow::closeEvent(e); +} + +void +AudioManagerDialog::slotClose() +{ + emit closing(); + close(); + //KDockMainWindow::slotClose(); + // delete this; +} + +void +AudioManagerDialog::setAudioSubsystemStatus(bool ok) +{ + // We can do something more fancy in the future but for the moment + // this will suffice. + // + m_audiblePreview = ok; +} + +bool +AudioManagerDialog::addAudioFile(const QString &filePath) +{ + return addFile(QFileInfo(filePath).absFilePath()); +} + +bool +AudioManagerDialog::isSelectedTrackAudio() +{ + Composition &comp = m_doc->getComposition(); + Studio &studio = m_doc->getStudio(); + + TrackId currentTrackId = comp.getSelectedTrack(); + Track *track = comp.getTrackById(currentTrackId); + + if (track) { + InstrumentId ii = track->getInstrument(); + Instrument *instrument = studio.getInstrumentById(ii); + + if (instrument && + instrument->getType() == Instrument::Audio) + return true; + } + + return false; + +} + +void +AudioManagerDialog::slotDistributeOnMidiSegment() +{ + RG_DEBUG << "AudioManagerDialog::slotDistributeOnMidiSegment" << endl; + + //Composition &comp = m_doc->getComposition(); + + QList& viewList = m_doc->getViewList(); + RosegardenGUIView *w = 0; + SegmentSelection selection; + + for (w = viewList.first(); w != 0; w = viewList.next()) { + selection = w->getSelection(); + } + + // Store the insert times in a local vector + // + std::vector insertTimes; + + for (SegmentSelection::iterator i = selection.begin(); + i != selection.end(); ++i) { + // For MIDI (Internal) Segments only of course + // + if ((*i)->getType() == Segment::Internal) { + for (Segment::iterator it = (*i)->begin(); it != (*i)->end(); ++it) { + if ((*it)->isa(Note::EventType)) + insertTimes.push_back((*it)->getAbsoluteTime()); + } + } + } + + for (unsigned int i = 0; i < insertTimes.size(); ++i) { + RG_DEBUG << "AudioManagerDialog::slotDistributeOnMidiSegment - " + << "insert audio segment at " << insertTimes[i] + << endl; + } +} + +} +#include "AudioManagerDialog.moc" diff --git a/src/gui/dialogs/AudioManagerDialog.h b/src/gui/dialogs/AudioManagerDialog.h new file mode 100644 index 0000000..728b700 --- /dev/null +++ b/src/gui/dialogs/AudioManagerDialog.h @@ -0,0 +1,206 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_AUDIOMANAGERDIALOG_H_ +#define _RG_AUDIOMANAGERDIALOG_H_ + +#include "sound/AudioFile.h" +#include +#include "document/ConfigGroups.h" + + +class QWidget; +class QTimer; +class QString; +class QListViewItem; +class QLabel; +class QDropEvent; +class QCloseEvent; +class QAccel; +class KURL; +class KListView; +class KCommand; + + +namespace Rosegarden +{ + +class SegmentSelection; +class Segment; +class RosegardenGUIDoc; +class RealTime; +class MultiViewCommandHistory; +class AudioPlayingDialog; +class AudioFile; + + +class AudioManagerDialog : public KMainWindow +{ + Q_OBJECT + +public: + AudioManagerDialog(QWidget *parent, + RosegardenGUIDoc *doc); + ~AudioManagerDialog(); + + // Populate the file list from the AudioFileManager + // + + // Return a pointer to the currently selected AudioFile - + // returns 0 if nothing is selected + // + AudioFile* getCurrentSelection(); + + // Scroll and expand to show this selected item + // + void setSelected(AudioFileId id, + const Segment *segment, + bool propagate); // if true then we tell the segmentcanvas + + MultiViewCommandHistory *getCommandHistory(); + + // Pop down playing dialog if it's currently up + // + void closePlayingDialog(AudioFileId id); + + // Can we playback audio currently? + // + void setAudioSubsystemStatus(bool ok); + + // Return the accelerator object + // + QAccel* getAccelerators() { return m_accelerators; } + + // Add a new file to the audio file manager + // + bool addAudioFile(const QString &filePath); + + +public slots: + void slotAdd(); + void slotPlayPreview(); + void slotRename(); + void slotInsert(); + void slotRemove(); + void slotRemoveAll(); + void slotRemoveAllUnused(); + void slotDeleteUnused(); + void slotExportAudio(); + + // get selection + void slotSelectionChanged(QListViewItem *); + + // Repopulate + // + void slotPopulateFileList(); + + // Commands + // + void slotCommandExecuted(KCommand *); + + /** + * Accept a list of Segments and highlight accordingly + * Used to reflect a selection on the main view + * (when the user selects an audio track, the corresponding item + * in the audio manager should be selected in turn) + * + * We check for embedded audio segments and if we find exactly one + * we highlight it. If we don't we unselect everything. + * + */ + void slotSegmentSelection(const SegmentSelection &); + + /** + * Cancel the currently playing audio file + */ + void slotCancelPlayingAudioFile(); + + void slotClose(); + + /** + * Turn a MIDI segment into a set of audio segments triggered + * by the MIDI Note Ons + */ + void slotDistributeOnMidiSegment(); + +signals: + + // Control signals so we can tell the sequencer about our changes + // or actions. + // + void addAudioFile(AudioFileId); + void deleteAudioFile(AudioFileId); + void playAudioFile(AudioFileId, + const RealTime &, + const RealTime &); + void cancelPlayingAudioFile(AudioFileId); + void deleteAllAudioFiles(); + + // We've selected a segment here, make the canvas select it too + // + void segmentsSelected(const SegmentSelection&); + void deleteSegments(const SegmentSelection&); + void insertAudioSegment(AudioFileId, + const RealTime &, + const RealTime &); + + void closing(); +protected slots: + void slotDropped(QDropEvent*, QListViewItem*); + void slotCancelPlayingAudio(); + +protected: + bool addFile(const KURL& kurl); + bool isSelectedTrackAudio(); + void selectFileListItemNoSignal(QListViewItem*); + void updateActionState(bool haveSelection); + + virtual void closeEvent(QCloseEvent *); + + //--------------- Data members --------------------------------- + + KListView *m_fileList; + QLabel *m_wrongSampleRates; + RosegardenGUIDoc *m_doc; + + QAccel *m_accelerators; + + AudioFileId m_playingAudioFile; + AudioPlayingDialog *m_audioPlayingDialog; + QTimer *m_playTimer; + + static const char* const m_listViewLayoutName; + static const int m_maxPreviewWidth; + static const int m_previewHeight; + + bool m_audiblePreview; + int m_sampleRate; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/AudioPlayingDialog.cpp b/src/gui/dialogs/AudioPlayingDialog.cpp new file mode 100644 index 0000000..0915ef2 --- /dev/null +++ b/src/gui/dialogs/AudioPlayingDialog.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AudioPlayingDialog.h" + +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +AudioPlayingDialog::AudioPlayingDialog(QWidget *parent, + const QString &name): + KDialogBase(parent, 0, true, + i18n("Playing audio file"), + Cancel) +{ + QHBox *w = makeHBoxMainWidget(); + QLabel *label = new + QLabel(i18n("Playing audio file \"%1\"").arg(name), w); + + label->setMinimumHeight(80); + + +} + +} +#include "AudioPlayingDialog.moc" diff --git a/src/gui/dialogs/AudioPlayingDialog.h b/src/gui/dialogs/AudioPlayingDialog.h new file mode 100644 index 0000000..880d0bd --- /dev/null +++ b/src/gui/dialogs/AudioPlayingDialog.h @@ -0,0 +1,56 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_AUDIOPLAYINGDIALOG_H_ +#define _RG_AUDIOPLAYINGDIALOG_H_ + +#include +#include "gui/application/RosegardenDCOP.h" + + +class QWidget; +class QString; + + +namespace Rosegarden +{ + + + +class AudioPlayingDialog : public KDialogBase +{ + Q_OBJECT + +public: + AudioPlayingDialog(QWidget *parent, const QString &label); + +signals: + +}; + + +} + +#endif diff --git a/src/gui/dialogs/AudioPluginDialog.cpp b/src/gui/dialogs/AudioPluginDialog.cpp new file mode 100644 index 0000000..7f54f71 --- /dev/null +++ b/src/gui/dialogs/AudioPluginDialog.cpp @@ -0,0 +1,916 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AudioPluginDialog.h" +#include + +#include +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/AudioPluginInstance.h" +#include "base/Instrument.h" +#include "base/MidiProgram.h" +#include "gui/studio/AudioPluginClipboard.h" +#include "gui/studio/AudioPlugin.h" +#include "gui/studio/AudioPluginManager.h" +#include "gui/studio/AudioPluginOSCGUIManager.h" +#include "gui/studio/StudioControl.h" +#include "gui/widgets/PluginControl.h" +#include "sound/MappedStudio.h" +#include "sound/PluginIdentifier.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Rosegarden +{ + +AudioPluginDialog::AudioPluginDialog(QWidget *parent, + AudioPluginManager *aPM, +#ifdef HAVE_LIBLO + AudioPluginOSCGUIManager *aGM, +#endif + PluginContainer *pluginContainer, + int index): + KDialogBase(parent, "", false, i18n("Audio Plugin"), +#ifdef HAVE_LIBLO + Close | Details | Help), +#else + Close | Help), +#endif + m_pluginManager(aPM), +#ifdef HAVE_LIBLO + m_pluginGUIManager(aGM), +#endif + m_pluginContainer(pluginContainer), + m_containerId(pluginContainer->getId()), + m_programLabel(0), + m_index(index), + m_generating(true), + m_guiShown(false) +{ + setHelp("studio-plugins"); + + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, + QSizePolicy::Fixed)); + +#ifdef HAVE_LIBLO + + setButtonText(Details, i18n("Editor")); +#endif + + QVBox *vbox = makeVBoxMainWidget(); + + QGroupBox *pluginSelectionBox = new QGroupBox + (1, Horizontal, i18n("Plugin"), vbox); + + makePluginParamsBox(vbox, 0, 10); + + m_pluginCategoryBox = new QHBox(pluginSelectionBox); + new QLabel(i18n("Category:"), m_pluginCategoryBox); + m_pluginCategoryList = new KComboBox(m_pluginCategoryBox); + m_pluginCategoryList->setSizeLimit(20); + + QHBox *hbox = new QHBox(pluginSelectionBox); + m_pluginLabel = new QLabel(i18n("Plugin:"), hbox); + m_pluginList = new KComboBox(hbox); + m_pluginList->setSizeLimit(20); + QToolTip::add + (m_pluginList, i18n("Select a plugin from this list.")); + + QHBox *h = new QHBox(pluginSelectionBox); + +// top line + m_bypass = new QCheckBox(i18n("Bypass"), h); + QToolTip::add + (m_bypass, i18n("Bypass this plugin.")); + + connect(m_bypass, SIGNAL(toggled(bool)), + this, SLOT(slotBypassChanged(bool))); + + + m_insOuts = new QLabel(i18n(""), h); + m_insOuts->setAlignment(AlignRight); + QToolTip::add + (m_insOuts, i18n("Input and output port counts.")); + + m_pluginId = new QLabel(i18n(""), h); + m_pluginId->setAlignment(AlignRight); + QToolTip::add + (m_pluginId, i18n("Unique ID of plugin.")); + + connect(m_pluginList, SIGNAL(activated(int)), + this, SLOT(slotPluginSelected(int))); + + connect(m_pluginCategoryList, SIGNAL(activated(int)), + this, SLOT(slotCategorySelected(int))); + +// new line + h = new QHBox(pluginSelectionBox); + m_copyButton = new QPushButton(i18n("Copy"), h); + connect(m_copyButton, SIGNAL(clicked()), + this, SLOT(slotCopy())); + QToolTip::add + (m_copyButton, i18n("Copy plugin parameters")); + + m_pasteButton = new QPushButton(i18n("Paste"), h); + connect(m_pasteButton, SIGNAL(clicked()), + this, SLOT(slotPaste())); + QToolTip::add + (m_pasteButton, i18n("Paste plugin parameters")); + + m_defaultButton = new QPushButton(i18n("Default"), h); + connect(m_defaultButton, SIGNAL(clicked()), + this, SLOT(slotDefault())); + QToolTip::add + (m_defaultButton, i18n("Set to defaults")); + + populatePluginCategoryList(); + populatePluginList(); + + m_generating = false; + + m_accelerators = new QAccel(this); +} + +#ifdef HAVE_LIBLO + +void +AudioPluginDialog::slotDetails() +{ + slotShowGUI(); +} +#endif + +void +AudioPluginDialog::slotShowGUI() +{ + emit showPluginGUI(m_containerId, m_index); + m_guiShown = true; + + //!!! need to get notification of when a plugin gui exits from the + //gui manager +} + +void +AudioPluginDialog::populatePluginCategoryList() +{ + AudioPluginInstance *inst = m_pluginContainer->getPlugin(m_index); + std::set + categories; + QString currentCategory; + + for (PluginIterator i = m_pluginManager->begin(); + i != m_pluginManager->end(); ++i) { + + if (( isSynth() && (*i)->isSynth()) || + (!isSynth() && (*i)->isEffect())) { + + if ((*i)->getCategory() != "") { + categories.insert((*i)->getCategory()); + } + + if (inst && inst->isAssigned() && + ((*i)->getIdentifier() == inst->getIdentifier().c_str())) { + currentCategory = (*i)->getCategory(); + } + } + } + + if (inst) { + RG_DEBUG << "AudioPluginDialog::populatePluginCategoryList: inst id " << inst->getIdentifier() << ", cat " << currentCategory << endl; + } + + if (categories.empty()) { + m_pluginCategoryBox->hide(); + m_pluginLabel->hide(); + } + + m_pluginCategoryList->clear(); + m_pluginCategoryList->insertItem(i18n("(any)")); + m_pluginCategoryList->insertItem(i18n("(unclassified)")); + m_pluginCategoryList->setCurrentItem(0); + + for (std::set + ::iterator i = categories.begin(); + i != categories.end(); ++i) { + + m_pluginCategoryList->insertItem(*i); + + if (*i == currentCategory) { + m_pluginCategoryList->setCurrentItem(m_pluginCategoryList->count() - 1); + } + } +} + +void +AudioPluginDialog::populatePluginList() +{ + m_pluginList->clear(); + m_pluginsInList.clear(); + + m_pluginList->insertItem(i18n("(none)")); + m_pluginsInList.push_back(0); + + QString category; + bool needCategory = false; + + if (m_pluginCategoryList->currentItem() > 0) { + needCategory = true; + if (m_pluginCategoryList->currentItem() == 1) { + category = ""; + } else { + category = m_pluginCategoryList->currentText(); + } + } + + // Check for plugin and setup as required + AudioPluginInstance *inst = m_pluginContainer->getPlugin(m_index); + if (inst) + m_bypass->setChecked(inst->isBypassed()); + + // Use this temporary map to ensure that the plugins are sorted + // by name when they go into the combobox + typedef std::pair PluginPair; + typedef std::map PluginMap; + PluginMap plugins; + int count = 0; + + for (PluginIterator i = m_pluginManager->begin(); + i != m_pluginManager->end(); ++i) { + + ++count; + + if (( isSynth() && (*i)->isSynth()) || + (!isSynth() && (*i)->isEffect())) { + + if (needCategory) { + QString cat = ""; + if ((*i)->getCategory()) + cat = (*i)->getCategory(); + if (cat != category) + continue; + } + + QString name = (*i)->getName(); + bool store = true; + + if (plugins.find(name) != plugins.end()) { + // We already have a plugin of this name. If it's a + // LADSPA plugin, replace it (this one might be + // something better); otherwise leave it alone. + QString id = plugins[name].second->getIdentifier(); + QString type, soname, label; + PluginIdentifier::parseIdentifier(id, type, soname, label); + if (type != "ladspa") { + store = false; + } + } + + if (store) { + plugins[(*i)->getName()] = PluginPair(count, *i); + } + } + } + + const char *currentId = 0; + if (inst && inst->isAssigned()) + currentId = inst->getIdentifier().c_str(); + + for (PluginMap::iterator i = plugins.begin(); i != plugins.end(); ++i) { + + QString name = i->first; + if (name.endsWith(" VST")) + name = name.left(name.length() - 4); + + m_pluginList->insertItem(name); + m_pluginsInList.push_back(i->second.first); + + if (currentId && currentId == i->second.second->getIdentifier()) { + m_pluginList->setCurrentItem(m_pluginList->count() - 1); + } + } + + slotPluginSelected(m_pluginList->currentItem()); +} + +void +AudioPluginDialog::makePluginParamsBox(QWidget *parent, int portCount, + int tooManyPorts) +{ + m_pluginParamsBox = new QFrame(parent); + + int columns = 2; + if (portCount > tooManyPorts) { + columns = 2; + } else if (portCount > 24) { + if (portCount > 60) { + columns = (portCount - 1) / 16 + 1; + } else { + columns = (portCount - 1) / 12 + 1; + } + } + + int perColumn = 4; + if (portCount > 48) { // no bounds will be shown + perColumn = 2; + } + + m_gridLayout = new QGridLayout(m_pluginParamsBox, + 1, // rows (will expand) + columns * perColumn, + 5); // margin + + m_gridLayout->setColStretch(3, 2); + m_gridLayout->setColStretch(7, 2); +} + +void +AudioPluginDialog::slotCategorySelected(int) +{ + populatePluginList(); +} + +void +AudioPluginDialog::slotPluginSelected(int i) +{ + bool guiWasShown = m_guiShown; + + if (m_guiShown) { + emit stopPluginGUI(m_containerId, m_index); + m_guiShown = false; + } + + int number = m_pluginsInList[i]; + + RG_DEBUG << "AudioPluginDialog::::slotPluginSelected - " + << "setting up plugin from position " << number << " at menu item " << i << endl; + + QString caption = + strtoqstr(m_pluginContainer->getName()) + + QString(" [ %1 ] - ").arg(m_index + 1); + + if (number == 0) { + setCaption(caption + i18n("")); + m_insOuts->setText(i18n("")); + m_pluginId->setText(i18n("")); + + QToolTip::hide(); + QToolTip::remove + (m_pluginList); + + QToolTip::add + (m_pluginList, i18n("Select a plugin from this list.")); + } + + AudioPlugin *plugin = m_pluginManager->getPlugin(number - 1); + + // Destroy old param widgets + // + QWidget* parent = dynamic_cast(m_pluginParamsBox->parent()); + + delete m_pluginParamsBox; + m_pluginWidgets.clear(); // The widgets are deleted with the parameter box + m_programCombo = 0; + + int portCount = 0; + if (plugin) { + for (AudioPlugin::PortIterator it = plugin->begin(); it != plugin->end(); ++it) { + if (((*it)->getType() & PluginPort::Control) && + ((*it)->getType() & PluginPort::Input)) + ++portCount; + } + } + + int tooManyPorts = 96; + makePluginParamsBox(parent, portCount, tooManyPorts); + bool showBounds = (portCount <= 48); + + if (portCount > tooManyPorts) { + + m_gridLayout->addMultiCellWidget( + new QLabel(i18n("This plugin has too many controls to edit here."), + m_pluginParamsBox), + 1, 1, 0, m_gridLayout->numCols() - 1, Qt::AlignCenter); + } + + AudioPluginInstance *inst = m_pluginContainer->getPlugin(m_index); + if (!inst) + return ; + + if (plugin) { + setCaption(caption + plugin->getName()); + m_pluginId->setText(i18n("Id: %1").arg(plugin->getUniqueId())); + + QString pluginInfo = plugin->getAuthor() + QString("\n") + + plugin->getCopyright(); + + QToolTip::hide(); + QToolTip::remove + (m_pluginList); + QToolTip::add + (m_pluginList, pluginInfo); + + std::string identifier = plugin->getIdentifier().data(); + + // Only clear ports &c if this method is accessed by user + // action (after the constructor) + // + if (m_generating == false) { + inst->clearPorts(); + if (inst->getIdentifier() != identifier) { + inst->clearConfiguration(); + } + } + + inst->setIdentifier(identifier); + + AudioPlugin::PortIterator it = plugin->begin(); + int count = 0; + int ins = 0, outs = 0; + + for (; it != plugin->end(); ++it) { + if (((*it)->getType() & PluginPort::Control) && + ((*it)->getType() & PluginPort::Input)) { + // Check for port existence and create with default value + // if it doesn't exist. Modification occurs through the + // slotPluginPortChanged signal. + // + if (inst->getPort(count) == 0) { + inst->addPort(count, (float)(*it)->getDefaultValue()); +// std::cerr << "Plugin port name " << (*it)->getName() << ", default: " << (*it)->getDefaultValue() << std::endl; + } + + } else if ((*it)->getType() & PluginPort::Audio) { + if ((*it)->getType() & PluginPort::Input) + ++ins; + else if ((*it)->getType() & PluginPort::Output) + ++outs; + } + + ++count; + } + + if (ins == 1 && outs == 1) + m_insOuts->setText(i18n("mono")); + else if (ins == 2 && outs == 2) + m_insOuts->setText(i18n("stereo")); + else + m_insOuts->setText(i18n("%1 in, %2 out").arg(ins).arg(outs)); + + QString shortName(plugin->getName()); + int parenIdx = shortName.find(" ("); + if (parenIdx > 0) { + shortName = shortName.left(parenIdx); + if (shortName == "Null") + shortName = "Plugin"; + } + } + + adjustSize(); + setFixedSize(minimumSizeHint()); + + // tell the sequencer + emit pluginSelected(m_containerId, m_index, number - 1); + + if (plugin) { + + int current = -1; + QStringList programs = getProgramsForInstance(inst, current); + + if (programs.count() > 0) { + + m_programLabel = new QLabel(i18n("Program: "), m_pluginParamsBox); + + m_programCombo = new KComboBox(m_pluginParamsBox); + m_programCombo->setSizeLimit(20); + m_programCombo->insertItem(i18n("")); + m_gridLayout->addMultiCellWidget(m_programLabel, + 0, 0, 0, 0, Qt::AlignRight); + m_gridLayout->addMultiCellWidget(m_programCombo, + 0, 0, 1, m_gridLayout->numCols() - 1, + Qt::AlignLeft); + connect(m_programCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotPluginProgramChanged(const QString &))); + + m_programCombo->clear(); + m_programCombo->insertItem(i18n("")); + m_programCombo->insertStringList(programs); + m_programCombo->setCurrentItem(current + 1); + m_programCombo->adjustSize(); + + m_programLabel->show(); + m_programCombo->show(); + } + + AudioPlugin::PortIterator it = plugin->begin(); + int count = 0; + + for (; it != plugin->end(); ++it) { + if (((*it)->getType() & PluginPort::Control) && + ((*it)->getType() & PluginPort::Input)) { + PluginControl *control = + new PluginControl(m_pluginParamsBox, + m_gridLayout, + PluginControl::Rotary, + *it, + m_pluginManager, + count, + inst->getPort(count)->value, + showBounds, + portCount > tooManyPorts); + + connect(control, SIGNAL(valueChanged(float)), + this, SLOT(slotPluginPortChanged(float))); + + m_pluginWidgets.push_back(control); + } + + ++count; + } + + m_pluginParamsBox->show(); + } + + if (guiWasShown) { + emit showPluginGUI(m_containerId, m_index); + m_guiShown = true; + } + +#ifdef HAVE_LIBLO + bool gui = m_pluginGUIManager->hasGUI(m_containerId, m_index); + actionButton(Details)->setEnabled(gui); +#endif + +} + +QStringList +AudioPluginDialog::getProgramsForInstance(AudioPluginInstance *inst, int ¤t) +{ + QStringList list; + int mappedId = inst->getMappedId(); + QString currentProgram = strtoqstr(inst->getProgram()); + + MappedObjectPropertyList propertyList = StudioControl::getStudioObjectProperty + (mappedId, MappedPluginSlot::Programs); + + current = -1; + + for (MappedObjectPropertyList::iterator i = propertyList.begin(); + i != propertyList.end(); ++i) { + if (*i == currentProgram) + current = list.count(); + list.append(*i); + } + + return list; +} + +void +AudioPluginDialog::slotPluginPortChanged(float value) +{ + const QObject* object = sender(); + + const PluginControl* control = dynamic_cast(object); + + if (!control) + return ; + + // store the new value + AudioPluginInstance *inst = m_pluginContainer->getPlugin(m_index); + inst->getPort(control->getIndex())->setValue(value); + + emit pluginPortChanged(m_containerId, m_index, control->getIndex()); +} + +void +AudioPluginDialog::slotPluginProgramChanged(const QString &value) +{ + // store the new value + AudioPluginInstance *inst = m_pluginContainer->getPlugin(m_index); + + if (m_programCombo && value == m_programCombo->text(0)) { // "" + inst->setProgram(""); + } else { + inst->setProgram(qstrtostr(value)); + emit pluginProgramChanged(m_containerId, m_index); + } +} + +void +AudioPluginDialog::updatePlugin(int number) +{ + for (unsigned int i = 0; i < m_pluginsInList.size(); ++i) { + if (m_pluginsInList[i] == number + 1) { + blockSignals(true); + m_pluginList->setCurrentItem(i); + blockSignals(false); + return ; + } + } +} + +void +AudioPluginDialog::updatePluginPortControl(int port) +{ + AudioPluginInstance *inst = m_pluginContainer->getPlugin(m_index); + if (inst) { + PluginPortInstance *pti = inst->getPort(port); + if (pti) { + for (std::vector::iterator i = m_pluginWidgets.begin(); + i != m_pluginWidgets.end(); ++i) { + if ((*i)->getIndex() == port) { + (*i)->setValue(pti->value, false); // don't emit + return ; + } + } + } + } +} + +void +AudioPluginDialog::updatePluginProgramControl() +{ + AudioPluginInstance *inst = m_pluginContainer->getPlugin(m_index); + if (inst) { + std::string program = inst->getProgram(); + if (m_programCombo) { + m_programCombo->blockSignals(true); + m_programCombo->setCurrentText(strtoqstr(program)); + m_programCombo->blockSignals(false); + } + for (std::vector::iterator i = m_pluginWidgets.begin(); + i != m_pluginWidgets.end(); ++i) { + PluginPortInstance *pti = inst->getPort((*i)->getIndex()); + if (pti) { + (*i)->setValue(pti->value, false); // don't emit + } + } + } +} + +void +AudioPluginDialog::updatePluginProgramList() +{ + if (!m_programLabel) + return ; + + AudioPluginInstance *inst = m_pluginContainer->getPlugin(m_index); + if (!inst) + return ; + + if (!m_programCombo) { + + int current = -1; + QStringList programs = getProgramsForInstance(inst, current); + + if (programs.count() > 0) { + + m_programLabel = new QLabel(i18n("Program: "), m_pluginParamsBox); + + m_programCombo = new KComboBox(m_pluginParamsBox); + m_programCombo->setSizeLimit(20); + m_programCombo->insertItem(i18n("")); + m_gridLayout->addMultiCellWidget(m_programLabel, + 0, 0, 0, 0, Qt::AlignRight); + m_gridLayout->addMultiCellWidget(m_programCombo, + 0, 0, 1, m_gridLayout->numCols() - 1, + Qt::AlignLeft); + + m_programCombo->clear(); + m_programCombo->insertItem(i18n("")); + m_programCombo->insertStringList(programs); + m_programCombo->setCurrentItem(current + 1); + m_programCombo->adjustSize(); + + m_programLabel->show(); + m_programCombo->show(); + + m_programCombo->blockSignals(true); + connect(m_programCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotPluginProgramChanged(const QString &))); + + } else { + return ; + } + } else { + } + + while (m_programCombo->count() > 0) { + m_programCombo->removeItem(0); + } + + int current = -1; + QStringList programs = getProgramsForInstance(inst, current); + + if (programs.count() > 0) { + m_programCombo->show(); + m_programLabel->show(); + m_programCombo->clear(); + m_programCombo->insertItem(i18n("")); + m_programCombo->insertStringList(programs); + m_programCombo->setCurrentItem(current + 1); + } else { + m_programLabel->hide(); + m_programCombo->hide(); + } + + m_programCombo->blockSignals(false); +} + +void +AudioPluginDialog::slotBypassChanged(bool bp) +{ + AudioPluginInstance *inst = m_pluginContainer->getPlugin(m_index); + + if (inst) + inst->setBypass(bp); + + emit bypassed(m_containerId, m_index, bp); +} + +void +AudioPluginDialog::windowActivationChange(bool oldState) +{ + if (isActiveWindow()) { + emit windowActivated(); + } +} + +void +AudioPluginDialog::closeEvent(QCloseEvent *e) +{ + e->accept(); + emit destroyed(m_containerId, m_index); +} + +void +AudioPluginDialog::slotClose() +{ + emit destroyed(m_containerId, m_index); + reject(); +} + +void +AudioPluginDialog::slotCopy() +{ + int item = m_pluginList->currentItem(); + int number = m_pluginsInList[item] - 1; + + if (number >= 0) { + AudioPluginClipboard *clipboard = + m_pluginManager->getPluginClipboard(); + + clipboard->m_pluginNumber = number; + + AudioPluginInstance *inst = m_pluginContainer->getPlugin(m_index); + if (inst) { + clipboard->m_configuration = inst->getConfiguration(); + } else { + clipboard->m_configuration.clear(); + } + + std::cout << "AudioPluginDialog::slotCopy - plugin number = " << number + << std::endl; + + if (m_programCombo && m_programCombo->currentItem() > 0) { + clipboard->m_program = qstrtostr(m_programCombo->currentText()); + } else { + clipboard->m_program = ""; + } + + clipboard->m_controlValues.clear(); + std::vector::iterator it; + for (it = m_pluginWidgets.begin(); it != m_pluginWidgets.end(); ++it) { + std::cout << "AudioPluginDialog::slotCopy - " + << "value = " << (*it)->getValue() << std::endl; + + clipboard->m_controlValues.push_back((*it)->getValue()); + } + } +} + +void +AudioPluginDialog::slotPaste() +{ + AudioPluginClipboard *clipboard = m_pluginManager->getPluginClipboard(); + + std::cout << "AudioPluginDialog::slotPaste - paste plugin id " + << clipboard->m_pluginNumber << std::endl; + + if (clipboard->m_pluginNumber != -1) { + int count = 0; + for (std::vector::iterator it = m_pluginsInList.begin(); + it != m_pluginsInList.end(); ++it) { + if ((*it) == clipboard->m_pluginNumber + 1) + break; + count++; + } + + if (count >= int(m_pluginsInList.size())) + return ; + + // now select the plugin + // + m_pluginList->setCurrentItem(count); + slotPluginSelected(count); + + // set configuration data + // + for (std::map::const_iterator i = + clipboard->m_configuration.begin(); + i != clipboard->m_configuration.end(); ++i) { + emit changePluginConfiguration(m_containerId, + m_index, + false, + strtoqstr(i->first), + strtoqstr(i->second)); + } + + // and set the program + // + if (m_programCombo && clipboard->m_program != "") { + m_programCombo->setCurrentText(strtoqstr(clipboard->m_program)); + slotPluginProgramChanged(strtoqstr(clipboard->m_program)); + } + + // and ports + // + count = 0; + + for (std::vector::iterator i = m_pluginWidgets.begin(); + i != m_pluginWidgets.end(); ++i) { + + if (count < clipboard->m_controlValues.size()) { + (*i)->setValue(clipboard->m_controlValues[count], true); + } + ++count; + } + } +} + +void +AudioPluginDialog::slotDefault() +{ + AudioPluginInstance *inst = m_pluginContainer->getPlugin(m_index); + if (!inst) + return ; + + int i = m_pluginList->currentItem(); + int n = m_pluginsInList[i]; + if (n == 0) + return ; + + AudioPlugin *plugin = m_pluginManager->getPlugin(n - 1); + if (!plugin) + return ; + + for (std::vector::iterator i = m_pluginWidgets.begin(); + i != m_pluginWidgets.end(); ++i) { + + for (AudioPlugin::PortIterator pi = plugin->begin(); pi != plugin->end(); ++pi) { + if ((*pi)->getNumber() == (*i)->getIndex()) { + (*i)->setValue((*pi)->getDefaultValue(), true); // and emit + break; + } + } + } +} + +} +#include "AudioPluginDialog.moc" diff --git a/src/gui/dialogs/AudioPluginDialog.h b/src/gui/dialogs/AudioPluginDialog.h new file mode 100644 index 0000000..bc8a38b --- /dev/null +++ b/src/gui/dialogs/AudioPluginDialog.h @@ -0,0 +1,167 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_AUDIOPLUGINDIALOG_H_ +#define _RG_AUDIOPLUGINDIALOG_H_ + +#include "base/Instrument.h" +#include "base/MidiProgram.h" +#include +#include +#include +#include + + +class QWidget; +class QPushButton; +class QLabel; +class QGridLayout; +class QFrame; +class QCloseEvent; +class QCheckBox; +class QAccel; +class KComboBox; + + +namespace Rosegarden +{ + +class PluginControl; +class PluginContainer; +class AudioPluginOSCGUIManager; +class AudioPluginManager; +class AudioPluginInstance; + + +class AudioPluginDialog : public KDialogBase +{ + Q_OBJECT + +public: + AudioPluginDialog(QWidget *parent, + AudioPluginManager *aPM, +#ifdef HAVE_LIBLO + AudioPluginOSCGUIManager *aGM, +#endif + PluginContainer *instrument, + int index); + + PluginContainer* getPluginContainer() const { return m_pluginContainer; } + + QAccel* getAccelerators() { return m_accelerators; } + + bool isSynth() { return m_index == int(Instrument::SYNTH_PLUGIN_POSITION); } + + void updatePlugin(int number); + void updatePluginPortControl(int port); + void updatePluginProgramControl(); + void updatePluginProgramList(); + void guiExited() { m_guiShown = false; } + +public slots: + void slotCategorySelected(int); + void slotPluginSelected(int index); + void slotPluginPortChanged(float value); + void slotPluginProgramChanged(const QString &value); + void slotBypassChanged(bool); + void slotCopy(); + void slotPaste(); + void slotDefault(); + void slotShowGUI(); + +#ifdef HAVE_LIBLO + virtual void slotDetails(); +#endif + +signals: + void pluginSelected(InstrumentId, int pluginIndex, int plugin); + void pluginPortChanged(InstrumentId, int pluginIndex, int portIndex); + void pluginProgramChanged(InstrumentId, int pluginIndex); + void changePluginConfiguration(InstrumentId, int pluginIndex, + bool global, QString key, QString value); + void showPluginGUI(InstrumentId, int pluginIndex); + void stopPluginGUI(InstrumentId, int pluginIndex); + + // is the plugin being bypassed + void bypassed(InstrumentId, int pluginIndex, bool bp); + void destroyed(InstrumentId, int index); + + void windowActivated(); + +protected slots: + virtual void slotClose(); + +protected: + virtual void closeEvent(QCloseEvent *e); + virtual void windowActivationChange(bool); + + void makePluginParamsBox(QWidget*, int portCount, int tooManyPorts); + QStringList getProgramsForInstance(AudioPluginInstance *inst, int ¤t); + + //--------------- Data members --------------------------------- + + AudioPluginManager *m_pluginManager; +#ifdef HAVE_LIBLO + AudioPluginOSCGUIManager *m_pluginGUIManager; +#endif + PluginContainer *m_pluginContainer; + InstrumentId m_containerId; + + QFrame *m_pluginParamsBox; + QWidget *m_pluginCategoryBox; + KComboBox *m_pluginCategoryList; + QLabel *m_pluginLabel; + KComboBox *m_pluginList; + std::vector m_pluginsInList; + QLabel *m_insOuts; + QLabel *m_pluginId; + QCheckBox *m_bypass; + QPushButton *m_copyButton; + QPushButton *m_pasteButton; + QPushButton *m_defaultButton; + QPushButton *m_guiButton; + + QLabel *m_programLabel; + KComboBox *m_programCombo; + std::vector m_pluginWidgets; + QGridLayout *m_gridLayout; + + int m_index; + + bool m_generating; + bool m_guiShown; + + QAccel *m_accelerators; + + void populatePluginCategoryList(); + void populatePluginList(); +}; + + +} // end of namespace + + + +#endif diff --git a/src/gui/dialogs/AudioSplitDialog.cpp b/src/gui/dialogs/AudioSplitDialog.cpp new file mode 100644 index 0000000..42290b3 --- /dev/null +++ b/src/gui/dialogs/AudioSplitDialog.cpp @@ -0,0 +1,339 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "AudioSplitDialog.h" +#include + +#include +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Exception.h" +#include "base/RealTime.h" +#include "base/Segment.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/application/RosegardenApplication.h" +#include "sound/AudioFileManager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +AudioSplitDialog::AudioSplitDialog(QWidget *parent, + Segment *segment, + RosegardenGUIDoc *doc): + KDialogBase(parent, 0, true, + i18n("Autosplit Audio Segment"), Ok | Cancel), + m_doc(doc), + m_segment(segment), + m_canvasWidth(500), + m_canvasHeight(200), + m_previewWidth(400), + m_previewHeight(100) +{ + if (!segment || segment->getType() != Segment::Audio) + reject(); + + QVBox *w = makeVBoxMainWidget(); + + new QLabel(i18n("AutoSplit Segment \"") + + strtoqstr(m_segment->getLabel()) + QString("\""), w); + + m_canvas = new QCanvas(w); + m_canvas->resize(m_canvasWidth, m_canvasHeight); + m_canvasView = new QCanvasView(m_canvas, w); + m_canvasView->setFixedWidth(m_canvasWidth); + m_canvasView->setFixedHeight(m_canvasHeight); + + m_canvasView->setHScrollBarMode(QScrollView::AlwaysOff); + m_canvasView->setVScrollBarMode(QScrollView::AlwaysOff); + m_canvasView->setDragAutoScroll(false); + + QHBox *hbox = new QHBox(w); + new QLabel(i18n("Threshold"), hbox); + m_thresholdSpin = new QSpinBox(hbox); + m_thresholdSpin->setSuffix(" %"); + connect(m_thresholdSpin, SIGNAL(valueChanged(int)), + SLOT(slotThresholdChanged(int))); + + // ensure this is cleared + m_previewBoxes.clear(); + + // Set thresholds + // + int threshold = 1; + m_thresholdSpin->setValue(threshold); + drawPreview(); + drawSplits(1); +} + +void +AudioSplitDialog::drawPreview() +{ + // Delete everything on the canvas + // + QCanvasItemList list = m_canvas->allItems(); + for (QCanvasItemList::Iterator it = list.begin(); it != list.end(); it++) + delete *it; + + // empty the preview boxes + m_previewBoxes.erase(m_previewBoxes.begin(), m_previewBoxes.end()); + + // Draw a bounding box + // + int border = 5; + QCanvasRectangle *rect = new QCanvasRectangle(m_canvas); + rect->setSize(m_canvasWidth - border * 2, m_canvasHeight - border * 2); + rect->setX(border); + rect->setY(border); + rect->setZ(1); + rect->setPen(kapp->palette().color(QPalette::Active, QColorGroup::Dark)); + rect->setBrush(kapp->palette().color(QPalette::Active, QColorGroup::Base)); + rect->setVisible(true); + + // Get preview in vector form + // + AudioFileManager &aFM = m_doc->getAudioFileManager(); + int channels = aFM.getAudioFile(m_segment->getAudioFileId())->getChannels(); + + std::vector values; + + try { + values = aFM.getPreview(m_segment->getAudioFileId(), + m_segment->getAudioStartTime(), + m_segment->getAudioEndTime(), + m_previewWidth, + false); + } catch (Exception e) { + QCanvasText *text = new QCanvasText(m_canvas); + text->setColor(kapp->palette(). + color(QPalette::Active, QColorGroup::Shadow)); + text->setText(i18n("")); + text->setX(30); + text->setY(30); + text->setZ(4); + text->setVisible(true); + m_canvas->update(); + return ; + } + + int startX = (m_canvasWidth - m_previewWidth) / 2; + int halfHeight = m_canvasHeight / 2; + float h1, h2; + std::vector::iterator it = values.begin(); + + // Draw preview + // + for (int i = 0; i < m_previewWidth; i++) { + if (channels == 1) { + h1 = *(it++); + h2 = h1; + } else { + h1 = *(it++); + h2 = *(it++); + } + + + int startY = halfHeight + int(h1 * float(m_previewHeight / 2)); + int endY = halfHeight - int(h2 * float(m_previewHeight / 2)); + + if ( startY < 0 ) { + RG_DEBUG << "AudioSplitDialog::AudioSplitDialog - " + << "startY - out of negative range" + << endl; + startY = 0; + } + + if (endY < 0) { + RG_DEBUG << "AudioSplitDialog::AudioSplitDialog - " + << "endY - out of negative range" + << endl; + endY = 0; + } + + QCanvasLine *line = new QCanvasLine(m_canvas); + line->setPoints(startX + i, + startY, + startX + i, + endY); + line->setZ(3); + line->setPen(kapp-> + palette().color(QPalette::Active, QColorGroup::Shadow)); + line->setBrush(kapp-> + palette().color(QPalette::Active, QColorGroup::Shadow)); + line->setVisible(true); + + } + + // Draw zero dc line + // + rect = new QCanvasRectangle(m_canvas); + rect->setX(startX); + rect->setY(halfHeight - 1); + rect->setSize(m_previewWidth, 2); + rect->setPen(kapp->palette().color(QPalette::Active, QColorGroup::Shadow)); + rect->setBrush(kapp->palette().color(QPalette::Active, QColorGroup::Shadow)); + rect->setZ(4); + rect->setVisible(true); + + // Start time + // + char msecs[100]; + sprintf(msecs, "%03d", m_segment->getAudioStartTime().msec()); + QString startText = QString("%1.%2s") + .arg(m_segment->getAudioStartTime().sec) + .arg(msecs); + QCanvasText *text = new QCanvasText(m_canvas); + text->setColor( + kapp->palette().color(QPalette::Active, QColorGroup::Shadow)); + text->setText(startText); + text->setX(startX - 20); + text->setY(m_canvasHeight / 2 - m_previewHeight / 2 - 35); + text->setZ(3); + text->setVisible(true); + + rect = new QCanvasRectangle(m_canvas); + rect->setX(startX - 1); + rect->setY(m_canvasHeight / 2 - m_previewHeight / 2 - 14); + rect->setSize(1, m_previewHeight + 28); + rect->setPen(kapp->palette().color(QPalette::Active, QColorGroup::Shadow)); + rect->setZ(3); + rect->setVisible(true); + + // End time + // + sprintf(msecs, "%03d", m_segment->getAudioEndTime().msec()); + QString endText = QString("%1.%2s") + .arg(m_segment->getAudioEndTime().sec) + .arg(msecs); + text = new QCanvasText(m_canvas); + text->setColor( + kapp->palette().color(QPalette::Active, QColorGroup::Shadow)); + text->setText(endText); + text->setX(startX + m_previewWidth - 20); + text->setY(m_canvasHeight / 2 - m_previewHeight / 2 - 35); + text->setZ(3); + text->setVisible(true); + + rect = new QCanvasRectangle(m_canvas); + rect->setX(startX + m_previewWidth - 1); + rect->setY(m_canvasHeight / 2 - m_previewHeight / 2 - 14); + rect->setSize(1, m_previewHeight + 28); + rect->setPen(kapp->palette().color(QPalette::Active, QColorGroup::Shadow)); + rect->setZ(3); + rect->setVisible(true); + + m_canvas->update(); +} + +void +AudioSplitDialog::drawSplits(int threshold) +{ + // Now get the current split points and paint them + // + RealTime startTime = m_segment->getAudioStartTime(); + RealTime endTime = m_segment->getAudioEndTime(); + + AudioFileManager &aFM = m_doc->getAudioFileManager(); + std::vector splitPoints = + aFM.getSplitPoints(m_segment->getAudioFileId(), + startTime, + endTime, + threshold); + + std::vector::iterator it; + std::vector tempRects; + + RealTime length = endTime - startTime; + double ticksPerUsec = double(m_previewWidth) / + double((length.sec * 1000000.0) + length.usec()); + + int startX = (m_canvasWidth - m_previewWidth) / 2; + int halfHeight = m_canvasHeight / 2; + int x1, x2; + int overlapHeight = 10; + + for (it = splitPoints.begin(); it != splitPoints.end(); it++) { + RealTime splitStart = it->first - startTime; + RealTime splitEnd = it->second - startTime; + + x1 = int(ticksPerUsec * double(double(splitStart.sec) * + 1000000.0 + (double)splitStart.usec())); + + x2 = int(ticksPerUsec * double(double(splitEnd.sec) * + 1000000.0 + double(splitEnd.usec()))); + + QCanvasRectangle *rect = new QCanvasRectangle(m_canvas); + rect->setX(startX + x1); + rect->setY(halfHeight - m_previewHeight / 2 - overlapHeight / 2); + rect->setZ(2); + rect->setSize(x2 - x1, m_previewHeight + overlapHeight); + rect->setPen(kapp-> + palette().color(QPalette::Active, QColorGroup::Mid)); + rect->setBrush(kapp-> + palette().color(QPalette::Active, QColorGroup::Mid)); + rect->setVisible(true); + tempRects.push_back(rect); + } + + std::vector::iterator pIt; + + // We've written the new Rects, now delete the old ones + // + if (m_previewBoxes.size()) { + // clear any previous preview boxes + // + for (pIt = m_previewBoxes.begin(); pIt != m_previewBoxes.end(); pIt++) { + //(*pIt)->setVisible(false); + delete (*pIt); + } + m_previewBoxes.erase(m_previewBoxes.begin(), m_previewBoxes.end()); + m_canvas->update(); + } + m_canvas->update(); + + // Now store the new ones + // + for (pIt = tempRects.begin(); pIt != tempRects.end(); pIt++) + m_previewBoxes.push_back(*pIt); +} + +void +AudioSplitDialog::slotThresholdChanged(int threshold) +{ + drawSplits(threshold); +} + +} +#include "AudioSplitDialog.moc" diff --git a/src/gui/dialogs/AudioSplitDialog.h b/src/gui/dialogs/AudioSplitDialog.h new file mode 100644 index 0000000..7dc52c0 --- /dev/null +++ b/src/gui/dialogs/AudioSplitDialog.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_AUDIOSPLITDIALOG_H_ +#define _RG_AUDIOSPLITDIALOG_H_ + +#include +#include +#include + + +class QWidget; +class QCanvasView; +class QCanvasRectangle; +class QCanvas; + + +namespace Rosegarden +{ + +class Segment; +class RosegardenGUIDoc; + + +class AudioSplitDialog : public KDialogBase +{ + Q_OBJECT +public: + AudioSplitDialog(QWidget *parent, + Segment *segment, + RosegardenGUIDoc *doc); + + // Draw an audio preview over the segment and draw + // the potential splits along it. + // + void drawPreview(); + void drawSplits(int threshold); + + // Get the threshold + // + int getThreshold() { return m_thresholdSpin->value(); } + +public slots: + void slotThresholdChanged(int); + +protected: + RosegardenGUIDoc *m_doc; + Segment *m_segment; + QCanvas *m_canvas; + QCanvasView *m_canvasView; + QSpinBox *m_thresholdSpin; + + int m_canvasWidth; + int m_canvasHeight; + int m_previewWidth; + int m_previewHeight; + + std::vector m_previewBoxes; + +}; + + + +} + +#endif diff --git a/src/gui/dialogs/BeatsBarsDialog.cpp b/src/gui/dialogs/BeatsBarsDialog.cpp new file mode 100644 index 0000000..774ddb9 --- /dev/null +++ b/src/gui/dialogs/BeatsBarsDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "BeatsBarsDialog.h" +#include + +#include +#include "base/Segment.h" +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +BeatsBarsDialog::BeatsBarsDialog(QWidget* parent) : + KDialogBase(parent, 0, true, i18n("Audio Segment Duration"), + Ok | Cancel, Ok) +{ + QHBox *hbox = makeHBoxMainWidget(); + + QGroupBox *gbox = new QGroupBox(1, Horizontal, + i18n("The selected audio segment contains:"), hbox); + + QFrame *frame = new QFrame(gbox); + QGridLayout *layout = new QGridLayout(frame, 1, 2, 5, 5); + + m_spinBox = new QSpinBox(1, INT_MAX, 1, frame, "glee"); + layout->addWidget(m_spinBox, 0, 0); + + m_comboBox = new KComboBox(false, frame); + m_comboBox->insertItem(i18n("beat(s)")); + m_comboBox->insertItem(i18n("bar(s)")); + m_comboBox->setCurrentItem(0); + layout->addWidget(m_comboBox, 0, 1); +} + +} +#include "BeatsBarsDialog.moc" diff --git a/src/gui/dialogs/BeatsBarsDialog.h b/src/gui/dialogs/BeatsBarsDialog.h new file mode 100644 index 0000000..6546f01 --- /dev/null +++ b/src/gui/dialogs/BeatsBarsDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_BEATSBARSDIALOG_H_ +#define _RG_BEATSBARSDIALOG_H_ + +#include +#include +#include + +class QWidget; + + +namespace Rosegarden +{ + +/** + * ask the user to give us information about the selected audio segment for + * Tempo calculations + */ +class BeatsBarsDialog : public KDialogBase +{ + Q_OBJECT + +public: + BeatsBarsDialog(); + BeatsBarsDialog(QWidget *parent); + + int getQuantity() { return m_spinBox->value(); } + int getMode() { return m_comboBox->currentItem(); } + +protected: + QSpinBox *m_spinBox; + KComboBox *m_comboBox; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/ClefDialog.cpp b/src/gui/dialogs/ClefDialog.cpp new file mode 100644 index 0000000..8f802b0 --- /dev/null +++ b/src/gui/dialogs/ClefDialog.cpp @@ -0,0 +1,273 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ClefDialog.h" + +#include "base/NotationTypes.h" +#include "gui/editors/notation/NotePixmapFactory.h" +#include "gui/widgets/BigArrowButton.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +ClefDialog::ClefDialog(QWidget *parent, + NotePixmapFactory *npf, + Clef defaultClef, + bool showConversionOptions) : + KDialogBase(parent, 0, true, i18n("Clef"), Ok | Cancel | Help), + m_notePixmapFactory(npf), + m_clef(defaultClef) +{ + setHelp("nv-signatures-clef"); + + QVBox *vbox = makeVBoxMainWidget(); + + QGroupBox *clefFrame = new QGroupBox + (1, Horizontal, i18n("Clef"), vbox); + + QButtonGroup *conversionFrame = new QButtonGroup + (1, Horizontal, i18n("Existing notes following clef change"), vbox); + + QHBox *clefBox = new QHBox(clefFrame); + + BigArrowButton *clefDown = new BigArrowButton(clefBox, Qt::LeftArrow); + QToolTip::add + (clefDown, i18n("Lower clef")); + + QHBox *clefLabelBox = new QVBox(clefBox); + + m_octaveUp = new BigArrowButton(clefLabelBox, Qt::UpArrow); + QToolTip::add + (m_octaveUp, i18n("Up an Octave")); + + m_clefLabel = new QLabel(i18n("Clef"), clefLabelBox); + m_clefLabel->setAlignment(AlignVCenter | AlignHCenter); + + m_octaveDown = new BigArrowButton(clefLabelBox, Qt::DownArrow); + QToolTip::add + (m_octaveDown, i18n("Down an Octave")); + + BigArrowButton *clefUp = new BigArrowButton(clefBox, Qt::RightArrow); + QToolTip::add + (clefUp, i18n("Higher clef")); + + m_clefNameLabel = new QLabel(i18n("Clef"), clefLabelBox); + m_clefNameLabel->setAlignment(AlignVCenter | AlignHCenter); + + if (showConversionOptions) { + m_noConversionButton = + new QRadioButton + (i18n("Maintain current pitches"), conversionFrame); + m_changeOctaveButton = + new QRadioButton + (i18n("Transpose into appropriate octave"), conversionFrame); + m_transposeButton = 0; + + //!!! why aren't we offering this option? does it not work? too difficult to describe? + // m_transposeButton = + // new QRadioButton + // (i18n("Maintain current positions on the staff"), conversionFrame); + m_changeOctaveButton->setChecked(true); + } else { + m_noConversionButton = 0; + m_changeOctaveButton = 0; + m_transposeButton = 0; + conversionFrame->hide(); + } + + QObject::connect(clefUp, SIGNAL(clicked()), this, SLOT(slotClefUp())); + QObject::connect(clefDown, SIGNAL(clicked()), this, SLOT(slotClefDown())); + QObject::connect(m_octaveUp, SIGNAL(clicked()), this, SLOT(slotOctaveUp())); + QObject::connect(m_octaveDown, SIGNAL(clicked()), this, SLOT(slotOctaveDown())); + + redrawClefPixmap(); +} + +Clef +ClefDialog::getClef() const +{ + return m_clef; +} + +ClefDialog::ConversionType + +ClefDialog::getConversionType() const +{ + if (m_noConversionButton && m_noConversionButton->isChecked()) { + return NoConversion; + } else if (m_changeOctaveButton && m_changeOctaveButton->isChecked()) { + return ChangeOctave; + } else if (m_transposeButton && m_transposeButton->isChecked()) { + return Transpose; + } + return NoConversion; +} + +void +ClefDialog::slotClefUp() +{ + int octaveOffset = m_clef.getOctaveOffset(); + Clef::ClefList clefs(Clef::getClefs()); + + for (Clef::ClefList::iterator i = clefs.begin(); + i != clefs.end(); ++i) { + + if (m_clef.getClefType() == i->getClefType()) { + if (++i == clefs.end()) + i = clefs.begin(); + m_clef = Clef(i->getClefType(), octaveOffset); + break; + } + } + + redrawClefPixmap(); +} + +void +ClefDialog::slotClefDown() +{ + int octaveOffset = m_clef.getOctaveOffset(); + Clef::ClefList clefs(Clef::getClefs()); + + for (Clef::ClefList::iterator i = clefs.begin(); + i != clefs.end(); ++i) { + + if (m_clef.getClefType() == i->getClefType()) { + if (i == clefs.begin()) + i = clefs.end(); + --i; + m_clef = Clef(i->getClefType(), octaveOffset); + break; + } + } + + redrawClefPixmap(); +} + +void +ClefDialog::slotOctaveUp() +{ + int octaveOffset = m_clef.getOctaveOffset(); + if (octaveOffset == 2) + return ; + + ++octaveOffset; + + m_octaveDown->setEnabled(true); + if (octaveOffset == 2) { + m_octaveUp->setEnabled(false); + } + + m_clef = Clef(m_clef.getClefType(), octaveOffset); + redrawClefPixmap(); +} + +void +ClefDialog::slotOctaveDown() +{ + int octaveOffset = m_clef.getOctaveOffset(); + if (octaveOffset == -2) + return ; + + --octaveOffset; + + m_octaveUp->setEnabled(true); + if (octaveOffset == 2) { + m_octaveDown->setEnabled(false); + } + + m_clef = Clef(m_clef.getClefType(), octaveOffset); + redrawClefPixmap(); +} + +void +ClefDialog::redrawClefPixmap() +{ + QPixmap pmap = NotePixmapFactory::toQPixmap + (m_notePixmapFactory->makeClefDisplayPixmap(m_clef)); + m_clefLabel->setPixmap(pmap); + + QString name; + int octave = m_clef.getOctaveOffset(); + + switch (octave) { + case - 1: + name = i18n("%1 down an octave"); + break; + case - 2: + name = i18n("%1 down two octaves"); + break; + case 1: + name = i18n("%1 up an octave"); + break; + case 2: + name = i18n("%1 up two octaves"); + break; + default: + name = "%1"; + break; + } + + std::string type = m_clef.getClefType(); + if (type == Clef::Treble) + name = name.arg(i18n("Treble")); + else if (type == Clef::French) + name = name.arg(i18n("French violin")); + else if (type == Clef::Soprano) + name = name.arg(i18n("Soprano")); + else if (type == Clef::Mezzosoprano) + name = name.arg(i18n("Mezzo-soprano")); + else if (type == Clef::Alto) + name = name.arg(i18n("Alto")); + else if (type == Clef::Tenor) + name = name.arg(i18n("Tenor")); + else if (type == Clef::Baritone) + name = name.arg(i18n("C-baritone")); + else if (type == Clef::Varbaritone) + name = name.arg(i18n("F-baritone")); + else if (type == Clef::Bass) + name = name.arg(i18n("Bass")); + else if (type == Clef::Subbass) + name = name.arg(i18n("Sub-bass")); + + m_clefNameLabel->setText(name); +} + +} +#include "ClefDialog.moc" diff --git a/src/gui/dialogs/ClefDialog.h b/src/gui/dialogs/ClefDialog.h new file mode 100644 index 0000000..771cd4a --- /dev/null +++ b/src/gui/dialogs/ClefDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CLEFDIALOG_H_ +#define _RG_CLEFDIALOG_H_ + +#include "base/NotationTypes.h" +#include + + +class QWidget; +class QRadioButton; +class QLabel; + + +namespace Rosegarden +{ + +class BigArrowButton; +class NotePixmapFactory; + + +class ClefDialog : public KDialogBase +{ + Q_OBJECT + +public: + enum ConversionType { + NoConversion, + ChangeOctave, + Transpose, + }; + + ClefDialog(QWidget *parent, + NotePixmapFactory *npf, + Clef defaultClef, + bool showConversionOptions = true); + + Clef getClef() const; + ConversionType getConversionType() const; + +public slots: + void slotClefUp(); + void slotClefDown(); + void slotOctaveUp(); + void slotOctaveDown(); + +protected: + void redrawClefPixmap(); + + //--------------- Data members --------------------------------- + + NotePixmapFactory *m_notePixmapFactory; + Clef m_clef; + + QLabel *m_clefLabel; + QLabel *m_clefNameLabel; + + BigArrowButton *m_octaveUp; + BigArrowButton *m_octaveDown; + + QRadioButton *m_noConversionButton; + QRadioButton *m_changeOctaveButton; + QRadioButton *m_transposeButton; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/CompositionLengthDialog.cpp b/src/gui/dialogs/CompositionLengthDialog.cpp new file mode 100644 index 0000000..24a3107 --- /dev/null +++ b/src/gui/dialogs/CompositionLengthDialog.cpp @@ -0,0 +1,84 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CompositionLengthDialog.h" + +#include +#include "base/Composition.h" +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +CompositionLengthDialog::CompositionLengthDialog( + QWidget *parent, + Composition *composition): + KDialogBase(parent, 0, true, i18n("Change Composition Length"), + Ok | Cancel), + m_composition(composition) +{ + QVBox *vBox = makeVBoxMainWidget(); + + new QLabel(i18n("Set the Start and End bar markers for this Composition"), + vBox); + + QHBox *startBox = new QHBox(vBox); + new QLabel(i18n("Start Bar"), startBox); + m_startMarkerSpinBox = new QSpinBox(startBox); + m_startMarkerSpinBox->setMinValue( -10); + m_startMarkerSpinBox->setMaxValue(10000); + m_startMarkerSpinBox->setValue( + m_composition->getBarNumber(m_composition->getStartMarker()) + 1); + + QHBox *endBox = new QHBox(vBox); + new QLabel(i18n("End Bar"), endBox); + m_endMarkerSpinBox = new QSpinBox(endBox); + m_endMarkerSpinBox->setMinValue( -10); + m_endMarkerSpinBox->setMaxValue(10000); + m_endMarkerSpinBox->setValue( + m_composition->getBarNumber(m_composition->getEndMarker())); + +} + +timeT +CompositionLengthDialog::getStartMarker() +{ + return m_composition->getBarStart(m_startMarkerSpinBox->value() - 1); +} + +timeT +CompositionLengthDialog::getEndMarker() +{ + return m_composition->getBarStart(m_endMarkerSpinBox->value()); +} + +} +#include "CompositionLengthDialog.moc" diff --git a/src/gui/dialogs/CompositionLengthDialog.h b/src/gui/dialogs/CompositionLengthDialog.h new file mode 100644 index 0000000..e6d688c --- /dev/null +++ b/src/gui/dialogs/CompositionLengthDialog.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_COMPOSITIONLENGTHDIALOG_H_ +#define _RG_COMPOSITIONLENGTHDIALOG_H_ + +#include +#include "base/Event.h" + + +class QWidget; +class QSpinBox; + + +namespace Rosegarden +{ + +class Composition; + + +class CompositionLengthDialog : public KDialogBase +{ + Q_OBJECT +public: + CompositionLengthDialog(QWidget *parent, + Composition *composition); + + timeT getStartMarker(); + timeT getEndMarker(); + +protected: + + QSpinBox *m_startMarkerSpinBox; + QSpinBox *m_endMarkerSpinBox; + Composition *m_composition; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/ConfigureDialog.cpp b/src/gui/dialogs/ConfigureDialog.cpp new file mode 100644 index 0000000..1bdd3b4 --- /dev/null +++ b/src/gui/dialogs/ConfigureDialog.cpp @@ -0,0 +1,118 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ConfigureDialog.h" +#include + +#include +#include "ConfigureDialogBase.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/configuration/GeneralConfigurationPage.h" +#include "gui/configuration/NotationConfigurationPage.h" +#include "gui/configuration/AudioConfigurationPage.h" +#include "gui/configuration/MIDIConfigurationPage.h" +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +static QPixmap loadIcon(const char *name) +{ + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QString fileBase = pixmapDir + "/misc/"; + fileBase += name; + if (QFile(fileBase + ".png").exists()) { + return QPixmap(fileBase + ".png"); + } else if (QFile(fileBase + ".xpm").exists()) { + return QPixmap(fileBase + ".xpm"); + } + QPixmap pmap = KGlobal::instance()->iconLoader() + ->loadIcon(QString::fromLatin1(name), KIcon::NoGroup, KIcon::SizeMedium); + return pmap; +} + + +ConfigureDialog::ConfigureDialog(RosegardenGUIDoc *doc, + KConfig* cfg, + QWidget *parent, + const char *name) + : ConfigureDialogBase(parent, i18n("Configure Rosegarden"), name) +{ + QWidget *pageWidget = 0; + QVBoxLayout *vlay = 0; + ConfigurationPage* page = 0; + + // General Page + // + pageWidget = addPage(GeneralConfigurationPage::iconLabel(), + GeneralConfigurationPage::title(), + loadIcon(GeneralConfigurationPage::iconName())); + vlay = new QVBoxLayout(pageWidget, 0, spacingHint()); + page = new GeneralConfigurationPage(doc, cfg, pageWidget); + vlay->addWidget(page); + page->setPageIndex(pageIndex(pageWidget)); + m_configurationPages.push_back(page); + + connect(page, SIGNAL(updateAutoSaveInterval(unsigned int)), + this, SIGNAL(updateAutoSaveInterval(unsigned int))); + connect(page, SIGNAL(updateSidebarStyle(unsigned int)), + this, SIGNAL(updateSidebarStyle(unsigned int))); + + pageWidget = addPage(MIDIConfigurationPage::iconLabel(), + MIDIConfigurationPage::title(), + loadIcon(MIDIConfigurationPage::iconName())); + vlay = new QVBoxLayout(pageWidget, 0, spacingHint()); + page = new MIDIConfigurationPage(doc, cfg, pageWidget); + vlay->addWidget(page); + page->setPageIndex(pageIndex(pageWidget)); + m_configurationPages.push_back(page); + + pageWidget = addPage(AudioConfigurationPage::iconLabel(), + AudioConfigurationPage::title(), + loadIcon(AudioConfigurationPage::iconName())); + vlay = new QVBoxLayout(pageWidget, 0, spacingHint()); + page = new AudioConfigurationPage(doc, cfg, pageWidget); + vlay->addWidget(page); + page->setPageIndex(pageIndex(pageWidget)); + m_configurationPages.push_back(page); + + // Notation Page + pageWidget = addPage(NotationConfigurationPage::iconLabel(), + NotationConfigurationPage::title(), + loadIcon(NotationConfigurationPage::iconName())); + vlay = new QVBoxLayout(pageWidget, 0, spacingHint()); + page = new NotationConfigurationPage(cfg, pageWidget); + vlay->addWidget(page); + page->setPageIndex(pageIndex(pageWidget)); + m_configurationPages.push_back(page); +} + +} +#include "ConfigureDialog.moc" diff --git a/src/gui/dialogs/ConfigureDialog.h b/src/gui/dialogs/ConfigureDialog.h new file mode 100644 index 0000000..4dd6fff --- /dev/null +++ b/src/gui/dialogs/ConfigureDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CONFIGUREDIALOG_H_ +#define _RG_CONFIGUREDIALOG_H_ + +#include "ConfigureDialogBase.h" + + +class QWidget; +class KConfig; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +class ConfigureDialog : public ConfigureDialogBase +{ + Q_OBJECT +public: + ConfigureDialog(RosegardenGUIDoc *doc, + KConfig* cfg, + QWidget *parent=0, + const char *name=0); +signals: + void updateAutoSaveInterval(unsigned int); + void updateSidebarStyle(unsigned int); +}; + + +} + +#endif diff --git a/src/gui/dialogs/ConfigureDialogBase.cpp b/src/gui/dialogs/ConfigureDialogBase.cpp new file mode 100644 index 0000000..7d5555a --- /dev/null +++ b/src/gui/dialogs/ConfigureDialogBase.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ConfigureDialogBase.h" + +#include +#include "gui/configuration/ConfigurationPage.h" +#include +#include +#include + + +namespace Rosegarden +{ + +ConfigureDialogBase::ConfigureDialogBase(QWidget *parent, + QString label, + const char *name): + KDialogBase(IconList, label ? label : i18n("Configure"), Help | Apply | Ok | Cancel, + Ok, parent, name, true) // modal +{ + setWFlags(WDestructiveClose); +} + +ConfigureDialogBase::~ConfigureDialogBase() +{} + +void +ConfigureDialogBase::slotApply() +{ + for (configurationpages::iterator i = m_configurationPages.begin(); + i != m_configurationPages.end(); ++i) + (*i)->apply(); +} + +void +ConfigureDialogBase::slotActivateApply() +{ + // ApplyButton->setDisabled(false); +} + +void +ConfigureDialogBase::slotOk() +{ + slotApply(); + accept(); +} + +void +ConfigureDialogBase::slotCancelOrClose() +{} + +} +#include "ConfigureDialogBase.moc" diff --git a/src/gui/dialogs/ConfigureDialogBase.h b/src/gui/dialogs/ConfigureDialogBase.h new file mode 100644 index 0000000..fe05ebe --- /dev/null +++ b/src/gui/dialogs/ConfigureDialogBase.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CONFIGUREDIALOGBASE_H_ +#define _RG_CONFIGUREDIALOGBASE_H_ + +#include +#include +#include + + +class QWidget; + + +namespace Rosegarden +{ + +class ConfigurationPage; + + +class ConfigureDialogBase : public KDialogBase +{ + Q_OBJECT +public: + ConfigureDialogBase(QWidget *parent=0, + QString label = 0, + const char *name=0); + virtual ~ConfigureDialogBase(); + + typedef std::vector configurationpages; + +protected slots: + virtual void slotOk(); + virtual void slotApply(); + virtual void slotCancelOrClose(); + + virtual void slotActivateApply(); + +protected: + + configurationpages m_configurationPages; +}; + + +} + +#endif diff --git a/src/gui/dialogs/CountdownBar.cpp b/src/gui/dialogs/CountdownBar.cpp new file mode 100644 index 0000000..cfad3d8 --- /dev/null +++ b/src/gui/dialogs/CountdownBar.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CountdownBar.h" + +#include "gui/general/GUIPalette.h" +#include +#include +#include + + +namespace Rosegarden +{ + +CountdownBar::CountdownBar(QWidget *parent, int width, int height): + QFrame(parent), m_width(width), m_height(height), m_position(0) +{ + resize(m_width, m_height); + repaint(); +} + +void +CountdownBar::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + + p.setClipRegion(e->region()); + p.setClipRect(e->rect().normalize()); + + p.setPen(GUIPalette::getColour(GUIPalette::AudioCountdownBackground)); + p.setBrush(GUIPalette::getColour(GUIPalette::AudioCountdownBackground)); + p.drawRect(0, 0, m_position, m_height); + p.setPen(GUIPalette::getColour(GUIPalette::AudioCountdownForeground)); + p.setBrush(GUIPalette::getColour(GUIPalette::AudioCountdownForeground)); + p.drawRect(m_position, 0, m_width, m_height); +} + +void +CountdownBar::setPosition(int position) +{ + m_position = position; + repaint(); +} + +} +#include "CountdownBar.moc" diff --git a/src/gui/dialogs/CountdownBar.h b/src/gui/dialogs/CountdownBar.h new file mode 100644 index 0000000..364d0cf --- /dev/null +++ b/src/gui/dialogs/CountdownBar.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_COUNTDOWNBAR_H_ +#define _RG_COUNTDOWNBAR_H_ + +#include + + +class QWidget; +class QPaintEvent; + + +namespace Rosegarden +{ + + + +class CountdownBar : public QFrame +{ + Q_OBJECT +public: + CountdownBar(QWidget *parent, int width, int height); + void setPosition(int position); + +protected: + virtual void paintEvent(QPaintEvent *e); + + int m_width; + int m_height; + int m_position; +}; + + +} + +#endif diff --git a/src/gui/dialogs/CountdownDialog.cpp b/src/gui/dialogs/CountdownDialog.cpp new file mode 100644 index 0000000..f624aba --- /dev/null +++ b/src/gui/dialogs/CountdownDialog.cpp @@ -0,0 +1,159 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CountdownDialog.h" +#include + +#include +#include "CountdownBar.h" +#include +#include +#include +#include +#include +#include +#include +#include "misc/Debug.h" + + +namespace Rosegarden +{ + +CountdownDialog::CountdownDialog(QWidget *parent, int seconds): + QDialog(parent, "", false, WStyle_StaysOnTop | WStyle_DialogBorder), + m_pastEndMode(false), + m_totalTime(seconds), + m_progressBarWidth(150), + m_progressBarHeight(15) +{ + QBoxLayout *layout = new QBoxLayout(this, QBoxLayout::TopToBottom, 10, 14); + setCaption(i18n("Recording...")); + + QHBox *hBox = new QHBox(this); + m_label = new QLabel(hBox); + m_time = new QLabel(hBox); + + layout->addWidget(hBox, 0, AlignCenter); + + m_label->setText(i18n("Recording time remaining: ")); + m_progressBar = + new CountdownBar(this, m_progressBarWidth, m_progressBarHeight); + + m_progressBar->setFixedSize(m_progressBarWidth, m_progressBarHeight); + + // Simply re-emit from Stop button + // + m_stopButton = new QPushButton(i18n("Stop"), this); + m_stopButton->setFixedWidth(60); + + layout->addWidget(m_progressBar, 0, AlignCenter); + layout->addWidget(m_stopButton, 0, AlignRight); + + connect (m_stopButton, SIGNAL(released()), this, SIGNAL(stopped())); + + // Set the total time to show the bar in initial position + // + setElapsedTime(0); + + m_accelerators = new QAccel(this); + +} + +void +CountdownDialog::setLabel(const QString &label) +{ + m_label->setText(label); +} + +void +CountdownDialog::setTotalTime(int seconds) +{ + m_totalTime = seconds; + setElapsedTime(0); // clear +} + +void +CountdownDialog::setElapsedTime(int elapsedSeconds) +{ + int seconds = m_totalTime - elapsedSeconds; + + if (seconds < 0) { + seconds = - seconds; + if (!m_pastEndMode) + setPastEndMode(); + } + + QString h, m, s; + h.sprintf("%02d", seconds / 3600); + m.sprintf("%02d", seconds / 60); + s.sprintf("%02d", seconds % 60); + + if (seconds < 3600) // less than an hour + { + m_time->setText(QString("%1:%2").arg(m).arg(s)); + } else if (seconds < 86400) // less than a day + { + m_time->setText(QString("%1:%2:%3").arg(h).arg(m).arg(s)); + } else { + m_time->setText(i18n("Just how big is your hard disk?")); + } + + // Draw the progress bar + // + if (m_pastEndMode) { + m_progressBar->setPosition(m_progressBarWidth); + } else { + // Attempt a simplistic fix for #1838190. In the context of an isolated + // test example, I'm fairly sure m_totalTime was 0, causing a divide by + // zero error, though the trace just listed it as an "Arithmetic + // exception." + if (m_totalTime == 0) { + RG_DEBUG << "CountdownDialog::setElapsedTime: FAILSAFE CODE FIRED, see bug #1838190 for details" << endl; + m_totalTime = 1; + } + int barPosition = m_progressBarWidth - + (elapsedSeconds * m_progressBarWidth) / m_totalTime; + m_progressBar->setPosition(barPosition); + } + + // Dialog complete if the display time is zero + if (seconds == 0) + emit completed(); + +} + +void +CountdownDialog::setPastEndMode() +{ + if (m_pastEndMode) // already called + return ; + + m_pastEndMode = true; + m_label->setText(i18n("Recording beyond end of composition: ")); + +} + +} +#include "CountdownDialog.moc" diff --git a/src/gui/dialogs/CountdownDialog.h b/src/gui/dialogs/CountdownDialog.h new file mode 100644 index 0000000..00aa6e0 --- /dev/null +++ b/src/gui/dialogs/CountdownDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_COUNTDOWNDIALOG_H_ +#define _RG_COUNTDOWNDIALOG_H_ + +#include +#include + + +class QWidget; +class QString; +class QPushButton; +class QLabel; +class QAccel; + + +namespace Rosegarden +{ + +class CountdownBar; + + +class CountdownDialog : public QDialog // KDialogBase +{ + Q_OBJECT + +public: + CountdownDialog(QWidget *parent, int seconds = 300); + + void setLabel(const QString &label); + void setElapsedTime(int seconds); + + int getTotalTime() const { return m_totalTime; } + void setTotalTime(int seconds); + + QAccel* getAccelerators() { return m_accelerators; } + +signals: + void completed(); // m_totalTime has elapsed + void stopped(); // someone pushed the stop button + +protected: + void setPastEndMode(); + + bool m_pastEndMode; + + int m_totalTime; + + QLabel *m_label; + QLabel *m_time; + CountdownBar *m_progressBar; + + QPushButton *m_stopButton; + + int m_progressBarWidth; + int m_progressBarHeight; + + QAccel *m_accelerators; +}; + + +} + +#endif diff --git a/src/gui/dialogs/DocumentConfigureDialog.cpp b/src/gui/dialogs/DocumentConfigureDialog.cpp new file mode 100644 index 0000000..5f79f33 --- /dev/null +++ b/src/gui/dialogs/DocumentConfigureDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "DocumentConfigureDialog.h" +#include + +#include +#include "ConfigureDialogBase.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/configuration/AudioPropertiesPage.h" +#include "gui/configuration/ColourConfigurationPage.h" +#include "gui/configuration/DocumentMetaConfigurationPage.h" +#include "gui/configuration/GeneralConfigurationPage.h" +#include +#include +#include +#include + + +namespace Rosegarden +{ +static QPixmap loadIcon(const char *name) +{ + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QString fileBase = pixmapDir + "/misc/"; + fileBase += name; + if (QFile(fileBase + ".png").exists()) { + return QPixmap(fileBase + ".png"); + } else if (QFile(fileBase + ".xpm").exists()) { + return QPixmap(fileBase + ".xpm"); + } + + QPixmap pmap = KGlobal::instance()->iconLoader() + ->loadIcon(QString::fromLatin1(name), KIcon::NoGroup, KIcon::SizeMedium); + return pmap; +} + + +DocumentConfigureDialog::DocumentConfigureDialog(RosegardenGUIDoc *doc, + QWidget *parent, + const char *name) + : ConfigureDialogBase(parent, i18n("Document Properties"), name) +{ + QWidget *pageWidget = 0; + QVBoxLayout *vlay = 0; + ConfigurationPage* page = 0; + + // Document Meta Page + // + pageWidget = addPage(DocumentMetaConfigurationPage::iconLabel(), + DocumentMetaConfigurationPage::title(), + loadIcon(DocumentMetaConfigurationPage::iconName())); + vlay = new QVBoxLayout(pageWidget, 0, spacingHint()); + page = new DocumentMetaConfigurationPage(doc, pageWidget); + vlay->addWidget(page); + page->setPageIndex(pageIndex(pageWidget)); + m_configurationPages.push_back(page); + + // Audio Page + // + pageWidget = addPage(AudioPropertiesPage::iconLabel(), + AudioPropertiesPage::title(), + loadIcon(AudioPropertiesPage::iconName())); + vlay = new QVBoxLayout(pageWidget, 0, spacingHint()); + page = new AudioPropertiesPage(doc, pageWidget); + vlay->addWidget(page); + page->setPageIndex(pageIndex(pageWidget)); + m_configurationPages.push_back(page); + + // Colour Page + pageWidget = addPage(ColourConfigurationPage::iconLabel(), + ColourConfigurationPage::title(), + loadIcon(ColourConfigurationPage::iconName())); + + vlay = new QVBoxLayout(pageWidget, 0, spacingHint()); + page = new ColourConfigurationPage(doc, pageWidget); + vlay->addWidget(page); + page->setPageIndex(pageIndex(pageWidget)); + m_configurationPages.push_back(page); + + resize(minimumSize()); +} + +void +DocumentConfigureDialog::showAudioPage() +{ + int index = 0; + + for (configurationpages::iterator i = m_configurationPages.begin(); + i != m_configurationPages.end(); ++i) { + + AudioPropertiesPage *page = + dynamic_cast(*i); + + if (!page) { + ++index; + continue; + } + + showPage(index); + return ; + } +} + +/* hjj: WHAT TO DO WITH THIS ? +void +DocumentConfigureDialog::selectMetadata(QString name) +{ + int index = 0; + + for (configurationpages::iterator i = m_configurationPages.begin(); + i != m_configurationPages.end(); ++i) { + + DocumentMetaConfigurationPage *page = + dynamic_cast(*i); + + if (!page) { + ++index; + continue; + } + + page->selectMetadata(name); + showPage(index); + return ; + } +} +*/ + +} diff --git a/src/gui/dialogs/DocumentConfigureDialog.h b/src/gui/dialogs/DocumentConfigureDialog.h new file mode 100644 index 0000000..6713047 --- /dev/null +++ b/src/gui/dialogs/DocumentConfigureDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_DOCUMENTCONFIGUREDIALOG_H_ +#define _RG_DOCUMENTCONFIGUREDIALOG_H_ + +#include "ConfigureDialogBase.h" +#include + + +class QWidget; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +class DocumentConfigureDialog : public ConfigureDialogBase +{ +public: + DocumentConfigureDialog(RosegardenGUIDoc *doc, + QWidget *parent=0, + const char *name=0); + + void showAudioPage(); + +/* hjj: WHAT TO DO WITH THIS ? + void selectMetadata(QString name); +*/ +}; + + + + +} + +#endif diff --git a/src/gui/dialogs/EventEditDialog.cpp b/src/gui/dialogs/EventEditDialog.cpp new file mode 100644 index 0000000..c9991f1 --- /dev/null +++ b/src/gui/dialogs/EventEditDialog.cpp @@ -0,0 +1,528 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EventEditDialog.h" + +#include +#include "misc/Strings.h" +#include "base/Event.h" +#include "base/MidiTypes.h" +#include "base/NotationTypes.h" +#include "base/PropertyName.h" +#include "base/RealTime.h" +#include "gui/editors/notation/NotePixmapFactory.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +EventEditDialog::EventEditDialog(QWidget *parent, + const Event &event, + bool editable) : + KDialogBase(parent, 0, true, i18n(editable ? "Advanced Event Edit" : "Advanced Event Viewer"), + (editable ? (Ok | Cancel) : Ok)), + m_durationDisplay(0), + m_durationDisplayAux(0), + m_persistentGrid(0), + m_nonPersistentGrid(0), + m_nonPersistentView(0), + m_originalEvent(event), + m_event(event), + m_type(event.getType()), + m_absoluteTime(event.getAbsoluteTime()), + m_duration(event.getDuration()), + m_subOrdering(event.getSubOrdering()), + m_modified(false) +{ + QVBox *vbox = makeVBoxMainWidget(); + + QGroupBox *intrinsicBox = new QGroupBox + (1, Horizontal, i18n("Intrinsics"), vbox); + + QGrid *intrinsicGrid = new QGrid(4, QGrid::Horizontal, intrinsicBox); + + new QLabel(i18n("Event type: "), intrinsicGrid); + new QLabel("", intrinsicGrid); + new QLabel("", intrinsicGrid); + QLineEdit *lineEdit = new QLineEdit(intrinsicGrid); + lineEdit->setText(strtoqstr(event.getType())); + + new QLabel(i18n("Absolute time: "), intrinsicGrid); + new QLabel("", intrinsicGrid); + new QLabel("", intrinsicGrid); + QSpinBox *absoluteTime = new QSpinBox + (INT_MIN, INT_MAX, Note(Note::Shortest).getDuration(), intrinsicGrid); + absoluteTime->setValue(event.getAbsoluteTime()); + QObject::connect(absoluteTime, SIGNAL(valueChanged(int)), + this, SLOT(slotAbsoluteTimeChanged(int))); + slotAbsoluteTimeChanged(event.getAbsoluteTime()); + + new QLabel(i18n("Duration: "), intrinsicGrid); + m_durationDisplay = new QLabel("(note)", intrinsicGrid); + m_durationDisplay->setMinimumWidth(20); + m_durationDisplayAux = new QLabel("(note)", intrinsicGrid); + m_durationDisplayAux->setMinimumWidth(20); + + QSpinBox *duration = new QSpinBox + (0, INT_MAX, Note(Note::Shortest).getDuration(), intrinsicGrid); + duration->setValue(event.getDuration()); + QObject::connect(duration, SIGNAL(valueChanged(int)), + this, SLOT(slotDurationChanged(int))); + slotDurationChanged(event.getDuration()); + + new QLabel(i18n("Sub-ordering: "), intrinsicGrid); + new QLabel("", intrinsicGrid); + new QLabel("", intrinsicGrid); + + QSpinBox *subOrdering = new QSpinBox( -100, 100, 1, intrinsicGrid); + subOrdering->setValue(event.getSubOrdering()); + QObject::connect(subOrdering, SIGNAL(valueChanged(int)), + this, SLOT(slotSubOrderingChanged(int))); + slotSubOrderingChanged(event.getSubOrdering()); + + QGroupBox *persistentBox = new QGroupBox + (1, Horizontal, i18n("Persistent properties"), vbox); + m_persistentGrid = new QGrid(4, QGrid::Horizontal, persistentBox); + + QLabel *label = new QLabel(i18n("Name"), m_persistentGrid); + QFont font(label->font()); + font.setItalic(true); + label->setFont(font); + + label = new QLabel(i18n("Type"), m_persistentGrid); + label->setFont(font); + label = new QLabel(i18n("Value"), m_persistentGrid); + label->setFont(font); + label = new QLabel("", m_persistentGrid); + label->setFont(font); + + Event::PropertyNames p = event.getPersistentPropertyNames(); + + for (Event::PropertyNames::iterator i = p.begin(); + i != p.end(); ++i) { + addPersistentProperty(*i); + } + + p = event.getNonPersistentPropertyNames(); + + if (p.begin() == p.end()) { + m_nonPersistentView = 0; + m_nonPersistentGrid = 0; + } else { + + QGroupBox *nonPersistentBox = new QGroupBox + (1, Horizontal, i18n("Non-persistent properties"), vbox); + new QLabel(i18n("These are cached values, lost if the event is modified."), + nonPersistentBox); + + m_nonPersistentView = new QScrollView(nonPersistentBox); + //m_nonPersistentView->setHScrollBarMode(QScrollView::AlwaysOff); + m_nonPersistentView->setResizePolicy(QScrollView::AutoOneFit); + + m_nonPersistentGrid = new QGrid + (4, QGrid::Horizontal, m_nonPersistentView->viewport()); + m_nonPersistentView->addChild(m_nonPersistentGrid); + + m_nonPersistentGrid->setSpacing(4); + m_nonPersistentGrid->setMargin(5); + + label = new QLabel(i18n("Name "), m_nonPersistentGrid); + label->setFont(font); + label = new QLabel(i18n("Type "), m_nonPersistentGrid); + label->setFont(font); + label = new QLabel(i18n("Value "), m_nonPersistentGrid); + label->setFont(font); + label = new QLabel("", m_nonPersistentGrid); + label->setFont(font); + + for (Event::PropertyNames::iterator i = p.begin(); + i != p.end(); ++i) { + + new QLabel(strtoqstr(*i), m_nonPersistentGrid, strtoqstr(*i)); + new QLabel(strtoqstr(event.getPropertyTypeAsString(*i)), m_nonPersistentGrid, strtoqstr(*i)); + new QLabel(strtoqstr(event.getAsString(*i)), m_nonPersistentGrid, strtoqstr(*i)); + QPushButton *button = new QPushButton("P", m_nonPersistentGrid, strtoqstr(*i)); + button->setFixedSize(QSize(24, 24)); + QToolTip::add + (button, i18n("Make persistent")); + QObject::connect(button, SIGNAL(clicked()), + this, SLOT(slotPropertyMadePersistent())); + } + } +} + +void +EventEditDialog::addPersistentProperty(const PropertyName &name) +{ + QLabel *label = new QLabel(strtoqstr(name), m_persistentGrid, strtoqstr(name)); + label->show(); + label = new QLabel(strtoqstr(m_originalEvent.getPropertyTypeAsString(name)), + m_persistentGrid, strtoqstr(name)); + label->show(); + + PropertyType type(m_originalEvent.getPropertyType(name)); + switch (type) { + + case Int: { + int min = INT_MIN, max = INT_MAX; + // DMM - constrain program changes to a useful range of values + // Might other types have a similar need for such limits? + if (m_originalEvent.isa(ProgramChange::EventType)) { + min = 0; + max = 127; + } + QSpinBox *spinBox = new QSpinBox + (min, max, 1, m_persistentGrid, strtoqstr(name)); + spinBox->setValue(m_originalEvent.get(name)); + QObject::connect(spinBox, SIGNAL(valueChanged(int)), + this, SLOT(slotIntPropertyChanged(int))); + spinBox->show(); + break; + } +case UInt: { + int min = 0; + int max = UINT_MAX; + if (m_originalEvent.isa(ProgramChange::EventType)) { + min = 0; + max = 65535; + } + QSpinBox *spinBox = new QSpinBox + (min, max, 1, m_persistentGrid, strtoqstr(name)); + spinBox->setValue(m_originalEvent.get(name)); + QObject::connect(spinBox, SIGNAL(valueChanged(int)), + this, SLOT(slotIntPropertyChanged(int))); + spinBox->show(); + break; + } + case RealTimeT: { + RealTime realTime = m_originalEvent.get(name); + + QHBox* hbox = new QHBox(m_persistentGrid); + + // seconds + // + QSpinBox *spinBox = new QSpinBox + (INT_MIN, INT_MAX, 1, + hbox, strtoqstr(name) + "%sec"); + spinBox->setValue(realTime.sec); + + QObject::connect(spinBox, SIGNAL(valueChanged(int)), + this, SLOT(slotRealTimePropertyChanged(int))); + + // nseconds + // + spinBox = new QSpinBox + (INT_MIN, INT_MAX, 1, + hbox, strtoqstr(name) + "%nsec"); + spinBox->setValue(realTime.nsec); + + QObject::connect(spinBox, SIGNAL(valueChanged(int)), + this, SLOT(slotRealTimePropertyChanged(int))); + spinBox->show(); + break; + } + + case Bool: { + QCheckBox *checkBox = new QCheckBox + ("", m_persistentGrid, strtoqstr(name)); + checkBox->setChecked(m_originalEvent.get(name)); + QObject::connect(checkBox, SIGNAL(activated()), + this, SLOT(slotBoolPropertyChanged())); + checkBox->show(); + break; + } + + case String: { + QLineEdit *lineEdit = new QLineEdit + (strtoqstr(m_originalEvent.get(name)), + m_persistentGrid, + strtoqstr(name)); + QObject::connect(lineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(slotStringPropertyChanged(const QString &))); + lineEdit->show(); + break; + } + } + + QPushButton *button = new QPushButton("X", m_persistentGrid, + strtoqstr(name)); + button->setFixedSize(QSize(24, 24)); + QToolTip::add + (button, i18n("Delete this property")); + QObject::connect(button, SIGNAL(clicked()), + this, SLOT(slotPropertyDeleted())); + button->show(); +} + +Event +EventEditDialog::getEvent() const +{ + return Event(m_event, m_absoluteTime, m_duration, m_subOrdering); +} + +void +EventEditDialog::slotEventTypeChanged(const QString &type) +{ + std::string t(qstrtostr(type)); + if (t != m_type) { + m_modified = true; + m_type = t; + } +} + +void +EventEditDialog::slotAbsoluteTimeChanged(int value) +{ + if (value == m_absoluteTime) + return ; + m_modified = true; + m_absoluteTime = value; +} + +void +EventEditDialog::slotDurationChanged(int value) +{ + timeT error = 0; + m_durationDisplay->setPixmap + (NotePixmapFactory::toQPixmap(m_notePixmapFactory.makeNoteMenuPixmap(timeT(value), error))); + + if (error >= value / 2) { + m_durationDisplayAux->setText("++ "); + } else if (error > 0) { + m_durationDisplayAux->setText("+ "); + } else if (error < 0) { + m_durationDisplayAux->setText("- "); + } else { + m_durationDisplayAux->setText(" "); + } + + if (timeT(value) == m_duration) + return ; + + m_modified = true; + m_duration = value; +} + +void +EventEditDialog::slotSubOrderingChanged(int value) +{ + if (value == m_subOrdering) + return ; + m_modified = true; + m_subOrdering = value; +} + +void +EventEditDialog::slotIntPropertyChanged(int value) +{ + const QObject *s = sender(); + const QSpinBox *spinBox = dynamic_cast(s); + if (!spinBox) + return ; + + m_modified = true; + QString propertyName = spinBox->name(); + m_event.set(qstrtostr(propertyName), value); +} + +void +EventEditDialog::slotRealTimePropertyChanged(int value) +{ + const QObject *s = sender(); + const QSpinBox *spinBox = dynamic_cast(s); + if (!spinBox) + return ; + + m_modified = true; + QString propertyFullName = spinBox->name(); + + QString propertyName = propertyFullName.section('%', 0, 0), + nsecOrSec = propertyFullName.section('%', 1, 1); + + RealTime realTime = m_event.get(qstrtostr(propertyName)); + + if (nsecOrSec == "sec") + realTime.sec = value; + else + realTime.nsec = value; + + m_event.set(qstrtostr(propertyName), value); +} + +void +EventEditDialog::slotBoolPropertyChanged() +{ + const QObject *s = sender(); + const QCheckBox *checkBox = dynamic_cast(s); + if (!checkBox) + return ; + + m_modified = true; + QString propertyName = checkBox->name(); + bool checked = checkBox->isChecked(); + + m_event.set(qstrtostr(propertyName), checked); +} + +void +EventEditDialog::slotStringPropertyChanged(const QString &value) +{ + const QObject *s = sender(); + const QLineEdit *lineEdit = dynamic_cast(s); + if (!lineEdit) + return ; + + m_modified = true; + QString propertyName = lineEdit->name(); + m_event.set(qstrtostr(propertyName), qstrtostr(value)); +} + +void +EventEditDialog::slotPropertyDeleted() +{ + const QObject *s = sender(); + const QPushButton *pushButton = dynamic_cast(s); + if (!pushButton) + return ; + + QString propertyName = pushButton->name(); + + if (KMessageBox::warningContinueCancel + (this, + i18n("Are you sure you want to delete the \"%1\" property?\n\n" + "Removing necessary properties may cause unexpected behavior."). + arg(propertyName), + i18n("Edit Event"), + i18n("&Delete")) != KMessageBox::Continue) + return ; + + m_modified = true; + QObjectList *list = m_persistentGrid->queryList(0, propertyName, false); + QObjectListIt i(*list); + QObject *obj; + while ((obj = i.current()) != 0) { + ++i; + delete obj; + } + delete list; + + m_event.unset(qstrtostr(propertyName)); +} + +void +EventEditDialog::slotPropertyMadePersistent() +{ + const QObject *s = sender(); + const QPushButton *pushButton = dynamic_cast(s); + if (!pushButton) + return ; + + QString propertyName = pushButton->name(); + + if (KMessageBox::warningContinueCancel + (this, + i18n("Are you sure you want to make the \"%1\" property persistent?\n\n" + "This could cause problems if it overrides a different " + "computed value later on."). + arg(propertyName), + i18n("Edit Event"), + i18n("Make &Persistent")) != KMessageBox::Continue) + return ; + + QObjectList *list = m_nonPersistentGrid->queryList(0, propertyName, false); + QObjectListIt i(*list); + QObject *obj; + while ((obj = i.current()) != 0) { + ++i; + delete obj; + } + delete list; + + m_modified = true; + addPersistentProperty(qstrtostr(propertyName)); + + PropertyType type = + m_originalEvent.getPropertyType(qstrtostr(propertyName)); + + switch (type) { + + case Int: + m_event.set + (qstrtostr(propertyName), + m_originalEvent.get + (qstrtostr(propertyName))); + break; + + case UInt: + m_event.set + (qstrtostr(propertyName), + m_originalEvent.get + (qstrtostr(propertyName))); + break; + + case RealTimeT: + m_event.set + (qstrtostr(propertyName), + m_originalEvent.get + (qstrtostr(propertyName))); + break; + + case Bool: + m_event.set + (qstrtostr(propertyName), + m_originalEvent.get + (qstrtostr(propertyName))); + break; + + case String: + m_event.set + (qstrtostr(propertyName), + m_originalEvent.get + (qstrtostr(propertyName))); + break; + } +} + +} +#include "EventEditDialog.moc" diff --git a/src/gui/dialogs/EventEditDialog.h b/src/gui/dialogs/EventEditDialog.h new file mode 100644 index 0000000..337a190 --- /dev/null +++ b/src/gui/dialogs/EventEditDialog.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EVENTEDITDIALOG_H_ +#define _RG_EVENTEDITDIALOG_H_ + +#include "base/Event.h" +#include "gui/editors/notation/NotePixmapFactory.h" +#include +#include + + +class QWidget; +class QString; +class QScrollView; +class QLabel; +class QGrid; + + +namespace Rosegarden +{ + +class PropertyName; + + +class EventEditDialog : public KDialogBase +{ + Q_OBJECT + +public: + /** + * Construct an event-edit dialog showing the properties of the + * given event. If editable is false, the user will not be allowed + * to modify the event; otherwise the event will be editable and + * the resulting edited version can subsequently be queried + * through getEvent(). + */ + EventEditDialog(QWidget *parent, + const Event &event, + bool editable = true); + + bool isModified() const { return m_modified; } + Event getEvent() const; + +public slots: + void slotEventTypeChanged(const QString &); + void slotAbsoluteTimeChanged(int value); + void slotDurationChanged(int value); + void slotSubOrderingChanged(int value); + + void slotIntPropertyChanged(int); + void slotRealTimePropertyChanged(int); + void slotBoolPropertyChanged(); + void slotStringPropertyChanged(const QString &); + + void slotPropertyDeleted(); + void slotPropertyMadePersistent(); + +protected: + void addPersistentProperty(const PropertyName &); + + //--------------- Data members --------------------------------- + NotePixmapFactory m_notePixmapFactory; + + QLabel *m_durationDisplay; + QLabel *m_durationDisplayAux; + + QGrid *m_persistentGrid; + QGrid *m_nonPersistentGrid; + + QScrollView *m_nonPersistentView; + + const Event &m_originalEvent; + Event m_event; + + std::string m_type; + timeT m_absoluteTime; + timeT m_duration; + int m_subOrdering; + + bool m_modified; +}; + +/* + * A simpler event editor for use by the EventView and MatrixView + * and people who want to remain sane. + */ + +} + +#endif diff --git a/src/gui/dialogs/EventFilterDialog.cpp b/src/gui/dialogs/EventFilterDialog.cpp new file mode 100644 index 0000000..7b0c15c --- /dev/null +++ b/src/gui/dialogs/EventFilterDialog.cpp @@ -0,0 +1,476 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2003-2006 + D. Michael McIntyre + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EventFilterDialog.h" + +#include "misc/Debug.h" +#include "base/BaseProperties.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/BasicQuantizer.h" +#include "gui/dialogs/PitchPickerDialog.h" +#include "gui/editors/notation/NotationStrings.h" +#include "gui/editors/notation/NotePixmapFactory.h" +#include "document/ConfigGroups.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +EventFilterDialog::EventFilterDialog(QWidget* parent) + : KDialogBase(parent, "eventfilerdialog", true, i18n("Event Filter"), Ok | Cancel, Ok), + m_standardQuantizations(BasicQuantizer::getStandardQuantizations()) +{ + cfg = kapp->config(); + initDialog(); +} + +EventFilterDialog::~EventFilterDialog() +{ + // nothing here +} + +void +EventFilterDialog::initDialog() +{ + QVBox* mainWidget = makeVBoxMainWidget(); + + + //----------[ Note Filter Widgets ]------------------------- + + // Frame + QGroupBox* noteFrame = new QGroupBox(i18n("Note Events"), mainWidget); + QGridLayout* noteFrameLayout = new QGridLayout(noteFrame, 1, 1, 20, 6); + + // Labels + QLabel* pitchFromLabel = new QLabel(i18n("lowest:"), noteFrame); + noteFrameLayout->addWidget(pitchFromLabel, 0, 2); + + QLabel* pitchToLabel = new QLabel(i18n("highest:"), noteFrame); + noteFrameLayout->addWidget(pitchToLabel, 0, 4); + + QLabel* pitchLabel = new QLabel(i18n("Pitch:"), noteFrame); + noteFrameLayout->addWidget(pitchLabel, 1, 1); + + QLabel* velocityLabel = new QLabel(i18n("Velocity:"), noteFrame); + noteFrameLayout->addWidget(velocityLabel, 2, 1); + + QLabel* durationLabel = new QLabel(i18n("Duration:"), noteFrame); + noteFrameLayout->addWidget(durationLabel, 3, 1); + + // Include Boxes + m_notePitchIncludeComboBox = new QComboBox(0, noteFrame); + m_notePitchIncludeComboBox->insertItem(i18n("include")); + m_notePitchIncludeComboBox->insertItem(i18n("exclude")); + cfg->setGroup(EventFilterDialogConfigGroup); + m_notePitchIncludeComboBox->setCurrentItem(cfg->readBoolEntry("pitchinclude", 0)); + noteFrameLayout->addWidget(m_notePitchIncludeComboBox, 1, 0); + + m_noteVelocityIncludeComboBox = new QComboBox(0, noteFrame); + m_noteVelocityIncludeComboBox->insertItem(i18n("include")); + m_noteVelocityIncludeComboBox->insertItem(i18n("exclude")); + cfg->setGroup(EventFilterDialogConfigGroup); + m_noteVelocityIncludeComboBox->setCurrentItem(cfg->readBoolEntry("velocityinclude", 0)); + noteFrameLayout->addWidget(m_noteVelocityIncludeComboBox, 2, 0); + + m_noteDurationIncludeComboBox = new QComboBox(0, noteFrame); + m_noteDurationIncludeComboBox->insertItem(i18n("include")); + m_noteDurationIncludeComboBox->insertItem(i18n("exclude")); + cfg->setGroup(EventFilterDialogConfigGroup); + m_noteDurationIncludeComboBox->setCurrentItem(cfg->readBoolEntry("durationinclude", 0)); + noteFrameLayout->addWidget(m_noteDurationIncludeComboBox, 3, 0); + + // Pitch From + m_pitchFromSpinBox = new QSpinBox(noteFrame); + m_pitchFromSpinBox->setMaxValue(127); + cfg->setGroup(EventFilterDialogConfigGroup); + m_pitchFromSpinBox->setValue(cfg->readUnsignedNumEntry("pitchfrom", 0)); + noteFrameLayout->addWidget(m_pitchFromSpinBox, 1, 2); + connect(m_pitchFromSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotPitchFromChanged(int))); + + m_pitchFromChooserButton = new QPushButton(i18n("edit"), noteFrame); + m_pitchFromChooserButton->setSizePolicy(QSizePolicy((QSizePolicy::SizeType)0, + (QSizePolicy::SizeType)0, 0, 0, m_pitchFromChooserButton-> + sizePolicy().hasHeightForWidth())); + QToolTip::add + (m_pitchFromChooserButton, i18n("choose a pitch using a staff")); + noteFrameLayout->addWidget(m_pitchFromChooserButton, 1, 3); + connect(m_pitchFromChooserButton, SIGNAL(clicked()), + SLOT(slotPitchFromChooser())); + + // Pitch To + m_pitchToSpinBox = new QSpinBox(noteFrame); + m_pitchToSpinBox->setMaxValue(127); + cfg->setGroup(EventFilterDialogConfigGroup); + m_pitchToSpinBox->setValue(cfg->readUnsignedNumEntry("pitchto", 127)); + noteFrameLayout->addWidget(m_pitchToSpinBox, 1, 4); + connect(m_pitchToSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotPitchToChanged(int))); + + m_pitchToChooserButton = new QPushButton(i18n("edit"), noteFrame); + QToolTip::add + (m_pitchToChooserButton, i18n("choose a pitch using a staff")); + noteFrameLayout->addWidget(m_pitchToChooserButton, 1, 5); + connect(m_pitchToChooserButton, SIGNAL(clicked()), + SLOT(slotPitchToChooser())); + + // Velocity From/To + m_velocityFromSpinBox = new QSpinBox(noteFrame); + m_velocityFromSpinBox->setMaxValue(127); + cfg->setGroup(EventFilterDialogConfigGroup); + m_velocityFromSpinBox->setValue(cfg->readUnsignedNumEntry("velocityfrom", 0)); + noteFrameLayout->addWidget(m_velocityFromSpinBox, 2, 2); + connect(m_velocityFromSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotVelocityFromChanged(int))); + + m_velocityToSpinBox = new QSpinBox(noteFrame); + m_velocityToSpinBox->setMaxValue(127); + cfg->setGroup(EventFilterDialogConfigGroup); + m_velocityToSpinBox->setValue(cfg->readUnsignedNumEntry("velocityto", 127)); + noteFrameLayout->addWidget( m_velocityToSpinBox, 2, 4 ); + connect(m_velocityToSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotVelocityToChanged(int))); + + + // Duration From/To + m_noteDurationFromComboBox = new QComboBox(0, noteFrame); + m_noteDurationFromComboBox->insertItem(i18n("longest")); + noteFrameLayout->addWidget(m_noteDurationFromComboBox, 3, 2); + connect(m_noteDurationFromComboBox, SIGNAL(activated(int)), + SLOT(slotDurationFromChanged(int))); + + m_noteDurationToComboBox = new QComboBox(0, noteFrame); + m_noteDurationToComboBox->insertItem(i18n("longest")); + noteFrameLayout->addWidget(m_noteDurationToComboBox, 3, 4); + connect(m_noteDurationToComboBox, SIGNAL(activated(int)), + SLOT(slotDurationToChanged(int))); + + populateDurationCombos(); + + + //---------[ Buttons ]-------------------------------------- + QFrame* privateLayoutWidget = new QFrame(mainWidget); + QGridLayout* buttonLayout = new QGridLayout(privateLayoutWidget, 1, 1, 20, 6); + + m_buttonAll = new QPushButton(i18n("Include all"), privateLayoutWidget); + m_buttonAll->setAutoDefault(true); + QToolTip::add + (m_buttonAll, i18n("Include entire range of values")); + buttonLayout->addWidget( m_buttonAll, 0, 0 ); + + m_buttonNone = new QPushButton(i18n("Exclude all"), privateLayoutWidget); + m_buttonNone->setAutoDefault(true); + QToolTip::add + (m_buttonNone, i18n("Exclude entire range of values")); + buttonLayout->addWidget( m_buttonNone, 0, 1 ); + + connect(m_buttonAll, SIGNAL(clicked()), this, SLOT(slotToggleAll())); + connect(m_buttonNone, SIGNAL(clicked()), this, SLOT(slotToggleNone())); + + +} + +void +EventFilterDialog::populateDurationCombos() +{ + 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_noteDurationFromComboBox->insertItem(error ? noMap : pmap, label); + m_noteDurationToComboBox ->insertItem(error ? noMap : pmap, label); + } + m_noteDurationFromComboBox->insertItem(noMap, i18n("shortest")); + m_noteDurationToComboBox->insertItem(noMap, i18n("shortest")); + + cfg->setGroup(EventFilterDialogConfigGroup); + m_noteDurationFromComboBox->setCurrentItem( + cfg->readUnsignedNumEntry("durationfrom", 0)); + m_noteDurationToComboBox->setCurrentItem( + cfg->readUnsignedNumEntry("durationto", (m_noteDurationToComboBox->count() - 1))); +} + +void +EventFilterDialog::slotToggleAll() +{ + RG_DEBUG << "EventFilterDialog::slotToggleAll()" << endl; + m_pitchFromSpinBox ->setValue(0); + m_pitchToSpinBox ->setValue(127); + m_velocityFromSpinBox ->setValue(0); + m_velocityToSpinBox ->setValue(127); + m_noteDurationFromComboBox ->setCurrentItem(11); // hard coded; should be variable + m_noteDurationToComboBox ->setCurrentItem(0); // 0 = unlimited; 11 = 0 +} + +void +EventFilterDialog::slotToggleNone() +{ + RG_DEBUG << "EventFilterDialog::slotToggleNone()" << endl; + m_pitchFromSpinBox ->setValue(0); + m_pitchToSpinBox ->setValue(0); + m_velocityFromSpinBox ->setValue(0); + m_velocityToSpinBox ->setValue(0); + m_noteDurationFromComboBox ->setCurrentItem(11); + m_noteDurationToComboBox ->setCurrentItem(11); +} + +void +EventFilterDialog::slotOk() +{ + cfg->setGroup(EventFilterDialogConfigGroup); + + cfg->writeEntry("pitchinclude", m_notePitchIncludeComboBox->currentItem()); + cfg->writeEntry("pitchfrom", m_pitchFromSpinBox->value()); + cfg->writeEntry("pitchto", m_pitchToSpinBox->value()); + + cfg->writeEntry("velocityinclude", m_noteVelocityIncludeComboBox->currentItem()); + cfg->writeEntry("velocityfrom", m_velocityFromSpinBox->value()); + cfg->writeEntry("velocityto", m_velocityToSpinBox->value()); + + cfg->writeEntry("durationinclude", m_noteDurationIncludeComboBox->currentItem()); + cfg->writeEntry("durationfrom", m_noteDurationFromComboBox->currentItem()); + cfg->writeEntry("durationto", m_noteDurationToComboBox->currentItem()); + + accept(); +} + +void +EventFilterDialog::slotPitchFromChanged(int pitch) +{ + if (pitch > m_pitchToSpinBox->value()) + m_pitchToSpinBox->setValue(pitch); +} + +void +EventFilterDialog::slotPitchToChanged(int pitch) +{ + if (pitch < m_pitchFromSpinBox->value()) + m_pitchFromSpinBox->setValue(pitch); +} + +void +EventFilterDialog::slotVelocityFromChanged(int velocity) +{ + if (velocity > m_velocityToSpinBox->value()) + m_velocityToSpinBox->setValue(velocity); +} + +void +EventFilterDialog::slotVelocityToChanged(int velocity) +{ + if (velocity < m_velocityFromSpinBox->value()) + m_velocityFromSpinBox->setValue(velocity); +} + +void +EventFilterDialog::slotDurationFromChanged(int index) +{ + if (index < m_noteDurationToComboBox->currentItem()) + m_noteDurationToComboBox->setCurrentItem(index); +} + +void +EventFilterDialog::slotDurationToChanged(int index) +{ + if (index > m_noteDurationFromComboBox->currentItem()) + m_noteDurationFromComboBox->setCurrentItem(index); +} + + +void +EventFilterDialog::slotPitchFromChooser() +{ + PitchPickerDialog dialog(this, m_pitchFromSpinBox->value(), i18n("Lowest pitch")); + + if (dialog.exec() == QDialog::Accepted) { + m_pitchFromSpinBox->setValue(dialog.getPitch()); + } +} + +void +EventFilterDialog::slotPitchToChooser() +{ + PitchPickerDialog dialog(this, m_pitchToSpinBox->value(), i18n("Highest pitch")); + + if (dialog.exec() == QDialog::Accepted) { + m_pitchToSpinBox->setValue(dialog.getPitch()); + } +} + +long +EventFilterDialog::getDurationFromIndex(int index) +{ + switch (index) { + // 0 + case 11: + return 0; + // 1/96 + case 10: + return long(Note(Note::SixtyFourthNote).getDuration() / 3); + // 1/64 + case 9 : + return long(Note(Note::SixtyFourthNote).getDuration()); + // 1/48 + case 8 : + return long(Note(Note::ThirtySecondNote).getDuration() / 3); + // 1/32 + case 7 : + return long(Note(Note::ThirtySecondNote).getDuration()); + // 1/24 + case 6 : + return long(Note(Note::SixteenthNote).getDuration() / 3); + // 1/16 + case 5 : + return long(Note(Note::SixteenthNote).getDuration()); + // 1/8 + case 4 : + return long(Note(Note::EighthNote).getDuration()); + // 1/4 + case 3 : + return long(Note(Note::QuarterNote).getDuration()); + // 1/2 + case 2 : + return long(Note(Note::HalfNote).getDuration()); + // 1/1 + case 1 : + return long(Note(Note::WholeNote).getDuration()); + // unlimited + case 0 : + return LONG_MAX; + } + // failsafe + return LONG_MAX; +} + +void +EventFilterDialog::invert(EventFilterDialog::filterRange &foo) +{ + long c = foo.first; + foo.first = foo.second; + foo.second = c; +} + +EventFilterDialog::filterRange +EventFilterDialog::getPitch() +{ + EventFilterDialog::filterRange foo; + foo.first = m_pitchFromSpinBox->value(); + foo.second = m_pitchToSpinBox ->value(); + if (!pitchIsInclusive()) + invert(foo); + return foo; +} + +EventFilterDialog::filterRange +EventFilterDialog::getVelocity() +{ + EventFilterDialog::filterRange foo; + foo.first = m_velocityFromSpinBox->value(); + foo.second = m_velocityToSpinBox ->value(); + if (!velocityIsInclusive()) + invert(foo); + return foo; +} + +EventFilterDialog::filterRange +EventFilterDialog::getDuration() +{ + EventFilterDialog::filterRange foo; + foo.first = getDurationFromIndex(m_noteDurationFromComboBox->currentItem()); + foo.second = getDurationFromIndex(m_noteDurationToComboBox ->currentItem()); + if (!durationIsInclusive()) + invert(foo); + return foo; +} + +bool +EventFilterDialog::keepEvent(Event* const &e) +{ + if ((*e).isa(Note::EventType)) { + long property = 0; + + // pitch + (*e).get(BaseProperties::PITCH, property); + if (!eventInRange(getPitch(), property)) { + RG_DEBUG << "EventFilterDialog::keepEvent(): rejecting event; pitch " << property + << " out of range." << endl; + return false; + } + property = 0; + + // velocity + (*e).get(BaseProperties::VELOCITY, property); + if (!EventFilterDialog::eventInRange(getVelocity(), property)) { + RG_DEBUG << "EventFilterDialog::keepEvent(): rejecting event; velocity " << property + << " out of range." << endl; + return false; + } + property = 0; + + // duration + property = (*e).getNotationDuration(); + if (!EventFilterDialog::eventInRange(getDuration(), property)) { + RG_DEBUG << "EventFilterDialog::keepEvent(): rejecting event; duration " << property + << " out of range." << endl; + return false; + } + property = 0; + + return true; + } + return false; +} + +} + +#include "EventFilterDialog.moc" diff --git a/src/gui/dialogs/EventFilterDialog.h b/src/gui/dialogs/EventFilterDialog.h new file mode 100644 index 0000000..0d3eb05 --- /dev/null +++ b/src/gui/dialogs/EventFilterDialog.h @@ -0,0 +1,170 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2003-2006 + D. Michael McIntyre + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EVENTFILTERDIALOG_H_ +#define _RG_EVENTFILTERDIALOG_H_ + +#include +#include +#include +#include "base/Event.h" +#include +#include + +class QWidget; +class QSpinBox; +class QPushButton; +class QGridLayout; +class KConfig; + + +namespace Rosegarden +{ + +class Event; + + +/** + * Creates a dialog box to allow the user to dial up various selection + * criteria used for removing events from a selection. It is up to the caller + * to actually manipulate the selection. After the dialog has been accepted, + * its filterEvent() method can be used to decide whether a particular event + * should continue to be selected. See matrixview.cpp slotFilterSelection() + * for an example of how to use this. + */ +class EventFilterDialog : public KDialogBase +{ + Q_OBJECT + +public: + + EventFilterDialog(QWidget* parent); + ~EventFilterDialog(); + + KConfig *cfg; + + //-------[ accessor functions ]------------------------ + + // NOTE: the filterRange type is used to return an A B pair with A and B set + // according to the state of the related include/exclude combo. If A > B + // then this is an inclusive range. If A < B then it's an exclusive + // range. This saves passing around a third variable. + typedef std::pair filterRange; + + filterRange getPitch(); + filterRange getVelocity(); + filterRange getDuration(); + + // returns TRUE if the property value falls with in the filterRange + bool eventInRange(filterRange foo, long property) { + if (foo.first > foo.second) + return (property <= foo.second || property >= foo.first); + else + return (property >= foo.first && property <= foo.second); } + + // Used to do the work of deciding whether to keep or reject an event + // based on the state of the dialog's widgets. Returns TRUE if an event + // should continue to be selected. This method is the heart of the + // EventFilterDialog's public interface. + bool keepEvent(Event* const &e); + +protected: + + //--------[ member functions ]------------------------- + + // initialize the dialog + void initDialog(); + + // populate the duration combos + void populateDurationCombos(); + + // convert duration from combobox index into actual RG duration + // between 0 and LONG_MAX + long getDurationFromIndex(int index); + + // simple A B swap used to flip inclusive/exclusive values + void invert (filterRange &); + + // return inclusive/exclusive toggle states concisely for tidy code + bool pitchIsInclusive() { return (m_notePitchIncludeComboBox->currentItem() == 0); } + bool velocityIsInclusive() { return (m_noteVelocityIncludeComboBox->currentItem() == 0); } + bool durationIsInclusive() { return (m_noteDurationIncludeComboBox->currentItem() == 0); } + +protected slots: + + // set widget values to include everything + void slotToggleAll(); + + // set widget values to include nothing + void slotToggleNone(); + + // write out settings to kconfig data for next time and call accept() + virtual void slotOk(); + + // update note name text display and ensure From <= To + void slotPitchFromChanged(int pitch); + void slotPitchToChanged(int pitch); + + // ensure From <= To to guarantee a logical range for these sets + void slotVelocityFromChanged(int velocity); + void slotVelocityToChanged(int velocity); + void slotDurationFromChanged(int index); + void slotDurationToChanged(int index); + + // create a pitch chooser widget sub-dialog to show pitch on staff + void slotPitchFromChooser(); + void slotPitchToChooser(); + +private: + //---------[ data members ]----------------------------- + + QGridLayout* layout; + + QComboBox* m_noteDurationFromComboBox; + QComboBox* m_noteDurationIncludeComboBox; + QComboBox* m_noteDurationToComboBox; + QComboBox* m_notePitchIncludeComboBox; + QComboBox* m_noteVelocityIncludeComboBox; + + QPushButton* m_pitchFromChooserButton; + QPushButton* m_pitchToChooserButton; + QPushButton* m_buttonAll; + QPushButton* m_buttonNone; + + QSpinBox* m_pitchFromSpinBox; + QSpinBox* m_pitchToSpinBox; + QSpinBox* m_velocityFromSpinBox; + QSpinBox* m_velocityToSpinBox; + + std::vector m_standardQuantizations; + +}; + + +} + +#endif diff --git a/src/gui/dialogs/EventParameterDialog.cpp b/src/gui/dialogs/EventParameterDialog.cpp new file mode 100644 index 0000000..036491e --- /dev/null +++ b/src/gui/dialogs/EventParameterDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EventParameterDialog.h" + +#include +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/PropertyName.h" +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +EventParameterDialog::EventParameterDialog( + QWidget *parent, + const QString &name, + const PropertyName &property, + int startValue): + KDialogBase(parent, 0, true, name, Ok | Cancel), + m_property(property) +{ + QVBox *vBox = makeVBoxMainWidget(); + + QHBox *topBox = new QHBox(vBox); + QLabel *explainLabel = new QLabel(topBox); + QString text = i18n("Set the %1 property of the event selection:"). + arg(strtoqstr(property)); + explainLabel->setText(text); + + QHBox *patternBox = new QHBox(vBox); + new QLabel(i18n("Pattern"), patternBox); + m_patternCombo = new KComboBox(patternBox); + + // create options + // 0 flat + text = i18n("Flat - set %1 to value").arg(strtoqstr(property)); + m_patternCombo->insertItem(text); + + // 1 alternating + text = i18n("Alternating - set %1 to max and min on alternate events").arg(strtoqstr(property)); + m_patternCombo->insertItem(text); + + // 2 crescendo + text = i18n("Crescendo - set %1 rising from min to max").arg(strtoqstr(property)); + m_patternCombo->insertItem(text); + + // 3 diminuendo + text = i18n("Diminuendo - set %1 falling from max to min").arg(strtoqstr(property)); + m_patternCombo->insertItem(text); + + // 4 ringing + text = i18n("Ringing - set %1 alternating from max to min with both dying to zero").arg(strtoqstr(property)); + m_patternCombo->insertItem(text); + + connect(m_patternCombo, SIGNAL(activated(int)), + this, SLOT(slotPatternSelected(int))); + + QHBox *value1Box = new QHBox(vBox); + m_value1Label = new QLabel(i18n("Value"), value1Box); + m_value1Combo = new KComboBox(value1Box); + + QHBox *value2Box = new QHBox(vBox); + m_value2Label = new QLabel(i18n("Value"), value2Box); + m_value2Combo = new KComboBox(value2Box); + + for (unsigned int i = 0; i < 128; i++) { + m_value1Combo->insertItem(QString("%1").arg(i)); + m_value2Combo->insertItem(QString("%1").arg(i)); + } + m_value1Combo->setCurrentItem(127); + + slotPatternSelected(0); + + // start value + m_value1Combo->setCurrentItem(startValue); + m_value2Combo->setCurrentItem(startValue); + +} + +void +EventParameterDialog::slotPatternSelected(int value) +{ + switch (value) { + case 0: // flat + m_value1Label->setText(i18n("Value")); + m_value1Label->show(); + m_value1Combo->show(); + m_value2Label->hide(); + m_value2Combo->hide(); + break; + + case 1: // alternating + m_value1Label->setText(i18n("First Value")); + m_value2Label->setText(i18n("Second Value")); + m_value1Label->show(); + m_value1Combo->show(); + m_value2Label->show(); + m_value2Combo->show(); + break; + + case 2: // crescendo + m_value1Label->setText(i18n("Low Value")); + m_value2Label->setText(i18n("High Value")); + m_value1Label->show(); + m_value1Combo->show(); + m_value2Label->show(); + m_value2Combo->show(); + break; + + case 3: // decrescendo + m_value1Label->setText(i18n("High Value")); + m_value2Label->setText(i18n("Low Value")); + m_value1Label->show(); + m_value1Combo->show(); + m_value2Label->show(); + m_value2Combo->show(); + break; + + case 4: // ringing + m_value1Label->setText(i18n("First Value")); + m_value2Label->setText(i18n("Second Value")); + m_value1Label->show(); + m_value1Combo->show(); + m_value2Label->show(); + m_value2Combo->show(); + break; + + default: + RG_DEBUG << "EventParameterDialog::slotPatternSelected - " + << "unrecognised pattern number" << endl; + break; + } + +} + +PropertyPattern +EventParameterDialog::getPattern() +{ + return PropertyPattern(m_patternCombo->currentItem()); +} + +int +EventParameterDialog::getValue1() +{ + return m_value1Combo->currentItem(); +} + +int +EventParameterDialog::getValue2() +{ + return m_value2Combo->currentItem(); +} + +} +#include "EventParameterDialog.moc" diff --git a/src/gui/dialogs/EventParameterDialog.h b/src/gui/dialogs/EventParameterDialog.h new file mode 100644 index 0000000..040e2f9 --- /dev/null +++ b/src/gui/dialogs/EventParameterDialog.h @@ -0,0 +1,80 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EVENTPARAMETERDIALOG_H_ +#define _RG_EVENTPARAMETERDIALOG_H_ + +#include "base/PropertyName.h" +#include +#include "commands/edit/SelectionPropertyCommand.h" + +class QWidget; +class QString; +class QLabel; +class KComboBox; + + +namespace Rosegarden +{ + + + +class EventParameterDialog : public KDialogBase +{ + Q_OBJECT + +public: + EventParameterDialog(QWidget *parent, + const QString &name, // name + const PropertyName &property, // property + int startValue); // start + + int getValue1(); + int getValue2(); + PropertyPattern getPattern(); + +public slots: + void slotPatternSelected(int value); + +protected: + //--------------- Data members --------------------------------- + PropertyName m_property; + PropertyPattern m_pattern; + + KComboBox *m_value1Combo; + KComboBox *m_value2Combo; + KComboBox *m_patternCombo; + + QLabel *m_value1Label; + QLabel *m_value2Label; + +}; + + +// ---------------- CompositionLengthDialog ----------- + +} + +#endif diff --git a/src/gui/dialogs/ExportDeviceDialog.cpp b/src/gui/dialogs/ExportDeviceDialog.cpp new file mode 100644 index 0000000..ce5f52e --- /dev/null +++ b/src/gui/dialogs/ExportDeviceDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ExportDeviceDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +ExportDeviceDialog::ExportDeviceDialog(QWidget *parent, QString deviceName) : + KDialogBase(parent, "exportdevicedialog", true, i18n("Export Devices..."), + Ok | Cancel, Ok) +{ + QVBox *vbox = makeVBoxMainWidget(); + QButtonGroup *bg = new QButtonGroup(1, Qt::Horizontal, + i18n("Export devices"), + vbox); + m_exportAll = new QRadioButton(i18n("Export all devices"), bg); + m_exportOne = new QRadioButton(i18n("Export selected device only"), bg); + new QLabel(i18n(" (\"%1\")").arg(deviceName), bg); + + m_exportOne->setChecked(true); +} + +ExportDeviceDialog::ExportType + +ExportDeviceDialog::getExportType() +{ + if (m_exportAll->isChecked()) + return ExportAll; + else + return ExportOne; +} + +} diff --git a/src/gui/dialogs/ExportDeviceDialog.h b/src/gui/dialogs/ExportDeviceDialog.h new file mode 100644 index 0000000..21fc183 --- /dev/null +++ b/src/gui/dialogs/ExportDeviceDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EXPORTDEVICEDIALOG_H_ +#define _RG_EXPORTDEVICEDIALOG_H_ + +#include +#include + + +class QWidget; +class QRadioButton; + + +namespace Rosegarden +{ + + + +class ExportDeviceDialog : public KDialogBase +{ +public: + enum ExportType { ExportOne, ExportAll }; + + ExportDeviceDialog(QWidget *parent, QString deviceName); + + ExportType getExportType(); + +protected: + QRadioButton *m_exportAll; + QRadioButton *m_exportOne; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/FileLocateDialog.cpp b/src/gui/dialogs/FileLocateDialog.cpp new file mode 100644 index 0000000..4f153c8 --- /dev/null +++ b/src/gui/dialogs/FileLocateDialog.cpp @@ -0,0 +1,104 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "FileLocateDialog.h" + +#include +#include "misc/Debug.h" +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +FileLocateDialog::FileLocateDialog(QWidget *parent, + const QString &file, + const QString & /*path*/): + KDialogBase(parent, 0, true, + i18n("Locate audio file"), + User1 | User2 | User3, + Ok, + false, + i18n("&Skip"), + i18n("Skip &All"), + i18n("&Locate")), + m_file(file) +{ + QHBox *w = makeHBoxMainWidget(); + QString label = + i18n("Can't find file \"%1\".\n" + "Would you like to try and locate this file or skip it?").arg(m_file); + + QLabel *labelW = new QLabel(label, w); + labelW->setAlignment(Qt::AlignCenter); + labelW->setMinimumHeight(60); +} + +void +FileLocateDialog::slotUser3() +{ + if (!m_file.isEmpty()) { + m_file = KFileDialog::getOpenFileName + (":WAVS", + i18n("%1|Requested file (%2)\n*.wav|WAV files (*.wav)") + .arg(QFileInfo(m_file).fileName()) + .arg(QFileInfo(m_file).fileName()), + this, i18n("Select an Audio File")); + + RG_DEBUG << "FileLocateDialog::slotUser3() : m_file = " << m_file << endl; + + if (m_file.isEmpty()) { + RG_DEBUG << "FileLocateDialog::slotUser3() : reject\n"; + reject(); + } else { + QFileInfo fileInfo(m_file); + m_path = fileInfo.dirPath(); + accept(); + } + + } else + reject(); +} + +void +FileLocateDialog::slotUser1() +{ + reject(); +} + +void +FileLocateDialog::slotUser2() +{ + done( -1); +} + +} +#include "FileLocateDialog.moc" diff --git a/src/gui/dialogs/FileLocateDialog.h b/src/gui/dialogs/FileLocateDialog.h new file mode 100644 index 0000000..1786221 --- /dev/null +++ b/src/gui/dialogs/FileLocateDialog.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_FILELOCATEDIALOG_H_ +#define _RG_FILELOCATEDIALOG_H_ + +#include +#include + + +class QWidget; + + +namespace Rosegarden +{ + + + +class FileLocateDialog : public KDialogBase +{ + Q_OBJECT + +public: + FileLocateDialog(QWidget *parent, + const QString &file, + const QString &path); + + QString getDirectory() { return m_path; } + QString getFilename() { return m_file; } + +protected: + virtual void slotUser1(); + virtual void slotUser2(); + virtual void slotUser3(); + + QString m_file; + QString m_path; + +}; + + +} + +#endif diff --git a/src/gui/dialogs/FileMergeDialog.cpp b/src/gui/dialogs/FileMergeDialog.cpp new file mode 100644 index 0000000..d997327 --- /dev/null +++ b/src/gui/dialogs/FileMergeDialog.cpp @@ -0,0 +1,84 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "FileMergeDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "document/RosegardenGUIDoc.h" + + +namespace Rosegarden +{ + +FileMergeDialog::FileMergeDialog(QWidget *parent, + QString /*fileName*/, + bool timingsDiffer) : + KDialogBase(parent, 0, true, i18n("Merge File"), Ok | Cancel | Help) +{ + setHelp("file-merge"); + + QVBox *vbox = makeVBoxMainWidget(); + + QHBox *hbox = new QHBox(vbox); + new QLabel(i18n("Merge new file "), hbox); + + m_choice = new KComboBox(hbox); + m_choice->insertItem(i18n("At start of existing composition")); + m_choice->insertItem(i18n("From end of existing composition")); + m_useTimings = 0; + + if (timingsDiffer) { + new QLabel(i18n("The file has different time signatures or tempos."), vbox); + m_useTimings = new QCheckBox(i18n("Import these as well"), vbox); + m_useTimings->setChecked(false); + } +} + +int +FileMergeDialog::getMergeOptions() +{ + int options = MERGE_KEEP_OLD_TIMINGS | MERGE_IN_NEW_TRACKS; + + if (m_choice->currentItem() == 1) { + options |= MERGE_AT_END; + } + + if (m_useTimings && m_useTimings->isChecked()) { + options |= MERGE_KEEP_NEW_TIMINGS; + } + + return options; +} + +} +#include "FileMergeDialog.moc" diff --git a/src/gui/dialogs/FileMergeDialog.h b/src/gui/dialogs/FileMergeDialog.h new file mode 100644 index 0000000..f305cae --- /dev/null +++ b/src/gui/dialogs/FileMergeDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_FILEMERGEDIALOG_H_ +#define _RG_FILEMERGEDIALOG_H_ + +#include +#include + + +class QWidget; +class QCheckBox; +class KComboBox; + + +namespace Rosegarden +{ + + + +class FileMergeDialog : public KDialogBase +{ + Q_OBJECT + +public: + FileMergeDialog(QWidget *parent, QString fileName, bool timingsDiffer); + + int getMergeOptions(); + +private: + KComboBox *m_choice; + QCheckBox *m_useTimings; +}; + + +// Locate a file +// + +} + +#endif diff --git a/src/gui/dialogs/FloatEdit.cpp b/src/gui/dialogs/FloatEdit.cpp new file mode 100644 index 0000000..06e8aa3 --- /dev/null +++ b/src/gui/dialogs/FloatEdit.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "FloatEdit.h" + +#include "gui/widgets/HSpinBox.h" +#include +#include +#include +#include +#include +#include +#include + +namespace Rosegarden +{ + +FloatEdit::FloatEdit(QWidget *parent, + const QString &title, + const QString &text, + float min, + float max, + float value, + float step): + KDialogBase(parent, "rosegardenFloatEdit", true, title, Ok | Cancel, Ok) +{ + QVBox *vbox = makeVBoxMainWidget(); + QGroupBox *groupBox = new QGroupBox(1, Horizontal, text, vbox); + QVBox *inVbox = new QVBox(groupBox); + + // Calculate decimal points according to the step size + // + double calDP = log10(step); + int dps = 0; + if (calDP < 0.0) + dps = int( -calDP); + //std::cout << "CAL DP = " << calDP << ", dps = " << dps << std::endl; + + m_spin = new HSpinBox(inVbox, value, 1, min, max, dps); + new QLabel(QString("(min: %1, max: %2)").arg(min).arg(max), inVbox); +} + +float +FloatEdit::getValue() const +{ + return m_spin->valuef(); +} + +} +#include "FloatEdit.moc" diff --git a/src/gui/dialogs/FloatEdit.h b/src/gui/dialogs/FloatEdit.h new file mode 100644 index 0000000..24f1b2c --- /dev/null +++ b/src/gui/dialogs/FloatEdit.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ROSEGARDENFLOATEDIT_H_ +#define _RG_ROSEGARDENFLOATEDIT_H_ + +#include + + +class QWidget; +class QString; +class QLabel; + + +namespace Rosegarden +{ + +class HSpinBox; + + +class FloatEdit : public KDialogBase +{ + Q_OBJECT + +public: + FloatEdit(QWidget *parent, + const QString &title, + const QString &text, + float min, + float max, + float value, + float step); + + float getValue() const; + +protected: + + QLabel *m_text; + HSpinBox *m_spin; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/IdentifyTextCodecDialog.cpp b/src/gui/dialogs/IdentifyTextCodecDialog.cpp new file mode 100644 index 0000000..07b5ec1 --- /dev/null +++ b/src/gui/dialogs/IdentifyTextCodecDialog.cpp @@ -0,0 +1,173 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "IdentifyTextCodecDialog.h" + +#include +#include "misc/Strings.h" +#include "base/NotationTypes.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +IdentifyTextCodecDialog::IdentifyTextCodecDialog(QWidget *parent, + std::string text) : + KDialogBase(parent, 0, true, i18n("Choose Text Encoding"), Ok), + m_text(text) +{ + QVBox *vbox = makeVBoxMainWidget(); + new QLabel(i18n("\nThis file contains text in an unknown language encoding.\n\nPlease select one of the following estimated text encodings\nfor use with the text in this file:\n"), vbox); + + KComboBox *codecs = new KComboBox(vbox); + + std::string defaultCodec; + QTextCodec *cc = QTextCodec::codecForContent(text.c_str(), text.length()); + QTextCodec *codec = 0; + + std::cerr << "cc is " << (cc ? cc->name() : "null") << std::endl; + + std::map codecDescriptions; + codecDescriptions["SJIS"] = i18n("Japanese Shift-JIS"); + codecDescriptions["UTF-8"] = i18n("Unicode variable-width"); + codecDescriptions["ISO 8859-1"] = i18n("Western Europe"); + codecDescriptions["ISO 8859-15"] = i18n("Western Europe + Euro"); + codecDescriptions["ISO 8859-2"] = i18n("Eastern Europe"); + codecDescriptions["ISO 8859-3"] = i18n("Southern Europe"); + codecDescriptions["ISO 8859-4"] = i18n("Northern Europe"); + codecDescriptions["ISO 8859-5"] = i18n("Cyrillic"); + codecDescriptions["ISO 8859-6"] = i18n("Arabic"); + codecDescriptions["ISO 8859-7"] = i18n("Greek"); + codecDescriptions["ISO 8859-8"] = i18n("Hebrew"); + codecDescriptions["ISO 8859-9"] = i18n("Turkish"); + codecDescriptions["ISO 8859-10"] = i18n("Nordic"); + codecDescriptions["ISO 8859-11"] = i18n("Thai"); + codecDescriptions["ISO 8859-13"] = i18n("Baltic"); + codecDescriptions["ISO 8859-14"] = i18n("Celtic"); + codecDescriptions["SJIS"] = i18n("Japanese Shift-JIS"); + codecDescriptions["Big5"] = i18n("Traditional Chinese"); + codecDescriptions["GB18030"] = i18n("Simplified Chinese"); + codecDescriptions["KOI8-R"] = i18n("Russian"); + codecDescriptions["KOI8-U"] = i18n("Ukrainian"); + codecDescriptions["TSCII"] = i18n("Tamil"); + + int i = 0; + int current = -1; + + int selectedProbability = 0; + if (cc) { + selectedProbability = cc->heuristicContentMatch + (m_text.c_str(), m_text.length()); + } + + while ((codec = QTextCodec::codecForIndex(i)) != 0) { + + int probability = codec->heuristicContentMatch + (m_text.c_str(), m_text.length()); + + if (probability <= 0) { + ++i; + continue; + } + + std::string name = codec->name(); + + std::cerr << "codec " << name << " probability " << probability << std::endl; + + if (name == "UTF-8" && + (!cc || (cc->name() != name)) && + probability > selectedProbability/2) { + std::cerr << "UTF-8 has a decent probability, selecting it instead to promote global harmony" << std::endl; + cc = codec; + } + + QString description = codecDescriptions[name]; + if (description == "") { + if (strtoqstr(name).left(3) == "CP ") { + description = i18n("Microsoft Code Page %1"). + arg(strtoqstr(name).right(name.length() - 3)); + } + } + + if (description != "") { + description = i18n("%1 (%2)").arg(strtoqstr(name)).arg(description); + } else { + description = strtoqstr(name); + } + + codecs->insertItem(description, 0); + m_codecs.push_front(name); + if (current >= 0) ++current; + + if (cc && (name == cc->name())) { + current = 0; + } + + ++i; + } + + connect(codecs, SIGNAL(activated(int)), + this, SLOT(slotCodecSelected(int))); + + new QLabel(i18n("\nExample text from file:"), vbox); + m_example = new QLabel("", vbox); + QFont font; + font.setStyleHint(QFont::TypeWriter); + m_example->setFont(font); + m_example->setPaletteForegroundColor(Qt::blue); + std::cerr << "calling slotCodecSelected(" << current << ")" << std::endl; + if (current < 0) current = 0; + codecs->setCurrentItem(current); + slotCodecSelected(current); +} + +void +IdentifyTextCodecDialog::slotCodecSelected(int i) +{ +// std::cerr << "codec index = " << i << std::endl; + if (i < 0 || i >= m_codecs.size()) return; + std::string name = m_codecs[i]; +// std::cerr << "codecs: "; +// for (int j = 0; j < m_codecs.size(); ++j) std::cerr << m_codecs[j] << " "; +// std::cerr << std::endl; + QTextCodec *codec = QTextCodec::codecForName(strtoqstr(name)); + if (!codec) return; + m_codec = qstrtostr(codec->name()); + std::cerr << "Applying codec " << m_codec << std::endl; + QString outText = codec->toUnicode(m_text.c_str(), m_text.length()); + if (outText.length() > 80) outText = outText.left(80); + m_example->setText("\"" + outText + "\""); +} + +} +#include "IdentifyTextCodecDialog.moc" diff --git a/src/gui/dialogs/IdentifyTextCodecDialog.h b/src/gui/dialogs/IdentifyTextCodecDialog.h new file mode 100644 index 0000000..288cd17 --- /dev/null +++ b/src/gui/dialogs/IdentifyTextCodecDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_IDENTIFYTEXTCODECDIALOG_H_ +#define _RG_IDENTIFYTEXTCODECDIALOG_H_ + +#include +#include +#include + + +class QWidget; +class QLabel; + + +namespace Rosegarden +{ + + + +class IdentifyTextCodecDialog : public KDialogBase +{ + Q_OBJECT + +public: + IdentifyTextCodecDialog(QWidget *parent, std::string text); + + std::string getCodec() const { return m_codec; } + +protected slots: + void slotCodecSelected(int); + +protected: + std::string m_text; + std::string m_codec; + std::deque m_codecs; + QLabel *m_example; +}; + + +/* + * Creates a small dialog box containing a PitchChooser widget. The + * info paramter provides extra information as a reminder what this particular + * picker is for, eg. Highest, Lowest, From, To + */ + +} + +#endif diff --git a/src/gui/dialogs/ImportDeviceDialog.cpp b/src/gui/dialogs/ImportDeviceDialog.cpp new file mode 100644 index 0000000..58a6ce5 --- /dev/null +++ b/src/gui/dialogs/ImportDeviceDialog.cpp @@ -0,0 +1,389 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ImportDeviceDialog.h" +#include +#include + +#include +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/MidiDevice.h" +#include "base/MidiProgram.h" +#include "document/RosegardenGUIDoc.h" +#include "sound/SF2PatchExtractor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +ImportDeviceDialog::ImportDeviceDialog(QWidget *parent, KURL url) : + KDialogBase(parent, "importdevicedialog", true, + i18n("Import from Device..."), + Ok | Cancel, Ok), + m_url(url), + m_fileDoc(0), + m_device(0) +{} + +ImportDeviceDialog::~ImportDeviceDialog() +{ + if (m_fileDoc) { + delete m_fileDoc; + } else { + delete m_device; + } +} + +bool +ImportDeviceDialog::doImport() +{ + QVBox *mainFrame = makeVBoxMainWidget(); + + if (m_url.isEmpty()) { + reject(); + return false; + } + + QString target; + if (KIO::NetAccess::download(m_url, target) == false) { + KMessageBox::error(this, i18n("Cannot download file %1").arg(m_url.prettyURL())); + return false; + } + + bool fileRead = false; + if (SF2PatchExtractor::isSF2File(target.data())) { + fileRead = importFromSF2(target); + } else { + fileRead = importFromRG(target); + } + if (!fileRead) { + KMessageBox::error + (this, i18n("Cannot open file %1").arg(m_url.prettyURL())); + reject(); + close(); + return false; + } + if (m_devices.size() == 0) { + KMessageBox::sorry + (this, i18n("No devices found in file %1").arg(m_url.prettyURL())); + reject(); + close(); + return false; + } + + QGroupBox *groupBox = new QGroupBox(2, Qt::Horizontal, + i18n("Source device"), + mainFrame); + + QHBox *deviceBox = new QHBox(groupBox); + QHBoxLayout *bl = new QHBoxLayout(deviceBox); + bl->addWidget(new QLabel(i18n("Import from: "), deviceBox)); + + bool showRenameOption = false; + + if (m_devices.size() > 1) { + m_deviceCombo = new KComboBox(deviceBox); + m_deviceLabel = 0; + bl->addWidget(m_deviceCombo); + } else { + m_deviceCombo = 0; + m_deviceLabel = new QLabel(deviceBox); + bl->addWidget(m_deviceLabel); + } + + bl->addStretch(10); + + int count = 1; + for (std::vector::iterator i = m_devices.begin(); + i != m_devices.end(); ++i) { + if ((*i)->getName() != "") { + showRenameOption = true; + } else { + (*i)->setName(qstrtostr(i18n("Device %1").arg(count))); + } + if (m_devices.size() > 1) { + m_deviceCombo->insertItem(strtoqstr((*i)->getName())); + } else { + m_deviceLabel->setText(strtoqstr((*i)->getName())); + } + ++count; + } + + QHBox *optionsBox = new QHBox(mainFrame); + + QGroupBox *gb = new QGroupBox(1, Horizontal, i18n("Options"), + optionsBox); + + m_importBanks = new QCheckBox(i18n("Import banks"), gb); + m_importKeyMappings = new QCheckBox(i18n("Import key mappings"), gb); + m_importControllers = new QCheckBox(i18n("Import controllers"), gb); + + if (showRenameOption) { + m_rename = new QCheckBox(i18n("Import device name"), gb); + } else { + m_rename = 0; + } + + m_buttonGroup = new QButtonGroup(1, Qt::Horizontal, + i18n("Bank import behavior"), + optionsBox); + m_mergeBanks = new QRadioButton(i18n("Merge banks"), m_buttonGroup); + m_overwriteBanks = new QRadioButton(i18n("Overwrite banks"), m_buttonGroup); + + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + + m_importBanks->setChecked(config->readBoolEntry("importbanks", true)); + m_importKeyMappings->setChecked(config->readBoolEntry("importkeymappings", true)); + m_importControllers->setChecked(config->readBoolEntry("importcontrollers", true)); + + bool rename = config->readBoolEntry("importbanksrename", true); + if (m_rename) + m_rename->setChecked(rename); + + bool overwrite = config->readBoolEntry("importbanksoverwrite", true); + if (overwrite) + m_buttonGroup->setButton(1); + else + m_buttonGroup->setButton(0); + + return true; +} + +void +ImportDeviceDialog::slotOk() +{ + int index = 0; + if (m_deviceCombo) + index = m_deviceCombo->currentItem(); + m_device = m_devices[index]; + + int v = m_buttonGroup->id(m_buttonGroup->selected()); + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + config->writeEntry("importbanksoverwrite", v == 1); + if (m_rename) + config->writeEntry("importbanksrename", m_rename->isChecked()); + accept(); +} + +void +ImportDeviceDialog::slotCancel() +{ + reject(); +} + +std::string ImportDeviceDialog::getDeviceName() const +{ + return m_device->getName(); +} + +const BankList& ImportDeviceDialog::getBanks() const +{ + return m_device->getBanks(); +} + +const ProgramList& ImportDeviceDialog::getPrograms() const +{ + return m_device->getPrograms(); +} + +const KeyMappingList& ImportDeviceDialog::getKeyMappings() const +{ + return m_device->getKeyMappings(); +} + +const ControlList& ImportDeviceDialog::getControllers() const +{ + return m_device->getControlParameters(); +} + +std::string ImportDeviceDialog::getLibrarianName() const +{ + return m_device->getLibrarianName(); +} + +std::string ImportDeviceDialog::getLibrarianEmail() const +{ + return m_device->getLibrarianEmail(); +} + +MidiDevice::VariationType +ImportDeviceDialog::getVariationType() const +{ + return m_device->getVariationType(); +} + +bool +ImportDeviceDialog::shouldImportBanks() const +{ + return m_importBanks->isChecked(); +} + +bool +ImportDeviceDialog::shouldImportKeyMappings() const +{ + return m_importKeyMappings->isChecked(); +} + +bool +ImportDeviceDialog::shouldImportControllers() const +{ + return m_importControllers->isChecked(); +} + +bool +ImportDeviceDialog::shouldOverwriteBanks() const +{ + return m_buttonGroup->id(m_buttonGroup->selected()) != 0; +} + +bool +ImportDeviceDialog::shouldRename() const +{ + return m_rename ? m_rename->isChecked() : false; +} + +bool +ImportDeviceDialog::importFromRG(QString fileName) +{ + m_fileDoc = new RosegardenGUIDoc(RosegardenGUIApp::self(), 0, true); // skipAutoload + + // Add some dummy devices for bank population when we open the document. + // We guess that the file won't have more than 32 devices. + // + // for (unsigned int i = 0; i < 32; i++) { + // m_fileDoc->getStudio().addDevice("", i, Device::Midi); + // } + + if (!m_fileDoc->openDocument(fileName, false)) { + return false; + } + + m_devices.clear(); + + DeviceList *list = m_fileDoc->getStudio().getDevices(); + if (list->size() == 0) { + return true; // true because we successfully read the document + } + + for (DeviceListIterator it = list->begin(); + it != list->end(); ++it) { + + MidiDevice *device = + dynamic_cast(*it); + + if (device) { + std::vector banks = + device->getBanks(); + + // DMM - check for controllers too, because some users have + // created .rgd files that contain only controllers + // see bug #1183522 + // + std::vector controllers = + device->getControlParameters(); + + // We've got a bank on a Device fom this file + // (or a device that contains controllers or key mappings) + // + if (banks.size() || + controllers.size() || + device->getKeyMappings().size()) + m_devices.push_back(device); + } + } + + return true; +} + +bool +ImportDeviceDialog::importFromSF2(QString filename) +{ + SF2PatchExtractor::Device sf2device; + try { + sf2device = SF2PatchExtractor::read(filename.data()); + + // These exceptions shouldn't happen -- the isSF2File call before this + // one should have weeded them out + } catch (SF2PatchExtractor::FileNotFoundException e) { + return false; + } catch (SF2PatchExtractor::WrongFileFormatException e) { + return false; + } + + std::vector banks; + std::vector programs; + + for (SF2PatchExtractor::Device::const_iterator i = sf2device.begin(); + i != sf2device.end(); ++i) { + + int bankNumber = i->first; + const SF2PatchExtractor::Bank &sf2bank = i->second; + + int msb = bankNumber / 128; + int lsb = bankNumber % 128; + + MidiBank bank + (msb == 1, msb, lsb, + qstrtostr(i18n("Bank %1:%2").arg(msb).arg(lsb))); + + banks.push_back(bank); + + for (SF2PatchExtractor::Bank::const_iterator j = sf2bank.begin(); + j != sf2bank.end(); ++j) { + + MidiProgram program(bank, j->first, j->second); + programs.push_back(program); + } + } + + MidiDevice *device = new MidiDevice + (0, "", MidiDevice::Play); + device->replaceBankList(banks); + device->replaceProgramList(programs); + m_devices.push_back(device); + + return true; +} + +} +#include "ImportDeviceDialog.moc" diff --git a/src/gui/dialogs/ImportDeviceDialog.h b/src/gui/dialogs/ImportDeviceDialog.h new file mode 100644 index 0000000..bb79e3b --- /dev/null +++ b/src/gui/dialogs/ImportDeviceDialog.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_IMPORTDEVICEDIALOG_H_ +#define _RG_IMPORTDEVICEDIALOG_H_ + +#include "base/MidiDevice.h" +#include +#include +#include +#include +#include + + +class QWidget; +class QRadioButton; +class QLabel; +class QCheckBox; +class QButtonGroup; +class ProgramList; +class KeyMappingList; +class KComboBox; +class ControlList; +class BankList; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +class ImportDeviceDialog : public KDialogBase +{ + Q_OBJECT + +public: + ImportDeviceDialog(QWidget *parent, KURL url); + virtual ~ImportDeviceDialog(); + + bool doImport(); + + bool shouldImportBanks() const; + bool shouldImportKeyMappings() const; + bool shouldImportControllers() const; + bool shouldOverwriteBanks() const; + bool shouldRename() const; + + std::string getDeviceName() const; + const BankList &getBanks() const; + const ProgramList &getPrograms() const; + const KeyMappingList &getKeyMappings() const; + const ControlList &getControllers() const; + std::string getLibrarianName() const; + std::string getLibrarianEmail() const; + MidiDevice::VariationType getVariationType() const; + +public slots: + void slotOk(); + void slotCancel(); + +protected: + bool importFromRG(QString fileName); + bool importFromSF2(QString fileName); + + KURL m_url; + + KComboBox *m_deviceCombo; + QLabel *m_deviceLabel; + + QCheckBox *m_importBanks; + QCheckBox *m_importKeyMappings; + QCheckBox *m_importControllers; + QCheckBox *m_rename; + + QButtonGroup *m_buttonGroup; + QRadioButton *m_mergeBanks; + QRadioButton *m_overwriteBanks; + + RosegardenGUIDoc *m_fileDoc; + std::vector m_devices; + MidiDevice *m_device; +}; + + +} + +#endif diff --git a/src/gui/dialogs/InterpretDialog.cpp b/src/gui/dialogs/InterpretDialog.cpp new file mode 100644 index 0000000..b11e3c4 --- /dev/null +++ b/src/gui/dialogs/InterpretDialog.cpp @@ -0,0 +1,123 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "InterpretDialog.h" +#include + +#include +#include "document/ConfigGroups.h" +#include "commands/notation/InterpretCommand.h" +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +InterpretDialog::InterpretDialog(QWidget *parent) : + KDialogBase(parent, 0, true, i18n("Interpret"), Ok | Cancel | Help) +{ + setHelp("nv-interpret"); + + QVBox *vbox = makeVBoxMainWidget(); + QGroupBox *groupBox = new QGroupBox + (1, Horizontal, i18n("Interpretations to apply"), vbox); + + m_applyTextDynamics = new QCheckBox + (i18n("Apply text dynamics (p, mf, ff etc)"), groupBox); + m_applyHairpins = new QCheckBox + (i18n("Apply hairpin dynamics"), groupBox); + m_stressBeats = new QCheckBox + (i18n("Stress beats"), groupBox); + m_articulate = new QCheckBox + (i18n("Articulate slurs, staccato, tenuto etc"), groupBox); + m_allInterpretations = new QCheckBox + (i18n("All available interpretations"), groupBox); + + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + + m_allInterpretations->setChecked + (config->readBoolEntry("interpretall", true)); + m_applyTextDynamics->setChecked + (config->readBoolEntry("interprettextdynamics", true)); + m_applyHairpins->setChecked + (config->readBoolEntry("interprethairpins", true)); + m_stressBeats->setChecked + (config->readBoolEntry("interpretstressbeats", true)); + m_articulate->setChecked + (config->readBoolEntry("interpretarticulate", true)); + + connect(m_allInterpretations, + SIGNAL(clicked()), this, SLOT(slotAllBoxChanged())); + + slotAllBoxChanged(); +} + +void +InterpretDialog::slotAllBoxChanged() +{ + bool all = m_allInterpretations->isChecked(); + m_applyTextDynamics->setEnabled(!all); + m_applyHairpins->setEnabled(!all); + m_stressBeats->setEnabled(!all); + m_articulate->setEnabled(!all); +} + +int +InterpretDialog::getInterpretations() +{ + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + + config->writeEntry("interpretall", m_allInterpretations->isChecked()); + config->writeEntry("interprettextdynamics", m_applyTextDynamics->isChecked()); + config->writeEntry("interprethairpins", m_applyHairpins->isChecked()); + config->writeEntry("interpretstressbeats", m_stressBeats->isChecked()); + config->writeEntry("interpretarticulate", m_articulate->isChecked()); + + if (m_allInterpretations->isChecked()) { + return InterpretCommand::AllInterpretations; + } else { + int in = 0; + if (m_applyTextDynamics->isChecked()) + in |= InterpretCommand::ApplyTextDynamics; + if (m_applyHairpins->isChecked()) + in |= InterpretCommand::ApplyHairpins; + if (m_stressBeats->isChecked()) + in |= InterpretCommand::StressBeats; + if (m_articulate->isChecked()) { + in |= InterpretCommand::Articulate; + } + return in; + } +} + +} +#include "InterpretDialog.moc" diff --git a/src/gui/dialogs/InterpretDialog.h b/src/gui/dialogs/InterpretDialog.h new file mode 100644 index 0000000..75c8694 --- /dev/null +++ b/src/gui/dialogs/InterpretDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_INTERPRETDIALOG_H_ +#define _RG_INTERPRETDIALOG_H_ + +#include + + +class QWidget; +class QCheckBox; + + +namespace Rosegarden +{ + + + +class InterpretDialog : public KDialogBase +{ + Q_OBJECT +public: + InterpretDialog(QWidget *parent); + + // an OR from InterpretCommand's constants + int getInterpretations(); + +protected slots: + void slotAllBoxChanged(); + +private: + QCheckBox *m_allInterpretations; + QCheckBox *m_applyTextDynamics; + QCheckBox *m_applyHairpins; + QCheckBox *m_stressBeats; + QCheckBox *m_articulate; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/IntervalDialog.cpp b/src/gui/dialogs/IntervalDialog.cpp new file mode 100644 index 0000000..061fc31 --- /dev/null +++ b/src/gui/dialogs/IntervalDialog.cpp @@ -0,0 +1,367 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "IntervalDialog.h" +#include + +#include +#include +#include "misc/Strings.h" +#include "base/MidiDevice.h" +#include "base/NotationRules.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +IntervalDialog::IntervalDialog(QWidget *parent, bool askChangeKey, bool askTransposeSegmentBack) : + KDialogBase(parent, 0, true, i18n("Specify Interval"), Ok | Cancel ) +{ + QVBox *vBox = makeVBoxMainWidget(); + + QHBox *hBox = new QHBox( vBox ); + + m_referencenote = new DiatonicPitchChooser( i18n("Reference note:"), hBox ); + m_targetnote = new DiatonicPitchChooser( i18n("Target note:"), hBox ); + + intervalChromatic = 0; + intervalDiatonic = 0; + + //m_intervalPitchLabel = new QLabel( i18n("Pitch: %1").arg(intervalChromatic), hBox); + //m_intervalOctavesLabel = new QLabel( i18n("Octaves: %1").arg(intervalDiatonic / 7), hBox); + //m_intervalStepsLabel = new QLabel( i18n("Steps: %1").arg(intervalDiatonic % 7), hBox); + + m_intervalLabel = new QLabel( i18n("a perfect unison"), vBox); + m_intervalLabel->setAlignment(Qt::AlignCenter); + QFont font(m_intervalLabel->font()); + font.setItalic(true); + m_intervalLabel->setFont(font); + + if (askChangeKey) + { + QButtonGroup *affectKeyGroup = new QButtonGroup(1, Horizontal, i18n("Effect on Key"), vBox); + m_transposeWithinKey = new QRadioButton(i18n("Transpose within key"), affectKeyGroup); + m_transposeWithinKey->setChecked(true); + m_transposeChangingKey = new QRadioButton(i18n("Change key for selection"), affectKeyGroup); + } + else + { + m_transposeChangingKey = NULL; + m_transposeWithinKey = NULL; + } + + if (askTransposeSegmentBack) + { + m_transposeSegmentBack = new QCheckBox( i18n("Adjust segment transposition in opposite direction (maintain audible pitch)"), vBox ); + m_transposeSegmentBack->setTristate(false); + m_transposeSegmentBack->setChecked(false); + } + else + { + m_transposeSegmentBack = NULL; + } + + connect(m_referencenote, SIGNAL(noteChanged(int,int,int)), + this, SLOT(slotSetReferenceNote(int,int,int))); + + connect(m_targetnote, SIGNAL(noteChanged(int,int,int)), + this, SLOT(slotSetTargetNote(int,int,int))); +} + +// number of octaves the notes are apart +int +IntervalDialog::getOctaveDistance() +{ + return m_targetnote->getOctave() - m_referencenote->getOctave(); +} + +// chromatic distance between the steps, not taking account octaves or +// accidentals +int +IntervalDialog::getStepDistanceChromatic() +{ + return scale_Cmajor[m_targetnote->getStep()] - scale_Cmajor[m_referencenote->getStep()]; + // - getChromaticStepValue(m_referencestep->currentItem()); + //return m_targetnote->getPitch() - m_referencenote->getPitch(); +} + +// correction due to accidentals +int +IntervalDialog::getAccidentalCorrectionChromatic() +{ + return m_targetnote->getAccidental() - m_referencenote->getAccidental(); +} + +int +IntervalDialog::getDiatonicDistance() +{ + return getOctaveDistance() * 7 + m_targetnote->getStep() - m_referencenote->getStep(); +} + +int +IntervalDialog::getChromaticDistance() +{ + return getOctaveDistance() * 12 + getStepDistanceChromatic() + getAccidentalCorrectionChromatic(); +} + +QString +IntervalDialog::getIntervalName(int intervalDiatonic, int intervalChromatic) +{ + // displayInterval: an intervalDiatonic of -3 will yield a displayInterval of 3 and + // set the boolean 'down' to true. + int displayIntervalDiatonic = intervalDiatonic; + int displayIntervalChromatic = intervalChromatic; + bool down = (intervalDiatonic < 0 || + (intervalDiatonic == 0 && + intervalChromatic < 0)); + if (down) + { + displayIntervalDiatonic = -displayIntervalDiatonic; + displayIntervalChromatic = -displayIntervalChromatic; + } + + int octaves = displayIntervalDiatonic / 7; + int deviation = displayIntervalChromatic % 12 - scale_Cmajor[displayIntervalDiatonic % 7]; + // Note (hjj): + // "1 octave and a diminished octave" is better than + // "2 octaves and a diminished unison" + if (displayIntervalDiatonic % 7 == 0) { + if (octaves > 0) { + deviation = (deviation < 5 ? deviation : deviation - 12); + } else if (octaves < 0) { + deviation = (deviation < 5 ? -deviation : 12 - deviation); + } + } else if (down) { + // Note (hjj): + // an augmented prime down, NOT a diminished prime down + deviation = -deviation; + } + + // show the step for an unison only if the octave doesn't change, any other interval + // always, and augmented/dimnished unisons (modulo octaves) always. + bool showStep = displayIntervalDiatonic == 0 || + displayIntervalDiatonic % 7 != 0 || deviation != 0; + + QString textInterval = ""; + QString textIntervalDeviated = ""; + if (showStep) + { + switch (displayIntervalDiatonic % 7) + { + // First the diminished/perfect/augmented: + case 0: // unison or octaves + case 3: // fourth + case 4: // fifth + if (deviation == -1) + textIntervalDeviated += i18n("a diminished"); + else if (deviation == 1) + textIntervalDeviated += i18n("an augmented"); + else if (deviation == -2) + textIntervalDeviated += i18n("a doubly diminished"); + else if (deviation == 2) + textIntervalDeviated += i18n("a doubly augmented"); + else if (deviation == -3) + textIntervalDeviated += i18n("a triply diminished"); + else if (deviation == 3) + textIntervalDeviated += i18n("a triply augmented"); + else if (deviation == -4) + textIntervalDeviated += i18n("a quadruply diminished"); + else if (deviation == 4) + textIntervalDeviated += i18n("a quadruply augmented"); + else if (deviation == 0) + textIntervalDeviated += i18n("a perfect"); + else + textIntervalDeviated += i18n("an (unknown, %1)").arg(deviation); + break; + // Then the major/minor: + case 1: // second + case 2: // third + case 5: // sixth + case 6: // seventh + if (deviation == -1) + textIntervalDeviated += i18n("a minor"); + else if (deviation == 0) + textIntervalDeviated += i18n("a major"); + else if (deviation == -2) + textIntervalDeviated += i18n("a diminished"); + else if (deviation == 1) + textIntervalDeviated += i18n("an augmented"); + else if (deviation == -3) + textIntervalDeviated += i18n("a doubly diminished"); + else if (deviation == 2) + textIntervalDeviated += i18n("a doubly augmented"); + else if (deviation == -4) + textIntervalDeviated += i18n("a triply diminished"); + else if (deviation == 3) + textIntervalDeviated += i18n("a triply augmented"); + else if (deviation == 4) + textIntervalDeviated += i18n("a quadruply augmented"); + else if (deviation == 0) + textIntervalDeviated += i18n("a perfect"); + else + textIntervalDeviated += i18n("an (unknown, %1)").arg(deviation); + break; + default: + textIntervalDeviated += i18n("an (unknown)"); + } + switch (displayIntervalDiatonic % 7) + { + case 0: + // Note (hjj): + // "1 octave and a diminished octave" is better than + // "2 octaves and a diminished unison" + if (octaves > 0) { + textInterval += i18n("%1 octave").arg(textIntervalDeviated); + octaves--; + } else if (octaves < 0) { + textInterval += i18n("%1 octave").arg(textIntervalDeviated); + octaves++; + } else { + textInterval += i18n("%1 unison").arg(textIntervalDeviated); + } + break; + case 1: + textInterval += i18n("%1 second").arg(textIntervalDeviated); + break; + case 2: + textInterval += i18n("%1 third").arg(textIntervalDeviated); + break; + case 3: + textInterval += i18n("%1 fourth").arg(textIntervalDeviated); + break; + case 4: + textInterval += i18n("%1 fifth").arg(textIntervalDeviated); + break; + case 5: + textInterval += i18n("%1 sixth").arg(textIntervalDeviated); + break; + case 6: + textInterval += i18n("%1 seventh").arg(textIntervalDeviated); + break; + default: + textInterval += i18n("%1").arg(textIntervalDeviated); + } + } + + if (displayIntervalChromatic != 0 || displayIntervalDiatonic != 0) + { + if (!down) + { + if (octaves != 0) { + if (showStep) { + return i18n("up 1 octave and %1", + "up %n octaves and %1", + octaves).arg(textInterval); + } else { + return i18n("up 1 octave", + "up %n octaves", + octaves); + } + } else { + return i18n("up %1").arg(textInterval); + } + } + else + { + if (octaves != 0) { + if (showStep) { + return i18n("down 1 octave and %1", + "down %n octaves and %1", + octaves).arg(textInterval); + } else { + return i18n("down 1 octave", + "down %n octaves", + octaves); + } + } else { + return i18n("down %1").arg(textInterval); + } + } + } else { + return i18n("a perfect unison"); + } +} + +void +IntervalDialog::slotSetTargetNote(int pitch, int octave, int step) +{ + intervalChromatic = pitch - m_referencenote->getPitch(); + intervalDiatonic = (octave * 7 + step) - (m_referencenote->getOctave() * 7 + m_referencenote->getStep()); + + m_intervalLabel->setText( getIntervalName( intervalDiatonic, intervalChromatic ) ); +} + +void +IntervalDialog::slotSetReferenceNote(int pitch, int octave, int step) +{ + // recalculate target note based on reference note and current interval + int pitch_new = pitch + intervalChromatic; + int diatonic_new = (octave * 7 + step) + intervalDiatonic; + int octave_new = diatonic_new / 7; + int step_new = diatonic_new % 7; + + m_targetnote->slotSetNote( pitch_new, octave_new, step_new ); +} + +bool +IntervalDialog::getChangeKey() +{ + if (m_transposeChangingKey == NULL) + { + return false; + } + else + { + return m_transposeChangingKey->isChecked(); + } +} + +bool +IntervalDialog::getTransposeSegmentBack() +{ + if (m_transposeSegmentBack == NULL) + { + return false; + } + else + { + return m_transposeSegmentBack->isChecked(); + } +} + +} +#include "IntervalDialog.moc" diff --git a/src/gui/dialogs/IntervalDialog.h b/src/gui/dialogs/IntervalDialog.h new file mode 100644 index 0000000..b9927d2 --- /dev/null +++ b/src/gui/dialogs/IntervalDialog.h @@ -0,0 +1,94 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_INTERVALDIALOG_H_ +#define _RG_INTERVALDIALOG_H_ + +#include +#include +#include "gui/application/RosegardenDCOP.h" +#include "gui/widgets/DiatonicPitchChooser.h" + + +class QWidget; +class KComboBox; +class QRadioButton; +class QCheckBox; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +class IntervalDialog : public KDialogBase +{ + Q_OBJECT +public: + IntervalDialog(QWidget *parent, bool askChangeKey = false, bool askTransposeSegmentBack = false); + + // Distance in semitones + int getChromaticDistance(); + + // Distance in steps + int getDiatonicDistance(); + + // Transpose within key or change the key? + bool getChangeKey(); + + // Transpose the segment itself in the opposite direction? + bool getTransposeSegmentBack(); + + static QString getIntervalName(int intervalDiatonic, int intervalChromatic); + +public slots: + void slotSetReferenceNote(int,int,int); + void slotSetTargetNote(int,int,int); + +private: + int getOctaveDistance(); + int getStepDistanceChromatic(); + int getAccidentalCorrectionChromatic(); + + DiatonicPitchChooser *m_referencenote; + DiatonicPitchChooser *m_targetnote; + + QRadioButton *m_transposeWithinKey; + QRadioButton *m_transposeChangingKey; + bool changeKey; + + QCheckBox *m_transposeSegmentBack; + + int intervalChromatic; + int intervalDiatonic; + QLabel *m_intervalLabel; + +}; + + +} + +#endif diff --git a/src/gui/dialogs/KeySignatureDialog.cpp b/src/gui/dialogs/KeySignatureDialog.cpp new file mode 100644 index 0000000..c703c0a --- /dev/null +++ b/src/gui/dialogs/KeySignatureDialog.cpp @@ -0,0 +1,402 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "KeySignatureDialog.h" + +#include +#include "misc/Strings.h" +#include "base/NotationTypes.h" +#include "gui/editors/notation/NotePixmapFactory.h" +#include "gui/widgets/BigArrowButton.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Rosegarden +{ + +KeySignatureDialog::KeySignatureDialog(QWidget *parent, + NotePixmapFactory *npf, + Clef clef, + Rosegarden::Key defaultKey, + bool showApplyToAll, + bool showConversionOptions, + QString explanatoryText) : + KDialogBase(parent, 0, true, i18n("Key Change"), Ok | Cancel | Help), + m_notePixmapFactory(npf), + m_key(defaultKey), + m_clef(clef), + m_valid(true), + m_ignoreComboChanges(false), + m_explanatoryLabel(0), + m_applyToAllButton(0), + m_noPercussionCheckBox(0) +{ + setHelp("nv-signatures-key"); + + QVBox *vbox = makeVBoxMainWidget(); + + QHBox *keyBox = 0; + QHBox *nameBox = 0; + + QGroupBox *keyFrame = new QGroupBox + (1, Horizontal, i18n("Key signature"), vbox); + + QGroupBox *transposeFrame = new QButtonGroup + (1, Horizontal, i18n("Key transposition"), vbox); + + QGroupBox *buttonFrame = new QButtonGroup + (1, Horizontal, i18n("Scope"), vbox); + + QButtonGroup *conversionFrame = new QButtonGroup + (1, Horizontal, i18n("Existing notes following key change"), vbox); + + keyBox = new QHBox(keyFrame); + nameBox = new QHBox(keyFrame); + + QLabel *explanatoryLabel = 0; + if (explanatoryText) { + explanatoryLabel = new QLabel(explanatoryText, keyFrame); + } + + BigArrowButton *keyDown = new BigArrowButton(keyBox, Qt::LeftArrow); + QToolTip::add + (keyDown, i18n("Flatten")); + + m_keyLabel = new QLabel(i18n("Key"), keyBox); + m_keyLabel->setAlignment(AlignVCenter | AlignHCenter); + + BigArrowButton *keyUp = new BigArrowButton(keyBox, Qt::RightArrow); + QToolTip::add + (keyUp, i18n("Sharpen")); + + m_keyCombo = new KComboBox(nameBox); + m_majorMinorCombo = new KComboBox(nameBox); + m_majorMinorCombo->insertItem(i18n("Major")); + m_majorMinorCombo->insertItem(i18n("Minor")); + if (m_key.isMinor()) { + m_majorMinorCombo->setCurrentItem(m_majorMinorCombo->count() - 1); + } + + regenerateKeyCombo(); + redrawKeyPixmap(); + m_explanatoryLabel = explanatoryLabel; + + m_keyLabel->setMinimumWidth(m_keyLabel->pixmap()->width()); + m_keyLabel->setMinimumHeight(m_keyLabel->pixmap()->height()); + + m_yesTransposeButton = + new QRadioButton(i18n("Transpose key according to segment transposition"), + transposeFrame); + QRadioButton *noTransposeButton = + new QRadioButton(i18n("Use specified key. Do not transpose"), transposeFrame); + m_yesTransposeButton->setChecked(true); + + // just to shut up the compiler warning about unused variable: + noTransposeButton->setChecked(false); + + if (showApplyToAll) { + QRadioButton *applyToOneButton = + new QRadioButton(i18n("Apply to current segment only"), + buttonFrame); + m_applyToAllButton = + new QRadioButton(i18n("Apply to all segments at this time"), + buttonFrame); + applyToOneButton->setChecked(true); + m_noPercussionCheckBox = + new QCheckBox(i18n("Exclude percussion segments"), buttonFrame); + m_noPercussionCheckBox->setChecked(true); + + } else { + m_applyToAllButton = 0; + buttonFrame->hide(); + } + + if (showConversionOptions) { + m_noConversionButton = + new QRadioButton + (i18n("Maintain current pitches"), conversionFrame); + m_convertButton = + new QRadioButton + (i18n("Maintain current accidentals"), conversionFrame); + m_transposeButton = + new QRadioButton + (i18n("Transpose into this key"), conversionFrame); + m_noConversionButton->setChecked(true); + } else { + m_noConversionButton = 0; + m_convertButton = 0; + m_transposeButton = 0; + conversionFrame->hide(); + } + + QObject::connect(keyUp, SIGNAL(clicked()), this, SLOT(slotKeyUp())); + QObject::connect(keyDown, SIGNAL(clicked()), this, SLOT(slotKeyDown())); + QObject::connect(m_keyCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotKeyNameChanged(const QString &))); + QObject::connect(m_keyCombo, SIGNAL(textChanged(const QString &)), + this, SLOT(slotKeyNameChanged(const QString &))); + QObject::connect(m_majorMinorCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotMajorMinorChanged(const QString &))); +} + +KeySignatureDialog::ConversionType + +KeySignatureDialog::getConversionType() const +{ + if (m_noConversionButton && m_noConversionButton->isChecked()) { + return NoConversion; + } else if (m_convertButton && m_convertButton->isChecked()) { + return Convert; + } else if (m_transposeButton && m_transposeButton->isChecked()) { + return Transpose; + } + return NoConversion; +} + +bool +KeySignatureDialog::shouldApplyToAll() const +{ + return m_applyToAllButton && m_applyToAllButton->isChecked(); +} + +bool +KeySignatureDialog::shouldBeTransposed() const +{ + return m_yesTransposeButton && m_yesTransposeButton->isChecked(); +} + +bool +KeySignatureDialog::shouldIgnorePercussion() const +{ + return m_noPercussionCheckBox && m_noPercussionCheckBox->isChecked(); +} + +void +KeySignatureDialog::slotKeyUp() +{ + bool sharp = m_key.isSharp(); + int ac = m_key.getAccidentalCount(); + if (ac == 0) + sharp = true; + if (sharp) { + if (++ac > 7) + ac = 7; + } else { + if (--ac < 1) { + ac = 0; + sharp = true; + } + } + + try { + m_key = Rosegarden::Key(ac, sharp, m_key.isMinor()); + setValid(true); + } catch (Rosegarden::Key::BadKeySpec s) { + std::cerr << s.getMessage() << std::endl; + setValid(false); + } + + regenerateKeyCombo(); + redrawKeyPixmap(); +} + +void +KeySignatureDialog::slotKeyDown() +{ + bool sharp = m_key.isSharp(); + int ac = m_key.getAccidentalCount(); + if (ac == 0) + sharp = false; + if (sharp) { + if (--ac < 0) { + ac = 1; + sharp = false; + } + } else { + if (++ac > 7) + ac = 7; + } + + try { + m_key = Rosegarden::Key(ac, sharp, m_key.isMinor()); + setValid(true); + } catch (Rosegarden::Key::BadKeySpec s) { + std::cerr << s.getMessage() << std::endl; + setValid(false); + } + + regenerateKeyCombo(); + redrawKeyPixmap(); +} + +struct KeyNameComparator +{ + bool operator()(const Rosegarden::Key &k1, const Rosegarden::Key &k2) { + return (k1.getName() < k2.getName()); + } +}; + + +void +KeySignatureDialog::regenerateKeyCombo() +{ + if (m_explanatoryLabel) + m_explanatoryLabel->hide(); + + m_ignoreComboChanges = true; + QString currentText = m_keyCombo->currentText(); + Rosegarden::Key::KeyList keys(Rosegarden::Key::getKeys(m_key.isMinor())); + m_keyCombo->clear(); + + std::sort(keys.begin(), keys.end(), KeyNameComparator()); + bool textSet = false; + + for (Rosegarden::Key::KeyList::iterator i = keys.begin(); + i != keys.end(); ++i) { + + QString name(strtoqstr(i->getName())); + int space = name.find(' '); + if (space > 0) + name = name.left(space); + + m_keyCombo->insertItem(name); + + if (m_valid && (*i == m_key)) { + m_keyCombo->setCurrentItem(m_keyCombo->count() - 1); + textSet = true; + } + } + + if (!textSet) { + m_keyCombo->setEditText(currentText); + } + m_ignoreComboChanges = false; +} + +bool +KeySignatureDialog::isValid() const +{ + return m_valid; +} + +Rosegarden::Key +KeySignatureDialog::getKey() const +{ + return m_key; +} + +void +KeySignatureDialog::redrawKeyPixmap() +{ + if (m_valid) { + QPixmap pmap = + NotePixmapFactory::toQPixmap(m_notePixmapFactory->makeKeyDisplayPixmap(m_key, m_clef)); + m_keyLabel->setPixmap(pmap); + } else { + m_keyLabel->setText(i18n("No such key")); + } +} + +void +KeySignatureDialog::slotKeyNameChanged(const QString &s) +{ + if (m_ignoreComboChanges) + return ; + + if (m_explanatoryLabel) + m_explanatoryLabel->hide(); + + std::string name(getKeyName(s, m_key.isMinor())); + + try { + m_key = Rosegarden::Key(name); + setValid(true); + + int space = name.find(' '); + if (space > 0) + name = name.substr(0, space); + m_keyCombo->setEditText(strtoqstr(name)); + + } catch (Rosegarden::Key::BadKeyName s) { + std::cerr << s.getMessage() << std::endl; + setValid(false); + } + + redrawKeyPixmap(); +} + +void +KeySignatureDialog::slotMajorMinorChanged(const QString &s) +{ + if (m_ignoreComboChanges) + return ; + + std::string name(getKeyName(m_keyCombo->currentText(), s == i18n("Minor"))); + + try { + m_key = Rosegarden::Key(name); + setValid(true); + } catch (Rosegarden::Key::BadKeyName s) { + std::cerr << s.getMessage() << std::endl; + setValid(false); + } + + regenerateKeyCombo(); + redrawKeyPixmap(); +} + +void +KeySignatureDialog::setValid(bool valid) +{ + m_valid = valid; + enableButton(Ok, m_valid); +} + +std::string +KeySignatureDialog::getKeyName(const QString &s, bool minor) +{ + QString u((s.length() >= 1) ? (s.left(1).upper() + s.right(s.length() - 1)) + : s); + + std::string name(qstrtostr(u)); + name = name + " " + (minor ? "minor" : "major"); + return name; +} + +} +#include "KeySignatureDialog.moc" diff --git a/src/gui/dialogs/KeySignatureDialog.h b/src/gui/dialogs/KeySignatureDialog.h new file mode 100644 index 0000000..cd4a340 --- /dev/null +++ b/src/gui/dialogs/KeySignatureDialog.h @@ -0,0 +1,118 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_KEYSIGNATUREDIALOG_H_ +#define _RG_KEYSIGNATUREDIALOG_H_ + +#include "base/NotationTypes.h" +#include +#include +#include +#include + + +class QWidget; +class QRadioButton; +class QLabel; +class KComboBox; +class QCheckBox; + + +namespace Rosegarden +{ + +class NotePixmapFactory; + + +class KeySignatureDialog : public KDialogBase +{ + Q_OBJECT + +public: + enum ConversionType { + NoConversion, + Convert, + Transpose + }; + + KeySignatureDialog(QWidget *parent, + NotePixmapFactory *npf, + Clef clef, + Rosegarden::Key defaultKey = + Rosegarden::Key::DefaultKey, + bool showApplyToAll = true, + bool showConversionOptions = true, + QString explanatoryText = 0); + + bool isValid() const; + ::Rosegarden::Key getKey() const; + + bool shouldApplyToAll() const; + bool shouldBeTransposed() const; + ConversionType getConversionType() const; + bool shouldIgnorePercussion() const; + +public slots: + void slotKeyUp(); + void slotKeyDown(); + void slotKeyNameChanged(const QString &); + void slotMajorMinorChanged(const QString &); + +protected: + + void redrawKeyPixmap(); + void regenerateKeyCombo(); + void setValid(bool valid); + std::string getKeyName(const QString &s, bool minor); + + //--------------- Data members --------------------------------- + + NotePixmapFactory *m_notePixmapFactory; + + Rosegarden::Key m_key; + Clef m_clef; + bool m_valid; + bool m_ignoreComboChanges; + + QLabel *m_keyLabel; + KComboBox *m_keyCombo; + KComboBox *m_majorMinorCombo; + QLabel *m_explanatoryLabel; + + QRadioButton *m_applyToAllButton; + QRadioButton *m_yesTransposeButton; + + QRadioButton *m_noConversionButton; + QRadioButton *m_convertButton; + QRadioButton *m_transposeButton; + + QCheckBox *m_noPercussionCheckBox; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/LilyPondOptionsDialog.cpp b/src/gui/dialogs/LilyPondOptionsDialog.cpp new file mode 100644 index 0000000..f693467 --- /dev/null +++ b/src/gui/dialogs/LilyPondOptionsDialog.cpp @@ -0,0 +1,363 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "LilyPondOptionsDialog.h" +#include "document/io/LilyPondExporter.h" +#include "gui/configuration/HeadersConfigurationPage.h" + +#include +#include + +#include "document/ConfigGroups.h" +#include "document/RosegardenGUIDoc.h" +#include "misc/Strings.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Rosegarden +{ + +LilyPondOptionsDialog::LilyPondOptionsDialog(QWidget *parent, + RosegardenGUIDoc *doc, + QString windowCaption, + QString heading) : + KDialogBase(parent, 0, true, + (windowCaption = "" ? i18n("LilyPond Export/Preview") : windowCaption), + Apply | Ok | Cancel), + m_doc(doc) +{ + setHelp("file-printing"); + + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + + QVBox * mainbox = makeVBoxMainWidget(); + + // + // Arrange options in "General" and "Advanced" tabs. + // + + QTabWidget * tabWidget = new QTabWidget(mainbox); + + QFrame *generalFrame; + QFrame *advancedFrame; + QGridLayout *generalGrid; + QGridLayout *advancedGrid; + + generalFrame = new QFrame(); + tabWidget->addTab(generalFrame, i18n("General options")); + + generalGrid = new QGridLayout(generalFrame, 1, 1, 5, 5); + + advancedFrame = new QFrame(); + tabWidget->addTab(advancedFrame, i18n("Advanced options")); + + advancedGrid = new QGridLayout(advancedFrame, 1, 1, 5, 5); + + m_headersPage = new HeadersConfigurationPage(this, m_doc); + tabWidget->addTab(m_headersPage, i18n("Headers")); + + m_headersPage->setSpacing(5); + m_headersPage->setMargin(5); + + // + // LilyPond export: Basic options + // + + QGroupBox *basicOptionsBox = new QGroupBox + (1, Horizontal, + i18n("Basic options"), generalFrame); + generalGrid->addWidget(basicOptionsBox, 0, 0); + + QFrame *frameBasic = new QFrame(basicOptionsBox); + QGridLayout *layoutBasic = new QGridLayout(frameBasic, 3, 2, 10, 5); + + layoutBasic->addWidget(new QLabel( + i18n("Compatibility level"), frameBasic), 0, 0); + + m_lilyLanguage = new KComboBox(frameBasic); + // See also setDefaultLilyPondVersion below + m_lilyLanguage->insertItem(i18n("LilyPond %1").arg("2.6")); + m_lilyLanguage->insertItem(i18n("LilyPond %1").arg("2.8")); + m_lilyLanguage->insertItem(i18n("LilyPond %1").arg("2.10")); + m_lilyLanguage->insertItem(i18n("LilyPond %1").arg("2.12")); + m_lilyLanguage->setCurrentItem(config->readUnsignedNumEntry("lilylanguage", 0)); + layoutBasic->addWidget(m_lilyLanguage, 0, 1); + + layoutBasic->addWidget(new QLabel( + i18n("Paper size"), frameBasic), 1, 0); + + QHBoxLayout *hboxPaper = new QHBoxLayout( frameBasic ); + m_lilyPaperSize = new KComboBox(frameBasic); + m_lilyPaperSize->insertItem(i18n("A3")); + m_lilyPaperSize->insertItem(i18n("A4")); + m_lilyPaperSize->insertItem(i18n("A5")); + m_lilyPaperSize->insertItem(i18n("A6")); + m_lilyPaperSize->insertItem(i18n("Legal")); + m_lilyPaperSize->insertItem(i18n("US Letter")); + m_lilyPaperSize->insertItem(i18n("Tabloid")); + m_lilyPaperSize->insertItem(i18n("do not specify")); + int defaultPaperSize = 1; // A4 + if (KGlobal::locale()->country() == "us" || + KGlobal::locale()->country() == "US") defaultPaperSize = 5; // Letter + m_lilyPaperSize->setCurrentItem(config->readUnsignedNumEntry + ("lilypapersize", defaultPaperSize)); + + m_lilyPaperLandscape = new QCheckBox(i18n("Landscape"), frameBasic); + m_lilyPaperLandscape->setChecked(config->readBoolEntry("lilypaperlandscape", false)); + + hboxPaper->addWidget( m_lilyPaperSize ); + hboxPaper->addWidget( new QLabel( " ", frameBasic ) ); // fixed-size spacer + hboxPaper->addWidget( m_lilyPaperLandscape ); + layoutBasic->addLayout(hboxPaper, 1, 1); + + layoutBasic->addWidget(new QLabel( + i18n("Font size"), frameBasic), 2, 0); + + m_lilyFontSize = new KComboBox(frameBasic); + int sizes[] = { 11, 13, 16, 19, 20, 23, 26 }; + for (int i = 0; i < sizeof(sizes)/sizeof(sizes[0]); ++i) { + m_lilyFontSize->insertItem(i18n("%1 pt").arg(sizes[i])); + } + m_lilyFontSize->setCurrentItem(config->readUnsignedNumEntry + ("lilyfontsize", 4)); + layoutBasic->addWidget(m_lilyFontSize, 2, 1); + + // + // LilyPond export: Staff level options + // + + QGroupBox *staffOptionsBox = new QGroupBox + (1, Horizontal, + i18n("Staff level options"), generalFrame); + generalGrid->addWidget(staffOptionsBox, 1, 0); + + QFrame *frameStaff = new QFrame(staffOptionsBox); + QGridLayout *layoutStaff = new QGridLayout(frameStaff, 2, 2, 10, 5); + + layoutStaff->addWidget(new QLabel( + i18n("Export content"), frameStaff), 0, 0); + + m_lilyExportSelection = new KComboBox(frameStaff); + m_lilyExportSelection->insertItem(i18n("All tracks")); + m_lilyExportSelection->insertItem(i18n("Non-muted tracks")); + m_lilyExportSelection->insertItem(i18n("Selected track")); + m_lilyExportSelection->insertItem(i18n("Selected segments")); + m_lilyExportSelection->setCurrentItem(config->readUnsignedNumEntry("lilyexportselection", 1)); + + layoutStaff->addWidget(m_lilyExportSelection, 0, 1); + + m_lilyExportStaffMerge = new QCheckBox( + i18n("Merge tracks that have the same name"), frameStaff); + m_lilyExportStaffMerge->setChecked(config->readBoolEntry("lilyexportstaffmerge", false)); + layoutStaff->addMultiCellWidget(m_lilyExportStaffMerge, 1, 1, 0, 1); + + // + // LilyPond export: Notation options + // + + QGroupBox *notationOptionsBox = new QGroupBox + (1, Horizontal, + i18n("Notation options"), generalFrame); + generalGrid->addWidget(notationOptionsBox, 2, 0); + + QFrame *frameNotation = new QFrame(notationOptionsBox); + QGridLayout *layoutNotation = new QGridLayout(frameNotation, 4, 2, 10, 5); + + m_lilyTempoMarks = new KComboBox( frameNotation ); + m_lilyTempoMarks->insertItem(i18n("None")); + m_lilyTempoMarks->insertItem(i18n("First")); + m_lilyTempoMarks->insertItem(i18n("All")); + m_lilyTempoMarks->setCurrentItem(config->readUnsignedNumEntry("lilyexporttempomarks", 0)); + + layoutNotation->addWidget( new QLabel( + i18n("Export tempo marks "), frameNotation), 0, 0 ); + layoutNotation->addWidget(m_lilyTempoMarks, 0, 1); + + m_lilyExportLyrics = new QCheckBox( + i18n("Export lyrics"), frameNotation); + // default to lyric export == false because if you export the default + // empty "- - -" lyrics, crap results ensue, and people will know if they + // do need to export the lyrics - DMM + // fixed, no "- - -" lyrics are generated for an empty lyrics + // default again into lyrics - HJJ + m_lilyExportLyrics->setChecked(config->readBoolEntry("lilyexportlyrics", true)); + layoutNotation->addMultiCellWidget(m_lilyExportLyrics, 1, 1, 0, 1); + + m_lilyExportBeams = new QCheckBox( + i18n("Export beamings"), frameNotation); + m_lilyExportBeams->setChecked(config->readBoolEntry("lilyexportbeamings", false)); + layoutNotation->addMultiCellWidget(m_lilyExportBeams, 2, 2, 0, 1); + + // recycle this for a new option to ignore the track brackets (so it is less + // obnoxious to print single parts where brackets are in place) + m_lilyExportStaffGroup = new QCheckBox( + i18n("Export track staff brackets"), frameNotation); + m_lilyExportStaffGroup->setChecked(config->readBoolEntry("lilyexportstaffbrackets", true)); + layoutNotation->addMultiCellWidget(m_lilyExportStaffGroup, 3, 3, 0, 1); + + generalGrid->setRowStretch(4, 10); + + // + // LilyPond export: Advanced options + // + + QGroupBox *advancedLayoutOptionsBox = new QGroupBox + (1, Horizontal, + i18n("Layout options"), advancedFrame); + advancedGrid->addWidget(advancedLayoutOptionsBox, 0, 0); + + QFrame *frameAdvancedLayout = new QFrame(advancedLayoutOptionsBox); + QGridLayout *layoutAdvancedLayout = new QGridLayout(frameAdvancedLayout, 2, 2, 10, 5); + + m_lilyLyricsHAlignment = new KComboBox( frameAdvancedLayout ); + m_lilyLyricsHAlignment->insertItem(i18n("Left")); + m_lilyLyricsHAlignment->insertItem(i18n("Center")); + m_lilyLyricsHAlignment->insertItem(i18n("Right")); + m_lilyLyricsHAlignment->setCurrentItem(config->readUnsignedNumEntry("lilylyricshalignment", 0)); + + layoutAdvancedLayout->addWidget(new QLabel( + i18n("Lyrics alignment"), frameAdvancedLayout), 0, 0); + layoutAdvancedLayout->addWidget(m_lilyLyricsHAlignment, 0, 1); + + m_lilyRaggedBottom = new QCheckBox( + i18n("Ragged bottom (systems will not be spread vertically across the page)"), frameAdvancedLayout); + m_lilyRaggedBottom->setChecked(config->readBoolEntry("lilyraggedbottom", false)); + layoutAdvancedLayout->addMultiCellWidget(m_lilyRaggedBottom, 1, 2, 0, 1); + + QGroupBox *miscOptionsBox = new QGroupBox + (1, Horizontal, + i18n("Miscellaneous options"), advancedFrame); + advancedGrid->addWidget(miscOptionsBox, 1, 0); + + QFrame *frameMisc = new QFrame(miscOptionsBox); + QGridLayout *layoutMisc = new QGridLayout(frameMisc, 2, 2, 10, 5); + + m_lilyExportPointAndClick = new QCheckBox( + i18n("Enable \"point and click\" debugging"), frameMisc); + m_lilyExportPointAndClick->setChecked(config->readBoolEntry("lilyexportpointandclick", false)); + layoutMisc->addMultiCellWidget(m_lilyExportPointAndClick, 0, 0, 0, 1); + + m_lilyExportMidi = new QCheckBox( + i18n("Export \\midi block"), frameMisc); + m_lilyExportMidi->setChecked(config->readBoolEntry("lilyexportmidi", false)); + layoutMisc->addMultiCellWidget(m_lilyExportMidi, 1, 1, 0, 1); + + m_lilyMarkerMode = new KComboBox(frameMisc); + m_lilyMarkerMode->insertItem(i18n("No markers")); + m_lilyMarkerMode->insertItem(i18n("Rehearsal marks")); + m_lilyMarkerMode->insertItem(i18n("Marker text")); + m_lilyMarkerMode->setCurrentItem(config->readUnsignedNumEntry("lilyexportmarkermode", 0)); + + layoutMisc->addWidget( new QLabel( + i18n("Export markers"), frameMisc),2, 0 ); + layoutMisc->addWidget(m_lilyMarkerMode, 2, 1); + + advancedGrid->setRowStretch(2, 10); + + resize(minimumSize()); +} + +void +LilyPondOptionsDialog::slotApply() +{ + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + + config->writeEntry("lilylanguage", m_lilyLanguage->currentItem()); + config->writeEntry("lilypapersize", m_lilyPaperSize->currentItem()); + config->writeEntry("lilypaperlandscape", m_lilyPaperLandscape->isChecked()); + config->writeEntry("lilyfontsize", m_lilyFontSize->currentItem()); + config->writeEntry("lilyraggedbottom", m_lilyRaggedBottom->isChecked()); + config->writeEntry("lilyexportlyrics", m_lilyExportLyrics->isChecked()); + config->writeEntry("lilyexportmidi", m_lilyExportMidi->isChecked()); + config->writeEntry("lilyexporttempomarks", m_lilyTempoMarks->currentItem()); + config->writeEntry("lilyexportselection", m_lilyExportSelection->currentItem()); + config->writeEntry("lilyexportpointandclick", m_lilyExportPointAndClick->isChecked()); + config->writeEntry("lilyexportbeamings", m_lilyExportBeams->isChecked()); + config->writeEntry("lilyexportstaffmerge", m_lilyExportStaffMerge->isChecked()); + config->writeEntry("lilyexportstaffbrackets", m_lilyExportStaffGroup->isChecked()); + config->writeEntry("lilylyricshalignment", m_lilyLyricsHAlignment->currentItem()); + config->writeEntry("lilyexportmarkermode", m_lilyMarkerMode->currentItem()); + m_headersPage->apply(); +} + +void +LilyPondOptionsDialog::slotOk() +{ + slotApply(); + accept(); +} + +void +LilyPondOptionsDialog::setDefaultLilyPondVersion(QString version) +{ + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + int index = -1; + bool unstable = false; + if (version == "2.6" || version.startsWith("2.6.")) { + index = 0; + } else if (version == "2.7" || version.startsWith("2.7.")) { + unstable = true; + index = 1; + } else if (version == "2.8" || version.startsWith("2.8.")) { + index = 1; + } else if (version == "2.9" || version.startsWith("2.9.")) { + unstable = true; + index = 2; + } else if (version == "2.10" || version.startsWith("2.10.")) { + index = 2; + } else if (version == "2.11" || version.startsWith("2.11.")) { + unstable = true; + index = 3; + } else if (version == "2.12" || version.startsWith("2.12.")) { + index = 3; + } + if (unstable) { + std::cerr << "\nWARNING: Unstable LilyPond version detected, selecting next language version up\n" << std::endl; + } + if (index >= 0) { + config->writeEntry("lilylanguage", index); + } +} + +} +#include "LilyPondOptionsDialog.moc" diff --git a/src/gui/dialogs/LilyPondOptionsDialog.h b/src/gui/dialogs/LilyPondOptionsDialog.h new file mode 100644 index 0000000..a8f2476 --- /dev/null +++ b/src/gui/dialogs/LilyPondOptionsDialog.h @@ -0,0 +1,86 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_LILYPONDOPTIONSDIALOG_H_ +#define _RG_LILYPONDOPTIONSDIALOG_H_ + +#include +#include + +#include "gui/configuration/HeadersConfigurationPage.h" + +class QWidget; +class QCheckBox; +class QComboBox; +class QLineEdit; +class QLineEdit; + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class HeadersConfigurationPage; + +class LilyPondOptionsDialog : public KDialogBase +{ + Q_OBJECT + +public: + LilyPondOptionsDialog(QWidget *parent, + RosegardenGUIDoc *doc, + QString windowCaption = "", + QString heading = ""); + + static void setDefaultLilyPondVersion(QString version); + +public slots: + void slotApply(); + void slotOk(); + +protected: + RosegardenGUIDoc *m_doc; + QComboBox *m_lilyLanguage; + QComboBox *m_lilyPaperSize; + QComboBox *m_lilyFontSize; + QComboBox *m_lilyTempoMarks; + QComboBox *m_lilyExportSelection; + QComboBox *m_lilyLyricsHAlignment; + QCheckBox *m_lilyPaperLandscape; + QCheckBox *m_lilyRaggedBottom; + QCheckBox *m_lilyExportLyrics; + QCheckBox *m_lilyExportMidi; + QCheckBox *m_lilyExportPointAndClick; + QCheckBox *m_lilyExportBeams; + QCheckBox *m_lilyExportStaffMerge; + QCheckBox *m_lilyExportStaffGroup; + QComboBox *m_lilyMarkerMode; + HeadersConfigurationPage *m_headersPage; + +}; + + + +} + +#endif diff --git a/src/gui/dialogs/LyricEditDialog.cpp b/src/gui/dialogs/LyricEditDialog.cpp new file mode 100644 index 0000000..4dfeba2 --- /dev/null +++ b/src/gui/dialogs/LyricEditDialog.cpp @@ -0,0 +1,253 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "LyricEditDialog.h" + +#include "base/Event.h" +#include "base/BaseProperties.h" +#include +#include "misc/Strings.h" +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +LyricEditDialog::LyricEditDialog(QWidget *parent, + Segment *segment) : + KDialogBase(parent, 0, true, i18n("Edit Lyrics"), Ok | Cancel | Help), + m_segment(segment), + m_verseCount(0) +{ + setHelp("nv-text-lyrics"); + + QVBox *vbox = makeVBoxMainWidget(); + + QGroupBox *groupBox = new QGroupBox + (1, Horizontal, i18n("Lyrics for this segment"), vbox); + + QHBox *hbox = new QHBox(groupBox); + hbox->setSpacing(5); +// new QLabel(i18n("Verse:"), hbox); + m_verseNumber = new KComboBox(hbox); + m_verseNumber->setEditable(false); + connect(m_verseNumber, SIGNAL(activated(int)), this, SLOT(slotVerseNumberChanged(int))); + m_verseAddButton = new QPushButton(i18n("Add Verse"), hbox); + connect(m_verseAddButton, SIGNAL(clicked()), this, SLOT(slotAddVerse())); + QFrame *f = new QFrame(hbox); + hbox->setStretchFactor(f, 10); + + m_textEdit = new QTextEdit(groupBox); + m_textEdit->setTextFormat(Qt::PlainText); + + m_textEdit->setMinimumWidth(300); + m_textEdit->setMinimumHeight(200); + + unparse(); + + for (int i = 0; i < m_verseCount; ++i) { + m_verseNumber->insertItem(i18n("Verse %1").arg(i + 1)); + } + m_currentVerse = 0; + if (m_verseCount == 12) m_verseAddButton->setEnabled(false); +} + +void +LyricEditDialog::slotVerseNumberChanged(int verse) +{ + NOTATION_DEBUG << "LyricEditDialog::slotVerseNumberChanged(" << verse << ")" << endl; + QString text = m_textEdit->text(); + m_texts[m_currentVerse] = text; + m_textEdit->setText(m_texts[verse]); + m_currentVerse = verse; +} + +void +LyricEditDialog::slotAddVerse() +{ + NOTATION_DEBUG << "LyricEditDialog::slotAddVerse" << endl; + m_verseCount++; + m_texts.push_back(m_skeleton); + m_verseNumber->insertItem(i18n("Verse %1").arg(m_verseCount)); + m_verseNumber->setCurrentItem(m_verseCount - 1); + slotVerseNumberChanged(m_verseCount - 1); + if (m_verseCount == 12) m_verseAddButton->setEnabled(false); +} + +void +LyricEditDialog::countVerses() +{ + m_verseCount = 1; + + for (Segment::iterator i = m_segment->begin(); + m_segment->isBeforeEndMarker(i); ++i) { + + if ((*i)->isa(Text::EventType)) { + + std::string textType; + if ((*i)->get(Text::TextTypePropertyName, textType) && + textType == Text::Lyric) { + + long verse = 0; + (*i)->get(Text::LyricVersePropertyName, verse); + + if (verse >= m_verseCount) m_verseCount = verse + 1; + } + } + } +} + +void +LyricEditDialog::unparse() +{ + // This and SetLyricsCommand::execute() are opposites that will + // need to be kept in sync with any changes in one another. (They + // should really both be in a common lyric management class.) + + countVerses(); + + Composition *comp = m_segment->getComposition(); + + bool firstNote = true; + timeT lastTime = m_segment->getStartTime(); + int lastBarNo = comp->getBarNumber(lastTime); + std::map haveLyric; + + QString fragment = QString("[%1] ").arg(lastBarNo + 1); + + m_skeleton = fragment; + m_texts.clear(); + for (size_t v = 0; v < m_verseCount; ++v) { + m_texts.push_back(fragment); + haveLyric[v] = false; + } + + for (Segment::iterator i = m_segment->begin(); + m_segment->isBeforeEndMarker(i); ++i) { + + bool isNote = (*i)->isa(Note::EventType); + bool isLyric = false; + + if (!isNote) { + if ((*i)->isa(Text::EventType)) { + std::string textType; + if ((*i)->get(Text::TextTypePropertyName, textType) && + textType == Text::Lyric) { + isLyric = true; + } + } + } else { + if ((*i)->has(BaseProperties::TIED_BACKWARD) && + (*i)->get(BaseProperties::TIED_BACKWARD)) { + continue; + } + } + + if (!isNote && !isLyric) continue; + + timeT myTime = (*i)->getNotationAbsoluteTime(); + int myBarNo = comp->getBarNumber(myTime); + + if (myBarNo > lastBarNo) { + + fragment = ""; + + while (myBarNo > lastBarNo) { + fragment += " /"; + ++lastBarNo; + } + + fragment += QString("\n[%1] ").arg(myBarNo + 1); + + m_skeleton += fragment; + for (size_t v = 0; v < m_verseCount; ++v) m_texts[v] += fragment; + } + + if (isNote) { + if ((myTime > lastTime) || firstNote) { + m_skeleton += " ."; + for (size_t v = 0; v < m_verseCount; ++v) { + if (!haveLyric[v]) m_texts[v] += " ."; + haveLyric[v] = false; + } + lastTime = myTime; + firstNote = false; + } + } + + if (isLyric) { + + std::string ssyllable; + (*i)->get(Text::TextPropertyName, ssyllable); + + long verse = 0; + (*i)->get(Text::LyricVersePropertyName, verse); + + QString syllable(strtoqstr(ssyllable)); + syllable.replace(QRegExp("\\s+"), "~"); + + m_texts[verse] += " " + syllable; + haveLyric[verse] = true; + } + } + + if (!m_texts.empty()) { + m_textEdit->setText(m_texts[0]); + } else { + m_texts.push_back(m_skeleton); + } +} + +int +LyricEditDialog::getVerseCount() const +{ + return m_verseCount; +} + +QString +LyricEditDialog::getLyricData(int verse) const +{ + if (verse == m_verseNumber->currentItem()) { + return m_textEdit->text(); + } else { + return m_texts[verse]; + } +} + +} +#include "LyricEditDialog.moc" diff --git a/src/gui/dialogs/LyricEditDialog.h b/src/gui/dialogs/LyricEditDialog.h new file mode 100644 index 0000000..f4a5154 --- /dev/null +++ b/src/gui/dialogs/LyricEditDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_LYRICEDITDIALOG_H_ +#define _RG_LYRICEDITDIALOG_H_ + +#include +#include +#include + + +class QWidget; +class QTextEdit; +class QComboBox; +class QPushButton; + + +namespace Rosegarden +{ + +class Segment; + + +class LyricEditDialog : public KDialogBase +{ + Q_OBJECT + +public: + LyricEditDialog(QWidget *parent, Segment *segment); + + int getVerseCount() const; + QString getLyricData(int verse) const; + +protected slots: + void slotVerseNumberChanged(int); + void slotAddVerse(); + +protected: + Segment *m_segment; + + int m_currentVerse; + QComboBox *m_verseNumber; + QTextEdit *m_textEdit; + QPushButton *m_verseAddButton; + + int m_verseCount; + std::vector m_texts; + QString m_skeleton; + + void countVerses(); + void unparse(); +}; + +} + +#endif diff --git a/src/gui/dialogs/MakeOrnamentDialog.cpp b/src/gui/dialogs/MakeOrnamentDialog.cpp new file mode 100644 index 0000000..7e82a22 --- /dev/null +++ b/src/gui/dialogs/MakeOrnamentDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MakeOrnamentDialog.h" + +#include +#include "gui/widgets/PitchChooser.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +MakeOrnamentDialog::MakeOrnamentDialog(QWidget *parent, QString defaultName, + int defaultBasePitch) : + KDialogBase(parent, "makeornamentdialog", true, i18n("Make Ornament"), + Ok | Cancel, Ok) +{ + QVBox *vbox = makeVBoxMainWidget(); + QGroupBox *nameBox = new QGroupBox(2, Vertical, i18n("Name"), vbox); + + new QLabel(i18n("The name is used to identify both the ornament\nand the triggered segment that stores\nthe ornament's notes."), nameBox); + + QHBox *hbox = new QHBox(nameBox); + new QLabel(i18n("Name: "), hbox); + m_name = new QLineEdit(defaultName, hbox); + + m_pitch = new PitchChooser(i18n("Base pitch"), vbox, defaultBasePitch); +} + +QString +MakeOrnamentDialog::getName() const +{ + return m_name->text(); +} + +int +MakeOrnamentDialog::getBasePitch() const +{ + return m_pitch->getPitch(); +} + +} +#include "MakeOrnamentDialog.moc" diff --git a/src/gui/dialogs/MakeOrnamentDialog.h b/src/gui/dialogs/MakeOrnamentDialog.h new file mode 100644 index 0000000..3f1957b --- /dev/null +++ b/src/gui/dialogs/MakeOrnamentDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MAKEORNAMENTDIALOG_H_ +#define _RG_MAKEORNAMENTDIALOG_H_ + +#include +#include + + +class QWidget; +class QLineEdit; + + +namespace Rosegarden +{ + +class PitchChooser; + + +class MakeOrnamentDialog : public KDialogBase +{ + Q_OBJECT + +public: + MakeOrnamentDialog(QWidget *parent, QString defaultName, int defaultBasePitch); + + QString getName() const; + int getBasePitch() const; + +protected: + QLineEdit *m_name; + PitchChooser *m_pitch; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/ManageMetronomeDialog.cpp b/src/gui/dialogs/ManageMetronomeDialog.cpp new file mode 100644 index 0000000..a0f73d6 --- /dev/null +++ b/src/gui/dialogs/ManageMetronomeDialog.cpp @@ -0,0 +1,508 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ManageMetronomeDialog.h" +#include + +#include +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "base/Composition.h" +#include "base/Device.h" +#include "base/Instrument.h" +#include "base/MidiDevice.h" +#include "base/MidiProgram.h" +#include "base/RealTime.h" +#include "base/Studio.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/editors/parameters/InstrumentParameterBox.h" +#include "gui/seqmanager/SequenceManager.h" +#include "gui/studio/StudioControl.h" +#include "gui/widgets/PitchChooser.h" +#include "sound/MappedEvent.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +ManageMetronomeDialog::ManageMetronomeDialog(QWidget *parent, + RosegardenGUIDoc *doc) : + KDialogBase(parent, 0, true, i18n("Metronome"), Ok | Apply | Close | Help), + m_doc(doc) +{ + setHelp("studio-metronome"); + + QHBox *hbox = makeHBoxMainWidget(); + + // I think having this as well probably just overcomplicates things + m_instrumentParameterBox = 0; + // m_instrumentParameterBox = new InstrumentParameterBox(doc, hbox); + + QVBox *vbox = new QVBox(hbox); + + QGroupBox *deviceBox = new QGroupBox + (1, Horizontal, i18n("Metronome Instrument"), vbox); + + QFrame *frame = new QFrame(deviceBox); + QGridLayout *layout = new QGridLayout(frame, 2, 2, 10, 5); + + layout->addWidget(new QLabel(i18n("Device"), frame), 0, 0); + m_metronomeDevice = new KComboBox(frame); + layout->addWidget(m_metronomeDevice, 0, 1); + + DeviceList *devices = doc->getStudio().getDevices(); + DeviceListConstIterator it; + + Studio &studio = m_doc->getStudio(); + DeviceId deviceId = studio.getMetronomeDevice(); + + for (it = devices->begin(); it != devices->end(); it++) { + MidiDevice *dev = + dynamic_cast(*it); + + if (dev && dev->getDirection() == MidiDevice::Play) { + QString label = strtoqstr(dev->getName()); + QString connection = strtoqstr(dev->getConnection()); + label += " - "; + if (connection == "") + label += i18n("No connection"); + else + label += connection; + m_metronomeDevice->insertItem(label); + if (dev->getId() == deviceId) { + m_metronomeDevice->setCurrentItem(m_metronomeDevice->count() - 1); + } + } + } + + layout->addWidget(new QLabel(i18n("Instrument"), frame), 1, 0); + m_metronomeInstrument = new KComboBox(frame); + connect(m_metronomeInstrument, SIGNAL(activated(int)), this, SLOT(slotSetModified())); + connect(m_metronomeInstrument, SIGNAL(activated(int)), this, SLOT(slotInstrumentChanged(int))); + layout->addWidget(m_metronomeInstrument, 1, 1); + + QGroupBox *beatBox = new QGroupBox + (1, Horizontal, i18n("Beats"), vbox); + + frame = new QFrame(beatBox); + layout = new QGridLayout(frame, 4, 2, 10, 5); + + layout->addWidget(new QLabel(i18n("Resolution"), frame), 0, 0); + m_metronomeResolution = new KComboBox(frame); + m_metronomeResolution->insertItem(i18n("None")); + m_metronomeResolution->insertItem(i18n("Bars only")); + m_metronomeResolution->insertItem(i18n("Bars and beats")); + m_metronomeResolution->insertItem(i18n("Bars, beats, and divisions")); + connect(m_metronomeResolution, SIGNAL(activated(int)), this, SLOT(slotResolutionChanged(int))); + layout->addWidget(m_metronomeResolution, 0, 1); + + layout->addWidget(new QLabel(i18n("Bar velocity"), frame), 1, 0); + m_metronomeBarVely = new QSpinBox(frame); + m_metronomeBarVely->setMinValue(0); + m_metronomeBarVely->setMaxValue(127); + connect(m_metronomeBarVely, SIGNAL(valueChanged(int)), this, SLOT(slotSetModified())); + layout->addWidget(m_metronomeBarVely, 1, 1); + + layout->addWidget(new QLabel(i18n("Beat velocity"), frame), 2, 0); + m_metronomeBeatVely = new QSpinBox(frame); + m_metronomeBeatVely->setMinValue(0); + m_metronomeBeatVely->setMaxValue(127); + connect(m_metronomeBeatVely, SIGNAL(valueChanged(int)), this, SLOT(slotSetModified())); + layout->addWidget(m_metronomeBeatVely, 2, 1); + + layout->addWidget(new QLabel(i18n("Sub-beat velocity"), frame), 3, 0); + m_metronomeSubBeatVely = new QSpinBox(frame); + m_metronomeSubBeatVely->setMinValue(0); + m_metronomeSubBeatVely->setMaxValue(127); + connect(m_metronomeSubBeatVely, SIGNAL(valueChanged(int)), this, SLOT(slotSetModified())); + layout->addWidget(m_metronomeSubBeatVely, 3, 1); + + vbox = new QVBox(hbox); + + m_metronomePitch = new PitchChooser(i18n("Pitch"), vbox, 60); + connect(m_metronomePitch, SIGNAL(pitchChanged(int)), this, SLOT(slotPitchChanged(int))); + connect(m_metronomePitch, SIGNAL(preview(int)), this, SLOT(slotPreviewPitch(int))); + + m_metronomePitchSelector = new KComboBox(m_metronomePitch); + m_metronomePitchSelector->insertItem(i18n("for Bar")); + m_metronomePitchSelector->insertItem(i18n("for Beat")); + m_metronomePitchSelector->insertItem(i18n("for Sub-beat")); + connect(m_metronomePitchSelector, SIGNAL(activated(int)), this, SLOT(slotPitchSelectorChanged(int))); + + QGroupBox *enableBox = new QGroupBox + (1, Horizontal, i18n("Metronome Activated"), vbox); + m_playEnabled = new QCheckBox(i18n("Playing"), enableBox); + m_recordEnabled = new QCheckBox(i18n("Recording"), enableBox); + connect(m_playEnabled, SIGNAL(clicked()), this, SLOT(slotSetModified())); + connect(m_recordEnabled, SIGNAL(clicked()), this, SLOT(slotSetModified())); + + // populate the dialog + populate(m_metronomeDevice->currentItem()); + + // connect up the device list + connect(m_metronomeDevice, SIGNAL(activated(int)), + this, SLOT(populate(int))); + // connect up the device list + connect(m_metronomeDevice, SIGNAL(activated(int)), + this, SLOT(slotSetModified())); + + setModified(false); +} + +void +ManageMetronomeDialog::slotResolutionChanged(int depth) +{ + m_metronomeBeatVely->setEnabled(depth > 1); + m_metronomeSubBeatVely->setEnabled(depth > 2); + slotSetModified(); +} + +void +ManageMetronomeDialog::populate(int deviceIndex) +{ + m_metronomeInstrument->clear(); + + DeviceList *devices = m_doc->getStudio().getDevices(); + DeviceListConstIterator it; + int count = 0; + MidiDevice *dev = 0; + + for (it = devices->begin(); it != devices->end(); it++) { + dev = dynamic_cast(*it); + + if (dev && dev->getDirection() == MidiDevice::Play) { + if (count == deviceIndex) + break; + + count++; + } + } + + // sanity + if (count < 0 || dev == 0) { + if (m_instrumentParameterBox) + m_instrumentParameterBox->useInstrument(0); + return ; + } + + // populate instrument list + InstrumentList list = dev->getPresentationInstruments(); + InstrumentList::iterator iit; + + const MidiMetronome *metronome = dev->getMetronome(); + + // if we've got no metronome against this device then create one + if (metronome == 0) { + InstrumentId id = SystemInstrumentBase; + + for (iit = list.begin(); iit != list.end(); ++iit) { + if ((*iit)->isPercussion()) { + id = (*iit)->getId(); + break; + } + } + + dev->setMetronome(MidiMetronome(id)); + + metronome = dev->getMetronome(); + } + + // metronome should now be set but we still check it + if (metronome) { + int position = 0; + int count = 0; + for (iit = list.begin(); iit != list.end(); ++iit) { + QString iname(strtoqstr((*iit)->getPresentationName())); + QString pname(strtoqstr((*iit)->getProgramName())); + if (pname != "") + iname += " (" + pname + ")"; + + bool used = false; + for (Composition::trackcontainer::iterator tit = + m_doc->getComposition().getTracks().begin(); + tit != m_doc->getComposition().getTracks().end(); ++tit) { + + if (tit->second->getInstrument() == (*iit)->getId()) { + used = true; + break; + } + } + + // if (used) iname = i18n("%1 [used]").arg(iname); + + m_metronomeInstrument->insertItem(iname); + + if ((*iit)->getId() == metronome->getInstrument()) { + position = count; + } + count++; + } + m_metronomeInstrument->setCurrentItem(position); + slotInstrumentChanged(position); + + m_barPitch = metronome->getBarPitch(); + m_beatPitch = metronome->getBeatPitch(); + m_subBeatPitch = metronome->getSubBeatPitch(); + slotPitchSelectorChanged(0); + m_metronomeResolution->setCurrentItem(metronome->getDepth()); + m_metronomeBarVely->setValue(metronome->getBarVelocity()); + m_metronomeBeatVely->setValue(metronome->getBeatVelocity()); + m_metronomeSubBeatVely->setValue(metronome->getSubBeatVelocity()); + m_playEnabled->setChecked(m_doc->getComposition().usePlayMetronome()); + m_recordEnabled->setChecked(m_doc->getComposition().useRecordMetronome()); + slotResolutionChanged(metronome->getDepth()); + } +} + +void +ManageMetronomeDialog::slotInstrumentChanged(int i) +{ + if (!m_instrumentParameterBox) + return ; + + int deviceIndex = m_metronomeDevice->currentItem(); + + DeviceList *devices = m_doc->getStudio().getDevices(); + DeviceListConstIterator it; + int count = 0; + MidiDevice *dev = 0; + + for (it = devices->begin(); it != devices->end(); it++) { + dev = dynamic_cast(*it); + + if (dev && dev->getDirection() == MidiDevice::Play) { + if (count == deviceIndex) + break; + + count++; + } + } + + // sanity + if (count < 0 || dev == 0) { + m_instrumentParameterBox->useInstrument(0); + return ; + } + + // populate instrument list + InstrumentList list = dev->getPresentationInstruments(); + + if (i < 0 || i >= (int)list.size()) + return ; + + m_instrumentParameterBox->useInstrument(list[i]); +} + +void +ManageMetronomeDialog::slotOk() +{ + slotApply(); + accept(); +} + +void +ManageMetronomeDialog::slotSetModified() +{ + setModified(true); +} + +void +ManageMetronomeDialog::setModified(bool value) +{ + if (m_modified == value) + return ; + + if (value) { + enableButtonApply(true); + } else { + enableButtonApply(false); + } + + m_modified = value; +} + +void +ManageMetronomeDialog::slotApply() +{ + Studio &studio = m_doc->getStudio(); + + DeviceList *devices = m_doc->getStudio().getDevices(); + DeviceListConstIterator it; + int count = 0; + MidiDevice *dev = 0; + + for (it = devices->begin(); it != devices->end(); it++) { + dev = dynamic_cast(*it); + + if (dev && dev->getDirection() == MidiDevice::Play) { + if (count == m_metronomeDevice->currentItem()) + break; + + count++; + } + } + + if (!dev) { + std::cerr << "Warning: ManageMetronomeDialog::slotApply: no " << m_metronomeDevice->currentItem() << "th device" << std::endl; + return ; + } + + DeviceId deviceId = dev->getId(); + studio.setMetronomeDevice(deviceId); + + if (dev->getMetronome() == 0) + return ; + MidiMetronome metronome(*dev->getMetronome()); + + // get instrument + InstrumentList list = dev->getPresentationInstruments(); + + Instrument *inst = + list[m_metronomeInstrument->currentItem()]; + + if (inst) { + metronome.setInstrument(inst->getId()); + } + + metronome.setBarPitch(m_barPitch); + metronome.setBeatPitch(m_beatPitch); + metronome.setSubBeatPitch(m_subBeatPitch); + + metronome.setDepth( + m_metronomeResolution->currentItem()); + + metronome.setBarVelocity( + MidiByte(m_metronomeBarVely->value())); + + metronome.setBeatVelocity( + MidiByte(m_metronomeBeatVely->value())); + + metronome.setSubBeatVelocity( + MidiByte(m_metronomeSubBeatVely->value())); + + dev->setMetronome(metronome); + + m_doc->getComposition().setPlayMetronome(m_playEnabled->isChecked()); + m_doc->getComposition().setRecordMetronome(m_recordEnabled->isChecked()); + + m_doc->getSequenceManager()->metronomeChanged(inst->getId(), true); + m_doc->slotDocumentModified(); + setModified(false); +} + +void +ManageMetronomeDialog::slotPreviewPitch(int pitch) +{ + RG_DEBUG << "ManageMetronomeDialog::slotPreviewPitch" << endl; + + DeviceList *devices = m_doc->getStudio().getDevices(); + DeviceListConstIterator it; + int count = 0; + MidiDevice *dev = 0; + + for (it = devices->begin(); it != devices->end(); it++) { + dev = dynamic_cast(*it); + + if (dev && dev->getDirection() == MidiDevice::Play) { + if (count == m_metronomeDevice->currentItem()) + break; + + count++; + } + } + + if (!dev) + return ; + + const MidiMetronome *metronome = dev->getMetronome(); + if (metronome == 0) + return ; + + InstrumentList list = dev->getPresentationInstruments(); + + Instrument *inst = + list[m_metronomeInstrument->currentItem()]; + + if (inst) { + RG_DEBUG << "ManageMetronomeDialog::slotPreviewPitch" + << " - previewing" << endl; + MappedEvent mE(inst->getId(), + MappedEvent::MidiNoteOneShot, + pitch, + MidiMaxValue, + RealTime::zeroTime, + RealTime(0, 10000000), + RealTime::zeroTime); + + StudioControl::sendMappedEvent(mE); + } +} + +void +ManageMetronomeDialog::slotPitchChanged(int pitch) +{ + switch (m_metronomePitchSelector->currentItem()) { + case 0: + m_barPitch = pitch; + break; + case 1: + m_beatPitch = pitch; + break; + case 2: + m_subBeatPitch = pitch; + break; + } + setModified(true); +} + +void +ManageMetronomeDialog::slotPitchSelectorChanged(int selection) +{ + switch (selection) { + case 0: + m_metronomePitch->slotSetPitch(m_barPitch); + break; + case 1: + m_metronomePitch->slotSetPitch(m_beatPitch); + break; + case 2: + m_metronomePitch->slotSetPitch(m_subBeatPitch); + break; + } +} + +} +#include "ManageMetronomeDialog.moc" diff --git a/src/gui/dialogs/ManageMetronomeDialog.h b/src/gui/dialogs/ManageMetronomeDialog.h new file mode 100644 index 0000000..08b806c --- /dev/null +++ b/src/gui/dialogs/ManageMetronomeDialog.h @@ -0,0 +1,94 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MANAGEMETRONOMEDIALOG_H_ +#define _RG_MANAGEMETRONOMEDIALOG_H_ + +#include "base/MidiProgram.h" +#include + + +class QWidget; +class QSpinBox; +class QCheckBox; +class KComboBox; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class PitchChooser; +class InstrumentParameterBox; + + +class ManageMetronomeDialog : public KDialogBase +{ + Q_OBJECT + +public: + ManageMetronomeDialog(QWidget *parent, RosegardenGUIDoc *doc); + + void setModified(bool value); + +public slots: + void slotOk(); + void slotApply(); + void slotSetModified(); + void slotResolutionChanged(int); + void slotPreviewPitch(int); + void slotInstrumentChanged(int); + void slotPitchSelectorChanged(int); + void slotPitchChanged(int); + void populate(int dev); + +protected: + + //--------------- Data members --------------------------------- + + RosegardenGUIDoc *m_doc; + + KComboBox *m_metronomeDevice; + KComboBox *m_metronomeInstrument; + KComboBox *m_metronomeResolution; + KComboBox *m_metronomePitchSelector; + PitchChooser *m_metronomePitch; + QSpinBox *m_metronomeBarVely; + QSpinBox *m_metronomeBeatVely; + QSpinBox *m_metronomeSubBeatVely; + InstrumentParameterBox *m_instrumentParameterBox; + QCheckBox *m_playEnabled; + QCheckBox *m_recordEnabled; + + bool m_modified; + MidiByte m_barPitch; + MidiByte m_beatPitch; + MidiByte m_subBeatPitch; +}; + + +} + +#endif diff --git a/src/gui/dialogs/MarkerModifyDialog.cpp b/src/gui/dialogs/MarkerModifyDialog.cpp new file mode 100644 index 0000000..69e658b --- /dev/null +++ b/src/gui/dialogs/MarkerModifyDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MarkerModifyDialog.h" +#include + +#include +#include "base/Composition.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/widgets/TimeWidget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "misc/Strings.h" + + +namespace Rosegarden +{ + +MarkerModifyDialog::MarkerModifyDialog(QWidget *parent, + Composition *composition, + int time, + const QString &name, + const QString &des): + KDialogBase(parent, 0, true, i18n("Edit Marker"), Ok | Cancel) +{ + initialise(composition, time, name, des); +} + +MarkerModifyDialog::MarkerModifyDialog(QWidget *parent, + Composition *composition, + Marker *marker) : + KDialogBase(parent, 0, true, i18n("Edit Marker"), Ok | Cancel) +{ + initialise(composition, marker->getTime(), + strtoqstr(marker->getName()), + strtoqstr(marker->getDescription())); +} + +void +MarkerModifyDialog::initialise(Composition *composition, + int time, + const QString &name, + const QString &des) +{ + m_originalTime = time; + + QVBox *vbox = makeVBoxMainWidget(); + + m_timeEdit = new TimeWidget(i18n("Marker Time"), vbox, composition, + time); + + /*!!! + + layout->addWidget(new QLabel(i18n("Absolute Time:"), frame), 0, 0); + m_timeEdit = new QSpinBox(frame); + layout->addWidget(m_timeEdit, 0, 1); + + m_timeEdit->setMinValue(INT_MIN); + m_timeEdit->setMaxValue(INT_MAX); + m_timeEdit->setLineStep( + Note(Note::Shortest).getDuration()); + m_timeEdit->setValue(time); + */ + QGroupBox *groupBox = new QGroupBox + (1, Horizontal, i18n("Marker Properties"), vbox); + + QFrame *frame = new QFrame(groupBox); + + QGridLayout *layout = new QGridLayout(frame, 2, 2, 5, 5); + + layout->addWidget(new QLabel(i18n("Text:"), frame), 0, 0); + m_nameEdit = new QLineEdit(name, frame); + layout->addWidget(m_nameEdit, 0, 1); + + layout->addWidget(new QLabel(i18n("Description:"), frame), 1, 0); + m_desEdit = new QLineEdit(des, frame); + layout->addWidget(m_desEdit, 1, 1); + + m_nameEdit->selectAll(); + m_nameEdit->setFocus(); +} + +} +#include "MarkerModifyDialog.moc" diff --git a/src/gui/dialogs/MarkerModifyDialog.h b/src/gui/dialogs/MarkerModifyDialog.h new file mode 100644 index 0000000..5b87b14 --- /dev/null +++ b/src/gui/dialogs/MarkerModifyDialog.h @@ -0,0 +1,84 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MARKERMODIFYDIALOG_H_ +#define _RG_MARKERMODIFYDIALOG_H_ + +#include +#include +#include + +#include "base/Marker.h" +#include "gui/widgets/TimeWidget.h" + + +namespace Rosegarden +{ + +class TimeWidget; +class RosegardenGUIDoc; +class Composition; + + +class MarkerModifyDialog : public KDialogBase +{ + Q_OBJECT +public: + MarkerModifyDialog(QWidget *parent, + Composition *composition, + int time, + const QString &name, + const QString &des); + + MarkerModifyDialog(QWidget *parent, + Composition *composition, + Marker *marker); + + QString getName() const { return m_nameEdit->text(); } + QString getDescription() const { return m_desEdit->text(); } + int getTime() const { return m_timeEdit->getTime(); } + int getOriginalTime() const { return m_originalTime; } + +protected: + void initialise(Composition *composition, + int time, + const QString &name, + const QString &des); + + RosegardenGUIDoc *m_doc; + + TimeWidget *m_timeEdit; + QLineEdit *m_nameEdit; + QLineEdit *m_desEdit; + + int m_originalTime; +}; + + + + +} + +#endif diff --git a/src/gui/dialogs/PasteNotationDialog.cpp b/src/gui/dialogs/PasteNotationDialog.cpp new file mode 100644 index 0000000..0c725d5 --- /dev/null +++ b/src/gui/dialogs/PasteNotationDialog.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "PasteNotationDialog.h" + +#include +#include "commands/edit/PasteEventsCommand.h" +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +PasteNotationDialog::PasteNotationDialog(QWidget *parent, + PasteEventsCommand::PasteType defaultType) : + KDialogBase(parent, 0, true, i18n("Paste"), Ok | Cancel | Help ), + m_defaultType(defaultType) +{ + setHelp("nv-paste-types"); + + QVBox *vbox = makeVBoxMainWidget(); + + QButtonGroup *pasteTypeGroup = new QButtonGroup + (1, Horizontal, i18n("Paste type"), vbox); + + PasteEventsCommand::PasteTypeMap pasteTypes = + PasteEventsCommand::getPasteTypes(); + + for (PasteEventsCommand::PasteTypeMap::iterator i = pasteTypes.begin(); + i != pasteTypes.end(); ++i) { + + QRadioButton *button = new QRadioButton(i->second, pasteTypeGroup); + button->setChecked(m_defaultType == i->first); + QObject::connect(button, SIGNAL(clicked()), + this, SLOT(slotPasteTypeChanged())); + + m_pasteTypeButtons.push_back(button); + } + + QButtonGroup *setAsDefaultGroup = new QButtonGroup + (1, Horizontal, i18n("Options"), vbox); + + m_setAsDefaultButton = new QCheckBox + (i18n("Make this the default paste type"), setAsDefaultGroup); + m_setAsDefaultButton->setChecked(true); +} + +PasteEventsCommand::PasteType +PasteNotationDialog::getPasteType() const +{ + for (unsigned int i = 0; i < m_pasteTypeButtons.size(); ++i) { + if (m_pasteTypeButtons[i]->isChecked()) { + return (PasteEventsCommand::PasteType)i; + } + } + + return PasteEventsCommand::Restricted; +} + +bool +PasteNotationDialog::setAsDefault() const +{ + return m_setAsDefaultButton->isChecked(); +} + +void +PasteNotationDialog::slotPasteTypeChanged() +{ + m_setAsDefaultButton->setChecked(m_defaultType == getPasteType()); +} + +} +#include "PasteNotationDialog.moc" diff --git a/src/gui/dialogs/PasteNotationDialog.h b/src/gui/dialogs/PasteNotationDialog.h new file mode 100644 index 0000000..213eaf8 --- /dev/null +++ b/src/gui/dialogs/PasteNotationDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PASTENOTATIONDIALOG_H_ +#define _RG_PASTENOTATIONDIALOG_H_ + +#include "commands/edit/PasteEventsCommand.h" +#include +#include + + +class QWidget; +class QRadioButton; +class QCheckBox; + + +namespace Rosegarden +{ + + + +class PasteNotationDialog : public KDialogBase +{ + Q_OBJECT + +public: + PasteNotationDialog(QWidget *parent, + PasteEventsCommand::PasteType defaultType); + + PasteEventsCommand::PasteType getPasteType() const; + bool setAsDefault() const; + +public slots: + void slotPasteTypeChanged(); + +protected: + + //--------------- Data members --------------------------------- + + std::vector m_pasteTypeButtons; + QCheckBox *m_setAsDefaultButton; + + PasteEventsCommand::PasteType m_defaultType; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/PitchDialog.cpp b/src/gui/dialogs/PitchDialog.cpp new file mode 100644 index 0000000..05fed08 --- /dev/null +++ b/src/gui/dialogs/PitchDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "PitchDialog.h" + +#include +#include "gui/widgets/PitchChooser.h" +#include +#include +#include +#include + + +namespace Rosegarden +{ + +PitchDialog::PitchDialog(QWidget *parent, QString title, int defaultPitch) : + KDialogBase(parent, 0, true, title, User1 | Ok) +{ + QVBox *vbox = makeVBoxMainWidget(); + m_pitchChooser = new PitchChooser(title, vbox, defaultPitch); + + setButtonText(User1, i18n("Reset")); + connect(this, SIGNAL(user1Clicked()), + m_pitchChooser, SLOT(slotResetToDefault())); +} + +int +PitchDialog::getPitch() const +{ + return m_pitchChooser->getPitch(); +} + +} +#include "PitchDialog.moc" diff --git a/src/gui/dialogs/PitchDialog.h b/src/gui/dialogs/PitchDialog.h new file mode 100644 index 0000000..72e6381 --- /dev/null +++ b/src/gui/dialogs/PitchDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PITCHDIALOG_H_ +#define _RG_PITCHDIALOG_H_ + +#include +#include + + +class QWidget; + + +namespace Rosegarden +{ + +class PitchChooser; + + +class PitchDialog : public KDialogBase +{ + Q_OBJECT +public: + PitchDialog(QWidget *parent, QString title, int defaultPitch = 60); + + int getPitch() const; + +protected: + PitchChooser *m_pitchChooser; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/PitchPickerDialog.cpp b/src/gui/dialogs/PitchPickerDialog.cpp new file mode 100644 index 0000000..ddd1f23 --- /dev/null +++ b/src/gui/dialogs/PitchPickerDialog.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "PitchPickerDialog.h" +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +PitchPickerDialog::PitchPickerDialog(QWidget *parent, int initialPitch, QString text) : + KDialogBase(parent, 0, true, i18n("Pitch Selector"), Ok | Cancel) +{ + QVBox *vBox = makeVBoxMainWidget(); + + QFrame *frame = new QFrame(vBox); + + QGridLayout *layout = new QGridLayout(frame, 4, 3, 10, 5); + + m_pitch = new PitchChooser(text, frame, initialPitch); + layout->addMultiCellWidget(m_pitch, 0, 0, 0, 2, Qt::AlignHCenter); +} + +PitchPickerDialog::~PitchPickerDialog() +{ + // Nothing here... +} + +} +#include "PitchPickerDialog.moc" diff --git a/src/gui/dialogs/PitchPickerDialog.h b/src/gui/dialogs/PitchPickerDialog.h new file mode 100644 index 0000000..ebd0a6d --- /dev/null +++ b/src/gui/dialogs/PitchPickerDialog.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PITCHPICKERDIALOG_H_ +#define _RG_PITCHPICKERDIALOG_H_ + +#include "gui/widgets/PitchChooser.h" +#include + + +class QWidget; + + +namespace Rosegarden +{ + +class PitchPickerDialog : public KDialogBase +{ + Q_OBJECT + +public: + + PitchPickerDialog(QWidget* parent, int initialPitch, QString text); + ~PitchPickerDialog(); + + int getPitch() { return m_pitch->getPitch(); } + +private: + PitchChooser* m_pitch; +}; + + +} + +#endif diff --git a/src/gui/dialogs/QuantizeDialog.cpp b/src/gui/dialogs/QuantizeDialog.cpp new file mode 100644 index 0000000..b934dd5 --- /dev/null +++ b/src/gui/dialogs/QuantizeDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "QuantizeDialog.h" + +#include +#include "base/Quantizer.h" +#include "gui/widgets/QuantizeParameters.h" +#include +#include +#include + + +namespace Rosegarden +{ + +QuantizeDialog::QuantizeDialog(QWidget *parent, bool inNotation) : + KDialogBase(parent, 0, true, i18n("Quantize"), Ok | Cancel | Details | Help) +{ + setHelp("quantization"); + + QVBox *vbox = makeVBoxMainWidget(); + + m_quantizeFrame = + new QuantizeParameters + (vbox, inNotation ? QuantizeParameters::Notation : + QuantizeParameters::Grid, + true, false, 0); + + setButtonText(Details, i18n("Advanced")); + setDetailsWidget(m_quantizeFrame->getAdvancedWidget()); + m_quantizeFrame->getAdvancedWidget()->hide(); + + m_quantizeFrame->adjustSize(); + vbox->adjustSize(); + adjustSize(); +} + +Quantizer * +QuantizeDialog::getQuantizer() const +{ + return m_quantizeFrame->getQuantizer(); +} + +} +#include "QuantizeDialog.moc" diff --git a/src/gui/dialogs/QuantizeDialog.h b/src/gui/dialogs/QuantizeDialog.h new file mode 100644 index 0000000..a787dd1 --- /dev/null +++ b/src/gui/dialogs/QuantizeDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_QUANTIZEDIALOG_H_ +#define _RG_QUANTIZEDIALOG_H_ + +#include + + +class QWidget; + + +namespace Rosegarden +{ + +class Quantizer; +class QuantizeParameters; + + +class QuantizeDialog : public KDialogBase +{ + Q_OBJECT + +public: + QuantizeDialog(QWidget *parent, bool inNotation = false); + + /// Returned quantizer object is on heap -- caller must delete + Quantizer *getQuantizer() const; + +protected: + QuantizeParameters *m_quantizeFrame; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/RescaleDialog.cpp b/src/gui/dialogs/RescaleDialog.cpp new file mode 100644 index 0000000..d99a6fb --- /dev/null +++ b/src/gui/dialogs/RescaleDialog.cpp @@ -0,0 +1,131 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RescaleDialog.h" + +#include +#include "document/ConfigGroups.h" +#include "base/Composition.h" +#include "gui/widgets/TimeWidget.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +RescaleDialog::RescaleDialog(QWidget *parent, + Composition *composition, + timeT startTime, + timeT originalDuration, + bool showCloseGapOption, + bool constrainToCompositionDuration) : + KDialogBase(parent, 0, true, i18n("Rescale"), User1 | Ok | Cancel) +{ + QVBox *vbox = makeVBoxMainWidget(); + + m_newDuration = new TimeWidget + (i18n("Duration of selection"), vbox, composition, + startTime, originalDuration, true, + constrainToCompositionDuration); + + if (showCloseGapOption) { + QGroupBox *optionBox = new QGroupBox(1, Horizontal, i18n("Options"), vbox); + m_closeGap = new QCheckBox(i18n("Adjust times of following events accordingly"), + optionBox); + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + m_closeGap->setChecked + (config->readBoolEntry("rescaledialogadjusttimes", true)); + } else { + m_closeGap = 0; + } + + setButtonText(User1, i18n("Reset")); + connect(this, SIGNAL(user1Clicked()), + m_newDuration, SLOT(slotResetToDefault())); +} + +timeT +RescaleDialog::getNewDuration() +{ + return m_newDuration->getTime(); +} + +bool +RescaleDialog::shouldCloseGap() +{ + if (m_closeGap) { + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + config->writeEntry("rescaledialogadjusttimes", m_closeGap->isChecked()); + return m_closeGap->isChecked(); + } else { + return true; + } +} + +/* +int +RescaleDialog::getMultiplier() +{ + return m_to; +} + +int +RescaleDialog::getDivisor() +{ + return m_from; +} + +void +RescaleDialog::slotFromChanged(int i) +{ + m_from = i + 1; + int perTenThou = m_to * 10000 / m_from; + m_percent->setText(QString("%1.%2%"). + arg(perTenThou / 100). + arg(perTenThou % 100)); +} + +void +RescaleDialog::slotToChanged(int i) +{ + m_to = i + 1; + int perTenThou = m_to * 10000 / m_from; + m_percent->setText(QString("%1.%2%"). + arg(perTenThou / 100). + arg(perTenThou % 100)); +} +*/ + +} +#include "RescaleDialog.moc" diff --git a/src/gui/dialogs/RescaleDialog.h b/src/gui/dialogs/RescaleDialog.h new file mode 100644 index 0000000..196dd87 --- /dev/null +++ b/src/gui/dialogs/RescaleDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_RESCALEDIALOG_H_ +#define _RG_RESCALEDIALOG_H_ + +#include +#include "base/Event.h" + + +class QWidget; +class QCheckBox; + + +namespace Rosegarden +{ + +class TimeWidget; +class Composition; + + +class RescaleDialog : public KDialogBase +{ + Q_OBJECT + +public: + RescaleDialog(QWidget *parent, + Composition *composition, // for TimeWidget calculations + timeT startTime, + timeT originalDuration, + bool showCloseGapOption, + bool constrainToCompositionDuration); + + timeT getNewDuration(); + bool shouldCloseGap(); + +protected: + TimeWidget *m_newDuration; + QCheckBox *m_closeGap; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/ShowSequencerStatusDialog.cpp b/src/gui/dialogs/ShowSequencerStatusDialog.cpp new file mode 100644 index 0000000..d98933c --- /dev/null +++ b/src/gui/dialogs/ShowSequencerStatusDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ShowSequencerStatusDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gui/application/RosegardenApplication.h" + + +namespace Rosegarden +{ + +ShowSequencerStatusDialog::ShowSequencerStatusDialog(QWidget *parent) : + KDialogBase(parent, 0, true, i18n("Sequencer status"), Close) +{ + QVBox *vbox = makeVBoxMainWidget(); + + new QLabel(i18n("Sequencer status:"), vbox); + + QString status(i18n("Status not available.")); + + QCString replyType; + QByteArray replyData; + QByteArray data; + + if (!rgapp->sequencerCall("getStatusLog()", replyType, replyData)) { + status = i18n("Sequencer is not running or is not responding."); + } + + QDataStream streamIn(replyData, IO_ReadOnly); + QString result; + streamIn >> result; + if (!result) { + status = i18n("Sequencer is not returning a valid status report."); + } else { + status = result; + } + + QTextEdit *text = new QTextEdit(vbox); + text->setTextFormat(Qt::PlainText); + text->setReadOnly(true); + text->setMinimumWidth(500); + text->setMinimumHeight(200); + + text->setText(status); +} + +} +#include "ShowSequencerStatusDialog.moc" diff --git a/src/gui/dialogs/ShowSequencerStatusDialog.h b/src/gui/dialogs/ShowSequencerStatusDialog.h new file mode 100644 index 0000000..ce21ab1 --- /dev/null +++ b/src/gui/dialogs/ShowSequencerStatusDialog.h @@ -0,0 +1,54 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SHOWSEQUENCERSTATUSDIALOG_H_ +#define _RG_SHOWSEQUENCERSTATUSDIALOG_H_ + +#include + + +class QWidget; + + +namespace Rosegarden +{ + + + +class ShowSequencerStatusDialog : public KDialogBase +{ + Q_OBJECT +public: + ShowSequencerStatusDialog(QWidget *parent); +}; + + +// Timer dialog for counting down +// + + +} + +#endif diff --git a/src/gui/dialogs/SimpleEventEditDialog.cpp b/src/gui/dialogs/SimpleEventEditDialog.cpp new file mode 100644 index 0000000..ca6b76a --- /dev/null +++ b/src/gui/dialogs/SimpleEventEditDialog.cpp @@ -0,0 +1,1061 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SimpleEventEditDialog.h" +#include + +#include "base/BaseProperties.h" +#include "base/Event.h" +#include "base/MidiTypes.h" +#include "base/NotationTypes.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/editors/guitar/Chord.h" +#include "misc/Strings.h" +#include "PitchDialog.h" +#include "TimeDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +SimpleEventEditDialog::SimpleEventEditDialog(QWidget *parent, + RosegardenGUIDoc *doc, + const Event &event, + bool inserting) : + KDialogBase(parent, 0, true, + i18n(inserting ? "Insert Event" : "Edit Event"), Ok | Cancel), + m_event(event), + m_doc(doc), + m_type(event.getType()), + m_absoluteTime(event.getAbsoluteTime()), + m_duration(event.getDuration()), + m_modified(false) +{ + QVBox *vbox = makeVBoxMainWidget(); + + QGroupBox *groupBox = new QGroupBox + (1, Horizontal, i18n("Event Properties"), vbox); + + QFrame *frame = new QFrame(groupBox); + + QGridLayout *layout = new QGridLayout(frame, 7, 3, 5, 5); + + layout->addWidget(new QLabel(i18n("Event type:"), frame), 0, 0); + + if (inserting) { + + m_typeLabel = 0; + + m_typeCombo = new KComboBox(frame); + layout->addWidget(m_typeCombo, 0, 1); + + m_typeCombo->insertItem(strtoqstr(Note::EventType)); + m_typeCombo->insertItem(strtoqstr(Controller::EventType)); + m_typeCombo->insertItem(strtoqstr(KeyPressure::EventType)); + m_typeCombo->insertItem(strtoqstr(ChannelPressure::EventType)); + m_typeCombo->insertItem(strtoqstr(ProgramChange::EventType)); + m_typeCombo->insertItem(strtoqstr(SystemExclusive::EventType)); + m_typeCombo->insertItem(strtoqstr(PitchBend::EventType)); + m_typeCombo->insertItem(strtoqstr(Indication::EventType)); + m_typeCombo->insertItem(strtoqstr(Text::EventType)); + m_typeCombo->insertItem(strtoqstr(Note::EventRestType)); + m_typeCombo->insertItem(strtoqstr(Clef::EventType)); + m_typeCombo->insertItem(strtoqstr(::Rosegarden::Key::EventType)); + m_typeCombo->insertItem(strtoqstr(Guitar::Chord::EventType)); + + // Connect up the combos + // + connect(m_typeCombo, SIGNAL(activated(int)), + SLOT(slotEventTypeChanged(int))); + + } else { + + m_typeCombo = 0; + + m_typeLabel = new QLabel(frame); + layout->addWidget(m_typeLabel, 0, 1); + } + + m_timeLabel = new QLabel(i18n("Absolute time:"), frame); + layout->addWidget(m_timeLabel, 1, 0); + m_timeSpinBox = new QSpinBox(INT_MIN, INT_MAX, Note(Note::Shortest).getDuration(), frame); + m_timeEditButton = new QPushButton("edit", frame); + layout->addWidget(m_timeSpinBox, 1, 1); + layout->addWidget(m_timeEditButton, 1, 2); + + connect(m_timeSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotAbsoluteTimeChanged(int))); + connect(m_timeEditButton, SIGNAL(released()), + SLOT(slotEditAbsoluteTime())); + + m_durationLabel = new QLabel(i18n("Duration:"), frame); + layout->addWidget(m_durationLabel, 2, 0); + m_durationSpinBox = new QSpinBox(0, INT_MAX, Note(Note::Shortest).getDuration(), frame); + m_durationEditButton = new QPushButton("edit", frame); + layout->addWidget(m_durationSpinBox, 2, 1); + layout->addWidget(m_durationEditButton, 2, 2); + + connect(m_durationSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotDurationChanged(int))); + connect(m_durationEditButton, SIGNAL(released()), + SLOT(slotEditDuration())); + + m_pitchLabel = new QLabel(i18n("Pitch:"), frame); + layout->addWidget(m_pitchLabel, 3, 0); + m_pitchSpinBox = new QSpinBox(frame); + m_pitchEditButton = new QPushButton("edit", frame); + layout->addWidget(m_pitchSpinBox, 3, 1); + layout->addWidget(m_pitchEditButton, 3, 2); + + connect(m_pitchSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotPitchChanged(int))); + connect(m_pitchEditButton, SIGNAL(released()), + SLOT(slotEditPitch())); + + m_pitchSpinBox->setMinValue(MidiMinValue); + m_pitchSpinBox->setMaxValue(MidiMaxValue); + + m_controllerLabel = new QLabel(i18n("Controller name:"), frame); + m_controllerLabelValue = new QLabel(i18n(""), frame); + m_controllerLabelValue->setAlignment(QLabel::AlignRight); + + layout->addWidget(m_controllerLabel, 4, 0); + layout->addWidget(m_controllerLabelValue, 4, 1); + + m_velocityLabel = new QLabel(i18n("Velocity:"), frame); + layout->addWidget(m_velocityLabel, 5, 0); + m_velocitySpinBox = new QSpinBox(frame); + layout->addWidget(m_velocitySpinBox, 5, 1); + + connect(m_velocitySpinBox, SIGNAL(valueChanged(int)), + SLOT(slotVelocityChanged(int))); + + m_velocitySpinBox->setMinValue(MidiMinValue); + m_velocitySpinBox->setMaxValue(MidiMaxValue); + + m_metaLabel = new QLabel(i18n("Meta string:"), frame); + layout->addWidget(m_metaLabel, 6, 0); + m_metaEdit = new QLineEdit(frame); + layout->addWidget(m_metaEdit, 6, 1); + + m_sysexLoadButton = new QPushButton(i18n("Load data"), frame); + layout->addWidget(m_sysexLoadButton, 6, 2); + m_sysexSaveButton = new QPushButton(i18n("Save data"), frame); + layout->addWidget(m_sysexSaveButton, 4, 2); + + connect(m_metaEdit, SIGNAL(textChanged(const QString &)), + SLOT(slotMetaChanged(const QString &))); + connect(m_sysexLoadButton, SIGNAL(released()), + SLOT(slotSysexLoad())); + connect(m_sysexSaveButton, SIGNAL(released()), + SLOT(slotSysexSave())); + + m_notationGroupBox = new QGroupBox + (1, Horizontal, i18n("Notation Properties"), vbox); + + frame = new QFrame(m_notationGroupBox); + + layout = new QGridLayout(frame, 3, 3, 5, 5); + + m_lockNotationValues = new QCheckBox(i18n("Lock to changes in performed values"), frame); + layout->addMultiCellWidget(m_lockNotationValues, 0, 0, 0, 2); + m_lockNotationValues->setChecked(true); + + connect(m_lockNotationValues, SIGNAL(released()), + SLOT(slotLockNotationChanged())); + + m_notationTimeLabel = new QLabel(i18n("Notation time:"), frame); + layout->addWidget(m_notationTimeLabel, 1, 0); + m_notationTimeSpinBox = new QSpinBox(INT_MIN, INT_MAX, Note(Note::Shortest).getDuration(), frame); + m_notationTimeEditButton = new QPushButton("edit", frame); + layout->addWidget(m_notationTimeSpinBox, 1, 1); + layout->addWidget(m_notationTimeEditButton, 1, 2); + + connect(m_notationTimeSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotNotationAbsoluteTimeChanged(int))); + connect(m_notationTimeEditButton, SIGNAL(released()), + SLOT(slotEditNotationAbsoluteTime())); + + m_notationDurationLabel = new QLabel(i18n("Notation duration:"), frame); + layout->addWidget(m_notationDurationLabel, 2, 0); + m_notationDurationSpinBox = new QSpinBox(0, INT_MAX, Note(Note::Shortest).getDuration(), frame); + m_notationDurationEditButton = new QPushButton("edit", frame); + layout->addWidget(m_notationDurationSpinBox, 2, 1); + layout->addWidget(m_notationDurationEditButton, 2, 2); + + connect(m_notationDurationSpinBox, SIGNAL(valueChanged(int)), + SLOT(slotNotationDurationChanged(int))); + connect(m_notationDurationEditButton, SIGNAL(released()), + SLOT(slotEditNotationDuration())); + + setupForEvent(); +} + +void +SimpleEventEditDialog::setupForEvent() +{ + using BaseProperties::PITCH; + using BaseProperties::VELOCITY; + + if (m_typeCombo) { + m_typeCombo->blockSignals(true); + } + m_timeSpinBox->blockSignals(true); + m_notationTimeSpinBox->blockSignals(true); + m_durationSpinBox->blockSignals(true); + m_notationDurationSpinBox->blockSignals(true); + m_pitchSpinBox->blockSignals(true); + m_velocitySpinBox->blockSignals(true); + m_metaEdit->blockSignals(true); + + m_pitchSpinBox->setMinValue(MidiMinValue); + m_pitchSpinBox->setMaxValue(MidiMaxValue); + + // Some common settings + // + m_durationLabel->setText(i18n("Absolute time:")); + m_timeLabel->show(); + m_timeSpinBox->show(); + m_timeEditButton->show(); + m_timeSpinBox->setValue(m_event.getAbsoluteTime()); + + m_durationLabel->setText(i18n("Duration:")); + m_durationLabel->show(); + m_durationSpinBox->show(); + m_durationEditButton->show(); + m_durationSpinBox->setValue(m_event.getDuration()); + + m_notationGroupBox->hide(); + m_lockNotationValues->setChecked(true); + + if (m_typeLabel) + m_typeLabel->setText(strtoqstr(m_event.getType())); + + m_absoluteTime = m_event.getAbsoluteTime(); + m_notationAbsoluteTime = m_event.getNotationAbsoluteTime(); + m_duration = m_event.getDuration(); + m_notationDuration = m_event.getNotationDuration(); + + m_sysexLoadButton->hide(); + m_sysexSaveButton->hide(); + + if (m_type == Note::EventType) { + m_notationGroupBox->show(); + m_notationTimeSpinBox->setValue(m_notationAbsoluteTime); + m_notationDurationSpinBox->setValue(m_notationDuration); + + m_pitchLabel->show(); + m_pitchLabel->setText(i18n("Note pitch:")); + m_pitchSpinBox->show(); + m_pitchEditButton->show(); + + m_controllerLabel->hide(); + m_controllerLabelValue->hide(); + + m_velocityLabel->show(); + m_velocityLabel->setText(i18n("Note velocity:")); + m_velocitySpinBox->show(); + + m_metaLabel->hide(); + m_metaEdit->hide(); + + try { + m_pitchSpinBox->setValue(m_event.get(PITCH)); + } catch (Event::NoData) { + m_pitchSpinBox->setValue(60); + } + + try { + m_velocitySpinBox->setValue(m_event.get(VELOCITY)); + } catch (Event::NoData) { + m_velocitySpinBox->setValue(100); + } + + if (m_typeCombo) + m_typeCombo->setCurrentItem(0); + + } else if (m_type == Controller::EventType) { + + m_durationLabel->hide(); + m_durationSpinBox->hide(); + m_durationEditButton->hide(); + + m_pitchLabel->show(); + m_pitchLabel->setText(i18n("Controller number:")); + m_pitchSpinBox->show(); + m_pitchEditButton->hide(); + + m_controllerLabel->show(); + m_controllerLabelValue->show(); + m_controllerLabel->setText(i18n("Controller name:")); + + m_velocityLabel->show(); + m_velocityLabel->setText(i18n("Controller value:")); + m_velocitySpinBox->show(); + + m_metaLabel->hide(); + m_metaEdit->hide(); + + try { + m_pitchSpinBox->setValue(m_event.get + (Controller::NUMBER)); + } catch (Event::NoData) { + m_pitchSpinBox->setValue(0); + } + + try { + m_velocitySpinBox->setValue(m_event.get + (Controller::VALUE)); + } catch (Event::NoData) { + m_velocitySpinBox->setValue(0); + } + + if (m_typeCombo) + m_typeCombo->setCurrentItem(1); + + } else if (m_type == KeyPressure::EventType) { + + m_durationLabel->hide(); + m_durationSpinBox->hide(); + m_durationEditButton->hide(); + + m_pitchLabel->show(); + m_pitchLabel->setText(i18n("Key pitch:")); + m_pitchSpinBox->show(); + m_pitchEditButton->show(); + + m_controllerLabel->hide(); + m_controllerLabelValue->hide(); + + m_velocityLabel->show(); + m_velocityLabel->setText(i18n("Key pressure:")); + m_velocitySpinBox->show(); + + m_metaLabel->hide(); + m_metaEdit->hide(); + + try { + m_pitchSpinBox->setValue(m_event.get + (KeyPressure::PITCH)); + } catch (Event::NoData) { + m_pitchSpinBox->setValue(0); + } + + try { + m_velocitySpinBox->setValue(m_event.get + (KeyPressure::PRESSURE)); + } catch (Event::NoData) { + m_velocitySpinBox->setValue(0); + } + + if (m_typeCombo) + m_typeCombo->setCurrentItem(2); + + } else if (m_type == ChannelPressure::EventType) { + + m_durationLabel->hide(); + m_durationSpinBox->hide(); + m_durationEditButton->hide(); + + m_pitchLabel->show(); + m_pitchLabel->setText(i18n("Channel pressure:")); + m_pitchSpinBox->show(); + m_pitchEditButton->hide(); + + m_controllerLabel->hide(); + m_controllerLabelValue->hide(); + + m_velocityLabel->hide(); + m_velocitySpinBox->hide(); + + m_metaLabel->hide(); + m_metaEdit->hide(); + + try { + m_pitchSpinBox->setValue(m_event.get + (ChannelPressure::PRESSURE)); + } catch (Event::NoData) { + m_pitchSpinBox->setValue(0); + } + + if (m_typeCombo) + m_typeCombo->setCurrentItem(3); + + } else if (m_type == ProgramChange::EventType) { + + m_durationLabel->hide(); + m_durationSpinBox->hide(); + m_durationEditButton->hide(); + + m_pitchSpinBox->setMinValue(MidiMinValue + 1); + m_pitchSpinBox->setMaxValue(MidiMaxValue + 1); + + m_pitchLabel->show(); + m_pitchLabel->setText(i18n("Program change:")); + m_pitchSpinBox->show(); + m_pitchEditButton->hide(); + + m_controllerLabel->hide(); + m_controllerLabelValue->hide(); + + m_velocityLabel->hide(); + m_velocitySpinBox->hide(); + + m_metaLabel->hide(); + m_metaEdit->hide(); + + try { + m_pitchSpinBox->setValue(m_event.get + (ProgramChange::PROGRAM) + 1); + } catch (Event::NoData) { + m_pitchSpinBox->setValue(0); + } + + if (m_typeCombo) + m_typeCombo->setCurrentItem(4); + + } else if (m_type == SystemExclusive::EventType) { + + m_durationLabel->hide(); + m_durationSpinBox->hide(); + m_durationEditButton->hide(); + + m_pitchLabel->hide(); + m_pitchSpinBox->hide(); + m_pitchEditButton->hide(); + + m_controllerLabel->show(); + m_controllerLabelValue->show(); + + m_velocityLabel->hide(); + m_velocitySpinBox->hide(); + + m_metaLabel->show(); + m_metaEdit->show(); + + m_sysexLoadButton->show(); + m_sysexSaveButton->show(); + + m_controllerLabel->setText(i18n("Data length:")); + m_metaLabel->setText(i18n("Data:")); + try { + SystemExclusive sysEx(m_event); + m_controllerLabelValue->setText(QString("%1"). + arg(sysEx.getRawData().length())); + m_metaEdit->setText(strtoqstr(sysEx.getHexData())); + } catch (...) { + m_controllerLabelValue->setText("0"); + } + + if (m_typeCombo) + m_typeCombo->setCurrentItem(5); + + } else if (m_type == PitchBend::EventType) { + + m_durationLabel->hide(); + m_durationSpinBox->hide(); + m_durationEditButton->hide(); + + m_pitchLabel->show(); + m_pitchLabel->setText(i18n("Pitchbend MSB:")); + m_pitchSpinBox->show(); + m_pitchEditButton->hide(); + + m_controllerLabel->hide(); + m_controllerLabelValue->hide(); + + m_velocityLabel->show(); + m_velocityLabel->setText(i18n("Pitchbend LSB:")); + m_velocitySpinBox->show(); + + m_metaLabel->hide(); + m_metaEdit->hide(); + + try { + m_pitchSpinBox->setValue(m_event.get + (PitchBend::MSB)); + } catch (Event::NoData) { + m_pitchSpinBox->setValue(0); + } + + try { + m_velocitySpinBox->setValue(m_event.get + (PitchBend::LSB)); + } catch (Event::NoData) { + m_velocitySpinBox->setValue(0); + } + + if (m_typeCombo) + m_typeCombo->setCurrentItem(6); + + } else if (m_type == Indication::EventType) { + + m_pitchLabel->hide(); + m_pitchSpinBox->hide(); + m_pitchEditButton->hide(); + + m_controllerLabel->hide(); + m_controllerLabelValue->hide(); + + m_velocityLabel->hide(); + m_velocitySpinBox->hide(); + + m_metaLabel->show(); + m_metaEdit->show(); + m_metaLabel->setText(i18n("Indication:")); + + try { + Indication ind(m_event); + m_metaEdit->setText(strtoqstr(ind.getIndicationType())); + m_durationSpinBox->setValue(ind.getIndicationDuration()); + } catch (...) { + m_metaEdit->setText(i18n("")); + } + + if (m_typeCombo) + m_typeCombo->setCurrentItem(7); + + } else if (m_type == Text::EventType) { + + m_durationLabel->hide(); + m_durationSpinBox->hide(); + m_durationEditButton->hide(); + + m_pitchLabel->hide(); + m_pitchSpinBox->hide(); + m_pitchEditButton->hide(); + + m_controllerLabel->show(); + m_controllerLabelValue->show(); + + m_velocityLabel->hide(); + m_velocitySpinBox->hide(); + + m_metaLabel->show(); + m_metaEdit->show(); + + m_controllerLabel->setText(i18n("Text type:")); + m_metaLabel->setText(i18n("Text:")); + + // get the text event + try { + Text text(m_event); + m_controllerLabelValue->setText(strtoqstr(text.getTextType())); + m_metaEdit->setText(strtoqstr(text.getText())); + } catch (...) { + m_controllerLabelValue->setText(i18n("")); + m_metaEdit->setText(i18n("")); + } + + if (m_typeCombo) + m_typeCombo->setCurrentItem(8); + + } else if (m_type == Note::EventRestType) { + + m_pitchLabel->hide(); + m_pitchSpinBox->hide(); + m_pitchEditButton->hide(); + + m_controllerLabel->hide(); + m_controllerLabelValue->hide(); + + m_velocityLabel->hide(); + m_velocitySpinBox->hide(); + + m_metaLabel->hide(); + m_metaEdit->hide(); + + if (m_typeCombo) + m_typeCombo->setCurrentItem(9); + + } else if (m_type == Clef::EventType) { + + m_durationLabel->hide(); + m_durationSpinBox->hide(); + m_durationEditButton->hide(); + + m_pitchLabel->hide(); + m_pitchSpinBox->hide(); + m_pitchEditButton->hide(); + + m_controllerLabel->show(); + m_controllerLabelValue->show(); + + m_controllerLabel->setText(i18n("Clef type:")); + + try { + Clef clef(m_event); + m_controllerLabelValue->setText(strtoqstr(clef.getClefType())); + } catch (...) { + m_controllerLabelValue->setText(i18n("")); + } + + m_velocityLabel->hide(); + m_velocitySpinBox->hide(); + + m_metaLabel->hide(); + m_metaEdit->hide(); + + if (m_typeCombo) + m_typeCombo->setCurrentItem(10); + + } else if (m_type == ::Rosegarden::Key::EventType) { + + m_durationLabel->hide(); + m_durationSpinBox->hide(); + m_durationEditButton->hide(); + + m_pitchLabel->hide(); + m_pitchSpinBox->hide(); + m_pitchEditButton->hide(); + + m_controllerLabel->show(); + m_controllerLabelValue->show(); + + m_controllerLabel->setText(i18n("Key name:")); + + try { + ::Rosegarden::Key key(m_event); + m_controllerLabelValue->setText(strtoqstr(key.getName())); + } catch (...) { + m_controllerLabelValue->setText(i18n("")); + } + + m_velocityLabel->hide(); + m_velocitySpinBox->hide(); + + m_metaLabel->hide(); + m_metaEdit->hide(); + + if (m_typeCombo) + m_typeCombo->setCurrentItem(11); + + } else if (m_type == Guitar::Chord::EventType) { + + m_durationLabel->hide(); + m_durationSpinBox->hide(); + m_durationEditButton->hide(); + + m_pitchLabel->hide(); + m_pitchSpinBox->hide(); + m_pitchEditButton->hide(); + + m_controllerLabel->hide(); + m_controllerLabelValue->hide(); + + m_velocityLabel->hide(); + m_velocitySpinBox->hide(); + + m_metaLabel->hide(); + m_metaEdit->hide(); + + // m_controllerLabel->setText(i18n("Text type:")); + // m_metaLabel->setText(i18n("Chord:")); + + // get the fingering event + try { + Guitar::Chord chord( m_event ); + } catch (...) { + // m_controllerLabelValue->setText(i18n("")); + // m_metaEdit->setText(i18n("")); + } + + if (m_typeCombo) + m_typeCombo->setCurrentItem(12); + + } else { + + m_durationLabel->setText(i18n("Unsupported event type:")); + m_durationLabel->show(); + m_durationSpinBox->hide(); + m_durationEditButton->hide(); + + m_pitchLabel->hide(); + m_pitchSpinBox->hide(); + m_pitchEditButton->hide(); + + m_controllerLabel->hide(); + m_controllerLabelValue->show(); + m_controllerLabelValue->setText(strtoqstr(m_type)); + + m_velocityLabel->hide(); + m_velocitySpinBox->hide(); + + m_metaLabel->hide(); + m_metaEdit->hide(); + + if (m_typeCombo) + m_typeCombo->setEnabled(false); + } + + if (m_typeCombo) + m_typeCombo->blockSignals(false); + m_timeSpinBox->blockSignals(false); + m_notationTimeSpinBox->blockSignals(false); + m_durationSpinBox->blockSignals(false); + m_notationDurationSpinBox->blockSignals(false); + m_pitchSpinBox->blockSignals(false); + m_velocitySpinBox->blockSignals(false); + m_metaEdit->blockSignals(false); + + slotLockNotationChanged(); +} + +Event +SimpleEventEditDialog::getEvent() +{ + bool useSeparateNotationValues = + (m_event.getType() == Note::EventType); + + if (m_typeCombo) { + + int subordering = 0; + if (m_type == Indication::EventType) { + subordering = Indication::EventSubOrdering; + } else if (m_type == Clef::EventType) { + subordering = Clef::EventSubOrdering; + } else if (m_type == ::Rosegarden::Key::EventType) { + subordering = ::Rosegarden::Key::EventSubOrdering; + } else if (m_type == Text::EventType) { + subordering = Text::EventSubOrdering; + } else if (m_type == Note::EventRestType) { + subordering = Note::EventRestSubOrdering; + } else if (m_type == PitchBend::EventType) { + subordering = PitchBend::EventSubOrdering; + } else if (m_type == Controller::EventType) { + subordering = Controller::EventSubOrdering; + } else if (m_type == KeyPressure::EventType) { + subordering = KeyPressure::EventSubOrdering; + } else if (m_type == ChannelPressure::EventType) { + subordering = ChannelPressure::EventSubOrdering; + } else if (m_type == ProgramChange::EventType) { + subordering = ProgramChange::EventSubOrdering; + } else if (m_type == SystemExclusive::EventType) { + subordering = SystemExclusive::EventSubOrdering; + } + + m_event = Event(m_type, + m_absoluteTime, + m_duration, + subordering, + (useSeparateNotationValues ? + m_notationAbsoluteTime : m_absoluteTime), + (useSeparateNotationValues ? + m_notationDuration : m_duration)); + + // ensure these are set on m_event correctly + slotPitchChanged(m_pitchSpinBox->value()); + slotVelocityChanged(m_velocitySpinBox->value()); + } + + Event event(m_event, + m_absoluteTime, + m_duration, + m_event.getSubOrdering(), + (useSeparateNotationValues ? + m_notationAbsoluteTime : m_absoluteTime), + (useSeparateNotationValues ? + m_notationDuration : m_duration)); + + // Values from the pitch and velocity spin boxes should already + // have been set on m_event (and thus on event) by slotPitchChanged + // and slotVelocityChanged. Absolute time and duration were set in + // the event ctor above; that just leaves the meta values. + + if (m_type == Indication::EventType) { + + event.set(Indication::IndicationTypePropertyName, + qstrtostr(m_metaEdit->text())); + + } else if (m_type == Text::EventType) { + + event.set(Text::TextTypePropertyName, + qstrtostr(m_controllerLabelValue->text())); + event.set(Text::TextPropertyName, + qstrtostr(m_metaEdit->text())); + + } else if (m_type == Clef::EventType) { + + event.set(Clef::ClefPropertyName, + qstrtostr(m_controllerLabelValue->text())); + + } else if (m_type == ::Rosegarden::Key::EventType) { + + event.set(::Rosegarden::Key::KeyPropertyName, + qstrtostr(m_controllerLabelValue->text())); + + } else if (m_type == SystemExclusive::EventType) { + + event.set(SystemExclusive::DATABLOCK, + qstrtostr(m_metaEdit->text())); + + } + + return event; +} + +void +SimpleEventEditDialog::slotEventTypeChanged(int value) +{ + m_type = qstrtostr(m_typeCombo->text(value)); + m_modified = true; + + if (m_type != m_event.getType()) + Event m_event(m_type, m_absoluteTime, m_duration); + + setupForEvent(); + + // update whatever pitch and velocity correspond to + if (!m_pitchSpinBox->isHidden()) + slotPitchChanged(m_pitchSpinBox->value()); + if (!m_velocitySpinBox->isHidden()) + slotVelocityChanged(m_velocitySpinBox->value()); +} + +void +SimpleEventEditDialog::slotAbsoluteTimeChanged(int value) +{ + m_absoluteTime = value; + + if (m_notationGroupBox->isHidden()) { + m_notationAbsoluteTime = value; + } else if (m_lockNotationValues->isChecked()) { + m_notationAbsoluteTime = value; + m_notationTimeSpinBox->setValue(value); + } + + m_modified = true; +} + +void +SimpleEventEditDialog::slotNotationAbsoluteTimeChanged(int value) +{ + m_notationAbsoluteTime = value; + m_modified = true; +} + +void +SimpleEventEditDialog::slotDurationChanged(int value) +{ + m_duration = value; + + if (m_notationGroupBox->isHidden()) { + m_notationDuration = value; + } else if (m_lockNotationValues->isChecked()) { + m_notationDuration = value; + m_notationDurationSpinBox->setValue(value); + } + + m_modified = true; +} + +void +SimpleEventEditDialog::slotNotationDurationChanged(int value) +{ + m_notationDuration = value; + m_modified = true; +} + +void +SimpleEventEditDialog::slotPitchChanged(int value) +{ + m_modified = true; + + if (m_type == Note::EventType) { + m_event.set(BaseProperties::PITCH, value); + + } else if (m_type == Controller::EventType) { + m_event.set(Controller::NUMBER, value); + + } else if (m_type == KeyPressure::EventType) { + m_event.set(KeyPressure::PITCH, value); + + } else if (m_type == ChannelPressure::EventType) { + m_event.set(ChannelPressure::PRESSURE, value); + + } else if (m_type == ProgramChange::EventType) { + if (value < 1) + value = 1; + m_event.set(ProgramChange::PROGRAM, value - 1); + + } else if (m_type == PitchBend::EventType) { + m_event.set(PitchBend::MSB, value); + } + //!!!??? sysex? +} + +void +SimpleEventEditDialog::slotVelocityChanged(int value) +{ + m_modified = true; + + if (m_type == Note::EventType) { + m_event.set(BaseProperties::VELOCITY, value); + + } else if (m_type == Controller::EventType) { + m_event.set(Controller::VALUE, value); + + } else if (m_type == KeyPressure::EventType) { + m_event.set(KeyPressure::PRESSURE, value); + + } else if (m_type == PitchBend::EventType) { + m_event.set(PitchBend::LSB, value); + } +} + +void +SimpleEventEditDialog::slotMetaChanged(const QString &) +{ + m_modified = true; +} + +void +SimpleEventEditDialog::slotLockNotationChanged() +{ + bool enable = !m_lockNotationValues->isChecked(); + m_notationTimeSpinBox->setEnabled(enable); + m_notationTimeEditButton->setEnabled(enable); + m_notationDurationSpinBox->setEnabled(enable); + m_notationDurationEditButton->setEnabled(enable); +} + +void +SimpleEventEditDialog::slotEditAbsoluteTime() +{ + TimeDialog dialog(this, i18n("Edit Event Time"), + &m_doc->getComposition(), + m_timeSpinBox->value(), + true); + if (dialog.exec() == QDialog::Accepted) { + m_timeSpinBox->setValue(dialog.getTime()); + } +} + +void +SimpleEventEditDialog::slotEditNotationAbsoluteTime() +{ + TimeDialog dialog(this, i18n("Edit Event Notation Time"), + &m_doc->getComposition(), + m_notationTimeSpinBox->value(), + true); + if (dialog.exec() == QDialog::Accepted) { + m_notationTimeSpinBox->setValue(dialog.getTime()); + } +} + +void +SimpleEventEditDialog::slotEditDuration() +{ + TimeDialog dialog(this, i18n("Edit Duration"), + &m_doc->getComposition(), + m_timeSpinBox->value(), + m_durationSpinBox->value(), + true); + if (dialog.exec() == QDialog::Accepted) { + m_durationSpinBox->setValue(dialog.getTime()); + } +} + +void +SimpleEventEditDialog::slotEditNotationDuration() +{ + TimeDialog dialog(this, i18n("Edit Notation Duration"), + &m_doc->getComposition(), + m_notationTimeSpinBox->value(), + m_notationDurationSpinBox->value(), + true); + if (dialog.exec() == QDialog::Accepted) { + m_notationDurationSpinBox->setValue(dialog.getTime()); + } +} + +void +SimpleEventEditDialog::slotEditPitch() +{ + PitchDialog dialog(this, i18n("Edit Pitch"), m_pitchSpinBox->value()); + if (dialog.exec() == QDialog::Accepted) { + m_pitchSpinBox->setValue(dialog.getPitch()); + } +} + +void +SimpleEventEditDialog::slotSysexLoad() +{ + QString path = KFileDialog::getOpenFileName(":SYSTEMEXCLUSIVE", + i18n("*.syx|System exclusive files (*.syx)"), + this, i18n("Load System Exclusive data in File")); + if (path.isNull()) + return ; + + QFile file(path); + file.open(IO_ReadOnly); + std::string s; + unsigned char c; + while (((c = (unsigned char)file.getch()) != 0xf0) && (file.status() == IO_Ok)) + ; + while ( file.status() == IO_Ok ) { + s += c; + if (c == 0xf7 ) + break; + c = (unsigned char)file.getch(); + } + file.close(); + m_metaEdit->setText(strtoqstr(SystemExclusive::toHex(s))); +} + +void +SimpleEventEditDialog::slotSysexSave() +{ + QString path = KFileDialog::getSaveFileName(":SYSTEMEXCLUSIVE", + i18n("*.syx|System exclusive files (*.syx)"), + this, i18n("Save System Exclusive data to...")); + if (path.isNull()) + return ; + + QFile file(path); + file.open(IO_WriteOnly); + SystemExclusive sysEx(m_event); + file.writeBlock(sysEx.getRawData().c_str(), sysEx.getRawData().length()); + file.close(); +} + +} +#include "SimpleEventEditDialog.moc" diff --git a/src/gui/dialogs/SimpleEventEditDialog.h b/src/gui/dialogs/SimpleEventEditDialog.h new file mode 100644 index 0000000..60b8441 --- /dev/null +++ b/src/gui/dialogs/SimpleEventEditDialog.h @@ -0,0 +1,134 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SIMPLEEVENTEDITDIALOG_H_ +#define _RG_SIMPLEEVENTEDITDIALOG_H_ + +#include "base/Event.h" +#include +#include + + +class QWidget; +class QString; +class QSpinBox; +class QPushButton; +class QLineEdit; +class QLabel; +class QGroupBox; +class QCheckBox; +class KComboBox; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +class SimpleEventEditDialog : public KDialogBase +{ + Q_OBJECT +public: + SimpleEventEditDialog(QWidget *parent, + RosegardenGUIDoc *doc, + const Event &event, + bool inserting = false); // inserting or editing + + bool isModified() const { return m_modified; } + Event getEvent(); + + // Setup the dialog for a new event type + void setupForEvent(); + +public slots: + void slotEventTypeChanged(int value); + void slotAbsoluteTimeChanged(int value); + void slotDurationChanged(int value); + void slotNotationAbsoluteTimeChanged(int value); + void slotNotationDurationChanged(int value); + void slotPitchChanged(int value); + void slotVelocityChanged(int value); + void slotMetaChanged(const QString &); + void slotEditAbsoluteTime(); + void slotEditNotationAbsoluteTime(); + void slotEditDuration(); + void slotEditNotationDuration(); + void slotLockNotationChanged(); + void slotEditPitch(); + void slotSysexLoad(); + void slotSysexSave(); + +protected: + Event m_event; + RosegardenGUIDoc *m_doc; + + std::string m_type; + timeT m_absoluteTime; + timeT m_notationAbsoluteTime; + timeT m_duration; + timeT m_notationDuration; + + KComboBox *m_typeCombo; + QLabel *m_typeLabel; + + QLabel *m_timeLabel; + QLabel *m_durationLabel; + QLabel *m_pitchLabel; + QLabel *m_velocityLabel; + QLabel *m_metaLabel; + QLabel *m_controllerLabel; + QLabel *m_controllerLabelValue; + + QSpinBox *m_timeSpinBox; + QSpinBox *m_durationSpinBox; + QSpinBox *m_pitchSpinBox; + QSpinBox *m_velocitySpinBox; + + QPushButton *m_timeEditButton; + QPushButton *m_durationEditButton; + QPushButton *m_pitchEditButton; + QPushButton *m_sysexLoadButton; + QPushButton *m_sysexSaveButton; + + QGroupBox *m_notationGroupBox; + QLabel *m_notationTimeLabel; + QLabel *m_notationDurationLabel; + QSpinBox *m_notationTimeSpinBox; + QSpinBox *m_notationDurationSpinBox; + QPushButton *m_notationTimeEditButton; + QPushButton *m_notationDurationEditButton; + QCheckBox *m_lockNotationValues; + + QLineEdit *m_metaEdit; + + bool m_modified; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/SplitByPitchDialog.cpp b/src/gui/dialogs/SplitByPitchDialog.cpp new file mode 100644 index 0000000..9b3dffa --- /dev/null +++ b/src/gui/dialogs/SplitByPitchDialog.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SplitByPitchDialog.h" + +#include +#include "commands/segment/SegmentSplitByPitchCommand.h" +#include "gui/general/ClefIndex.h" +#include "gui/widgets/PitchChooser.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +SplitByPitchDialog::SplitByPitchDialog(QWidget *parent) : + KDialogBase(parent, 0, true, i18n("Split by Pitch"), Ok | Cancel) +{ + QVBox *vBox = makeVBoxMainWidget(); + + QFrame *frame = new QFrame(vBox); + + QGridLayout *layout = new QGridLayout(frame, 4, 3, 10, 5); + + m_pitch = new PitchChooser(i18n("Starting split pitch"), frame, 60); + layout->addMultiCellWidget(m_pitch, 0, 0, 0, 2, Qt::AlignHCenter); + + m_range = new QCheckBox(i18n("Range up and down to follow music"), frame); + layout->addMultiCellWidget(m_range, + 1, 1, // fromRow, toRow + 0, 2 // fromCol, toCol + ); + + m_duplicate = new QCheckBox(i18n("Duplicate non-note events"), frame); + layout->addMultiCellWidget(m_duplicate, 2, 2, 0, 2); + + layout->addWidget(new QLabel(i18n("Clef handling:"), frame), 3, 0); + + m_clefs = new KComboBox(frame); + m_clefs->insertItem(i18n("Leave clefs alone")); + m_clefs->insertItem(i18n("Guess new clefs")); + m_clefs->insertItem(i18n("Use treble and bass clefs")); + layout->addMultiCellWidget(m_clefs, 3, 3, 1, 2); + + m_range->setChecked(true); + m_duplicate->setChecked(true); + m_clefs->setCurrentItem(2); +} + +int +SplitByPitchDialog::getPitch() +{ + return m_pitch->getPitch(); +} + +bool +SplitByPitchDialog::getShouldRange() +{ + return m_range->isChecked(); +} + +bool +SplitByPitchDialog::getShouldDuplicateNonNoteEvents() +{ + return m_duplicate->isChecked(); +} + +int +SplitByPitchDialog::getClefHandling() +{ + switch (m_clefs->currentItem()) { + case 0: + return (int)SegmentSplitByPitchCommand::LeaveClefs; + case 1: + return (int)SegmentSplitByPitchCommand::RecalculateClefs; + default: + return (int)SegmentSplitByPitchCommand::UseTrebleAndBassClefs; + } +} + +} +#include "SplitByPitchDialog.moc" diff --git a/src/gui/dialogs/SplitByPitchDialog.h b/src/gui/dialogs/SplitByPitchDialog.h new file mode 100644 index 0000000..40a6fb8 --- /dev/null +++ b/src/gui/dialogs/SplitByPitchDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SPLITBYPITCHDIALOG_H_ +#define _RG_SPLITBYPITCHDIALOG_H_ + +#include + + +class QWidget; +class QCheckBox; +class KComboBox; + + +namespace Rosegarden +{ + +class PitchChooser; + + +class SplitByPitchDialog : public KDialogBase +{ + Q_OBJECT +public: + SplitByPitchDialog(QWidget *parent); + + int getPitch(); + + bool getShouldRange(); + bool getShouldDuplicateNonNoteEvents(); + int getClefHandling(); // actually SegmentSplitByPitchCommand::ClefHandling + +private: + PitchChooser *m_pitch; + + QCheckBox *m_range; + QCheckBox *m_duplicate; + KComboBox *m_clefs; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/SplitByRecordingSrcDialog.cpp b/src/gui/dialogs/SplitByRecordingSrcDialog.cpp new file mode 100644 index 0000000..cc61bfa --- /dev/null +++ b/src/gui/dialogs/SplitByRecordingSrcDialog.cpp @@ -0,0 +1,114 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "SplitByRecordingSrcDialog.h" +#include + +#include +#include "misc/Strings.h" +#include "base/MidiDevice.h" +#include "document/RosegardenGUIDoc.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +SplitByRecordingSrcDialog::SplitByRecordingSrcDialog(QWidget *parent, RosegardenGUIDoc *doc) : + KDialogBase(parent, 0, true, i18n("Split by Recording Source"), Ok | Cancel ) +{ + QVBox *vBox = makeVBoxMainWidget(); + + QGroupBox *groupBox = new QGroupBox + (1, Horizontal, i18n("Recording Source"), vBox); + QFrame *frame = new QFrame(groupBox); + QGridLayout *layout = new QGridLayout(frame, 2, 2, 10, 5); + + layout->addWidget(new QLabel( i18n("Channel:"), frame ), 0, 0); + m_channel = new KComboBox( frame ); + m_channel->setSizeLimit( 17 ); + layout->addWidget(m_channel, 0, 1); + QSpacerItem *spacer = new QSpacerItem( 1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum ); + layout->addItem( spacer, 0, 2 ); + + m_channel->insertItem(i18n("any")); + for (int i = 1; i < 17; ++i) { + m_channel->insertItem(QString::number(i)); + } + + layout->addWidget(new QLabel( i18n("Device:"), frame ), 1, 0); + m_device = new KComboBox( frame ); + layout->addMultiCellWidget( m_device, 1, 1, 1, 2 ); + + m_deviceIds.clear(); + m_deviceIds.push_back( -1); + m_device->insertItem(i18n("any")); + + DeviceList *devices = doc->getStudio().getDevices(); + DeviceListConstIterator it; + for (it = devices->begin(); it != devices->end(); it++) { + MidiDevice *dev = + dynamic_cast(*it); + if (dev && dev->getDirection() == MidiDevice::Record) { + QString label = QString::number(dev->getId()); + label += ": "; + label += strtoqstr(dev->getName()); + QString connection = strtoqstr(dev->getConnection()); + label += " - "; + if (connection == "") + label += i18n("No connection"); + else + label += connection; + m_device->insertItem(label); + m_deviceIds.push_back(dev->getId()); + } + } + + m_channel->setCurrentItem(0); + m_device->setCurrentItem(0); +} + +int +SplitByRecordingSrcDialog::getChannel() +{ + return m_channel->currentItem() - 1; +} + +int +SplitByRecordingSrcDialog::getDevice() +{ + return m_deviceIds[m_device->currentItem()]; +} + +} +#include "SplitByRecordingSrcDialog.moc" diff --git a/src/gui/dialogs/SplitByRecordingSrcDialog.h b/src/gui/dialogs/SplitByRecordingSrcDialog.h new file mode 100644 index 0000000..af982a2 --- /dev/null +++ b/src/gui/dialogs/SplitByRecordingSrcDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SPLITBYRECORDINGSRCDIALOG_H_ +#define _RG_SPLITBYRECORDINGSRCDIALOG_H_ + +#include +#include +#include "gui/application/RosegardenDCOP.h" + + +class QWidget; +class KComboBox; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; + + +class SplitByRecordingSrcDialog : public KDialogBase +{ + Q_OBJECT +public: + SplitByRecordingSrcDialog(QWidget *parent, RosegardenGUIDoc *doc); + + int getChannel(); + int getDevice(); + +private: + std::vector m_deviceIds; + KComboBox *m_channel; + KComboBox *m_device; +}; + + +} + +#endif diff --git a/src/gui/dialogs/TempoDialog.cpp b/src/gui/dialogs/TempoDialog.cpp new file mode 100644 index 0000000..3896fde --- /dev/null +++ b/src/gui/dialogs/TempoDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TempoDialog.h" +#include + +#include +#include "misc/Debug.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/RealTime.h" +#include "document/RosegardenGUIDoc.h" +#include "gui/editors/notation/NotePixmapFactory.h" +#include "gui/widgets/TimeWidget.h" +#include "gui/widgets/HSpinBox.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +TempoDialog::TempoDialog(QWidget *parent, RosegardenGUIDoc *doc, + bool timeEditable): + KDialogBase(parent, 0, true, i18n("Insert Tempo Change"), Ok | Cancel | Help), + m_doc(doc), + m_tempoTime(0) +{ + setHelp("tempo"); + + QVBox *vbox = makeVBoxMainWidget(); + QGroupBox *groupBox = new QGroupBox(1, Horizontal, i18n("Tempo"), vbox); + + QFrame *frame = new QFrame(groupBox); + QGridLayout *layout = new QGridLayout(frame, 4, 3, 5, 5); + + // Set tempo + layout->addWidget(new QLabel(i18n("New tempo:"), frame), 0, 1); + m_tempoValueSpinBox = new HSpinBox(frame, 0, 100000, 0.0, 1000.0, 5); + layout->addWidget(m_tempoValueSpinBox, 0, 2); + + connect(m_tempoValueSpinBox, SIGNAL(valueChanged(const QString &)), + SLOT(slotTempoChanged(const QString &))); + + m_tempoTap= new QPushButton(i18n("Tap"), frame); + layout->addWidget(m_tempoTap, 0, 3); + connect(m_tempoTap, SIGNAL(clicked()), SLOT(slotTapClicked())); + + + m_tempoConstant = new QRadioButton(i18n("Tempo is fixed until the following tempo change"), frame); + m_tempoRampToNext = new QRadioButton(i18n("Tempo ramps to the following tempo"), frame); + m_tempoRampToTarget = new QRadioButton(i18n("Tempo ramps to:"), frame); + + // m_tempoTargetCheckBox = new QCheckBox(i18n("Ramping to:"), frame); + m_tempoTargetSpinBox = new HSpinBox(frame, 0, 100000, 0.0, 1000.0, 5); + + // layout->addMultiCellWidget(m_tempoTargetCheckBox, 1, 1, 0, 1, AlignRight); + // layout->addWidget(m_tempoTargetSpinBox, 1, 2); + + layout->addMultiCellWidget(m_tempoConstant, 1, 1, 1, 2); + layout->addMultiCellWidget(m_tempoRampToNext, 2, 2, 1, 2); + layout->addWidget(m_tempoRampToTarget, 3, 1); + layout->addWidget(m_tempoTargetSpinBox, 3, 2); + + // connect(m_tempoTargetCheckBox, SIGNAL(clicked()), + // SLOT(slotTargetCheckBoxClicked())); + connect(m_tempoConstant, SIGNAL(clicked()), + SLOT(slotTempoConstantClicked())); + connect(m_tempoRampToNext, SIGNAL(clicked()), + SLOT(slotTempoRampToNextClicked())); + connect(m_tempoRampToTarget, SIGNAL(clicked()), + SLOT(slotTempoRampToTargetClicked())); + connect(m_tempoTargetSpinBox, SIGNAL(valueChanged(const QString &)), + SLOT(slotTargetChanged(const QString &))); + + m_tempoBeatLabel = new QLabel(frame); + layout->addWidget(m_tempoBeatLabel, 0, 4); + + m_tempoBeat = new QLabel(frame); + layout->addWidget(m_tempoBeat, 0, 5); + + m_tempoBeatsPerMinute = new QLabel(frame); + layout->addWidget(m_tempoBeatsPerMinute, 0, 6); + + m_timeEditor = 0; + + if (timeEditable) { + m_timeEditor = new TimeWidget + (i18n("Time of tempo change"), + vbox, &m_doc->getComposition(), 0, true); + populateTempo(); + return ; + } + + // Scope Box + QButtonGroup *scopeGroup = new QButtonGroup(1, Horizontal, + i18n("Scope"), vbox); + +// new QLabel(scopeBox); + + QVBox *scopeBox = new QVBox(scopeGroup); + + scopeBox->setSpacing(5); + scopeBox->setMargin(5); + + QHBox *currentBox = new QHBox(scopeBox); + new QLabel(i18n("The pointer is currently at "), currentBox); + m_tempoTimeLabel = new QLabel(currentBox); + m_tempoBarLabel = new QLabel(currentBox); + QLabel *spare = new QLabel(currentBox); + currentBox->setStretchFactor(spare, 20); + + m_tempoStatusLabel = new QLabel(scopeBox); + +// new QLabel(scopeBox); + + QHBox *changeWhereBox = new QHBox(scopeBox); + spare = new QLabel(" ", changeWhereBox); + QVBox *changeWhereVBox = new QVBox(changeWhereBox); + changeWhereBox->setStretchFactor(changeWhereVBox, 20); + + m_tempoChangeHere = new QRadioButton + (i18n("Apply this tempo from here onwards"), + changeWhereVBox); + + m_tempoChangeBefore = new QRadioButton + (i18n("Replace the last tempo change"), + changeWhereVBox); + m_tempoChangeBeforeAt = new QLabel(changeWhereVBox); + m_tempoChangeBeforeAt->hide(); + + m_tempoChangeStartOfBar = new QRadioButton + (i18n("Apply this tempo from the start of this bar"), changeWhereVBox); + + m_tempoChangeGlobal = new QRadioButton + (i18n("Apply this tempo to the whole composition"), changeWhereVBox); + + QHBox *optionHBox = new QHBox(changeWhereVBox); + new QLabel(" ", optionHBox); + m_defaultBox = new QCheckBox + (i18n("Also make this the default tempo"), optionHBox); + spare = new QLabel(optionHBox); + optionHBox->setStretchFactor(spare, 20); + +// new QLabel(scopeBox); + + connect(m_tempoChangeHere, SIGNAL(clicked()), + SLOT(slotActionChanged())); + connect(m_tempoChangeBefore, SIGNAL(clicked()), + SLOT(slotActionChanged())); + connect(m_tempoChangeStartOfBar, SIGNAL(clicked()), + SLOT(slotActionChanged())); + connect(m_tempoChangeGlobal, SIGNAL(clicked()), + SLOT(slotActionChanged())); + + m_tempoChangeHere->setChecked(true); + + // disable initially + m_defaultBox->setEnabled(false); + + populateTempo(); +} + +TempoDialog::~TempoDialog() +{} + +void +TempoDialog::setTempoPosition(timeT time) +{ + m_tempoTime = time; + populateTempo(); +} + +void +TempoDialog::populateTempo() +{ + Composition &comp = m_doc->getComposition(); + tempoT tempo = comp.getTempoAtTime(m_tempoTime); + std::pair ramping(false, tempo); + + int tempoChangeNo = comp.getTempoChangeNumberAt(m_tempoTime); + if (tempoChangeNo >= 0) { + tempo = comp.getTempoChange(tempoChangeNo).second; + ramping = comp.getTempoRamping(tempoChangeNo, false); + } + + m_tempoValueSpinBox->setValue(tempo); + + if (ramping.first) { + if (ramping.second) { + m_tempoTargetSpinBox->setEnabled(true); + m_tempoTargetSpinBox->setValue(ramping.second); + m_tempoConstant->setChecked(false); + m_tempoRampToNext->setChecked(false); + m_tempoRampToTarget->setChecked(true); + } else { + ramping = comp.getTempoRamping(tempoChangeNo, true); + m_tempoTargetSpinBox->setEnabled(false); + m_tempoTargetSpinBox->setValue(ramping.second); + m_tempoConstant->setChecked(false); + m_tempoRampToNext->setChecked(true); + m_tempoRampToTarget->setChecked(false); + } + } else { + m_tempoTargetSpinBox->setEnabled(false); + m_tempoTargetSpinBox->setValue(tempo); + m_tempoConstant->setChecked(true); + m_tempoRampToNext->setChecked(false); + m_tempoRampToTarget->setChecked(false); + } + + // m_tempoTargetCheckBox->setChecked(ramping.first); + m_tempoTargetSpinBox->setEnabled(ramping.first); + + updateBeatLabels(comp.getTempoQpm(tempo)); + + if (m_timeEditor) { + m_timeEditor->slotSetTime(m_tempoTime); + return ; + } + + RealTime tempoTime = comp.getElapsedRealTime(m_tempoTime); + QString milliSeconds; + milliSeconds.sprintf("%03d", tempoTime.msec()); + m_tempoTimeLabel->setText(i18n("%1.%2 s,").arg(tempoTime.sec) + .arg(milliSeconds)); + + int barNo = comp.getBarNumber(m_tempoTime); + if (comp.getBarStart(barNo) == m_tempoTime) { + m_tempoBarLabel->setText + (i18n("at the start of measure %1.").arg(barNo + 1)); + m_tempoChangeStartOfBar->setEnabled(false); + } else { + m_tempoBarLabel->setText( + i18n("in the middle of measure %1.").arg(barNo + 1)); + m_tempoChangeStartOfBar->setEnabled(true); + } + + m_tempoChangeBefore->setEnabled(false); + m_tempoChangeBeforeAt->setEnabled(false); + + bool havePrecedingTempo = false; + + if (tempoChangeNo >= 0) { + + timeT lastTempoTime = comp.getTempoChange(tempoChangeNo).first; + if (lastTempoTime < m_tempoTime) { + + RealTime lastRT = comp.getElapsedRealTime(lastTempoTime); + QString lastms; + lastms.sprintf("%03d", lastRT.msec()); + int lastBar = comp.getBarNumber(lastTempoTime); + m_tempoChangeBeforeAt->setText + (i18n(" (at %1.%2 s, in measure %3)").arg(lastRT.sec) + .arg(lastms).arg(lastBar + 1)); + m_tempoChangeBeforeAt->show(); + + m_tempoChangeBefore->setEnabled(true); + m_tempoChangeBeforeAt->setEnabled(true); + + havePrecedingTempo = true; + } + } + + if (comp.getTempoChangeCount() > 0) { + + if (havePrecedingTempo) { + m_tempoStatusLabel->hide(); + } else { + m_tempoStatusLabel->setText + (i18n("There are no preceding tempo changes.")); + } + + m_tempoChangeGlobal->setEnabled(true); + + } else { + + m_tempoStatusLabel->setText + (i18n("There are no other tempo changes.")); + + m_tempoChangeGlobal->setEnabled(false); + } + + m_defaultBox->setEnabled(false); +} + +void +TempoDialog::updateBeatLabels(double qpm) +{ + Composition &comp = m_doc->getComposition(); + + // If the time signature's beat is not a crotchet, need to show + // bpm separately + + timeT beat = comp.getTimeSignatureAt(m_tempoTime).getBeatDuration(); + if (beat == Note(Note::Crotchet).getDuration()) { + m_tempoBeatLabel->setText(i18n(" bpm")); + m_tempoBeatLabel->show(); + m_tempoBeat->hide(); + m_tempoBeatsPerMinute->hide(); + } else { + // m_tempoBeatLabel->setText(" ("); + m_tempoBeatLabel->setText(" "); + + timeT error = 0; + m_tempoBeat->setPixmap(NotePixmapFactory::toQPixmap + (NotePixmapFactory::makeNoteMenuPixmap(beat, error))); + m_tempoBeat->setMaximumWidth(25); + if (error) + m_tempoBeat->setPixmap(NotePixmapFactory::toQPixmap + (NotePixmapFactory::makeToolbarPixmap + ("menu-no-note"))); + + m_tempoBeatsPerMinute->setText + // (QString("= %1 )").arg + (QString("= %1 ").arg + (int(qpm * Note(Note::Crotchet).getDuration() / beat))); + m_tempoBeatLabel->show(); + m_tempoBeat->show(); + m_tempoBeatsPerMinute->show(); + } +} + +void +TempoDialog::slotTempoChanged(const QString &) +{ + updateBeatLabels(double(m_tempoValueSpinBox->valuef())); +} + +void +TempoDialog::slotTargetChanged(const QString &) +{ + //... +} + +void +TempoDialog::slotTempoConstantClicked() +{ + m_tempoRampToNext->setChecked(false); + m_tempoRampToTarget->setChecked(false); + m_tempoTargetSpinBox->setEnabled(false); +} + +void +TempoDialog::slotTempoRampToNextClicked() +{ + m_tempoConstant->setChecked(false); + m_tempoRampToTarget->setChecked(false); + m_tempoTargetSpinBox->setEnabled(false); +} + +void +TempoDialog::slotTempoRampToTargetClicked() +{ + m_tempoConstant->setChecked(false); + m_tempoRampToNext->setChecked(false); + m_tempoTargetSpinBox->setEnabled(true); +} + +void +TempoDialog::slotActionChanged() +{ + m_defaultBox->setEnabled(m_tempoChangeGlobal->isChecked()); +} + +void +TempoDialog::slotOk() +{ + tempoT tempo = m_tempoValueSpinBox->value(); + RG_DEBUG << "Tempo is " << tempo << endl; + + tempoT target = -1; + if (m_tempoRampToNext->isChecked()) { + target = 0; + } else if (m_tempoRampToTarget->isChecked()) { + target = m_tempoTargetSpinBox->value(); + } + + RG_DEBUG << "Target is " << target << endl; + + if (m_timeEditor) { + + emit changeTempo(m_timeEditor->getTime(), + tempo, + target, + AddTempo); + + } else { + + TempoDialogAction action = AddTempo; + + if (m_tempoChangeBefore->isChecked()) { + action = ReplaceTempo; + } else if (m_tempoChangeStartOfBar->isChecked()) { + action = AddTempoAtBarStart; + } else if (m_tempoChangeGlobal->isChecked()) { + action = GlobalTempo; + if (m_defaultBox->isChecked()) { + action = GlobalTempoWithDefault; + } + } + + emit changeTempo(m_tempoTime, + tempo, + target, + action); + } + + KDialogBase::slotOk(); +} + +void +TempoDialog::slotTapClicked() +{ + QTime now = QTime::currentTime(); + + if (m_tapMinusOne != QTime()) { + + int ms1 = m_tapMinusOne.msecsTo(now); + + if (ms1 < 10000) { + + int msec = ms1; + + if (m_tapMinusTwo != QTime()) { + int ms2 = m_tapMinusTwo.msecsTo(m_tapMinusOne); + if (ms2 < 10000) { + msec = (ms1 + ms2) / 2; + } + } + + int bpm = 60000 / msec; + m_tempoValueSpinBox->setValue(bpm * 100000); + } + } + + m_tapMinusTwo = m_tapMinusOne; + m_tapMinusOne = now; +} + + +} + +#include "TempoDialog.moc" diff --git a/src/gui/dialogs/TempoDialog.h b/src/gui/dialogs/TempoDialog.h new file mode 100644 index 0000000..dd3edf1 --- /dev/null +++ b/src/gui/dialogs/TempoDialog.h @@ -0,0 +1,128 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_TEMPODIALOG_H_ +#define _RG_TEMPODIALOG_H_ + +#include +#include "base/Event.h" +#include "base/Composition.h" +#include +#include + + +class QWidget; +class QString; +class QRadioButton; +class QLabel; +class QCheckBox; + + +namespace Rosegarden +{ + +class TimeWidget; +class RosegardenGUIDoc; +class HSpinBox; + + +class TempoDialog : public KDialogBase +{ + Q_OBJECT +public: + enum TempoDialogAction { + AddTempo, + ReplaceTempo, + AddTempoAtBarStart, + GlobalTempo, + GlobalTempoWithDefault + }; + + TempoDialog(QWidget *parent, RosegardenGUIDoc *doc, + bool timeEditable = false); + ~TempoDialog(); + + // Set the position at which we're checking the tempo + // + void setTempoPosition(timeT time); + +public slots: + virtual void slotOk(); + void slotActionChanged(); + void slotTempoChanged(const QString &); + void slotTempoConstantClicked(); + void slotTempoRampToNextClicked(); + void slotTempoRampToTargetClicked(); + void slotTargetChanged(const QString &); + void slotTapClicked(); + +signals: + // Return results in this signal + // + void changeTempo(timeT, // tempo change time + tempoT, // tempo value + tempoT, // target tempo value + TempoDialog::TempoDialogAction); // tempo action + +protected: + void populateTempo(); + void updateBeatLabels(double newTempo); + + //--------------- Data members --------------------------------- + + RosegardenGUIDoc *m_doc; + timeT m_tempoTime; + HSpinBox *m_tempoValueSpinBox; + QPushButton *m_tempoTap; + QTime m_tapMinusTwo; + QTime m_tapMinusOne; + + QRadioButton *m_tempoConstant; + QRadioButton *m_tempoRampToNext; + QRadioButton *m_tempoRampToTarget; + HSpinBox *m_tempoTargetSpinBox; + + QLabel *m_tempoBeatLabel; + QLabel *m_tempoBeat; + QLabel *m_tempoBeatsPerMinute; + + TimeWidget *m_timeEditor; + + QLabel *m_tempoTimeLabel; + QLabel *m_tempoBarLabel; + QLabel *m_tempoStatusLabel; + + QRadioButton *m_tempoChangeHere; + QRadioButton *m_tempoChangeBefore; + QLabel *m_tempoChangeBeforeAt; + QRadioButton *m_tempoChangeStartOfBar; + QRadioButton *m_tempoChangeGlobal; + QCheckBox *m_defaultBox; +}; + + +} + +#endif diff --git a/src/gui/dialogs/TextEventDialog.cpp b/src/gui/dialogs/TextEventDialog.cpp new file mode 100644 index 0000000..156b5d1 --- /dev/null +++ b/src/gui/dialogs/TextEventDialog.cpp @@ -0,0 +1,593 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TextEventDialog.h" +#include + +#include +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/NotationTypes.h" +#include "gui/editors/notation/NotePixmapFactory.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Rosegarden +{ + +TextEventDialog::TextEventDialog(QWidget *parent, + NotePixmapFactory *npf, + Text defaultText, + int maxLength) : + KDialogBase(parent, 0, true, i18n("Text"), Ok | Cancel | Help), + m_notePixmapFactory(npf), + m_styles(Text::getUserStyles()) /*, + //m_directives(Text::getLilyPondDirectives()) */ +{ + setHelp("nv-text"); + QVBox *vbox = makeVBoxMainWidget(); + + QGroupBox *entryBox = new QGroupBox + (1, Horizontal, i18n("Specification"), vbox); + QGroupBox *exampleBox = new QGroupBox + (1, Horizontal, i18n("Preview"), vbox); + + QGrid *entryGrid = new QGrid(2, QGrid::Horizontal, entryBox); + + new QLabel(i18n("Text: "), entryGrid); + m_text = new QLineEdit(entryGrid); + m_text->setText(strtoqstr(defaultText.getText())); + if (maxLength > 0) + m_text->setMaxLength(maxLength); + + // style combo + new QLabel(i18n("Style: "), entryGrid); + m_typeCombo = new KComboBox(entryGrid); + + for (unsigned int i = 0; i < m_styles.size(); ++i) + { + + std::string style = m_styles[i]; + + // if the style is in this list, we can i18n it (kludgy): + + if (style == Text::Dynamic) { // index // + m_typeCombo->insertItem(i18n("Dynamic")); // 0 + + } else if (style == Text::Direction) { + m_typeCombo->insertItem(i18n("Direction")); // 1 + + } else if (style == Text::LocalDirection) { + m_typeCombo->insertItem(i18n("Local Direction")); // 2 + + } else if (style == Text::Tempo) { + m_typeCombo->insertItem(i18n("Tempo")); // 3 + + } else if (style == Text::LocalTempo) { + m_typeCombo->insertItem(i18n("Local Tempo")); // 4 + + } else if (style == Text::Lyric) { + m_typeCombo->insertItem(i18n("Lyric")); // 5 + + } else if (style == Text::Chord) { + m_typeCombo->insertItem(i18n("Chord")); // 6 + + } else if (style == Text::Annotation) { + m_typeCombo->insertItem(i18n("Annotation")); // 7 + + } else if (style == Text::LilyPondDirective) { + m_typeCombo->insertItem(i18n("LilyPond Directive")); // 8 + + } else { + // not i18n()-able + + std::string styleName; + styleName += (char)toupper(style[0]); + styleName += style.substr(1); + + int uindex = styleName.find('_'); + if (uindex > 0) { + styleName = + styleName.substr(0, uindex) + " " + + styleName.substr(uindex + 1); + } + + m_typeCombo->insertItem(strtoqstr(styleName)); + } + + if (style == defaultText.getTextType()) { + m_typeCombo->setCurrentItem(m_typeCombo->count() - 1); + } + } + + m_verseLabel = new QLabel(i18n("Verse: "), entryGrid); + m_verseLabel->hide(); + m_verseSpin = new QSpinBox(entryGrid); + m_verseSpin->setMinValue(1); + m_verseSpin->setMaxValue(12); + m_verseSpin->setLineStep(1); + m_verseSpin->setValue(defaultText.getVerse() + 1); + m_verseSpin->hide(); + + // dynamic shortcuts combo + m_dynamicShortcutLabel = new QLabel(i18n("Dynamic: "), entryGrid); + m_dynamicShortcutLabel->hide(); + + m_dynamicShortcutCombo = new KComboBox(entryGrid); + m_dynamicShortcutCombo->insertItem(i18n("ppp")); + m_dynamicShortcutCombo->insertItem(i18n("pp")); + m_dynamicShortcutCombo->insertItem(i18n("p")); + m_dynamicShortcutCombo->insertItem(i18n("mp")); + m_dynamicShortcutCombo->insertItem(i18n("mf")); + m_dynamicShortcutCombo->insertItem(i18n("f")); + m_dynamicShortcutCombo->insertItem(i18n("ff")); + m_dynamicShortcutCombo->insertItem(i18n("fff")); + m_dynamicShortcutCombo->insertItem(i18n("rfz")); + m_dynamicShortcutCombo->insertItem(i18n("sf")); + m_dynamicShortcutCombo->hide(); + + // direction shortcuts combo + m_directionShortcutLabel = new QLabel(i18n("Direction: "), entryGrid); + m_directionShortcutLabel->hide(); + + m_directionShortcutCombo = new KComboBox(entryGrid); + // note, the " ," is a breath mark; the extra spaces are a cheap hack to + // try to improve the probability of Rosegarden drawing the blasted thing + // where it's supposed to go, without the need to micro-diddle each and + // every bliffin' one. (Micro-diddling is not exportable to LilyPond + // either, is it? I rather doubt it.) + m_directionShortcutCombo->insertItem(i18n(" ,")); + m_directionShortcutCombo->insertItem(i18n("D.C. al Fine")); + m_directionShortcutCombo->insertItem(i18n("D.S. al Fine")); + m_directionShortcutCombo->insertItem(i18n("Fine")); + m_directionShortcutCombo->insertItem(i18n("D.S. al Coda")); + m_directionShortcutCombo->insertItem(i18n("to Coda")); + m_directionShortcutCombo->insertItem(i18n("Coda")); + m_directionShortcutCombo->hide(); + + // local direction shortcuts combo + m_localDirectionShortcutLabel = new QLabel(i18n("Local Direction: "), entryGrid); + m_localDirectionShortcutLabel->hide(); + + m_localDirectionShortcutCombo = new KComboBox(entryGrid); + m_localDirectionShortcutCombo->insertItem(i18n("accel.")); + m_localDirectionShortcutCombo->insertItem(i18n("ritard.")); + m_localDirectionShortcutCombo->insertItem(i18n("ralletando")); + m_localDirectionShortcutCombo->insertItem(i18n("a tempo")); + m_localDirectionShortcutCombo->insertItem(i18n("legato")); + m_localDirectionShortcutCombo->insertItem(i18n("simile")); + m_localDirectionShortcutCombo->insertItem(i18n("pizz.")); + m_localDirectionShortcutCombo->insertItem(i18n("arco")); + m_localDirectionShortcutCombo->insertItem(i18n("non vib.")); + m_localDirectionShortcutCombo->insertItem(i18n("sul pont.")); + m_localDirectionShortcutCombo->insertItem(i18n("sul tasto")); + m_localDirectionShortcutCombo->insertItem(i18n("con legno")); + m_localDirectionShortcutCombo->insertItem(i18n("sul tasto")); + m_localDirectionShortcutCombo->insertItem(i18n("sul G")); + m_localDirectionShortcutCombo->insertItem(i18n("ordinario")); + m_localDirectionShortcutCombo->insertItem(i18n("Muta in ")); + m_localDirectionShortcutCombo->insertItem(i18n("volti subito ")); + m_localDirectionShortcutCombo->insertItem(i18n("soli")); + m_localDirectionShortcutCombo->insertItem(i18n("div.")); + m_localDirectionShortcutCombo->hide(); + + // tempo shortcuts combo + m_tempoShortcutLabel = new QLabel(i18n("Tempo: "), entryGrid); + m_tempoShortcutLabel->hide(); + + m_tempoShortcutCombo = new KComboBox(entryGrid); + m_tempoShortcutCombo->insertItem(i18n("Grave")); + m_tempoShortcutCombo->insertItem(i18n("Adagio")); + m_tempoShortcutCombo->insertItem(i18n("Largo")); + m_tempoShortcutCombo->insertItem(i18n("Lento")); + m_tempoShortcutCombo->insertItem(i18n("Andante")); + m_tempoShortcutCombo->insertItem(i18n("Moderato")); + m_tempoShortcutCombo->insertItem(i18n("Allegretto")); + m_tempoShortcutCombo->insertItem(i18n("Allegro")); + m_tempoShortcutCombo->insertItem(i18n("Vivace")); + m_tempoShortcutCombo->insertItem(i18n("Presto")); + m_tempoShortcutCombo->insertItem(i18n("Prestissimo")); + m_tempoShortcutCombo->insertItem(i18n("Maestoso")); + m_tempoShortcutCombo->insertItem(i18n("Sostenuto")); + m_tempoShortcutCombo->insertItem(i18n("Tempo Primo")); + m_tempoShortcutCombo->hide(); + + // local tempo shortcuts combo (duplicates the non-local version, because + // nobody is actually sure what is supposed to distinguish Tempo from + // Local Tempo, or what this text style is supposed to be good for in the + // way of standard notation) + m_localTempoShortcutLabel = new QLabel(i18n("Local Tempo: "), entryGrid); + m_localTempoShortcutLabel->hide(); + + m_localTempoShortcutCombo = new KComboBox(entryGrid); + m_localTempoShortcutCombo->insertItem(i18n("Grave")); + m_localTempoShortcutCombo->insertItem(i18n("Adagio")); + m_localTempoShortcutCombo->insertItem(i18n("Largo")); + m_localTempoShortcutCombo->insertItem(i18n("Lento")); + m_localTempoShortcutCombo->insertItem(i18n("Andante")); + m_localTempoShortcutCombo->insertItem(i18n("Moderato")); + m_localTempoShortcutCombo->insertItem(i18n("Allegretto")); + m_localTempoShortcutCombo->insertItem(i18n("Allegro")); + m_localTempoShortcutCombo->insertItem(i18n("Vivace")); + m_localTempoShortcutCombo->insertItem(i18n("Presto")); + m_localTempoShortcutCombo->insertItem(i18n("Prestissimo")); + m_localTempoShortcutCombo->insertItem(i18n("Maestoso")); + m_localTempoShortcutCombo->insertItem(i18n("Sostenuto")); + m_localTempoShortcutCombo->insertItem(i18n("Tempo Primo")); + m_localTempoShortcutCombo->hide(); + + // LilyPond directive combo + m_directiveLabel = new QLabel(i18n("Directive: "), entryGrid); + m_directiveLabel->hide(); + + m_lilyPondDirectiveCombo = new KComboBox(entryGrid); + m_lilyPondDirectiveCombo->hide(); + + // not i18nable, because the directive exporter currently depends on the + // textual contents of these strings, not some more abstract associated + // type label + m_lilyPondDirectiveCombo->insertItem(Text::Segno); + m_lilyPondDirectiveCombo->insertItem(Text::Coda); + m_lilyPondDirectiveCombo->insertItem(Text::Alternate1); + m_lilyPondDirectiveCombo->insertItem(Text::Alternate2); + m_lilyPondDirectiveCombo->insertItem(Text::BarDouble); + m_lilyPondDirectiveCombo->insertItem(Text::BarEnd); + m_lilyPondDirectiveCombo->insertItem(Text::BarDot); + m_lilyPondDirectiveCombo->insertItem(Text::Gliss); + m_lilyPondDirectiveCombo->insertItem(Text::Arpeggio); + // m_lilyPondDirectiveCombo->insertItem(Text::ArpeggioUp); + // m_lilyPondDirectiveCombo->insertItem(Text::ArpeggioDn); + m_lilyPondDirectiveCombo->insertItem(Text::Tiny); + m_lilyPondDirectiveCombo->insertItem(Text::Small); + m_lilyPondDirectiveCombo->insertItem(Text::NormalSize); + + QVBox *exampleVBox = new QVBox(exampleBox); + + int ls = m_notePixmapFactory->getLineSpacing(); + + int mapWidth = 200; + QPixmap map(mapWidth, ls * 5 + 1); + QBitmap mask(mapWidth, ls * 5 + 1); + + map.fill(); + mask.fill(Qt::color0); + + QPainter p, pm; + + p.begin(&map); + pm.begin(&mask); + + p.setPen(Qt::black); + pm.setPen(Qt::white); + + for (int i = 0; i < 5; ++i) + { + p.drawLine(0, ls * i, mapWidth - 1, ls * i); + pm.drawLine(0, ls * i, mapWidth - 1, ls * i); + } + + p.end(); + pm.end(); + + map.setMask(mask); + + m_staffAboveLabel = new QLabel("staff", exampleVBox); + m_staffAboveLabel->setPixmap(map); + + m_textExampleLabel = new QLabel(i18n("Example"), exampleVBox); + + m_staffBelowLabel = new QLabel("staff", exampleVBox); + m_staffBelowLabel->setPixmap(map); + + // restore last setting for shortcut combos + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + + m_dynamicShortcutCombo->setCurrentItem(config->readNumEntry("dynamic_shortcut", 0)); + m_directionShortcutCombo->setCurrentItem(config->readNumEntry("direction_shortcut", 0)); + m_localDirectionShortcutCombo->setCurrentItem(config->readNumEntry("local_direction_shortcut", 0)); + m_tempoShortcutCombo->setCurrentItem(config->readNumEntry("tempo_shortcut", 0)); + m_localTempoShortcutCombo->setCurrentItem(config->readNumEntry("local_tempo_shortcut", 0)); + m_lilyPondDirectiveCombo->setCurrentItem(config->readNumEntry("lilyPond_directive_combo", 0)); + + m_prevChord = config->readEntry("previous_chord", ""); + m_prevLyric = config->readEntry("previous_lyric", ""); + m_prevAnnotation = config->readEntry("previous_annotation", ""); + + QObject::connect(m_text, SIGNAL(textChanged(const QString &)), + this, SLOT(slotTextChanged(const QString &))); + QObject::connect(m_typeCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotTypeChanged(const QString &))); + QObject::connect(this, SIGNAL(okClicked()), this, SLOT(slotOK())); + QObject::connect(m_dynamicShortcutCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotDynamicShortcutChanged(const QString &))); + QObject::connect(m_directionShortcutCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotDirectionShortcutChanged(const QString &))); + QObject::connect(m_localDirectionShortcutCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotLocalDirectionShortcutChanged(const QString &))); + QObject::connect(m_tempoShortcutCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotTempoShortcutChanged(const QString &))); + QObject::connect(m_localTempoShortcutCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotLocalTempoShortcutChanged(const QString &))); + QObject::connect(m_lilyPondDirectiveCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotLilyPondDirectiveChanged(const QString &))); + + m_text->setFocus(); + slotTypeChanged(strtoqstr(getTextType())); + + // a hacky little fix for #1512143, to restore the capability to edit + // existing annotations and other whatnots + //!!! tacking another one of these on the bottom strikes me as lame in the + // extreme, but it works, and it costs little, and other solutions I can + // imagine would cost so much more. + m_text->setText(strtoqstr(defaultText.getText())); +} + +Text +TextEventDialog::getText() const +{ + Text text(getTextString(), getTextType()); + text.setVerse(m_verseSpin->value() - 1); + return text; +} + +std::string +TextEventDialog::getTextType() const +{ + return m_styles[m_typeCombo->currentItem()]; +} + +std::string +TextEventDialog::getTextString() const +{ + return std::string(qstrtostr(m_text->text())); +} + +void +TextEventDialog::slotTextChanged(const QString &qtext) +{ + std::string type(getTextType()); + + QString qtrunc(qtext); + if (qtrunc.length() > 20) + qtrunc = qtrunc.left(20) + "..."; + std::string text(qstrtostr(qtrunc)); + if (text == "") + text = "Sample"; + + Text rtext(text, type); + m_textExampleLabel->setPixmap + (NotePixmapFactory::toQPixmap(m_notePixmapFactory->makeTextPixmap(rtext))); +} + +void +TextEventDialog::slotTypeChanged(const QString &) +{ + std::string type(getTextType()); + + QString qtrunc(m_text->text()); + if (qtrunc.length() > 20) + qtrunc = qtrunc.left(20) + "..."; + std::string text(qstrtostr(qtrunc)); + if (text == "") + text = "Sample"; + + Text rtext(text, type); + m_textExampleLabel->setPixmap + (NotePixmapFactory::toQPixmap(m_notePixmapFactory->makeTextPixmap(rtext))); + + // + // swap widgets in and out, depending on the current text type + // + if (type == Text::Dynamic) { + m_dynamicShortcutLabel->show(); + m_dynamicShortcutCombo->show(); + slotDynamicShortcutChanged(text); + } else { + m_dynamicShortcutLabel->hide(); + m_dynamicShortcutCombo->hide(); + } + + if (type == Text::Direction) { + m_directionShortcutLabel->show(); + m_directionShortcutCombo->show(); + slotDirectionShortcutChanged(text); + } else { + m_directionShortcutLabel->hide(); + m_directionShortcutCombo->hide(); + } + + if (type == Text::LocalDirection) { + m_localDirectionShortcutLabel->show(); + m_localDirectionShortcutCombo->show(); + slotLocalDirectionShortcutChanged(text); + } else { + m_localDirectionShortcutLabel->hide(); + m_localDirectionShortcutCombo->hide(); + } + + if (type == Text::Tempo) { + m_tempoShortcutLabel->show(); + m_tempoShortcutCombo->show(); + slotTempoShortcutChanged(text); + } else { + m_tempoShortcutLabel->hide(); + m_tempoShortcutCombo->hide(); + } + + if (type == Text::LocalTempo) { + m_localTempoShortcutLabel->show(); + m_localTempoShortcutCombo->show(); + slotLocalTempoShortcutChanged(text); + } else { + m_localTempoShortcutLabel->hide(); + m_localTempoShortcutCombo->hide(); + } + + // restore previous text of appropriate type + if (type == Text::Lyric) + m_text->setText(m_prevLyric); + else if (type == Text::Chord) + m_text->setText(m_prevChord); + else if (type == Text::Annotation) + m_text->setText(m_prevAnnotation); + + // + // LilyPond directives only taking temporary residence here; will move out + // into some new class eventually + // + if (type == Text::LilyPondDirective) { + m_lilyPondDirectiveCombo->show(); + m_directiveLabel->show(); + m_staffAboveLabel->hide(); + m_staffBelowLabel->show(); + m_text->setReadOnly(true); + m_text->setEnabled(false); + slotLilyPondDirectiveChanged(text); + } else { + m_lilyPondDirectiveCombo->hide(); + m_directiveLabel->hide(); + m_text->setReadOnly(false); + m_text->setEnabled(true); + + if (type == Text::Dynamic || + type == Text::LocalDirection || + type == Text::UnspecifiedType || + type == Text::Lyric || + type == Text::Annotation) { + + m_staffAboveLabel->show(); + m_staffBelowLabel->hide(); + + } else { + m_staffAboveLabel->hide(); + m_staffBelowLabel->show(); + + } + + if (type == Text::Lyric) { + m_verseLabel->show(); + m_verseSpin->show(); + } + } +} + +void +TextEventDialog::slotOK() +{ + // store last setting for shortcut combos + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + + config->writeEntry("dynamic_shortcut", m_dynamicShortcutCombo->currentItem()); + config->writeEntry("direction_shortcut", m_directionShortcutCombo->currentItem()); + config->writeEntry("local_direction_shortcut", m_localDirectionShortcutCombo->currentItem()); + config->writeEntry("tempo_shortcut", m_tempoShortcutCombo->currentItem()); + config->writeEntry("local_tempo_shortcut", m_localTempoShortcutCombo->currentItem()); + // temporary home: + config->writeEntry("lilyPond_directive_combo", m_lilyPondDirectiveCombo->currentItem()); + + // store last chord, lyric, annotation, depending on what's currently in + // the text entry widget + int index = m_typeCombo->currentItem(); + if (index == 5) + config->writeEntry("previous_chord", m_text->text()); + else if (index == 6) + config->writeEntry("previous_lyric", m_text->text()); + else if (index == 7) + config->writeEntry("previous_annotation", m_text->text()); +} + +void +TextEventDialog::slotDynamicShortcutChanged(const QString &text) +{ + if (text == "" || text == "Sample") { + m_text->setText(strtoqstr(m_dynamicShortcutCombo->currentText())); + } else { + m_text->setText(text); + } +} + +void +TextEventDialog::slotDirectionShortcutChanged(const QString &text) +{ + if (text == "" || text == "Sample") { + m_text->setText(strtoqstr(m_directionShortcutCombo->currentText())); + } else { + m_text->setText(text); + } +} + +void +TextEventDialog::slotLocalDirectionShortcutChanged(const QString &text) +{ + if (text == "" || text == "Sample") { + m_text->setText(strtoqstr(m_localDirectionShortcutCombo->currentText())); + } else { + m_text->setText(text); + } +} + +void +TextEventDialog::slotTempoShortcutChanged(const QString &text) +{ + if (text == "" || text == "Sample") { + m_text->setText(strtoqstr(m_tempoShortcutCombo->currentText())); + } else { + m_text->setText(text); + } +} + +void +TextEventDialog::slotLocalTempoShortcutChanged(const QString &text) +{ + if (text == "" || text == "Sample") { + m_text->setText(strtoqstr(m_localTempoShortcutCombo->currentText())); + } else { + m_text->setText(text); + } +} + +void +TextEventDialog::slotLilyPondDirectiveChanged(const QString &) +{ + m_text->setText(strtoqstr(m_lilyPondDirectiveCombo->currentText())); +} + +} +#include "TextEventDialog.moc" diff --git a/src/gui/dialogs/TextEventDialog.h b/src/gui/dialogs/TextEventDialog.h new file mode 100644 index 0000000..0f389b0 --- /dev/null +++ b/src/gui/dialogs/TextEventDialog.h @@ -0,0 +1,129 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_TEXTEVENTDIALOG_H_ +#define _RG_TEXTEVENTDIALOG_H_ + +#include "base/NotationTypes.h" +#include +#include +#include +#include + + +class QWidget; +class QLineEdit; +class QLabel; +class KComboBox; +class QSpinBox; + +namespace Rosegarden +{ + +class NotePixmapFactory; + + +class TextEventDialog : public KDialogBase +{ + Q_OBJECT + +public: + TextEventDialog(QWidget *parent, + NotePixmapFactory *npf, + Text defaultText, + int maxLength = -1); // for Qt default + + Text getText() const; + +public slots: + void slotTextChanged(const QString &); + void slotTypeChanged(const QString &); + + /* + * Save previous state of assorted widgets for restoration in the next + * instance + */ + void slotOK(); + + // convenience canned texts + void slotDynamicShortcutChanged(const QString &); + void slotDirectionShortcutChanged(const QString &); + void slotLocalDirectionShortcutChanged(const QString &); + void slotTempoShortcutChanged(const QString &); + void slotLocalTempoShortcutChanged(const QString &); + + // + // special LilyPond directives, initial phase, as cheap text events; will + // eventually move out of Text, and out of this dialog into + // some other less cheesy interface + // + void slotLilyPondDirectiveChanged(const QString &); + +protected: + + std::string getTextType() const; + std::string getTextString() const; + + //--------------- Data members --------------------------------- + + QLineEdit *m_text; + KComboBox *m_typeCombo; + QSpinBox *m_verseSpin; + KComboBox *m_dynamicShortcutCombo; + KComboBox *m_directionShortcutCombo; + KComboBox *m_localDirectionShortcutCombo; + KComboBox *m_tempoShortcutCombo; + KComboBox *m_localTempoShortcutCombo; + // temporary home: + KComboBox *m_lilyPondDirectiveCombo; + + + QLabel *m_staffAboveLabel; + QLabel *m_textExampleLabel; + QLabel *m_staffBelowLabel; + QLabel *m_dynamicShortcutLabel; + QLabel *m_directionShortcutLabel; + QLabel *m_localDirectionShortcutLabel; + QLabel *m_tempoShortcutLabel; + QLabel *m_localTempoShortcutLabel; + QLabel *m_verseLabel; + // temporary home: + QLabel *m_directiveLabel; + + QString m_prevChord; + QString m_prevLyric; + QString m_prevAnnotation; + + NotePixmapFactory *m_notePixmapFactory; + std::vector m_styles; +// std::vector m_directives; + +}; + + + +} + +#endif diff --git a/src/gui/dialogs/TimeDialog.cpp b/src/gui/dialogs/TimeDialog.cpp new file mode 100644 index 0000000..40d1da2 --- /dev/null +++ b/src/gui/dialogs/TimeDialog.cpp @@ -0,0 +1,80 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TimeDialog.h" + +#include +#include "base/Composition.h" +#include "gui/widgets/TimeWidget.h" +#include +#include +#include +#include + + +namespace Rosegarden +{ + +TimeDialog::TimeDialog(QWidget *parent, QString title, + Composition *composition, + timeT defaultTime, + bool constrainToCompositionDuration) : + KDialogBase(parent, 0, true, title, User1 | Ok | Cancel) +{ + QVBox *vbox = makeVBoxMainWidget(); + m_timeWidget = new TimeWidget + (title, vbox, composition, defaultTime, true, + constrainToCompositionDuration); + + setButtonText(User1, i18n("Reset")); + connect(this, SIGNAL(user1Clicked()), + m_timeWidget, SLOT(slotResetToDefault())); +} + +TimeDialog::TimeDialog(QWidget *parent, QString title, + Composition *composition, + timeT startTime, + timeT defaultTime, + bool constrainToCompositionDuration) : + KDialogBase(parent, 0, true, title, User1 | Ok | Cancel) +{ + QVBox *vbox = makeVBoxMainWidget(); + m_timeWidget = new TimeWidget + (title, vbox, composition, startTime, defaultTime, true, + constrainToCompositionDuration); + + setButtonText(User1, i18n("Reset")); + connect(this, SIGNAL(user1Clicked()), + m_timeWidget, SLOT(slotResetToDefault())); +} + +timeT +TimeDialog::getTime() const +{ + return m_timeWidget->getTime(); +} + +} +#include "TimeDialog.moc" diff --git a/src/gui/dialogs/TimeDialog.h b/src/gui/dialogs/TimeDialog.h new file mode 100644 index 0000000..e12a007 --- /dev/null +++ b/src/gui/dialogs/TimeDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_TIMEDIALOG_H_ +#define _RG_TIMEDIALOG_H_ + +#include +#include +#include "base/Event.h" + + +class QWidget; + + +namespace Rosegarden +{ + +class TimeWidget; +class Composition; + + +class TimeDialog : public KDialogBase +{ + Q_OBJECT +public: + /// for absolute times + TimeDialog(QWidget *parent, QString title, Composition *composition, + timeT defaultTime, bool constrainToCompositionDuration); + + /// for durations + TimeDialog(QWidget *parent, QString title, Composition *composition, + timeT startTime, timeT defaultDuration, + bool constrainToCompositionDuration); + + timeT getTime() const; + +protected: + TimeWidget *m_timeWidget; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/TimeSignatureDialog.cpp b/src/gui/dialogs/TimeSignatureDialog.cpp new file mode 100644 index 0000000..082f123 --- /dev/null +++ b/src/gui/dialogs/TimeSignatureDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TimeSignatureDialog.h" +#include + +#include +#include "document/ConfigGroups.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "gui/widgets/TimeWidget.h" +#include "gui/widgets/BigArrowButton.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +TimeSignatureDialog::TimeSignatureDialog(QWidget *parent, + Composition *composition, + timeT insertionTime, + TimeSignature sig, + bool timeEditable, + QString explanatoryText) : + KDialogBase(parent, 0, true, i18n("Time Signature"), Ok | Cancel | Help), + m_composition(composition), + m_timeSignature(sig), + m_time(insertionTime), + m_numLabel(0), + m_denomLabel(0), + m_explanatoryLabel(0), + m_commonTimeButton(0), + m_hideSignatureButton(0), + m_normalizeRestsButton(0), + m_asGivenButton(0), + m_startOfBarButton(0), + m_timeEditor(0) +{ + static QFont *timeSigFont = 0; + + if (timeSigFont == 0) { + timeSigFont = new QFont("new century schoolbook", 8, QFont::Bold); + timeSigFont->setPixelSize(20); + } + + QVBox *vbox = makeVBoxMainWidget(); + QGroupBox *groupBox = new QGroupBox + (1, Horizontal, i18n("Time signature"), vbox); + QHBox *numBox = new QHBox(groupBox); + QHBox *denomBox = new QHBox(groupBox); + + QLabel *explanatoryLabel = 0; + if (explanatoryText) { + explanatoryLabel = new QLabel(explanatoryText, groupBox); + } + + BigArrowButton *numDown = new BigArrowButton(numBox, Qt::LeftArrow); + BigArrowButton *denomDown = new BigArrowButton(denomBox, Qt::LeftArrow); + + m_numLabel = new QLabel + (QString("%1").arg(m_timeSignature.getNumerator()), numBox); + m_denomLabel = new QLabel + (QString("%1").arg(m_timeSignature.getDenominator()), denomBox); + + m_numLabel->setAlignment(AlignHCenter | AlignVCenter); + m_denomLabel->setAlignment(AlignHCenter | AlignVCenter); + + m_numLabel->setFont(*timeSigFont); + m_denomLabel->setFont(*timeSigFont); + + BigArrowButton *numUp = new BigArrowButton(numBox, Qt::RightArrow); + BigArrowButton *denomUp = new BigArrowButton(denomBox, Qt::RightArrow); + + QObject::connect(numDown, SIGNAL(clicked()), this, SLOT(slotNumDown())); + QObject::connect(numUp, SIGNAL(clicked()), this, SLOT(slotNumUp())); + QObject::connect(denomDown, SIGNAL(clicked()), this, SLOT(slotDenomDown())); + QObject::connect(denomUp, SIGNAL(clicked()), this, SLOT(slotDenomUp())); + + if (timeEditable) { + + m_timeEditor = new TimeWidget + (i18n("Time where signature takes effect"), + vbox, + composition, + m_time, + true); + + m_asGivenButton = 0; + m_startOfBarButton = 0; + + } else { + + m_timeEditor = 0; + + groupBox = new QButtonGroup(1, Horizontal, i18n("Scope"), vbox); + + int barNo = composition->getBarNumber(m_time); + bool atStartOfBar = (m_time == composition->getBarStart(barNo)); + + if (!atStartOfBar) { + + QString scopeText; + + if (barNo != 0 || !atStartOfBar) { + if (atStartOfBar) { + scopeText = QString + (i18n("Insertion point is at start of measure %1.")) + .arg(barNo + 1); + } else { + scopeText = QString + (i18n("Insertion point is in the middle of measure %1.")) + .arg(barNo + 1); + } + } else { + scopeText = QString + (i18n("Insertion point is at start of composition.")); + } + + new QLabel(scopeText, groupBox); + m_asGivenButton = new QRadioButton + (i18n("Start measure %1 here").arg(barNo + 2), groupBox); + + if (!atStartOfBar) { + m_startOfBarButton = new QRadioButton + (i18n("Change time from start of measure %1") + .arg(barNo + 1), groupBox); + m_startOfBarButton->setChecked(true); + } else { + m_asGivenButton->setChecked(true); + } + } else { + new QLabel(i18n("Time change will take effect at the start of measure %1.") + .arg(barNo + 1), groupBox); + } + } + + groupBox = new QGroupBox(1, Horizontal, i18n("Options"), vbox); + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + + m_hideSignatureButton = new QCheckBox + (i18n("Hide the time signature"), groupBox); + m_hideSignatureButton->setChecked + (config->readBoolEntry("timesigdialogmakehidden", false)); + + m_hideBarsButton = new QCheckBox + (i18n("Hide the affected bar lines"), groupBox); + m_hideBarsButton->setChecked + (config->readBoolEntry("timesigdialogmakehiddenbars", false)); + + m_commonTimeButton = new QCheckBox + (i18n("Show as common time"), groupBox); + m_commonTimeButton->setChecked + (config->readBoolEntry("timesigdialogshowcommon", true)); + + m_normalizeRestsButton = new QCheckBox + (i18n("Correct the durations of following measures"), groupBox); + m_normalizeRestsButton->setChecked + (config->readBoolEntry("timesigdialognormalize", true)); + + QObject::connect(m_hideSignatureButton, SIGNAL(clicked()), this, + SLOT(slotUpdateCommonTimeButton())); + slotUpdateCommonTimeButton(); + m_explanatoryLabel = explanatoryLabel; + + setHelp("time-signature"); +} + +TimeSignature +TimeSignatureDialog::getTimeSignature() const +{ + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + + config->writeEntry("timesigdialogmakehidden", m_hideSignatureButton->isChecked()); + config->writeEntry("timesigdialogmakehiddenbars", m_hideBarsButton->isChecked()); + config->writeEntry("timesigdialogshowcommon", m_commonTimeButton->isChecked()); + config->writeEntry("timesigdialognormalize", m_normalizeRestsButton->isChecked()); + + TimeSignature ts(m_timeSignature.getNumerator(), + m_timeSignature.getDenominator(), + (m_commonTimeButton && + m_commonTimeButton->isEnabled() && + m_commonTimeButton->isChecked()), + (m_hideSignatureButton && + m_hideSignatureButton->isEnabled() && + m_hideSignatureButton->isChecked()), + (m_hideBarsButton && + m_hideBarsButton->isEnabled() && + m_hideBarsButton->isChecked())); + return ts; +} + +void +TimeSignatureDialog::slotNumDown() +{ + int n = m_timeSignature.getNumerator(); + if (--n >= 1) { + m_timeSignature = TimeSignature(n, m_timeSignature.getDenominator()); + m_numLabel->setText(QString("%1").arg(n)); + } + slotUpdateCommonTimeButton(); +} + +void +TimeSignatureDialog::slotNumUp() +{ + int n = m_timeSignature.getNumerator(); + if (++n <= 99) { + m_timeSignature = TimeSignature(n, m_timeSignature.getDenominator()); + m_numLabel->setText(QString("%1").arg(n)); + } + slotUpdateCommonTimeButton(); +} + +void +TimeSignatureDialog::slotDenomDown() +{ + int n = m_timeSignature.getDenominator(); + if ((n /= 2) >= 1) { + m_timeSignature = TimeSignature(m_timeSignature.getNumerator(), n); + m_denomLabel->setText(QString("%1").arg(n)); + } + slotUpdateCommonTimeButton(); +} + +void +TimeSignatureDialog::slotDenomUp() +{ + int n = m_timeSignature.getDenominator(); + if ((n *= 2) <= 64) { + m_timeSignature = TimeSignature(m_timeSignature.getNumerator(), n); + m_denomLabel->setText(QString("%1").arg(n)); + } + slotUpdateCommonTimeButton(); +} + +void +TimeSignatureDialog::slotUpdateCommonTimeButton() +{ + if (m_explanatoryLabel) + m_explanatoryLabel->hide(); + if (!m_hideSignatureButton || !m_hideSignatureButton->isChecked()) { + if (m_timeSignature.getDenominator() == m_timeSignature.getNumerator()) { + if (m_timeSignature.getNumerator() == 4) { + m_commonTimeButton->setText(i18n("Display as common time")); + m_commonTimeButton->setEnabled(true); + return ; + } else if (m_timeSignature.getNumerator() == 2) { + m_commonTimeButton->setText(i18n("Display as cut common time")); + m_commonTimeButton->setEnabled(true); + return ; + } + } + } + m_commonTimeButton->setEnabled(false); +} + +timeT +TimeSignatureDialog::getTime() const +{ + if (m_timeEditor) { + return m_timeEditor->getTime(); + } else if (m_asGivenButton && m_asGivenButton->isChecked()) { + return m_time; + } else if (m_startOfBarButton && m_startOfBarButton->isChecked()) { + int barNo = m_composition->getBarNumber(m_time); + return m_composition->getBarStart(barNo); + } else { + return m_time; + } +} + +bool +TimeSignatureDialog::shouldNormalizeRests() const +{ + return (m_normalizeRestsButton && m_normalizeRestsButton->isEnabled() && + m_normalizeRestsButton->isChecked()); +} + +} +#include "TimeSignatureDialog.moc" diff --git a/src/gui/dialogs/TimeSignatureDialog.h b/src/gui/dialogs/TimeSignatureDialog.h new file mode 100644 index 0000000..330134c --- /dev/null +++ b/src/gui/dialogs/TimeSignatureDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_TIMESIGNATUREDIALOG_H_ +#define _RG_TIMESIGNATUREDIALOG_H_ + +#include "base/NotationTypes.h" +#include +#include +#include "base/Event.h" + + +class QWidget; +class QRadioButton; +class QLabel; +class QCheckBox; + + +namespace Rosegarden +{ + +class TimeWidget; +class Composition; + + +class TimeSignatureDialog : public KDialogBase +{ + Q_OBJECT + +public: + TimeSignatureDialog(QWidget *parent, + Composition *composition, + timeT insertionTime, + TimeSignature defaultSig = + TimeSignature::DefaultTimeSignature, + bool timeEditable = false, + QString explanatoryText = 0); + + TimeSignature getTimeSignature() const; + + timeT getTime() const; + bool shouldNormalizeRests() const; + +public slots: + void slotNumUp(); + void slotNumDown(); + void slotDenomUp(); + void slotDenomDown(); + void slotUpdateCommonTimeButton(); + +protected: + //--------------- Data members --------------------------------- + + Composition *m_composition; + TimeSignature m_timeSignature; + timeT m_time; + + QLabel *m_numLabel; + QLabel *m_denomLabel; + QLabel *m_explanatoryLabel; + + QCheckBox *m_commonTimeButton; + QCheckBox *m_hideSignatureButton; + QCheckBox *m_hideBarsButton; + QCheckBox *m_normalizeRestsButton; + + QRadioButton *m_asGivenButton; + QRadioButton *m_startOfBarButton; + + TimeWidget *m_timeEditor; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/TransportDialog.cpp b/src/gui/dialogs/TransportDialog.cpp new file mode 100644 index 0000000..115a528 --- /dev/null +++ b/src/gui/dialogs/TransportDialog.cpp @@ -0,0 +1,1164 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TransportDialog.h" + +#include +#include +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/RealTime.h" +#include "misc/Debug.h" +#include "gui/application/RosegardenApplication.h" +#include "gui/general/MidiPitchLabel.h" +#include "gui/studio/StudioControl.h" +#include "gui/widgets/Label.h" +#include "sound/MappedEvent.h" +#include "document/ConfigGroups.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +TransportDialog::TransportDialog(QWidget *parent, + const char *name, + WFlags flags): + QWidget(parent, name, WType_TopLevel | WStyle_DialogBorder | WStyle_Minimize | WStyle_SysMenu | WDestructiveClose), + m_transport(0), + m_lastTenHours(0), + m_lastUnitHours(0), + m_lastTenMinutes(0), + m_lastUnitMinutes(0), + m_lastTenSeconds(0), + m_lastUnitSeconds(0), + m_lastTenths(0), + m_lastHundreths(0), + m_lastThousandths(0), + m_lastTenThousandths(0), + m_lastNegative(false), + m_lastMode(RealMode), + m_currentMode(RealMode), + m_tempo(0), + m_numerator(0), + m_denominator(0), + m_framesPerSecond(24), + m_bitsPerFrame(80), + m_isExpanded(true), + m_haveOriginalBackground(false), + m_isBackgroundSet(false), + m_sampleRate(0) +{ + m_transport = new RosegardenTransport(this); + + setCaption(i18n("Rosegarden Transport")); + + resetFonts(); + + initModeMap(); + + // set the LCD frame background to black + // + m_transport->LCDBoxFrame->setBackgroundColor(Qt::black); + + // set all the pixmap backgrounds to black to avoid + // flickering when we update + // + m_transport->TenThousandthsPixmap->setBackgroundColor(Qt::black); + m_transport->ThousandthsPixmap->setBackgroundColor(Qt::black); + m_transport->HundredthsPixmap->setBackgroundColor(Qt::black); + m_transport->TenthsPixmap->setBackgroundColor(Qt::black); + m_transport->UnitSecondsPixmap->setBackgroundColor(Qt::black); + m_transport->TenSecondsPixmap->setBackgroundColor(Qt::black); + m_transport->UnitMinutesPixmap->setBackgroundColor(Qt::black); + m_transport->TenMinutesPixmap->setBackgroundColor(Qt::black); + m_transport->UnitHoursPixmap->setBackgroundColor(Qt::black); + m_transport->TenHoursPixmap->setBackgroundColor(Qt::black); + m_transport->NegativePixmap->setBackgroundColor(Qt::black); + + // unset the negative sign to begin with + m_transport->NegativePixmap->clear(); + + // Set our toggle buttons + // + m_transport->PlayButton->setToggleButton(true); + m_transport->RecordButton->setToggleButton(true); + +// Disable the loop button if JACK transport enabled, because this +// causes a nasty race condition, and it just seems our loops are not JACK compatible +// #1240039 - DMM +// KConfig* config = rgapp->config(); +// config->setGroup(SequencerOptionsConfigGroup); +// if (config->readBoolEntry("jacktransport", false)) +// { +// m_transport->LoopButton->setEnabled(false); +// } + + // fix and hold the size of the dialog + // + setMinimumSize(m_transport->width(), m_transport->height()); + setMaximumSize(m_transport->width(), m_transport->height()); + + loadPixmaps(); + + // Create Midi label timers + m_midiInTimer = new QTimer(this); + m_midiOutTimer = new QTimer(this); + m_clearMetronomeTimer = new QTimer(this); + + connect(m_midiInTimer, SIGNAL(timeout()), + SLOT(slotClearMidiInLabel())); + + connect(m_midiOutTimer, SIGNAL(timeout()), + SLOT(slotClearMidiOutLabel())); + + connect(m_clearMetronomeTimer, SIGNAL(timeout()), + SLOT(slotResetBackground())); + + m_transport->TimeDisplayLabel->hide(); + m_transport->ToEndLabel->hide(); + + connect(m_transport->TimeDisplayButton, SIGNAL(clicked()), + SLOT(slotChangeTimeDisplay())); + + connect(m_transport->ToEndButton, SIGNAL(clicked()), + SLOT(slotChangeToEnd())); + + connect(m_transport->LoopButton, SIGNAL(clicked()), + SLOT(slotLoopButtonClicked())); + + connect(m_transport->PanelOpenButton, SIGNAL(clicked()), + SLOT(slotPanelOpenButtonClicked())); + + connect(m_transport->PanelCloseButton, SIGNAL(clicked()), + SLOT(slotPanelCloseButtonClicked())); + + connect(m_transport->PanicButton, SIGNAL(clicked()), SIGNAL(panic())); + + m_panelOpen = *m_transport->PanelOpenButton->pixmap(); + m_panelClosed = *m_transport->PanelCloseButton->pixmap(); + + + connect(m_transport->SetStartLPButton, SIGNAL(clicked()), SLOT(slotSetStartLoopingPointAtMarkerPos())); + connect(m_transport->SetStopLPButton, SIGNAL(clicked()), SLOT(slotSetStopLoopingPointAtMarkerPos())); + + // clear labels + // + slotClearMidiInLabel(); + slotClearMidiOutLabel(); + + // and by default we close the lower panel + // + int rfh = m_transport->RecordingFrame->height(); + m_transport->RecordingFrame->hide(); + setFixedSize(width(), height() - rfh); + m_transport->PanelOpenButton->setPixmap(m_panelClosed); + + // and since by default we show real time (not SMPTE), by default + // we hide the small colon pixmaps + // + m_transport->SecondColonPixmap->hide(); + m_transport->HundredthColonPixmap->hide(); + + // We have to specify these settings in this class (copied + // from rosegardentransport.cpp) as we're using a specialised + // widgets for TempoDisplay. Ugly but works - does mean that + // if the rest of the Transport ever changes then this code + // will have to as well. + // + QPalette pal; + pal.setColor(QColorGroup::Foreground, QColor(192, 216, 255)); + + m_transport->TempoDisplay->setPalette(pal); + m_transport->TempoDisplay->setAlignment(int(QLabel::AlignVCenter | QLabel::AlignRight)); + + m_transport->TimeSigDisplay->setPalette(pal); + m_transport->TimeSigDisplay->setAlignment(int(QLabel::AlignVCenter | QLabel::AlignRight)); + + QFont localFont(m_transport->OutDisplay->font() ); + localFont.setFamily( "lucida" ); + localFont.setBold( TRUE ); + + m_transport->TempoDisplay->setFont( localFont ); + m_transport->TimeSigDisplay->setFont( localFont ); + + connect(m_transport->TempoDisplay, SIGNAL(doubleClicked()), + this, SLOT(slotEditTempo())); + + connect(m_transport->TempoDisplay, SIGNAL(scrollWheel(int)), + this, SIGNAL(scrollTempo(int))); + + connect(m_transport->TimeSigDisplay, SIGNAL(doubleClicked()), + this, SLOT(slotEditTimeSignature())); + + // toil through the individual pixmaps + connect(m_transport->NegativePixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->TenHoursPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->UnitHoursPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->HourColonPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->TenMinutesPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->UnitMinutesPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->MinuteColonPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->TenSecondsPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->UnitSecondsPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->SecondColonPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->TenthsPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->HundredthsPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->HundredthColonPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->TenThousandthsPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + connect(m_transport->ThousandthsPixmap, SIGNAL(doubleClicked()), + this, SLOT(slotEditTime())); + + // accelerator object + // + m_accelerators = new QAccel(this); +} + +TransportDialog::~TransportDialog() +{ + if (isVisible()) { + KConfig* config = rgapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + config->writeEntry("transportx", x()); + config->writeEntry("transporty", y()); + } +} + +std::string +TransportDialog::getCurrentModeAsString() +{ + bool found = false; + for (std::map::iterator iter = m_modeMap.begin(); + iter != m_modeMap.end() && !found; + iter++) + { + if (iter->second == m_currentMode) { + return iter->first; + } + } + + // we shouldn't get here unless the map is not well-configured + RG_DEBUG << "TransportDialog::getCurrentModeAsString: could not map current mode " + << m_currentMode << " to string." << endl; + throw Exception("could not map current mode to string."); +} + +void +TransportDialog::initModeMap() +{ + m_modeMap["RealMode"] = RealMode; + m_modeMap["SMPTEMode"] = SMPTEMode; + m_modeMap["BarMode"] = BarMode; + m_modeMap["BarMetronomeMode"] = BarMetronomeMode; + m_modeMap["FrameMode"] = FrameMode; +} + +void +TransportDialog::show() +{ + KConfig* config = rgapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + int x = config->readNumEntry("transportx", -1); + int y = config->readNumEntry("transporty", -1); + if (x >= 0 && y >= 0) { + int dw = QApplication::desktop()->availableGeometry(QPoint(x, y)).width(); + int dh = QApplication::desktop()->availableGeometry(QPoint(x, y)).height(); + if (x + m_transport->width() > dw) x = dw - m_transport->width(); + if (y + m_transport->height() > dh) y = dh - m_transport->height(); + move(x, y); +// std::cerr << "TransportDialog::show(): moved to " << x << "," << y << std::endl; + QWidget::show(); +// std::cerr << "TransportDialog::show(): now at " << this->x() << "," << this->y() << std::endl; + } else { + QWidget::show(); + } +} + +void +TransportDialog::hide() +{ + if (isVisible()) { + KConfig* config = rgapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + config->writeEntry("transportx", x()); + config->writeEntry("transporty", y()); + } + QWidget::hide(); +} + +void +TransportDialog::loadPixmaps() +{ + m_lcdList.clear(); + QString fileName; + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + + for (int i = 0; i < 10; i++) { + fileName = QString("%1/transport/led-%2.xpm").arg(pixmapDir).arg(i); + if (!m_lcdList[i].load(fileName)) { + std::cerr << "TransportDialog - failed to load pixmap \"" + << fileName << "\"" << std::endl; + } + } + + // Load the "negative" sign pixmap + // + fileName = QString("%1/transport/led--.xpm").arg(pixmapDir); + m_lcdNegative.load(fileName); + +} + +void +TransportDialog::resetFonts() +{ + resetFont(m_transport->TimeSigLabel); + resetFont(m_transport->TimeSigDisplay); + resetFont(m_transport->TempoLabel); + resetFont(m_transport->TempoDisplay); + resetFont(m_transport->DivisionLabel); + resetFont(m_transport->DivisionDisplay); + resetFont(m_transport->InLabel); + resetFont(m_transport->InDisplay); + resetFont(m_transport->OutLabel); + resetFont(m_transport->OutDisplay); + resetFont(m_transport->ToEndLabel); + resetFont(m_transport->TimeDisplayLabel); +} + +void +TransportDialog::resetFont(QWidget *w) +{ + QFont font = w->font(); + font.setPixelSize(10); + w->setFont(font); +} + +void +TransportDialog::setSMPTEResolution(int framesPerSecond, + int bitsPerFrame) +{ + m_framesPerSecond = framesPerSecond; + m_bitsPerFrame = bitsPerFrame; +} + +void +TransportDialog::getSMPTEResolution(int &framesPerSecond, + int &bitsPerFrame) +{ + framesPerSecond = m_framesPerSecond; + bitsPerFrame = m_bitsPerFrame; +} + +void +TransportDialog::computeSampleRate() +{ + if (m_sampleRate == 0) { + + QCString replyType; + QByteArray replyData; + m_sampleRate = 0; + + if (rgapp->sequencerCall("getSampleRate()", replyType, replyData)) { + QDataStream streamIn(replyData, IO_ReadOnly); + unsigned int result; + streamIn >> result; + m_sampleRate = result; + } else { + m_sampleRate = -1; + } + } + +} + +void +TransportDialog::cycleThroughModes() +{ + switch (m_currentMode) { + + case RealMode: + if (m_sampleRate > 0) + m_currentMode = FrameMode; + else + m_currentMode = BarMode; + break; + + case FrameMode: + m_currentMode = BarMode; + break; + + case SMPTEMode: + m_currentMode = BarMode; + break; + + case BarMode: + m_currentMode = BarMetronomeMode; + break; + + case BarMetronomeMode: + m_currentMode = RealMode; + break; + } +} + +void +TransportDialog::displayTime() +{ + switch (m_currentMode) { + case RealMode: + m_clearMetronomeTimer->stop(); + m_transport->TimeDisplayLabel->hide(); + break; + + case SMPTEMode: + m_clearMetronomeTimer->stop(); + m_transport->TimeDisplayLabel->setText("SMPTE"); // DO NOT i18n + m_transport->TimeDisplayLabel->show(); + break; + + case BarMode: + m_clearMetronomeTimer->stop(); + m_transport->TimeDisplayLabel->setText("BAR"); // DO NOT i18n + m_transport->TimeDisplayLabel->show(); + break; + + case BarMetronomeMode: + m_clearMetronomeTimer->start(1700, FALSE); + m_transport->TimeDisplayLabel->setText("MET"); // DO NOT i18n + m_transport->TimeDisplayLabel->show(); + break; + + case FrameMode: + m_clearMetronomeTimer->stop(); + m_transport->TimeDisplayLabel->setText(QString("%1").arg(m_sampleRate)); + m_transport->TimeDisplayLabel->show(); + break; + } +} + +void +TransportDialog::setNewMode(const std::string& newModeAsString) +{ + TimeDisplayMode newMode = RealMode; // default value if not found + + std::map::iterator iter = + m_modeMap.find(newModeAsString); + + if (iter != m_modeMap.end()) { + // value found + newMode = iter->second; + } else { + // don't fail: use default value set at declaration + } + + setNewMode(newMode); +} + +void +TransportDialog::setNewMode(const TimeDisplayMode& newMode) +{ + computeSampleRate(); + + m_currentMode = newMode; + + displayTime(); +} + + +void +TransportDialog::slotChangeTimeDisplay() +{ + computeSampleRate(); + + cycleThroughModes(); + + displayTime(); +} + +void +TransportDialog::slotChangeToEnd() +{ + if (m_transport->ToEndButton->isOn()) { + m_transport->ToEndLabel->show(); + } else { + m_transport->ToEndLabel->hide(); + } +} + +bool +TransportDialog::isShowingTimeToEnd() +{ + return m_transport->ToEndButton->isOn(); +} + +void +TransportDialog::displayRealTime(const RealTime &rt) +{ + RealTime st = rt; + + slotResetBackground(); + + if (m_lastMode != RealMode) { + m_transport->HourColonPixmap->show(); + m_transport->MinuteColonPixmap->show(); + m_transport->SecondColonPixmap->hide(); + m_transport->HundredthColonPixmap->hide(); + m_lastMode = RealMode; + } + + // If time is negative then reverse the time and set the minus flag + // + if (st < RealTime::zeroTime) { + st = RealTime::zeroTime - st; + if (!m_lastNegative) { + m_transport->NegativePixmap->setPixmap(m_lcdNegative); + m_lastNegative = true; + } + } else // don't show the flag + { + if (m_lastNegative) { + m_transport->NegativePixmap->clear(); + m_lastNegative = false; + } + } + + m_tenThousandths = ( st.usec() / 100 ) % 10; + m_thousandths = ( st.usec() / 1000 ) % 10; + m_hundreths = ( st.usec() / 10000 ) % 10; + m_tenths = ( st.usec() / 100000 ) % 10; + + m_unitSeconds = ( st.sec ) % 10; + m_tenSeconds = ( st.sec / 10 ) % 6; + + m_unitMinutes = ( st.sec / 60 ) % 10; + m_tenMinutes = ( st.sec / 600 ) % 6; + + m_unitHours = ( st.sec / 3600 ) % 10; + m_tenHours = (st.sec / 36000 ) % 10; + + updateTimeDisplay(); +} + +void +TransportDialog::displayFrameTime(const RealTime &rt) +{ + RealTime st = rt; + + slotResetBackground(); + + if (m_lastMode != FrameMode) { + m_transport->HourColonPixmap->hide(); + m_transport->MinuteColonPixmap->hide(); + m_transport->SecondColonPixmap->hide(); + m_transport->HundredthColonPixmap->hide(); + m_lastMode = FrameMode; + } + + // If time is negative then reverse the time and set the minus flag + // + if (st < RealTime::zeroTime) { + st = RealTime::zeroTime - st; + if (!m_lastNegative) { + m_transport->NegativePixmap->setPixmap(m_lcdNegative); + m_lastNegative = true; + } + } else // don't show the flag + { + if (m_lastNegative) { + m_transport->NegativePixmap->clear(); + m_lastNegative = false; + } + } + + long frame = RealTime::realTime2Frame(st, m_sampleRate); + + m_tenThousandths = frame % 10; + frame /= 10; + m_thousandths = frame % 10; + frame /= 10; + m_hundreths = frame % 10; + frame /= 10; + m_tenths = frame % 10; + frame /= 10; + m_unitSeconds = frame % 10; + frame /= 10; + m_tenSeconds = frame % 10; + frame /= 10; + m_unitMinutes = frame % 10; + frame /= 10; + m_tenMinutes = frame % 10; + frame /= 10; + m_unitHours = frame % 10; + frame /= 10; + m_tenHours = frame % 10; + frame /= 10; + + updateTimeDisplay(); +} + +void +TransportDialog::displaySMPTETime(const RealTime &rt) +{ + RealTime st = rt; + + slotResetBackground(); + + if (m_lastMode != SMPTEMode) { + m_transport->HourColonPixmap->show(); + m_transport->MinuteColonPixmap->show(); + m_transport->SecondColonPixmap->show(); + m_transport->HundredthColonPixmap->show(); + m_lastMode = SMPTEMode; + } + + // If time is negative then reverse the time and set the minus flag + // + if (st < RealTime::zeroTime) { + st = RealTime::zeroTime - st; + if (!m_lastNegative) { + m_transport->NegativePixmap->setPixmap(m_lcdNegative); + m_lastNegative = true; + } + } else // don't show the flag + { + if (m_lastNegative) { + m_transport->NegativePixmap->clear(); + m_lastNegative = false; + } + } + + m_tenThousandths = + (( st.usec() * m_framesPerSecond * m_bitsPerFrame) / 1000000 ) % 10; + m_thousandths = + (( st.usec() * m_framesPerSecond * m_bitsPerFrame) / 10000000 ) % + (m_bitsPerFrame / 10); + m_hundreths = + (( st.usec() * m_framesPerSecond) / 1000000 ) % 10; + m_tenths = + (( st.usec() * m_framesPerSecond) / 10000000 ) % 10; + + m_unitSeconds = ( st.sec ) % 10; + m_tenSeconds = ( st.sec / 10 ) % 6; + + m_unitMinutes = ( st.sec / 60 ) % 10; + m_tenMinutes = ( st.sec / 600 ) % 6; + + m_unitHours = ( st.sec / 3600 ) % 10; + m_tenHours = ( st.sec / 36000 ) % 10; + + updateTimeDisplay(); +} + +void +TransportDialog::displayBarTime(int bar, int beat, int unit) +{ + if (m_lastMode != BarMode) { + m_transport->HourColonPixmap->hide(); + m_transport->MinuteColonPixmap->show(); + m_transport->SecondColonPixmap->hide(); + m_transport->HundredthColonPixmap->hide(); + m_lastMode = BarMode; + } + + // If time is negative then reverse the time and set the minus flag + // + if (bar < 0) { + bar = -bar; + if (!m_lastNegative) { + m_transport->NegativePixmap->setPixmap(m_lcdNegative); + m_lastNegative = true; + } + } else // don't show the flag + { + if (m_lastNegative) { + m_transport->NegativePixmap->clear(); + m_lastNegative = false; + } + } + + if (m_currentMode == BarMetronomeMode && unit < 2) { + if (beat == 1) { + slotSetBackground(Qt::red); + } else { + slotSetBackground(Qt::cyan); + } + } else { + slotResetBackground(); + } + + m_tenThousandths = ( unit ) % 10; + m_thousandths = ( unit / 10 ) % 10; + m_hundreths = ( unit / 100 ) % 10; + m_tenths = ( unit / 1000 ) % 10; + + if (m_tenths == 0) { + m_tenths = -1; + if (m_hundreths == 0) { + m_hundreths = -1; + if (m_thousandths == 0) { + m_thousandths = -1; + } + } + } + + m_unitSeconds = ( beat ) % 10; + m_tenSeconds = ( beat / 10 ) % 6; + + if (m_tenSeconds == 0) { + m_tenSeconds = -1; + } + + m_unitMinutes = ( bar ) % 10; + m_tenMinutes = ( bar / 10 ) % 10; + + m_unitHours = ( bar / 100 ) % 10; + m_tenHours = ( bar / 1000 ) % 10; + + if (m_tenHours == 0) { + m_tenHours = -1; + if (m_unitHours == 0) { + m_unitHours = -1; + if (m_tenMinutes == 0) { + m_tenMinutes = -1; + } + } + } + + updateTimeDisplay(); +} + +void +TransportDialog::updateTimeDisplay() +{ + if (m_tenThousandths != m_lastTenThousandths) { + if (m_tenThousandths < 0) + m_transport->TenThousandthsPixmap->clear(); + else + m_transport->TenThousandthsPixmap->setPixmap(m_lcdList[m_tenThousandths]); + m_lastTenThousandths = m_tenThousandths; + } + + if (m_thousandths != m_lastThousandths) { + if (m_thousandths < 0) + m_transport->ThousandthsPixmap->clear(); + else + m_transport->ThousandthsPixmap->setPixmap(m_lcdList[m_thousandths]); + m_lastThousandths = m_thousandths; + } + + if (m_hundreths != m_lastHundreths) { + if (m_hundreths < 0) + m_transport->HundredthsPixmap->clear(); + else + m_transport->HundredthsPixmap->setPixmap(m_lcdList[m_hundreths]); + m_lastHundreths = m_hundreths; + } + + if (m_tenths != m_lastTenths) { + if (m_tenths < 0) + m_transport->TenthsPixmap->clear(); + else + m_transport->TenthsPixmap->setPixmap(m_lcdList[m_tenths]); + m_lastTenths = m_tenths; + } + + if (m_unitSeconds != m_lastUnitSeconds) { + if (m_unitSeconds < 0) + m_transport->UnitSecondsPixmap->clear(); + else + m_transport->UnitSecondsPixmap->setPixmap(m_lcdList[m_unitSeconds]); + m_lastUnitSeconds = m_unitSeconds; + } + + if (m_tenSeconds != m_lastTenSeconds) { + if (m_tenSeconds < 0) + m_transport->TenSecondsPixmap->clear(); + else + m_transport->TenSecondsPixmap->setPixmap(m_lcdList[m_tenSeconds]); + m_lastTenSeconds = m_tenSeconds; + } + + if (m_unitMinutes != m_lastUnitMinutes) { + if (m_unitMinutes < 0) + m_transport->UnitMinutesPixmap->clear(); + else + m_transport->UnitMinutesPixmap->setPixmap(m_lcdList[m_unitMinutes]); + m_lastUnitMinutes = m_unitMinutes; + } + + if (m_tenMinutes != m_lastTenMinutes) { + if (m_tenMinutes < 0) + m_transport->TenMinutesPixmap->clear(); + else + m_transport->TenMinutesPixmap->setPixmap(m_lcdList[m_tenMinutes]); + m_lastTenMinutes = m_tenMinutes; + } + + if (m_unitHours != m_lastUnitHours) { + if (m_unitHours < 0) + m_transport->UnitHoursPixmap->clear(); + else + m_transport->UnitHoursPixmap->setPixmap(m_lcdList[m_unitHours]); + m_lastUnitHours = m_unitHours; + } + + if (m_tenHours != m_lastTenHours) { + if (m_tenHours < 0) + m_transport->TenHoursPixmap->clear(); + else + m_transport->TenHoursPixmap->setPixmap(m_lcdList[m_tenHours]); + m_lastTenHours = m_tenHours; + } +} + +void +TransportDialog::setTempo(const tempoT &tempo) +{ + if (m_tempo == tempo) + return ; + m_tempo = tempo; + + // Send the quarter note length to the sequencer - shouldn't + // really hang this off here but at least it's a single point + // where the tempo should always be consistent. Quarter Note + // Length is sent (MIDI CLOCK) at 24ppqn. + // + double qnD = 60.0 / Composition::getTempoQpm(tempo); + RealTime qnTime = + RealTime(long(qnD), + long((qnD - double(long(qnD))) * 1000000000.0)); + + StudioControl::sendQuarterNoteLength(qnTime); + + QString tempoString; + tempoString.sprintf("%4.3f", Composition::getTempoQpm(tempo)); + + m_transport->TempoDisplay->setText(tempoString); +} + +void +TransportDialog::setTimeSignature(const TimeSignature &timeSig) +{ + int numerator = timeSig.getNumerator(); + int denominator = timeSig.getDenominator(); + if (m_numerator == numerator && m_denominator == denominator) + return ; + m_numerator = numerator; + m_denominator = denominator; + + QString timeSigString; + timeSigString.sprintf("%d/%d", numerator, denominator); + m_transport->TimeSigDisplay->setText(timeSigString); +} + +void +TransportDialog::setMidiInLabel(const MappedEvent *mE) +{ + assert(mE > 0); + + switch (mE->getType()) { + case MappedEvent::MidiNote: + case MappedEvent::MidiNoteOneShot: + { + // don't do anything if we've got an effective NOTE OFF + // + if (mE->getVelocity() == 0) + return ; + + MidiPitchLabel mPL(mE->getPitch()); + m_transport->InDisplay->setText + (mPL.getQString() + + QString(" %1").arg(mE->getVelocity())); + } + break; + + case MappedEvent::MidiPitchBend: + m_transport->InDisplay->setText(i18n("PITCH WHEEL")); + break; + + case MappedEvent::MidiController: + m_transport->InDisplay->setText(i18n("CONTROLLER")); + break; + + case MappedEvent::MidiProgramChange: + m_transport->InDisplay->setText(i18n("PROG CHNGE")); + break; + + case MappedEvent::MidiKeyPressure: + case MappedEvent::MidiChannelPressure: + m_transport->InDisplay->setText(i18n("PRESSURE")); + break; + + case MappedEvent::MidiSystemMessage: + m_transport->InDisplay->setText(i18n("SYS MESSAGE")); + break; + + default: // do nothing + return ; + } + + // Reset the timer if it's already running + // + if (m_midiInTimer->isActive()) + m_midiInTimer->stop(); + + // 1.5 second timeout for MIDI event + // + m_midiInTimer->start(1500, true); +} + +void +TransportDialog::slotClearMidiInLabel() +{ + m_transport->InDisplay->setText(i18n(QString("NO EVENTS"))); + + // also, just to be sure: + slotResetBackground(); +} + +void +TransportDialog::setMidiOutLabel(const MappedEvent *mE) +{ + assert(mE > 0); + + switch (mE->getType()) { + case MappedEvent::MidiNote: + case MappedEvent::MidiNoteOneShot: + { + MidiPitchLabel mPL(mE->getPitch()); + m_transport->OutDisplay->setText + (mPL.getQString() + + QString(" %1").arg(mE->getVelocity())); + } + break; + + case MappedEvent::MidiPitchBend: + m_transport->OutDisplay->setText(i18n("PITCH WHEEL")); + break; + + case MappedEvent::MidiController: + m_transport->OutDisplay->setText(i18n("CONTROLLER")); + break; + + case MappedEvent::MidiProgramChange: + m_transport->OutDisplay->setText(i18n("PROG CHNGE")); + break; + + case MappedEvent::MidiKeyPressure: + case MappedEvent::MidiChannelPressure: + m_transport->OutDisplay->setText(i18n("PRESSURE")); + break; + + case MappedEvent::MidiSystemMessage: + m_transport->OutDisplay->setText(i18n("SYS MESSAGE")); + break; + + default: // do nothing + return ; + } + + // Reset the timer if it's already running + // + if (m_midiOutTimer->isActive()) + m_midiOutTimer->stop(); + + // 200 millisecond timeout + // + m_midiOutTimer->start(200, true); +} + +void +TransportDialog::slotClearMidiOutLabel() +{ + m_transport->OutDisplay->setText(i18n(QString("NO EVENTS"))); +} + +void +TransportDialog::closeEvent (QCloseEvent * /*e*/) +{ + //e->accept(); // accept the close event here + emit closed(); +} + +void +TransportDialog::slotLoopButtonClicked() +{ + // disable if JACK transport has been set #1240039 - DMM + // KConfig* config = rgapp->config(); + // config->setGroup(SequencerOptionsConfigGroup); + // if (config->readBoolEntry("jacktransport", false)) + // { + // //!!! - this will fail silently + // m_transport->LoopButton->setEnabled(false); + // m_transport->LoopButton->setOn(false); + // return; + // } + + if (m_transport->LoopButton->isOn()) { + emit setLoop(); + } else { + emit unsetLoop(); + } +} + +void +TransportDialog::slotSetStartLoopingPointAtMarkerPos() +{ + emit setLoopStartTime(); +} + +void +TransportDialog::slotSetStopLoopingPointAtMarkerPos() +{ + emit setLoopStopTime(); +} + +void +TransportDialog::slotPanelOpenButtonClicked() +{ + int rfh = m_transport->RecordingFrame->height(); + + if (m_transport->RecordingFrame->isVisible()) { + m_transport->RecordingFrame->hide(); + setFixedSize(width(), height() - rfh); + m_transport->PanelOpenButton->setPixmap(m_panelClosed); + m_isExpanded = false; + } else { + setFixedSize(width(), height() + rfh); + m_transport->RecordingFrame->show(); + m_transport->PanelOpenButton->setPixmap(m_panelOpen); + m_isExpanded = true; + } +} + +void +TransportDialog::slotPanelCloseButtonClicked() +{ + int rfh = m_transport->RecordingFrame->height(); + + if (m_transport->RecordingFrame->isVisible()) { + m_transport->RecordingFrame->hide(); + setFixedSize(width(), height() - rfh); + m_transport->PanelOpenButton->setPixmap(m_panelClosed); + m_isExpanded = false; + } +} + +bool +TransportDialog::isExpanded() +{ + return m_isExpanded; +} + +void +TransportDialog::slotEditTempo() +{ + emit editTempo(this); +} + +void +TransportDialog::slotEditTimeSignature() +{ + emit editTimeSignature(this); +} + +void +TransportDialog::slotEditTime() +{ + emit editTransportTime(this); +} + +void +TransportDialog::slotSetBackground(QColor c) +{ + if (!m_haveOriginalBackground) { + m_originalBackground = m_transport->LCDBoxFrame->paletteBackgroundColor(); + m_haveOriginalBackground = true; + } + + m_transport->LCDBoxFrame->setPaletteBackgroundColor(c); + m_transport->NegativePixmap->setPaletteBackgroundColor(c); + m_transport->TenHoursPixmap->setPaletteBackgroundColor(c); + m_transport->UnitHoursPixmap->setPaletteBackgroundColor(c); + m_transport->TimeDisplayLabel->setPaletteBackgroundColor(c); + + /* this is a bit more thorough, but too slow and flickery: + + const QObjectList *children = m_transport->LCDBoxFrame->children(); + QObjectListIt it(*children); + QObject *obj; + + while ((obj = it.current()) != 0) { + + QWidget *w = dynamic_cast(obj); + if (w) { + w->setPaletteBackgroundColor(c); + } + ++it; + } + + */ + + m_isBackgroundSet = true; +} + +void +TransportDialog::slotResetBackground() +{ + if (m_isBackgroundSet) { + slotSetBackground(m_originalBackground); + } + m_isBackgroundSet = false; +} + +} +#include "TransportDialog.moc" diff --git a/src/gui/dialogs/TransportDialog.h b/src/gui/dialogs/TransportDialog.h new file mode 100644 index 0000000..e5c4948 --- /dev/null +++ b/src/gui/dialogs/TransportDialog.h @@ -0,0 +1,231 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ROSEGARDENTRANSPORTDIALOG_H_ +#define _RG_ROSEGARDENTRANSPORTDIALOG_H_ + +#include +#include +#include +#include +#include "base/Composition.h" // for tempoT +#include "RosegardenTransport.h" // generated by uic + +class RosegardenTransport; +class QWidget; +class QTimer; +class QPushButton; +class QCloseEvent; +class QAccel; + + +namespace Rosegarden +{ + +class TimeSignature; +class RealTime; +class MappedEvent; + + +class TransportDialog : public QWidget +{ +Q_OBJECT +public: + TransportDialog(QWidget *parent=0, + const char *name=0, + WFlags flags = /*Qt::WStyle_StaysOnTop |*/ + Qt::WStyle_NormalBorder); + ~TransportDialog(); + + enum TimeDisplayMode { RealMode, SMPTEMode, BarMode, BarMetronomeMode, FrameMode }; + + std::string getCurrentModeAsString(); + TimeDisplayMode getCurrentMode() { return m_currentMode; } + void setNewMode(const std::string& newModeAsString); + void setNewMode(const TimeDisplayMode& newMode); + bool isShowingTimeToEnd(); + bool isExpanded(); + + void displayRealTime(const RealTime &rt); + void displaySMPTETime(const RealTime &rt); + void displayFrameTime(const RealTime &rt); + void displayBarTime(int bar, int beat, int unit); + + void setTempo(const tempoT &tempo); + void setTimeSignature(const TimeSignature &timeSig); + + void setSMPTEResolution(int framesPerSecond, int bitsPerFrame); + void getSMPTEResolution(int &framesPerSecond, int &bitsPerFrame); + + // Called indirectly from the sequencer and from the GUI to + // show incoming and outgoing MIDI events on the Transport + // + void setMidiInLabel(const MappedEvent *mE); + void setMidiOutLabel(const MappedEvent *mE); + + // Return the accelerator object + // + QAccel* getAccelerators() { return m_accelerators; } + + // RosegardenTransport member accessors + QPushButton* MetronomeButton() { return m_transport->MetronomeButton; } + QPushButton* SoloButton() { return m_transport->SoloButton; } + QPushButton* LoopButton() { return m_transport->LoopButton; } + QPushButton* PlayButton() { return m_transport->PlayButton; } + QPushButton* StopButton() { return m_transport->StopButton; } + QPushButton* FfwdButton() { return m_transport->FfwdButton; } + QPushButton* RewindButton() { return m_transport->RewindButton; } + QPushButton* RecordButton() { return m_transport->RecordButton; } + QPushButton* RewindEndButton() { return m_transport->RewindEndButton; } + QPushButton* FfwdEndButton() { return m_transport->FfwdEndButton; } + QPushButton* TimeDisplayButton() { return m_transport->TimeDisplayButton; } + QPushButton* ToEndButton() { return m_transport->ToEndButton; } + + virtual void show(); + virtual void hide(); + +protected: + virtual void closeEvent(QCloseEvent * e); + void computeSampleRate(); + void cycleThroughModes(); + void displayTime(); + +public slots: + + // These two slots are activated by QTimers + // + void slotClearMidiInLabel(); + void slotClearMidiOutLabel(); + + // These just change the little labels that say what + // mode we're in, nothing else + // + void slotChangeTimeDisplay(); + void slotChangeToEnd(); + + void slotLoopButtonClicked(); + + void slotPanelOpenButtonClicked(); + void slotPanelCloseButtonClicked(); + + void slotEditTempo(); + void slotEditTimeSignature(); + void slotEditTime(); + + void slotSetBackground(QColor); + void slotResetBackground(); + + void slotSetStartLoopingPointAtMarkerPos(); + void slotSetStopLoopingPointAtMarkerPos(); + +signals: + void closed(); + + // Set and unset the loop at the RosegardenGUIApp + // + void setLoop(); + void unsetLoop(); + void setLoopStartTime(); + void setLoopStopTime(); + + void editTempo(QWidget *); + void editTimeSignature(QWidget *); + void editTransportTime(QWidget *); + void scrollTempo(int); + void panic(); + +private: + void loadPixmaps(); + void resetFonts(); + void resetFont(QWidget *); + void initModeMap(); + + //--------------- Data members --------------------------------- + + RosegardenTransport* m_transport; + + std::map m_lcdList; + QPixmap m_lcdNegative; + + int m_lastTenHours; + int m_lastUnitHours; + int m_lastTenMinutes; + int m_lastUnitMinutes; + int m_lastTenSeconds; + int m_lastUnitSeconds; + int m_lastTenths; + int m_lastHundreths; + int m_lastThousandths; + int m_lastTenThousandths; + + bool m_lastNegative; + TimeDisplayMode m_lastMode; + TimeDisplayMode m_currentMode; + + int m_tenHours; + int m_unitHours; + int m_tenMinutes; + int m_unitMinutes; + int m_tenSeconds; + int m_unitSeconds; + int m_tenths; + int m_hundreths; + int m_thousandths; + int m_tenThousandths; + + tempoT m_tempo; + int m_numerator; + int m_denominator; + + int m_framesPerSecond; + int m_bitsPerFrame; + + QTimer *m_midiInTimer; + QTimer *m_midiOutTimer; + QTimer *m_clearMetronomeTimer; + + QPixmap m_panelOpen; + QPixmap m_panelClosed; + + void updateTimeDisplay(); + + QAccel *m_accelerators; + bool m_isExpanded; + + bool m_haveOriginalBackground; + bool m_isBackgroundSet; + QColor m_originalBackground; + + int m_sampleRate; + + std::map m_modeMap; +}; + + + + + +} + +#endif diff --git a/src/gui/dialogs/TriggerSegmentDialog.cpp b/src/gui/dialogs/TriggerSegmentDialog.cpp new file mode 100644 index 0000000..a5064e1 --- /dev/null +++ b/src/gui/dialogs/TriggerSegmentDialog.cpp @@ -0,0 +1,181 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TriggerSegmentDialog.h" +#include + +#include "base/BaseProperties.h" +#include +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/Composition.h" +#include "base/TriggerSegment.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +TriggerSegmentDialog::TriggerSegmentDialog(QWidget *parent, + Composition *composition) : + KDialogBase(parent, "triggersegmentdialog", true, i18n("Trigger Segment"), + Ok | Cancel, Ok), + m_composition(composition) +{ + QVBox *vbox = makeVBoxMainWidget(); + + QFrame *frame = new QFrame(vbox); + QGridLayout *layout = new QGridLayout(frame, 3, 2, 5, 5); + + QLabel *label = new QLabel(i18n("Trigger segment: "), frame); + layout->addWidget(label, 0, 0); + + m_segment = new KComboBox(frame); + layout->addWidget(m_segment, 0, 1); + + int n = 1; + for (Composition::triggersegmentcontaineriterator i = + m_composition->getTriggerSegments().begin(); + i != m_composition->getTriggerSegments().end(); ++i) { + m_segment->insertItem + (QString("%1. %2").arg(n++).arg(strtoqstr((*i)->getSegment()->getLabel()))); + } + + label = new QLabel(i18n("Perform with timing: "), frame); + layout->addWidget(label, 1, 0); + + m_adjustTime = new KComboBox(frame); + layout->addWidget(m_adjustTime, 1, 1); + + m_adjustTime->insertItem(i18n("As stored")); + m_adjustTime->insertItem(i18n("Truncate if longer than note")); + m_adjustTime->insertItem(i18n("End at same time as note")); + m_adjustTime->insertItem(i18n("Stretch or squash segment to note duration")); + + m_retune = new QCheckBox(i18n("Adjust pitch to note"), frame); + m_retune->setChecked(true); + + layout->addWidget(m_retune, 2, 1); + + setupFromConfig(); +} + +void +TriggerSegmentDialog::setupFromConfig() +{ + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + + int seg = config->readNumEntry("triggersegmentlastornament", 0); + std::string timing = qstrtostr + (config->readEntry + ("triggersegmenttiming", + strtoqstr(BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH))); + bool retune = config->readBoolEntry("triggersegmentretune", true); + + if (seg >= 0 && seg < m_segment->count()) + m_segment->setCurrentItem(seg); + + if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_NONE) { + m_adjustTime->setCurrentItem(0); + } else if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH) { + m_adjustTime->setCurrentItem(3); + } else if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_START) { + m_adjustTime->setCurrentItem(1); + } else if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_END) { + m_adjustTime->setCurrentItem(2); + } + + m_retune->setChecked(retune); +} + +TriggerSegmentId +TriggerSegmentDialog::getId() const +{ + int ix = m_segment->currentItem(); + + for (Composition::triggersegmentcontaineriterator i = + m_composition->getTriggerSegments().begin(); + i != m_composition->getTriggerSegments().end(); ++i) { + + if (ix == 0) + return (*i)->getId(); + --ix; + } + + return 0; +} + +bool +TriggerSegmentDialog::getRetune() const +{ + return m_retune->isChecked(); +} + +std::string +TriggerSegmentDialog::getTimeAdjust() const +{ + int option = m_adjustTime->currentItem(); + + switch (option) { + + case 0: + return BaseProperties::TRIGGER_SEGMENT_ADJUST_NONE; + case 1: + return BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_START; + case 2: + return BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_END; + case 3: + return BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH; + + default: + return BaseProperties::TRIGGER_SEGMENT_ADJUST_NONE; + } +} + +void +TriggerSegmentDialog::slotOk() +{ + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + + config->writeEntry("triggersegmenttiming", strtoqstr(getTimeAdjust())); + config->writeEntry("triggersegmentretune", m_retune->isChecked()); + config->writeEntry("triggersegmentlastornament", m_segment->currentItem()); + + accept(); +} + +} +#include "TriggerSegmentDialog.moc" diff --git a/src/gui/dialogs/TriggerSegmentDialog.h b/src/gui/dialogs/TriggerSegmentDialog.h new file mode 100644 index 0000000..3f74f45 --- /dev/null +++ b/src/gui/dialogs/TriggerSegmentDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_TRIGGERSEGMENTDIALOG_H_ +#define _RG_TRIGGERSEGMENTDIALOG_H_ + +#include "base/TriggerSegment.h" +#include +#include + + +class QWidget; +class QCheckBox; +class KComboBox; + + +namespace Rosegarden +{ + +class Composition; + + +class TriggerSegmentDialog : public KDialogBase +{ + Q_OBJECT + +public: + TriggerSegmentDialog(QWidget *parent, Composition *); + + TriggerSegmentId getId() const; + bool getRetune() const; + std::string getTimeAdjust() const; + +public slots: + void slotOk(); + +protected: + void setupFromConfig(); + + Composition *m_composition; + KComboBox *m_segment; + QCheckBox *m_retune; + KComboBox *m_adjustTime; +}; + + +} + +#endif diff --git a/src/gui/dialogs/TupletDialog.cpp b/src/gui/dialogs/TupletDialog.cpp new file mode 100644 index 0000000..ed1c583 --- /dev/null +++ b/src/gui/dialogs/TupletDialog.cpp @@ -0,0 +1,365 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "TupletDialog.h" +#include + +#include +#include "base/NotationTypes.h" +#include "gui/editors/notation/NotationStrings.h" +#include "gui/editors/notation/NotePixmapFactory.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +TupletDialog::TupletDialog(QWidget *parent, Note::Type defaultUnitType, + timeT maxDuration) : + KDialogBase(parent, 0, true, i18n("Tuplet"), Ok | Cancel | Help), + m_maxDuration(maxDuration) +{ + setHelp("nv-tuplets"); + QVBox *vbox = makeVBoxMainWidget(); + + QGroupBox *timingBox = new QGroupBox + (1, Horizontal, i18n("New timing for tuplet group"), vbox); + + if (m_maxDuration > 0) { + + // bit of a sanity check + if (maxDuration < Note(Note::Semiquaver).getDuration()) { + maxDuration = Note(Note::Semiquaver).getDuration(); + } + + Note::Type maxUnitType = + Note::getNearestNote(maxDuration / 2, 0).getNoteType(); + if (defaultUnitType > maxUnitType) + defaultUnitType = maxUnitType; + } + + QFrame *timingFrame = new QFrame(timingBox); + QGridLayout *timingLayout = new QGridLayout(timingFrame, 3, 3, 5, 5); + + timingLayout->addWidget(new QLabel(i18n("Play "), timingFrame), 0, 0); + + m_untupledCombo = new KComboBox(timingFrame); + timingLayout->addWidget(m_untupledCombo, 0, 1); + + m_unitCombo = new KComboBox(timingFrame); + timingLayout->addWidget(m_unitCombo, 0, 2); + + for (Note::Type t = Note::Shortest; t <= Note::Longest; ++t) { + Note note(t); + timeT duration(note.getDuration()); + if (maxDuration > 0 && (2 * duration > maxDuration)) + break; + timeT e; // error factor, ignore + m_unitCombo->insertItem(NotePixmapFactory::toQPixmap + (NotePixmapFactory::makeNoteMenuPixmap(duration, e)), + NotationStrings::makeNoteMenuLabel(duration, false, e, true)); + if (defaultUnitType == t) { + m_unitCombo->setCurrentItem(m_unitCombo->count() - 1); + } + } + + timingLayout->addWidget(new QLabel(i18n("in the time of "), timingFrame), 1, 0); + + m_tupledCombo = new KComboBox(timingFrame); + timingLayout->addWidget(m_tupledCombo, 1, 1); + + m_hasTimingAlready = new QCheckBox + (i18n("Timing is already correct: update display only"), timingFrame); + m_hasTimingAlready->setChecked(false); + timingLayout->addMultiCellWidget(m_hasTimingAlready, 2, 2, 0, 2); + + connect(m_hasTimingAlready, SIGNAL(clicked()), this, SLOT(slotHasTimingChanged())); + + updateUntupledCombo(); + updateTupledCombo(); + + m_timingDisplayBox = new QGroupBox + (1, Horizontal, i18n("Timing calculations"), vbox); + + QGrid *timingDisplayGrid = new QGrid(3, QGrid::Horizontal, m_timingDisplayBox); + + if (maxDuration > 0) { + + new QLabel(i18n("Selected region:"), timingDisplayGrid); + new QLabel("", timingDisplayGrid); + m_selectionDurationDisplay = new QLabel("x", timingDisplayGrid); + m_selectionDurationDisplay->setAlignment(int(QLabel::AlignVCenter | + QLabel::AlignRight)); + } else { + m_selectionDurationDisplay = 0; + } + + new QLabel(i18n("Group with current timing:"), timingDisplayGrid); + m_untupledDurationCalculationDisplay = new QLabel("x", timingDisplayGrid); + m_untupledDurationDisplay = new QLabel("x", timingDisplayGrid); + m_untupledDurationDisplay->setAlignment(int(QLabel::AlignVCenter | + QLabel::AlignRight)); + + new QLabel(i18n("Group with new timing:"), timingDisplayGrid); + m_tupledDurationCalculationDisplay = new QLabel("x", timingDisplayGrid); + m_tupledDurationDisplay = new QLabel("x", timingDisplayGrid); + m_tupledDurationDisplay->setAlignment(int(QLabel::AlignVCenter | + QLabel::AlignRight)); + + new QLabel(i18n("Gap created by timing change:"), timingDisplayGrid); + m_newGapDurationCalculationDisplay = new QLabel("x", timingDisplayGrid); + m_newGapDurationDisplay = new QLabel("x", timingDisplayGrid); + m_newGapDurationDisplay->setAlignment(int(QLabel::AlignVCenter | + QLabel::AlignRight)); + + if (maxDuration > 0) { + + new QLabel(i18n("Unchanged at end of selection:"), timingDisplayGrid); + m_unchangedDurationCalculationDisplay = new QLabel + ("x", timingDisplayGrid); + m_unchangedDurationDisplay = new QLabel("x", timingDisplayGrid); + m_unchangedDurationDisplay->setAlignment(int(QLabel::AlignVCenter | + QLabel::AlignRight)); + + } else { + m_unchangedDurationDisplay = 0; + } + + updateTimingDisplays(); + + QObject::connect(m_unitCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotUnitChanged(const QString &))); + + QObject::connect(m_untupledCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotUntupledChanged(const QString &))); + QObject::connect(m_untupledCombo, SIGNAL(textChanged(const QString &)), + this, SLOT(slotUntupledChanged(const QString &))); + + QObject::connect(m_tupledCombo, SIGNAL(activated(const QString &)), + this, SLOT(slotTupledChanged(const QString &))); + QObject::connect(m_tupledCombo, SIGNAL(textChanged(const QString &)), + this, SLOT(slotTupledChanged(const QString &))); +} + +void +TupletDialog::slotHasTimingChanged() +{ + updateUntupledCombo(); + updateTupledCombo(); + m_timingDisplayBox->setEnabled(!m_hasTimingAlready->isChecked()); +} + +Note::Type +TupletDialog::getUnitType() const +{ + return Note::Shortest + m_unitCombo->currentItem(); +} + +int +TupletDialog::getUntupledCount() const +{ + bool isNumeric = true; + int count = m_untupledCombo->currentText().toInt(&isNumeric); + if (count == 0 || !isNumeric) + return 1; + else + return count; +} + +int +TupletDialog::getTupledCount() const +{ + bool isNumeric = true; + int count = m_tupledCombo->currentText().toInt(&isNumeric); + if (count == 0 || !isNumeric) + return 1; + else + return count; +} + +bool +TupletDialog::hasTimingAlready() const +{ + return m_hasTimingAlready->isChecked(); +} + +void +TupletDialog::updateUntupledCombo() +{ + // Untupled combo can contain numbers up to the maximum + // duration divided by the unit duration. If there's no + // maximum, we'll have to put in some likely values and + // allow the user to edit it. Both the numerical combos + // should possibly be spinboxes, except I think I like + // being able to "suggest" a few values + + int maxValue = 12; + + if (m_maxDuration) { + if (m_hasTimingAlready->isChecked()) { + maxValue = (m_maxDuration * 2) / Note(getUnitType()).getDuration(); + } else { + maxValue = m_maxDuration / Note(getUnitType()).getDuration(); + } + } + + QString previousText = m_untupledCombo->currentText(); + if (previousText.toInt() == 0) { + if (maxValue < 3) + previousText = QString("%1").arg(maxValue); + else + previousText = "3"; + } + + m_untupledCombo->clear(); + bool setText = false; + + for (int i = 1; i <= maxValue; ++i) { + QString text = QString("%1").arg(i); + m_untupledCombo->insertItem(text); + if (m_hasTimingAlready->isChecked()) { + if (i == (m_maxDuration * 3) / (Note(getUnitType()).getDuration()*2)) { + m_untupledCombo->setCurrentItem(m_untupledCombo->count() - 1); + } + } else if (text == previousText) { + m_untupledCombo->setCurrentItem(m_untupledCombo->count() - 1); + setText = true; + } + } + + if (!setText) { + m_untupledCombo->setEditText(previousText); + } +} + +void +TupletDialog::updateTupledCombo() +{ + // should contain all positive integers less than the + // largest value in the untupled combo. In principle + // we can support values larger, but we can't quite + // do the tupleting transformation yet + + int untupled = getUntupledCount(); + + QString previousText = m_tupledCombo->currentText(); + if (previousText.toInt() == 0 || + previousText.toInt() > untupled) { + if (untupled < 2) + previousText = QString("%1").arg(untupled); + else + previousText = "2"; + } + + m_tupledCombo->clear(); + + for (int i = 1; i < untupled; ++i) { + QString text = QString("%1").arg(i); + m_tupledCombo->insertItem(text); + if (m_hasTimingAlready->isChecked()) { + if (i == m_maxDuration / Note(getUnitType()).getDuration()) { + m_tupledCombo->setCurrentItem(m_tupledCombo->count() - 1); + } + } else if (text == previousText) { + m_tupledCombo->setCurrentItem(m_tupledCombo->count() - 1); + } + } +} + +void +TupletDialog::updateTimingDisplays() +{ + timeT unitDuration = Note(getUnitType()).getDuration(); + + int untupledCount = getUntupledCount(); + int tupledCount = getTupledCount(); + + timeT untupledDuration = unitDuration * untupledCount; + timeT tupledDuration = unitDuration * tupledCount; + + if (m_selectionDurationDisplay) { + m_selectionDurationDisplay->setText(QString("%1").arg(m_maxDuration)); + } + + m_untupledDurationCalculationDisplay->setText + (QString(" %1 x %2 = ").arg(untupledCount).arg(unitDuration)); + m_untupledDurationDisplay->setText + (QString("%1").arg(untupledDuration)); + + m_tupledDurationCalculationDisplay->setText + (QString(" %1 x %2 = ").arg(tupledCount).arg(unitDuration)); + m_tupledDurationDisplay->setText + (QString("%1").arg(tupledDuration)); + + m_newGapDurationCalculationDisplay->setText + (QString(" %1 - %2 = ").arg(untupledDuration).arg(tupledDuration)); + m_newGapDurationDisplay->setText + (QString("%1").arg(untupledDuration - tupledDuration)); + + if (m_selectionDurationDisplay && m_unchangedDurationDisplay) { + if (m_maxDuration != untupledDuration) { + m_unchangedDurationCalculationDisplay->setText + (QString(" %1 - %2 = ").arg(m_maxDuration).arg(untupledDuration)); + } else { + m_unchangedDurationCalculationDisplay->setText(""); + } + m_unchangedDurationDisplay->setText + (QString("%1").arg(m_maxDuration - untupledDuration)); + } +} + +void +TupletDialog::slotUnitChanged(const QString &) +{ + updateUntupledCombo(); + updateTupledCombo(); + updateTimingDisplays(); +} + +void +TupletDialog::slotUntupledChanged(const QString &) +{ + updateTupledCombo(); + updateTimingDisplays(); +} + +void +TupletDialog::slotTupledChanged(const QString &) +{ + updateTimingDisplays(); +} + +} +#include "TupletDialog.moc" diff --git a/src/gui/dialogs/TupletDialog.h b/src/gui/dialogs/TupletDialog.h new file mode 100644 index 0000000..bc7252b --- /dev/null +++ b/src/gui/dialogs/TupletDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_TUPLETDIALOG_H_ +#define _RG_TUPLETDIALOG_H_ + +#include "base/NotationTypes.h" +#include +#include "base/Event.h" + + +class QWidget; +class QString; +class QLabel; +class QGroupBox; +class QCheckBox; +class KComboBox; + + +namespace Rosegarden +{ + + + +class TupletDialog : public KDialogBase +{ + Q_OBJECT + +public: + TupletDialog(QWidget *parent, + Note::Type defaultUnitType, + timeT maxDuration = 0); + + Note::Type getUnitType() const; + int getUntupledCount() const; + int getTupledCount() const; + bool hasTimingAlready() const; + +public slots: + void slotUnitChanged(const QString &); + void slotUntupledChanged(const QString &); + void slotTupledChanged(const QString &); + void slotHasTimingChanged(); + +protected: + + void updateUntupledCombo(); + void updateTupledCombo(); + void updateTimingDisplays(); + + //--------------- Data members --------------------------------- + + KComboBox *m_unitCombo; + KComboBox *m_untupledCombo; + KComboBox *m_tupledCombo; + + QCheckBox *m_hasTimingAlready; + + QGroupBox *m_timingDisplayBox; + QLabel *m_selectionDurationDisplay; + QLabel *m_untupledDurationCalculationDisplay; + QLabel *m_untupledDurationDisplay; + QLabel *m_tupledDurationCalculationDisplay; + QLabel *m_tupledDurationDisplay; + QLabel *m_newGapDurationCalculationDisplay; + QLabel *m_newGapDurationDisplay; + QLabel *m_unchangedDurationCalculationDisplay; + QLabel *m_unchangedDurationDisplay; + + timeT m_maxDuration; +}; + + + +} + +#endif diff --git a/src/gui/dialogs/UnusedAudioSelectionDialog.cpp b/src/gui/dialogs/UnusedAudioSelectionDialog.cpp new file mode 100644 index 0000000..0a44168 --- /dev/null +++ b/src/gui/dialogs/UnusedAudioSelectionDialog.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "UnusedAudioSelectionDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +UnusedAudioSelectionDialog::UnusedAudioSelectionDialog(QWidget *parent, + QString introductoryText, + std::vector fileNames, + bool offerCancel) : + KDialogBase(parent, 0, true, i18n("Select Unused Audio Files"), (offerCancel ? (Ok | Cancel) : Ok)) +{ + QVBox *vbox = makeVBoxMainWidget(); + new QLabel(introductoryText, vbox); + + m_listView = new KListView(vbox); + + m_listView->addColumn(i18n("File name")); + m_listView->addColumn(i18n("File size")); + m_listView->addColumn(i18n("Last modified date")); + + for (unsigned int i = 0; i < fileNames.size(); ++i) { + QString fileName = fileNames[i]; + QFileInfo info(fileName); + QString fileSize = i18n(" (not found) "); + QString fileDate; + if (info.exists()) { + fileSize = QString(" %1 ").arg(info.size()); + fileDate = QString(" %1 ").arg(info.lastModified().toString()); + } + QListViewItem *item = new KListViewItem + (m_listView, fileName, fileSize, fileDate); + } + + m_listView->setSelectionMode(QListView::Multi); +} + +std::vector +UnusedAudioSelectionDialog::getSelectedAudioFileNames() const +{ + std::vector selectedNames; + + QListViewItem *item = m_listView->firstChild(); + + while (item) { + + if (m_listView->isSelected(item)) { + selectedNames.push_back(item->text(0)); + } + + item = item->nextSibling(); + } + + return selectedNames; +} + +} diff --git a/src/gui/dialogs/UnusedAudioSelectionDialog.h b/src/gui/dialogs/UnusedAudioSelectionDialog.h new file mode 100644 index 0000000..e11301a --- /dev/null +++ b/src/gui/dialogs/UnusedAudioSelectionDialog.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_UNUSEDAUDIOSELECTIONDIALOG_H_ +#define _RG_UNUSEDAUDIOSELECTIONDIALOG_H_ + +#include +#include +#include + + +class QWidget; +class QListView; + + +namespace Rosegarden +{ + + + +class UnusedAudioSelectionDialog : public KDialogBase +{ +public: + UnusedAudioSelectionDialog(QWidget *, + QString introductoryText, + std::vector fileNames, + bool offerCancel = true); + + std::vector getSelectedAudioFileNames() const; + +protected: + QListView *m_listView; +}; + + + + +} + +#endif diff --git a/src/gui/dialogs/UseOrnamentDialog.cpp b/src/gui/dialogs/UseOrnamentDialog.cpp new file mode 100644 index 0000000..971f170 --- /dev/null +++ b/src/gui/dialogs/UseOrnamentDialog.cpp @@ -0,0 +1,264 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "UseOrnamentDialog.h" +#include +#include + +#include "base/BaseProperties.h" +#include +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/Composition.h" +#include "base/NotationTypes.h" +#include "base/TriggerSegment.h" +#include "gui/editors/notation/NotePixmapFactory.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +UseOrnamentDialog::UseOrnamentDialog(QWidget *parent, + Composition *composition) : + KDialogBase(parent, "useornamentdialog", true, i18n("Use Ornament"), + Ok | Cancel, Ok), + m_composition(composition) +{ + QVBox *vbox = makeVBoxMainWidget(); + QLabel *label; + + QGroupBox *notationBox = new QGroupBox(1, Horizontal, i18n("Notation"), vbox); + + QFrame *frame = new QFrame(notationBox); + QGridLayout *layout = new QGridLayout(frame, 4, 1, 5, 5); + + label = new QLabel(i18n("Display as: "), frame); + layout->addWidget(label, 0, 0); + + m_mark = new KComboBox(frame); + layout->addWidget(m_mark, 0, 1); + + m_marks.push_back(Marks::Trill); + m_marks.push_back(Marks::LongTrill); + m_marks.push_back(Marks::TrillLine); + m_marks.push_back(Marks::Turn); + m_marks.push_back(Marks::Mordent); + m_marks.push_back(Marks::MordentInverted); + m_marks.push_back(Marks::MordentLong); + m_marks.push_back(Marks::MordentLongInverted); + + const QString markLabels[] = { + i18n("Trill"), i18n("Trill with line"), i18n("Trill line only"), + i18n("Turn"), i18n("Mordent"), i18n("Inverted mordent"), + i18n("Long mordent"), i18n("Long inverted mordent"), + }; + + for (size_t i = 0; i < m_marks.size(); ++i) { + m_mark->insertItem(NotePixmapFactory::toQPixmap + (NotePixmapFactory::makeMarkMenuPixmap(m_marks[i])), + markLabels[i]); + } + m_mark->insertItem(i18n("Text mark")); + + connect(m_mark, SIGNAL(activated(int)), this, SLOT(slotMarkChanged(int))); + + m_textLabel = new QLabel(i18n(" Text: "), frame); + layout->addWidget(m_textLabel, 0, 2); + + m_text = new QLineEdit(frame); + layout->addWidget(m_text, 0, 3); + + QGroupBox *performBox = new QGroupBox(1, Horizontal, i18n("Performance"), vbox); + + frame = new QFrame(performBox); + layout = new QGridLayout(frame, 3, 2, 5, 5); + + label = new QLabel(i18n("Perform using triggered segment: "), frame); + layout->addWidget(label, 0, 0); + + m_ornament = new KComboBox(frame); + layout->addWidget(m_ornament, 0, 1); + + int n = 1; + for (Composition::triggersegmentcontaineriterator i = + m_composition->getTriggerSegments().begin(); + i != m_composition->getTriggerSegments().end(); ++i) { + m_ornament->insertItem + (QString("%1. %2").arg(n++).arg(strtoqstr((*i)->getSegment()->getLabel()))); + } + + label = new QLabel(i18n("Perform with timing: "), frame); + layout->addWidget(label, 1, 0); + + m_adjustTime = new KComboBox(frame); + layout->addWidget(m_adjustTime, 1, 1); + + m_adjustTime->insertItem(i18n("As stored")); + m_adjustTime->insertItem(i18n("Truncate if longer than note")); + m_adjustTime->insertItem(i18n("End at same time as note")); + m_adjustTime->insertItem(i18n("Stretch or squash segment to note duration")); + + m_retune = new QCheckBox(i18n("Adjust pitch to note"), frame); + m_retune->setChecked(true); + + layout->addWidget(m_retune, 2, 1); + + setupFromConfig(); +} + +void +UseOrnamentDialog::setupFromConfig() +{ + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + + Mark mark = qstrtostr(config->readEntry("useornamentmark", "trill")); + int seg = config->readNumEntry("useornamentlastornament", 0); + std::string timing = qstrtostr + (config->readEntry + ("useornamenttiming", + strtoqstr(BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH))); + bool retune = config->readBoolEntry("useornamentretune", true); + + size_t i = 0; + for (i = 0; i < m_marks.size(); ++i) { + if (mark == m_marks[i]) { + m_mark->setCurrentItem(i); + m_text->setEnabled(false); + break; + } + } + if (i >= m_marks.size()) { + m_mark->setCurrentItem(m_marks.size()); + m_text->setEnabled(true); + m_text->setText(strtoqstr(Marks::getTextFromMark(mark))); + } + + if (seg >= 0 && seg < m_ornament->count()) + m_ornament->setCurrentItem(seg); + + if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_NONE) { + m_adjustTime->setCurrentItem(0); + } else if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH) { + m_adjustTime->setCurrentItem(3); + } else if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_START) { + m_adjustTime->setCurrentItem(1); + } else if (timing == BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_END) { + m_adjustTime->setCurrentItem(2); + } + + m_retune->setChecked(retune); +} + +TriggerSegmentId +UseOrnamentDialog::getId() const +{ + int ix = m_ornament->currentItem(); + + for (Composition::triggersegmentcontaineriterator i = + m_composition->getTriggerSegments().begin(); + i != m_composition->getTriggerSegments().end(); ++i) { + + if (ix == 0) + return (*i)->getId(); + --ix; + } + + return 0; +} + +Mark +UseOrnamentDialog::getMark() const +{ + if (int(m_marks.size()) > m_mark->currentItem()) + return m_marks[m_mark->currentItem()]; + else + return Marks::getTextMark(qstrtostr(m_text->text())); +} + +bool +UseOrnamentDialog::getRetune() const +{ + return m_retune->isChecked(); +} + +std::string +UseOrnamentDialog::getTimeAdjust() const +{ + int option = m_adjustTime->currentItem(); + + switch (option) { + + case 0: + return BaseProperties::TRIGGER_SEGMENT_ADJUST_NONE; + case 1: + return BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_START; + case 2: + return BaseProperties::TRIGGER_SEGMENT_ADJUST_SYNC_END; + case 3: + return BaseProperties::TRIGGER_SEGMENT_ADJUST_SQUISH; + + default: + return BaseProperties::TRIGGER_SEGMENT_ADJUST_NONE; + } +} + +void +UseOrnamentDialog::slotMarkChanged(int i) +{ + if (i == 2) { + m_text->setEnabled(true); + } else { + m_text->setEnabled(false); + } +} + +void +UseOrnamentDialog::slotOk() +{ + KConfig *config = kapp->config(); + config->setGroup(NotationViewConfigGroup); + + config->writeEntry("useornamentmark", strtoqstr(getMark())); + config->writeEntry("useornamenttiming", strtoqstr(getTimeAdjust())); + config->writeEntry("useornamentretune", m_retune->isChecked()); + config->writeEntry("useornamentlastornament", m_ornament->currentItem()); + + accept(); +} + +} +#include "UseOrnamentDialog.moc" diff --git a/src/gui/dialogs/UseOrnamentDialog.h b/src/gui/dialogs/UseOrnamentDialog.h new file mode 100644 index 0000000..d721329 --- /dev/null +++ b/src/gui/dialogs/UseOrnamentDialog.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_USEORNAMENTDIALOG_H_ +#define _RG_USEORNAMENTDIALOG_H_ + +#include "base/TriggerSegment.h" +#include "base/NotationTypes.h" +#include +#include +#include + + +class QWidget; +class QLineEdit; +class QLabel; +class QCheckBox; +class KComboBox; + + +namespace Rosegarden +{ + +class Composition; + + +class UseOrnamentDialog : public KDialogBase +{ + Q_OBJECT + +public: + UseOrnamentDialog(QWidget *parent, Composition *); + + TriggerSegmentId getId() const; + Mark getMark() const; + bool getRetune() const; + std::string getTimeAdjust() const; + +public slots: + void slotOk(); + void slotMarkChanged(int); + +protected: + void setupFromConfig(); + + std::vector m_marks; + + Composition *m_composition; + KComboBox *m_ornament; + KComboBox *m_mark; + QLabel *m_textLabel; + QLineEdit *m_text; + QCheckBox *m_retune; + KComboBox *m_adjustTime; +}; + + +} + +#endif 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +int +EventView::m_lastSetEventFilter = -1; + + +EventView::EventView(RosegardenGUIDoc *doc, + std::vector 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(""); + 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::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 selection = m_eventList->selectedItems(); + + if (selection.count()) { + QPtrListIterator 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 + (BaseProperties::PITCH); + pitchStr = QString("%1 %2 ") + .arg(p).arg(MidiPitchLabel(p).getQString()); + } else if ((*it)->isa(Note::EventType)) { + pitchStr = ""; + } + + if ((*it)->has(BaseProperties::VELOCITY)) { + velyStr = QString("%1 "). + arg((*it)->get + (BaseProperties::VELOCITY)); + } else if ((*it)->isa(Note::EventType)) { + velyStr = ""; + } + + if ((*it)->has(Controller::NUMBER)) { + data1Str = QString("%1 "). + arg((*it)->get + (Controller::NUMBER)); + } else if ((*it)->has(Text::TextTypePropertyName)) { + data1Str = QString("%1 "). + arg(strtoqstr((*it)->get + + (Text::TextTypePropertyName))); + } else if ((*it)->has(Indication:: + IndicationTypePropertyName)) { + data1Str = QString("%1 "). + arg(strtoqstr((*it)->get + + (Indication:: + IndicationTypePropertyName))); + } else if ((*it)->has(::Rosegarden::Key::KeyPropertyName)) { + data1Str = QString("%1 "). + arg(strtoqstr((*it)->get + + (::Rosegarden::Key::KeyPropertyName))); + } else if ((*it)->has(Clef::ClefPropertyName)) { + data1Str = QString("%1 "). + arg(strtoqstr((*it)->get + + (Clef::ClefPropertyName))); + } else if ((*it)->has(PitchBend::MSB)) { + data1Str = QString("%1 "). + arg((*it)->get + (PitchBend::MSB)); + } else if ((*it)->has(BaseProperties::BEAMED_GROUP_TYPE)) { + data1Str = QString("%1 "). + arg(strtoqstr((*it)->get + + (BaseProperties::BEAMED_GROUP_TYPE))); + } + + if ((*it)->has(Controller::VALUE)) { + data2Str = QString("%1 "). + arg((*it)->get + (Controller::VALUE)); + } else if ((*it)->has(Text::TextPropertyName)) { + data2Str = QString("%1 "). + arg(strtoqstr((*it)->get + + (Text::TextPropertyName))); + /*!!! + } else if ((*it)->has(Indication:: + IndicationTypePropertyName)) { + data2Str = QString("%1 "). + arg((*it)->get(Indication:: + IndicationDurationPropertyName)); + */ + } else if ((*it)->has(PitchBend::LSB)) { + data2Str = QString("%1 "). + arg((*it)->get + (PitchBend::LSB)); + } else if ((*it)->has(BaseProperties::BEAMED_GROUP_ID)) { + data2Str = i18n("(group %1) "). + arg((*it)->get + (BaseProperties::BEAMED_GROUP_ID)); + } + + if ((*it)->has(ProgramChange::PROGRAM)) { + data1Str = QString("%1 "). + arg((*it)->get + (ProgramChange::PROGRAM) + 1); + } + + if ((*it)->has(ChannelPressure::PRESSURE)) { + data1Str = QString("%1 "). + arg((*it)->get + (ChannelPressure::PRESSURE)); + } + + if ((*it)->isa(KeyPressure::EventType) && + (*it)->has(KeyPressure::PITCH)) { + data1Str = QString("%1 "). + arg((*it)->get + (KeyPressure::PITCH)); + } + + if ((*it)->has(KeyPressure::PRESSURE)) { + data2Str = QString("%1 "). + arg((*it)->get + (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("")); + else + new QListViewItem(m_eventList, i18n("")); + + 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::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(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 + (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 selection = m_eventList->selectedItems(); + + if (selection.count() == 0) + return ; + + RG_DEBUG << "EventView::slotEditCut - cutting " + << selection.count() << " items" << endl; + + QPtrListIterator it(selection); + QListViewItem *listItem; + EventViewItem *item; + EventSelection *cutSelection = 0; + int itemIndex = -1; + + while ((listItem = it.current()) != 0) { + item = dynamic_cast((*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 selection = m_eventList->selectedItems(); + + if (selection.count() == 0) + return ; + + RG_DEBUG << "EventView::slotEditCopy - copying " + << selection.count() << " items" << endl; + + QPtrListIterator 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((*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 selection = m_eventList->selectedItems(); + if (selection.count()) { + EventViewItem *item = dynamic_cast(selection.at(0)); + + if (item) + insertionTime = item->getEvent()->getAbsoluteTime(); + + // remember the selection + // + m_listSelection.clear(); + + QPtrListIterator 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 selection = m_eventList->selectedItems(); + if (selection.count() == 0) + return ; + + RG_DEBUG << "EventView::slotEditDelete - deleting " + << selection.count() << " items" << endl; + + QPtrListIterator it(selection); + QListViewItem *listItem; + EventViewItem *item; + EventSelection *deleteSelection = 0; + int itemIndex = -1; + + while ((listItem = it.current()) != 0) { + item = dynamic_cast((*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 selection = m_eventList->selectedItems(); + + if (selection.count() > 0) { + EventViewItem *item = + dynamic_cast(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 + (BaseProperties::PITCH, 70); + event->set + (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 selection = m_eventList->selectedItems(); + + if (selection.count() > 0) { + EventViewItem *item = + dynamic_cast(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 selection = m_eventList->selectedItems(); + + if (selection.count() > 0) { + EventViewItem *item = + dynamic_cast(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 selection = m_eventList->selectedItems(); + if (selection.count() == 0) + return ; + + EventFilterDialog dialog(this); + if (dialog.exec() == QDialog::Accepted) { + + QPtrListIterator it(selection); + QListViewItem *listItem; + + while ((listItem = it.current()) != 0) { + + EventViewItem * item = dynamic_cast(*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(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(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(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 + (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 + (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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#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 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 m_listSelection; + std::set 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +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(RootPropertyName, f); + if (ok) + m_root = f; + + ok = e.get(ExtPropertyName, f); + if (ok) { + if (f.length() == 0) + m_ext = QString::null; + else + m_ext = f; + } + + ok = e.get(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(RootPropertyName, m_root); + e->set(ExtPropertyName, m_ext); + e->set(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +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 << "\n" + << "\n" + << "\n"; + + outStream << "\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\n"; + + // open new chordset + outStream << "\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 << "\n"; + + // open new chord + outStream << "\n"; + } + + outStream << "" << chord.getFingering().toString() << "\n"; + } + + if (!m_map.empty()) + outStream << "\n"; // close last written chord + + outStream << "\n"; + outStream << "\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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +namespace Rosegarden +{ + +namespace Guitar +{ + +class ChordMap +{ + typedef std::set chordset; + +public: + typedef std::vector 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 , + Chris Cannam , + Richard Bown + + 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include + +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::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::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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include "base/Event.h" + +namespace Rosegarden +{ + +namespace Guitar +{ + +class Fingering +{ +public: + friend bool operator<(const Fingering&, const Fingering&); + + typedef std::vector::iterator iterator; + typedef std::vector::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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include +#include + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include +#include + +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 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(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& chordFiles) +{ + for(std::vector::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 +GuitarChordSelectorDialog::getAvailableChordFiles() +{ + std::vector 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + +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& 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 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 , + Chris Cannam , + Richard Bown + + 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( columnWidth * 0.7 ); + unsigned int height = static_cast( 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( 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( columnWidth /* * 0.9 */ ); + p->setBrush( QBrush(p->brush().color(), Qt::NoBrush) ); + } else { + radius = static_cast( 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( 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( 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( TOP_BORDER_PERCENTAGE * imgHeight ); +} + +unsigned int +NoteSymbols::getBottomBorder ( unsigned int imgHeight ) const +{ + return static_cast( imgHeight * BOTTOM_BORDER_PERCENTAGE ); +} + +unsigned int +NoteSymbols::getLeftBorder ( unsigned int imgWidth ) const +{ + unsigned int left = static_cast( imgWidth * LEFT_BORDER_PERCENTAGE ); + if ( left < 15 ) { + left = 15; + } + return left; +} + +unsigned int +NoteSymbols::getRightBorder ( unsigned int imgWidth ) const +{ + return static_cast( imgWidth * RIGHT_BORDER_PERCENTAGE ); +} + +unsigned int +NoteSymbols::getGuitarChordWidth ( int imgWidth ) const +{ + return static_cast( imgWidth * GUITAR_CHORD_WIDTH_PERCENTAGE ); +} + +unsigned int +NoteSymbols::getGuitarChordHeight ( int imgHeight ) const +{ + return static_cast( imgHeight * GUITAR_CHORD_HEIGHT_PERCENTAGE ); +} + +unsigned int +NoteSymbols::getFontPixelSize ( int imgWidth, int imgHeight ) const +{ + return std::max(8, imgHeight / 10); +} + +std::pair +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 +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 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 , + Chris Cannam , + Richard Bown + + 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 +#include + +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 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 + getStringNumber ( int imgWidth, + unsigned int x_pos, + unsigned int string_num ) const; + + std::pair + 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#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(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(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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + +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(*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(*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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include "QCanvasMatrixRectangle.h" + +class QColor; + + +namespace Rosegarden +{ + +class Event; + +class MatrixElement : public ViewElement +{ + + typedef std::vector 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include "base/ViewElement.h" +#include "commands/matrix/MatrixEraseCommand.h" +#include "gui/general/EditTool.h" +#include "MatrixStaff.h" +#include "MatrixTool.h" +#include "MatrixView.h" +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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(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((*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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 TimeSigData; + // pair of layout-x and time-signature if there is one + typedef std::pair BarData; + typedef FastVector 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#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 +#include +#include +#include +#include +#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(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(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(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(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(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(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(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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#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 +#include +#include +#include +#include +#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(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(BaseProperties::PITCH, pitch); + ev->set(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(PITCH)) { + + m_currentElement->event()->set(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#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 m_quantizations; + std::vector 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#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 +#include +#include +#include +#include +#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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#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(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(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(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 + + (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(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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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() : "") << 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(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 + (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(*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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + * Wrap only notes + */ + virtual bool wrapEvent(Event*); + + /** + * Override from Staff + * 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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(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((*i)); + + if (!el->isNote()) + continue; // notes only + + long pitch = 60; + el->event()->get + (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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +static double xorigin = 0.0; + + +MatrixView::MatrixView(RosegardenGUIDoc *doc, + std::vector 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(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(BaseProperties::PITCH, pitch)) { + long velocity = -1; + (void)((*i)->get(BaseProperties::VELOCITY, velocity)); + if (!((*i)->has(BaseProperties::TIED_BACKWARD) && + (*i)->get(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(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(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 + + (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(BaseProperties::PITCH, pitch); + modelEvent.set(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 + (BaseProperties::VELOCITY, eventVelocity)) + velocity = eventVelocity; + + RealTime duration = + comp.getElapsedRealTime(event->getDuration()); + + // create + MappedEvent mE(ins->getId(), + MappedEvent::MidiNoteOneShot, + (MidiByte) + event->get + + (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 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 + (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(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(newRuler, newControl)); + + return m_propertyViewRulers.size() - 1; +} + +bool +MatrixView::removePropertyViewRuler(unsigned int number) +{ + if (number > m_propertyViewRulers.size() - 1) + return false; + + std::vector >::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(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 > 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::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(*eit); + if (el->isNote()) { + Event* ev = el->event(); + int pitch = ev->get + + (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 + (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(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(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 + (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 + (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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#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 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 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 *m_hZoomSlider; + ZoomSlider *m_vZoomSlider; + QLabel *m_zoomLabel; + + // Hold our matrix quantization values and snap values + // + std::vector m_quantizations; + std::vector m_snapValues; + + std::vector > m_propertyViewRulers; + + ChordNameRuler *m_chordNameRuler; + QWidget *m_tempoRuler; + + // ruler used to scale tempo and chord name ruler + ZoomableMatrixHLayoutRulerScale* m_referenceRuler; + + std::vector > 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include + + +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::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::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::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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 m_whiteKeyPos; + std::vector m_blackKeyPos; + std::vector m_labelKeyPos; + std::vector 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_XFT +#include +#include FT_FREETYPE_H +#include FT_OUTLINE_H +#include FT_GLYPH_H +#include +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 +#include +#include +#include + + +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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + This file is Copyright 2007-2008 + Yves Guillemot + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include + +#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 , + Chris Cannam , + Richard Bown + + This file is Copyright 2007-2008 + Yves Guillemot + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include + + +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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include +#include +#include + + +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 + (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 + (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(*it); + if (!sprite) { + if (dynamic_cast(*it)) { + emit nonNotationItemPressed(e, *it); + return ; + } else if (dynamic_cast(*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 + + (staff->getProperties().NOTE_HEAD_SHIFTED, shifted) && shifted) { + cx += nbw; + } + } + + if (el.isNote() && haveClickHeight) { + long eventHeight = 0; + if (el.event()->get + + (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 + (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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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::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 + (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 + (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 + (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(m_properties.VIEW_LOCAL_STEM_UP); + } + */ + if (e->has(NotationProperties::STEM_UP)) { + return e->get + (NotationProperties::STEM_UP); + } + + if (e->has(NotationProperties::BEAM_ABOVE)) { + if (e->has(NotationProperties::BEAMED) && + e->get + (NotationProperties::BEAMED)) { + return e->get + (NotationProperties::BEAM_ABOVE); + } + else { + return !e->get + (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 + (m_properties.DISPLAY_ACCIDENTAL, acc) && + acc != Accidentals::NoAccidental) { + e->setMaybe(m_properties.ACCIDENTAL_SHIFT, minShift); + e->setMaybe(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 + (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(m_properties.ACCIDENTAL_SHIFT, shift); + + lastHeight = height; + lastShift = shift; + + lastWidth = 1; + bool c = false; + if (e->get + (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 + (m_properties.ACCIDENTAL_SHIFT); + if (shift > maxShift) { + maxShift = shift; + e->get + (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 + (m_properties.ACCIDENTAL_SHIFT); + getAsEvent(i)->get + (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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +{ +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +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 + (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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#include +#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 +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 barRange, + const NotationProperties &p, + const Clef &clef, const Key &key) : + AbstractSet(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 + (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(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(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(BaseProperties::BEAMED_GROUP_TYPE, t)) { + NOTATION_DEBUG << "NotationGroup::NotationGroup: Rejecting sample() for non-beamed element" << endl; + return false; + } + + long n; + if (!(*i)->event()->get(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::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(*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(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(NotationProperties::HEIGHT_ON_STAFF, h)) { + return h; + } + + //!!! int pitch = (*i)->event()->get(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(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(*i); + if (el->isNote()) { + if (el->event()->has(NotationProperties::STEM_UP)) { + if (el->event()->get(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(STEM_UP)) { + aboveNotes = (*initialNote)->event()->get(STEM_UP); + } + + if ((*initialNote)->event()->has(NotationProperties::BEAM_ABOVE) && + (*initialNote)->event()->isPersistent(NotationProperties::BEAM_ABOVE)) { + aboveNotes = (*initialNote)->event()->get + (NotationProperties::BEAM_ABOVE); + } + */ + for (NELIterator i = initialNote; i != getContainer().end(); ++i) { + + NotationElement* el = static_cast(*i); + + el->event()->setMaybe(NotationProperties::BEAM_ABOVE, aboveNotes); + + if (el->isNote() && + el->event()->has(BaseProperties::NOTE_TYPE) && + el->event()->get(BaseProperties::NOTE_TYPE) < Note::Crotchet && + el->event()->has(BaseProperties::BEAMED_GROUP_ID) && + el->event()->get(BaseProperties::BEAMED_GROUP_ID) == m_groupNo) { + + el->event()->setMaybe(NotationProperties::BEAMED, true); + // el->event()->setMaybe(m_properties.VIEW_LOCAL_STEM_UP, aboveNotes); + + } else if (el->isNote()) { + + if (i == initialNote || i == finalNote) { + (*i)->event()->setMaybe + (m_properties.VIEW_LOCAL_STEM_UP, aboveNotes); + } else { + (*i)->event()->setMaybe + (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(*i); + + if (el->isNote() && + el->event()->has(BaseProperties::NOTE_TYPE) && + el->event()->get(BaseProperties::NOTE_TYPE) < Note::Crotchet && + el->event()->has(BaseProperties::BEAMED_GROUP_ID) && + el->event()->get(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 + + (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(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 + (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(*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 + (BaseProperties::NOTE_TYPE) < Note::Crotchet && + el->event()->has(BaseProperties::BEAMED_GROUP_ID) && + el->event()->get(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(*chord[j]); + + el->event()->setMaybe + (m_properties.CHORD_PRIMARY_NOTE, false); + + el->event()->setMaybe + (m_properties.DRAW_FLAG, false); + + el->event()->setMaybe + (NotationProperties::BEAMED, true); + + el->event()->setMaybe + (NotationProperties::BEAM_ABOVE, beam.aboveNotes); + + el->event()->setMaybe + (m_properties.VIEW_LOCAL_STEM_UP, beam.aboveNotes); + + bool shifted = chord.isNoteHeadShifted(chord[j]); + el->event()->setMaybe + (m_properties.NOTE_HEAD_SHIFTED, shifted); + + long dots = 0; + (void)el->event()->get + (BaseProperties::NOTE_DOTS, dots); + + el->event()->setMaybe + (m_properties.NOTE_DOT_SHIFTED, false); + if (hasShifted && beam.aboveNotes) { + long dots = 0; + (void)el->event()->get + (BaseProperties::NOTE_DOTS, dots); + if (dots > 0) { + el->event()->setMaybe + (m_properties.NOTE_DOT_SHIFTED, true); + } + } + + el->event()->setMaybe + (m_properties.NEEDS_EXTRA_SHIFT_SPACE, + chord.hasNoteHeadShifted() && !beam.aboveNotes); + } + + if (beam.aboveNotes) + j = 0; + else + j = chord.size() - 1; + + NotationElement *el = static_cast(*chord[j]); + el->event()->setMaybe(NotationProperties::BEAMED, false); // set later + el->event()->setMaybe(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 + (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(*prev); + int secWidth = x - (int)prevEl->getLayoutX(); + + // prevEl->event()->setMaybe(BEAM_NEXT_Y, myY); + + prevEl->event()->setMaybe + (m_properties.BEAM_SECTION_WIDTH, secWidth); + prevEl->event()->setMaybe + (m_properties.BEAM_NEXT_BEAM_COUNT, beamCount); + + int prevBeamCount = + NoteStyleFactory::getStyleForEvent(prevEl->event())-> + getFlagCount(prevEl->event()->get + (BaseProperties::NOTE_TYPE)); + + if ((beamCount > 0) && (prevBeamCount > 0)) { + el->event()->setMaybe(m_properties.BEAMED, true); + el->event()->setMaybe(m_properties.DRAW_FLAG, false); + prevEl->event()->setMaybe(m_properties.BEAMED, true); + prevEl->event()->setMaybe(m_properties.DRAW_FLAG, false); + } + + if (beamCount >= prevBeamCount) { + prevEl->event()->setMaybe + (m_properties.BEAM_THIS_PART_BEAMS, false); + if (prevprev != getContainer().end()) { + (*prevprev)->event()->setMaybe + (m_properties.BEAM_NEXT_PART_BEAMS, false); + } + } + + if (beamCount > prevBeamCount) { + prevEl->event()->setMaybe + (m_properties.BEAM_NEXT_PART_BEAMS, true); + } + + } else { + el->event()->setMaybe(m_properties.BEAM_THIS_PART_BEAMS, true); + } + + el->event()->setMaybe(m_properties.CHORD_PRIMARY_NOTE, true); + + el->event()->setMaybe(m_properties.BEAM_MY_Y, myY); + el->event()->setMaybe(m_properties.BEAM_GRADIENT, beam.gradient); + + // until they're set next time around the loop, as (*prev)->... + // el->event()->setMaybe(m_properties.BEAM_NEXT_Y, myY); + el->event()->setMaybe(m_properties.BEAM_SECTION_WIDTH, 0); + el->event()->setMaybe(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(m_properties.VIEW_LOCAL_STEM_UP, beam.aboveNotes); + } else { + (*i)->event()->setMaybe(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(*initialNoteOrRest); + + while (initialNoteOrRest != finalElement && + !(initialNoteOrRestEl->isNote() || + initialNoteOrRestEl->isRest())) { + ++initialNoteOrRest; + initialNoteOrRestEl = static_cast(*initialNoteOrRest); + } + + if (!initialNoteOrRestEl->isRest()) { + initialNoteOrRest = initialNote; + initialNoteOrRestEl = static_cast(*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(BaseProperties::IS_GRACE_NOTE); + } + + // NOTATION_DEBUG << "NotationGroup::applyTuplingLine: first element is " << (initialNoteOrRestEl->isNote() ? "Note" : "Non-Note") << ", last is " << (static_cast(*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(m_properties.TUPLING_LINE_MY_Y, + staff.getLayoutYForHeight(12)); + e->setMaybe(m_properties.TUPLING_LINE_WIDTH, finalX - initialX); + e->setMaybe(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 + (m_properties.BEAM_NEXT_BEAM_COUNT, bc)) { + if (bc > maxEndBeamCount) + maxEndBeamCount = bc; + } + if ((*finalNote)->event()->get + (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(m_properties.TUPLING_LINE_MY_Y, startY); + e->setMaybe(m_properties.TUPLING_LINE_WIDTH, finalX - initialX); + e->setMaybe(m_properties.TUPLING_LINE_GRADIENT, beam.gradient); + e->setMaybe(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 +{ +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 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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#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 +#include +#include + +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 +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 +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(&staff); + if (ns) return &ns->getNotePixmapFactory(false); + else return 0; +} + +NotePixmapFactory * +NotationHLayout::getGraceNotePixmapFactory(Staff &staff) +{ + NotationStaff *ns = dynamic_cast(&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 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 + 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((*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(INVISIBLE, invisible) && invisible) { + if (!showInvisibles) + continue; + } + + if (el->event()->has(BEAMED_GROUP_ID)) { + NOTATION_DEBUG << "element is beamed" << endl; + long groupId = el->event()->get(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(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(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(*i); + if (el->isRest()) { + el->event()->setMaybe(m_properties.REST_TOO_SHORT, true); + if (i == chord.getFinalElement()) + break; + continue; + } + + if (el->isGrace()) { + grace = true; + } + + long pitch = 64; + if (!el->event()->get(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(ACCIDENTAL, explicitAccidental); + + Pitch p(pitch, explicitAccidental); + int h = p.getHeightOnStaff(clef, key); + Accidental acc = p.getDisplayAccidental(key); + + h -= 7 * ottavaShift; + + el->event()->setMaybe(NotationProperties::OTTAVA_SHIFT, ottavaShift); + el->event()->setMaybe(NotationProperties::HEIGHT_ON_STAFF, h); + el->event()->setMaybe(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(m_properties.USE_CAUTIONARY_ACCIDENTAL); + } + Accidental dacc = accTable.processDisplayAccidental(acc, h, cautionary); + el->event()->setMaybe(m_properties.DISPLAY_ACCIDENTAL, dacc); + el->event()->setMaybe(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 ChunkRefList; + typedef std::map 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 > 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(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(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(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(*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(*it); + delta = 0; + float fixed = 0; + + if (el->event()->isa(Note::EventType)) { + long pitch = 0; + el->event()->get(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(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(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(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 + (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(*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(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(*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(*citr); + + double displacedX = 0.0; + long dxRaw = 0; + elt->event()->get(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(*citr); + if (!note->isNote()) { + if (citr == chord.getFinalElement()) + break; + continue; + } + + bool tiedForwards = false; + bool tiedBack = false; + + note->event()->get(TIED_FORWARD, tiedForwards); + note->event()->get(TIED_BACKWARD, tiedBack); + + if (!note->event()->has(PITCH)) + continue; + int pitch = note->event()->get(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 + (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(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(ve); + + if ((e.isNote() || e.isRest()) && e.event()->has(NOTE_TYPE)) { + + long noteType = e.event()->get(NOTE_TYPE); + long dots = 0; + (void)e.event()->get(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(*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(*vli))->getCanvasItem()))) + ++vli; + + if (vli != staff->getViewElementList()->end()) { + NotationElement *element = static_cast(*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 NotationHLayout::m_availableSpacings; +std::vector 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#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 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 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 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 BarDataList; + typedef BarDataList::value_type BarDataPair; + typedef std::map BarDataMap; + typedef std::map BarPositionList; + + typedef std::map StaffIntMap; + typedef std::map 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 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 m_availableSpacings; + static std::vector m_availableProportions; + + const Quantizer *m_notationQuantizer; + const NotationProperties &m_properties; + + int m_timePerProgressIncrement; + std::map 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 +#include +#include +#include +#include +#include + + +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(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(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(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(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(ACCIDENTAL, clickedAccidental); + + long clickedPitch = 0; + (void)m_clickedElement->event()->get(PITCH, clickedPitch); + + long clickedHeight = 0; + (void)m_clickedElement->event()->get + (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(*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(*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 + (DISPLACED_X, prevX); + (*i)->get + (DISPLACED_Y, prevY); + (*i)->setMaybe(xProperty, prevX); + (*i)->setMaybe(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 + (xProperty, prevX); + (*i)->get + (yProperty, prevY); + (*i)->setMaybe(DISPLACED_X, prevX); + (*i)->setMaybe(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 + (xProperty, prevX); + (*i)->get + (yProperty, prevY); + (*i)->setMaybe(DISPLACED_X, prevX + long(dx)); + (*i)->setMaybe(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(*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 + (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(*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 + (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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#include +#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 +#include +#include +#include +#include +#include + + +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 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(*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(*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(*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((*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(*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 + + (properties.BEAMED, spanning)); + if (!spanning) { + (void)(el->event()->get + (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::iterator i = m_clefChanges.begin(); + i != m_clefChanges.end(); ++i) { + if (i->first >= x) { + m_clefChanges.erase(i, m_clefChanges.end()); + break; + } + } + + for (FastVector::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(*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(*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(*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(*vli); + + bool invisible = false; + if (elt->event()->get + (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 + (properties.REST_TOO_SHORT, ignoreRest); + + if (!ignoreRest) { + + Note::Type note = elt->event()->get + (BaseProperties::NOTE_TYPE); + int dots = elt->event()->get + (BaseProperties::NOTE_DOTS); + restParams.setNoteType(note); + restParams.setDots(dots); + setTuplingParameters(elt, restParams); + restParams.setQuantized(false); + bool restOutside = false; + elt->event()->get + (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 + + (Text::TextTypePropertyName) == + Text::Annotation && + !m_notationView->areAnnotationsVisible()) { + + // nothing I guess + + } + else if (elt->event()->has(Text::TextTypePropertyName) && + elt->event()->get + + (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(*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 + (properties.SLUR_ABOVE, above); + elt->event()->get + (properties.SLUR_Y_DELTA, dy); + elt->event()->get + (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 + (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 + (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 + (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 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(*vli); + + const NotationProperties &properties(getProperties()); + static NotePixmapParameters params(Note::Crotchet, 0); + + Note::Type note = elt->event()->get + (BaseProperties::NOTE_TYPE); + int dots = elt->event()->get + (BaseProperties::NOTE_DOTS); + + Accidental accidental = Accidentals::NoAccidental; + (void)elt->event()->get + (properties.DISPLAY_ACCIDENTAL, accidental); + + bool cautionary = false; + if (accidental != Accidentals::NoAccidental) { + (void)elt->event()->get + (properties.DISPLAY_ACCIDENTAL_IS_CAUTIONARY, + cautionary); + } + + bool up = true; + // (void)(elt->event()->get(properties.STEM_UP, up)); + (void)(elt->event()->get + (properties.VIEW_LOCAL_STEM_UP, up)); + + bool flag = true; + (void)(elt->event()->get + (properties.DRAW_FLAG, flag)); + + bool beamed = false; + (void)(elt->event()->get + (properties.BEAMED, beamed)); + + bool shifted = false; + (void)(elt->event()->get + (properties.NOTE_HEAD_SHIFTED, shifted)); + + bool dotShifted = false; + (void)(elt->event()->get + (properties.NOTE_DOT_SHIFTED, dotShifted)); + + long stemLength = m_notePixmapFactory->getNoteBodyHeight(); + (void)(elt->event()->get + (properties.UNBEAMED_STEM_LENGTH, stemLength)); + + long heightOnStaff = 0; + int legerLines = 0; + + (void)(elt->event()->get + (properties.HEIGHT_ON_STAFF, heightOnStaff)); + if (heightOnStaff < 0) { + legerLines = heightOnStaff; + } else if (heightOnStaff > 8) { + legerLines = heightOnStaff - 8; + } + + long slashes = 0; + (void)(elt->event()->get + (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 + (properties.CHORD_PRIMARY_NOTE, primary) + && primary) { + + long marks = 0; + elt->event()->get + (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(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(BaseProperties::TIE_IS_ABOVE)); + } else { + params.setTiePosition(false, false); // the default + } + + long accidentalShift = 0; + bool accidentalExtra = false; + if (elt->event()->get(properties.ACCIDENTAL_SHIFT, accidentalShift)) { + elt->event()->get(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(properties.CHORD_PRIMARY_NOTE, primary) + && primary) { + + int myY = elt->event()->get(properties.BEAM_MY_Y); + + stemLength = myY - (int)elt->getLayoutY(); + if (stemLength < 0) + stemLength = -stemLength; + + int nextBeamCount = + elt->event()->get + (properties.BEAM_NEXT_BEAM_COUNT); + int width = + elt->event()->get + (properties.BEAM_SECTION_WIDTH); + int gradient = + elt->event()->get + (properties.BEAM_GRADIENT); + + bool thisPartialBeams(false), nextPartialBeams(false); + (void)elt->event()->get + + (properties.BEAM_THIS_PART_BEAMS, thisPartialBeams); + (void)elt->event()->get + + (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 + (properties.TUPLING_LINE_MY_Y, tuplingLineY)); + + if (tupled) { + + long tuplingLineWidth = 0; + if (!elt->event()->get + (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 + (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 + (properties.TUPLING_LINE_FOLLOWS_BEAM, + tuplingLineFollowsBeam); + + long tupletCount; + if (elt->event()->get + (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(*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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#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. + * We want to avoid wrapping things like controller events, if + * our showUnknowns preference is off + */ + virtual bool wrapEvent(Event *); + + /** + * Override from Staff + * 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 SpriteSet; + SpriteSet m_timeSigs; + + typedef std::set ItemSet; + ItemSet m_repeatedClefsAndKeys; + + typedef std::pair ClefChange; + FastVector m_clefChanges; + + typedef std::pair KeyChange; + FastVector 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 BarStatusMap; + BarStatusMap m_status; + std::pair 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#include +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/Exception.h" +#include "base/NotationTypes.h" +#include "gui/configuration/GeneralConfigurationPage.h" +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "NotationVLayout.h" +#include "misc/Debug.h" + +#include +#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 +#include +#include +#include + + +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(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(*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(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 + (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 + // 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(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(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 h; + for (unsigned int j = 0; j < chord.size(); ++j) { + long height = 0; + if (!(*chord[j])->event()->get + + (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(*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(STEM_UP, stemUp); + el->event()->setMaybe(m_properties.VIEW_LOCAL_STEM_UP, stemUp); + + bool primary = + ((stemmed && stemUp) ? (j == 0) : (j == chord.size() - 1)); + el->event()->setMaybe + (m_properties.CHORD_PRIMARY_NOTE, primary); + + if (primary) { + el->event()->setMaybe + (m_properties.CHORD_MARK_COUNT, chord.getMarkCountForChord()); + } + + bool shifted = chord.isNoteHeadShifted(chord[j]); + el->event()->setMaybe + (m_properties.NOTE_HEAD_SHIFTED, shifted); + + el->event()->setMaybe + (m_properties.NOTE_DOT_SHIFTED, false); + if (hasShifted && stemUp) { + long dots = 0; + (void)el->event()->get + (NOTE_DOTS, dots); + if (dots > 0) { + el->event()->setMaybe + (m_properties.NOTE_DOT_SHIFTED, true); + } + } + + el->event()->setMaybe + (m_properties.NEEDS_EXTRA_SHIFT_SPACE, + hasNoteHeadShifted && !stemUp); + + el->event()->setMaybe + (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 + (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(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(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 + (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(*(mi->first)); + + positionSlur(staff, i); + } + } + + PRINT_ELAPSED("NotationVLayout::finishLayout"); +} + +void +NotationVLayout::positionSlur(NotationStaff &staff, + NotationElementList::iterator i) +{ + NotationRules rules; + + bool phrasing = ((*i)->event()->get + (Indication::IndicationTypePropertyName) + == Indication::PhrasingSlur); + + NotationElementList::iterator scooter = i; + + timeT slurDuration = (*i)->event()->getDuration(); + if (slurDuration == 0 && (*i)->event()->has("indicationduration")) { + slurDuration = (*i)->event()->get + ("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 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 + (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 + (m_properties.VIEW_LOCAL_STEM_UP, stemUp); + + bool beamed = false; + event->get + (m_properties.BEAMED, beamed); + + bool primary = false; + + if (event->get + + (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 + (TIED_FORWARD, tied) && tied) || + (event->get(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 + (TIED_FORWARD, tied) && tied) || + (event->get(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 + (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(NotationProperties::SLUR_ABOVE)) { + + (*i)->event()->get + (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(NotationProperties::SLUR_ABOVE, above); + (*i)->event()->setMaybe(m_properties.SLUR_Y_DELTA, dy); + (*i)->event()->setMaybe(m_properties.SLUR_LENGTH, length); + + double displacedX = 0.0, displacedY = 0.0; + + long dxRaw = 0; + (*i)->event()->get(DISPLACED_X, dxRaw); + displacedX = double(dxRaw * m_npf->getNoteBodyWidth()) / 1000.0; + + long dyRaw = 0; + (*i)->event()->get(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 SlurList; + typedef std::map 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include "misc/Debug.h" +#include + +#include "gui/editors/segment/TrackEditor.h" +#include "gui/editors/segment/TrackButtons.h" +#include "base/BaseProperties.h" +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 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(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 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 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 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 + (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 + (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 + (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 + (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 + fs(NoteFontFactory::getFontNames()); + std::vector f(fs.begin(), fs.end()); + std::sort(f.begin(), f.end()); + + for (std::vector::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 spacings = NotationHLayout::getAvailableSpacings(); + + for (std::vector::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 proportions = NotationHLayout::getAvailableProportions(); + + for (std::vector::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 styles + (NoteStyleFactory::getAvailableStyleNames()); + + for (std::vector::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 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 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 + (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 + fs(NoteFontFactory::getFontNames()); + std::vector f(fs.begin(), fs.end()); + std::sort(f.begin(), f.end()); + + bool foundFont = false; + + for (std::vector::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 sizes = NoteFontFactory::getScreenSizes(m_fontName); + m_fontSizeCombo = new KComboBox(layoutToolbar, "font size combo"); + + for (std::vector::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 spacings = NotationHLayout::getAvailableSpacings(); + + m_spacingCombo = new KComboBox(layoutToolbar, "spacing combo"); + for (std::vector::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 > 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(m_toolBox->getTool(RestInserter::ToolName)); + else + inserter = dynamic_cast(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 + (BaseProperties::PITCH, + pitch)) continue; + + long velocity = -1; + (void)(*i)->get + (BaseProperties::VELOCITY, + velocity); + + if (!((*i)->has(BaseProperties::TIED_BACKWARD) && + (*i)->get + + (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 pages = printer.pageList(); + + for (QValueList::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(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 before + // dynamic_cast (which will succeed for both) + + if (dynamic_cast(m_tool)) { + NOTATION_DEBUG << "Have rest inserter " << endl; + stateChanged("note_insert_tool_current", StateReverse); + stateChanged("rest_insert_tool_current", StateNoReverse); + } else if (dynamic_cast(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 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(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 + + (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 + (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 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 + (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 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 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 fs(NoteFontFactory::getFontNames()); + std::vector 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 + (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 sizes = NoteFontFactory::getScreenSizes(m_fontName); + m_fontSizeCombo->clear(); + QString value; + for (std::vector::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 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(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 + (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(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(m_tool); + + if (!restInserter) { + + NoteInserter *noteInserter = dynamic_cast(m_tool); + if (!noteInserter) { + KMessageBox::sorry(this, i18n("No note duration selected")); + return ; + } + + Note note(noteInserter->getCurrentNote()); + + restInserter = dynamic_cast + (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(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 + (actionCollection()->action(actionName)); + + if (!action) { + std::cerr << "WARNING: Failed to find note action \"" + << actionName << "\"" << std::endl; + } else { + action->activate(); + } + + NoteInserter *noteInserter = dynamic_cast + (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(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 + (actionCollection()->action(actionName)); + + if (!action) { + std::cerr << "WARNING: Failed to find rest action \"" + << actionName << "\"" << std::endl; + } else { + action->activate(); + } + + RestInserter *restInserter = dynamic_cast + (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(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(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 + + (BaseProperties::PITCH); + style = NoteStyleFactory::getStyleForEvent(*i); + if (baseVelocity != -1) + break; + } + if ((*i)->has(BaseProperties::VELOCITY)) { + baseVelocity = (*i)->get + + (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 + (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 + + (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(*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(*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(*i)->isNote() && + !static_cast(*i)->isRest()) { + NotationElementList::iterator j = i; + while (j != staff->getViewElementList()->end()) { + if (static_cast(*j)->getViewAbsoluteTime() != + static_cast(*i)->getViewAbsoluteTime()) + break; + if (static_cast(*j)->getCanvasItem()) { + if (static_cast(*j)->isNote() || + static_cast(*j)->isRest()) { + i = j; + break; + } + } + ++j; + } + } + + if (static_cast(*i)->getCanvasItem()) { + + staff->setInsertCursorPosition + (static_cast(*i)->getCanvasX() - 2, + int(static_cast(*i)->getCanvasY())); + + if (type == CursorMoveAndMakeVisible) { + getCanvasView()->slotScrollHoriz + (int(static_cast(*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(*i)->getCanvasItem()) { + ccx = static_cast(*i)->getCanvasX(); + static_cast(*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(*i)->getCanvasItem()) { + ccx = static_cast(*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(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(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(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(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(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(it)) { + + double layoutX = (dynamic_cast(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 + (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(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(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 + (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 + (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(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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include +#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 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 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(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 m_staffs; + int m_currentStaff; + int m_lastFinishingStaff; + + QCanvasItem *m_title; + QCanvasItem *m_subtitle; + QCanvasItem *m_composer; + QCanvasItem *m_copyright; + std::vector m_pages; + std::vector 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 NoteActionDataMap; + static NoteActionDataMap* m_noteActionDataMap; + + typedef QMap NoteChangeActionDataMap; + static NoteChangeActionDataMap* m_noteChangeActionDataMap; + + typedef QMap 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 > 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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + 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 , + Chris Cannam , + Richard Bown + + 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include +#include + + +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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "NoteCharacter.h" +#include "NoteFontMap.h" +#include +#include +#include +#include +#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 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 PixmapPair; + typedef std::map PixmapMap; + typedef std::map FontPixmapMap; + + typedef std::map 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#include +#include +#include "misc/Strings.h" +#include "document/ConfigGroups.h" +#include "base/Exception.h" +#include "gui/kdeext/KStartupLogo.h" +#include "NoteFont.h" +#include "NoteFontMap.h" +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +std::set +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 +NoteFontFactory::getAllSizes(std::string fontName) +{ + NoteFont *font = getFont(fontName, 0); + if (!font) + return std::vector(); + + std::set + s(font->getSizes()); + std::vector v; + for (std::set + ::iterator i = s.begin(); i != s.end(); ++i) { + v.push_back(*i); + } + + std::sort(v.begin(), v.end()); + return v; +} + +std::vector +NoteFontFactory::getScreenSizes(std::string fontName) +{ + NoteFont *font = getFont(fontName, 0); + if (!font) + return std::vector(); + + std::set + s(font->getSizes()); + std::vector v; + for (std::set + ::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, NoteFont *>::iterator i = + m_fonts.find(std::pair(fontName, size)); + + if (i == m_fonts.end()) { + try { + NoteFont *font = new NoteFont(fontName, size); + m_fonts[std::pair(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 + 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 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 sizes(getAllSizes(fontName)); + for (unsigned int i = 0; i < sizes.size(); ++i) { + if (sizes[i] == size) + return true; + } + return false; +} + +std::set NoteFontFactory::m_fontNames; +std::map, 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include + + + + +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 getFontNames(bool forceRescan = false); + static std::vector getAllSizes(std::string fontName); // sorted + static std::vector 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 m_fontNames; + static std::map, 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include "misc/Strings.h" +#include "base/Exception.h" +#include "SystemFont.h" +#include +#include +#include +#include +#include +#include +#include + + +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 +NoteFontMap::getSizes() const +{ + std::set sizes; + + for (SizeDataMap::const_iterator i = m_sizes.begin(); + i != m_sizes.end(); ++i) { + sizes.insert(i->first); + } + + return sizes; +} + +std::set +NoteFontMap::getCharNames() const +{ + std::set 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 sizes = getSizes(); + std::set names = getCharNames(); + + for (std::set::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::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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include "SystemFont.h" +#include +#include +#include +#include +#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 getSizes() const; + std::set 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 Point; + typedef std::pair ScaledPoint; + typedef std::map 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::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 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 SymbolDataMap; + SymbolDataMap m_data; + + typedef std::map HotspotDataMap; + HotspotDataMap m_hotspots; + + typedef std::map SizeDataMap; + SizeDataMap m_sizes; + + typedef std::map SystemFontNameMap; + SystemFontNameMap m_systemFontNames; + + typedef std::map SystemFontStrategyMap; + SystemFontStrategyMap m_systemFontStrategies; + + typedef std::map SystemFontMap; + mutable SystemFontMap m_systemFontCache; + + typedef std::map 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "FontViewFrame.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#include "base/BaseProperties.h" +#include +#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 +#include +#include +#include +#include +#include + + +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(*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(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(*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(NotationProperties::HEIGHT_ON_STAFF); + if (h == height) { + pitch = (*i)->get(BaseProperties::PITCH); + (*i)->get(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(*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 + //## 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 + (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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "NotePixmapFactory.h" +#include "misc/Debug.h" +#include "base/NotationRules.h" +#include + +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 +{ + // 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 normalMarks = params.getNormalMarks(); + std::vector aboveMarks = params.getAboveMarks(); + + for (std::vector::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::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 normalMarks = params.getNormalMarks(); + std::vector aboveMarks = params.getAboveMarks(); + + bool normalMarksAreAbove = !(isStemmed && params.m_stemGoesUp); + + for (std::vector::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::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 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, 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 ah0 = previousKey.getAccidentalHeights(clef); + std::vector 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 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 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 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 ah0 = previousKey.getAccidentalHeights(Clef()); + std::vector 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "NoteCharacter.h" +#include +#include +#include +#include +#include +#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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 &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 +NotePixmapParameters::getNormalMarks() const +{ + std::vector marks; + + for (std::vector::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 +NotePixmapParameters::getAboveMarks() const +{ + std::vector marks; + + // fingerings before other marks + + for (std::vector::const_iterator mi = m_marks.begin(); + mi != m_marks.end(); ++mi) { + + if (Marks::isFingeringMark(*mi)) { + marks.push_back(*mi); + } + } + + for (std::vector::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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + + + +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 &marks); + void removeMarks(); + + void setInRange(bool inRange) { m_inRange = inRange; } + + std::vector getNormalMarks() const; + std::vector 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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "NoteCharacterNames.h" +#include +#include +#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 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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "misc/Strings.h" +#include "base/Event.h" +#include "base/Exception.h" +#include "NotationProperties.h" +#include "NoteStyle.h" +#include "NoteStyleFileReader.h" +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +const NoteStyleName NoteStyleFactory::DefaultStyle = "Classical"; + +std::vector +NoteStyleFactory::getAvailableStyleNames() +{ + std::vector 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 + (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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include "NoteStyle.h" + + +namespace Rosegarden +{ + +class NoteStyle; +class Event; + +class NoteStyleFactory +{ +public: + static std::vector getAvailableStyleNames(); + static const NoteStyleName DefaultStyle; + + static NoteStyle *getStyle(NoteStyleName name); + static NoteStyle *getStyleForEvent(Event *event); + + typedef Exception StyleUnavailable; + +private: + typedef std::map 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "NoteStyle.h" +#include +#include + +#include +#include +#include + +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "NoteFontMap.h" +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "gui/editors/notation/NoteCharacterNames.h" + + +class SystemFontSpec; + + +namespace Rosegarden +{ + +typedef std::pair 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include FT_FREETYPE_H +#include FT_OUTLINE_H +#include FT_GLYPH_H +#include + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + This file is Copyright 2007-2008 + Yves Guillemot + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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(parent)->getNotationView(); + + setFrameStyle(QFrame::Box | QFrame::Plain); + setCurrent(false); + + + // + // Tooltip text creation + + Composition *comp = + static_cast(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; igetStaffCount(); 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=, 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(parent())->getComposition(); + Track *track = comp->getTrackById(m_track); + int trackPos = comp->getTrackPositionById(m_track); + + int status = 0; + bool current = false; + for (int i=0; igetStaffCount(); 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 , + Chris Cannam , + Richard Bown + + This file is Copyright 2007-2008 + Yves Guillemot + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + +#include + +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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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(""); + } else { + button = m_audioFader->m_plugins[index]; + noneText = i18n(""); + } + + 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(""); + } else { + index = i; + button = m_audioFader->m_plugins[i]; + noneText = i18n(""); + } + + 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#include +#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 +#include +#include +#include +#include +#include +#include +#include + + +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::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::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::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::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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + +class QWidget; +class QLabel; + + +namespace Rosegarden +{ + +class RosegardenGUIDoc; +class Instrument; +class Rotary; + +typedef std::pair RotaryPair; +typedef std::vector > 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#include "sound/Midi.h" +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 + (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 + (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 > + (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 + (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 + (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 + (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 + (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 + (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 + (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 + (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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + 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 . + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 sorted; + std::set unsorted; + + for (unsigned int i = 0; i < m_parameterBoxes.size(); i++) { + unsorted.insert(m_parameterBoxes[i]); + } + + QString previous = ""; + + while (!unsorted.empty()) { + std::set::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::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 , + Chris Cannam , + Richard Bown + + 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 . + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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::iterator it = + m_segments.begin(); it != m_segments.end(); ++it) { + + if (*it == segment) { + m_segments.erase(it); + return ; + } + } + } +} + +void +SegmentParameterBox::populateBoxFromSegments() +{ + std::vector::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::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::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::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::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::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::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::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::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::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::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::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::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::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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#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 m_segments; + std::vector m_standardQuantizations; + std::vector m_delays; + std::vector 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 , + Chris Cannam , + Richard Bown + + 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 + D. Michael McIntyre + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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(""), 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(""), 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(*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(""); + 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::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 , + Chris Cannam , + Richard Bown + + 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 + D. Michael McIntyre + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "RosegardenParameterArea.h" +#include "RosegardenParameterBox.h" +#include +#include // #include in QT4, thinking ahead +#include + + +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 IdsVector; + + IdsVector m_playDeviceIds; + IdsVector m_recDeviceIds; + + std::map m_instrumentIds; + std::map 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +const QString notShowing(i18n("")); + +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("")); + MidiDevice *md = + dynamic_cast(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 selection = m_listView->selectedItems(); + + MidiDevice *md = + dynamic_cast(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(""); + + 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("")); + 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(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(i); + + MidiDevice *md = + dynamic_cast(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +const QString notShowing(i18n("")); + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 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("")); + ((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(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(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 + // placeholder + return ; + } + + // Need to get the raw time from the ListViewItem + // + MarkerEditorViewItem *item = + dynamic_cast(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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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(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(getListView()->firstChild()); + + while (item) { + urlList << item->getURL().url(); + item = dynamic_cast(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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("")); + } + + m_trackLabels[i]->update(); + } + +} + +std::vector +TrackButtons::mutedTracks() +{ + std::vector 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::iterator tit = m_trackLabels.begin(); + tit += position; + m_trackLabels.erase(tit); + + std::vector::iterator vit = m_trackMeters.begin(); + vit += position; + m_trackMeters.erase(vit); + + std::vector::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::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("")); + } else { + trackLabel->setText(i18n("")); + } + } 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 +TrackButtons::getHighlightedTracks() +{ + std::vector 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(""); + 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 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("")); + 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("")); + } else { + trackLabel->getTrackLabel()->setText(i18n("")); + } + } + 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("")); + 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + +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 mutedTracks(); + + /// Return a vector of highlighted tracks + std::vector 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 m_muteLeds; + std::vector m_recordLeds; + std::vector m_trackLabels; + std::vector m_trackMeters; + std::vector 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 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(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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "TrackEditorIface.h" +#include +#include +#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 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 + 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "base/Track.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +#include "base/BaseProperties.h" +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 + 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 + (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(""); + + 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("")); + 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(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(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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +#include +#include + +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& 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include + + +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 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 &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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include + + +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 &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 RequestRec; + typedef std::multimap RequestQueue; + RequestQueue m_queue; + + typedef std::pair > ResultsPair; + typedef std::map 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + +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(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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + +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 &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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#include "CompositionItemHelper.h" + +#include "base/Segment.h" +#include "base/SnapGrid.h" +#include "misc/Debug.h" +#include "CompositionModel.h" +#include "CompositionItemImpl.h" +#include +#include +#include + +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((_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((_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((_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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include +#include +#include "base/Event.h" +#include "CompositionRect.h" +#include "CompositionItem.h" + + +class RectRanges; +class CompositionItem; +class AudioPreviewDrawData; + + +namespace Rosegarden +{ + +class SnapGrid; +typedef std::vector 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 rectlist; + typedef std::vector heightlist; + typedef std::vector rectcontainer; + typedef std::set 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 AudioPreviewDrawData; + + struct RectRange { + std::pair range; + QPoint basePoint; + QColor color; + }; + + typedef std::vector 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 &getValues() const { return m_values; } + void setValues(const std::vector&v) { m_values = v; } + + QRect getSegmentRect() { return m_segmentRect; } + void setSegmentRect(const QRect& r) { m_segmentRect = r; } + + protected: + std::vector 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#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 +#include +#include +#include +#include +#include +#include +#include + + + +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((_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(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 &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(s)); + } else { + m_audioPreviewDataCache.remove(const_cast(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((_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(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((_CompositionItem*)item); + if (itemImpl) { + Segment* segment = const_cast(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)); + } else { + SegmentSelection::iterator i = m_selectedSegments.find(const_cast(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((_CompositionItem*)ci); + return itemImpl ? isSelected(itemImpl->getSegment()) : 0; +} + +bool CompositionModelImpl::isSelected(const Segment* s) const +{ + return m_selectedSegments.find(const_cast(s)) != m_selectedSegments.end(); +} + +bool CompositionModelImpl::isTmpSelected(const Segment* s) const +{ + return m_tmpSelectedSegments.find(const_cast(s)) != m_tmpSelectedSegments.end(); +} + +bool CompositionModelImpl::wasTmpSelected(const Segment* s) const +{ + return m_previousTmpSelectedSegments.find(const_cast(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((_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(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(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(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(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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include "SegmentOrderer.h" +#include +#include +#include +#include +#include +#include +#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 recordingsegmentset; + recordingsegmentset m_recordingSegments; + + typedef std::vector itemgc; + + AudioPreviewThread* m_audioPreviewThread; + + typedef QPtrDict NotationPreviewDataCache; + typedef QPtrDict 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 m_segmentRectMap; + std::map m_segmentEndTimeMap; + std::map m_audioSegmentPreviewMap; + std::map m_trackHeights; + + typedef std::map + 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include + + +class QSize; +class QPoint; + + +namespace Rosegarden +{ + +class CompositionRect : public QRect +{ +public: + typedef QValueVector 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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(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(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(getModel())->clearSegmentRectsCache(clearPreviews); +} + +SegmentSelection +CompositionView::getSelectedSegments() +{ + return (dynamic_cast(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(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 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((_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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + + + +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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include + + +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((_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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 +#include +#include +#include +#include +#include +#include + + +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 + (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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + + + +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 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 +#include +#include +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 +#include +#include +#include +#include +#include +#include + + +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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include +#include + + +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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#include + + +namespace Rosegarden +{ + +SegmentTool::SegmentTool(CompositionView* canvas, RosegardenGUIDoc *doc) + : BaseTool("segment_tool_menu", dynamic_cast(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(m_doc->parent()); + + if (app) { + m_menu = static_cast + //(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include + + +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 SegmentItemPair; +typedef std::vector 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include + +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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +#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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +int +TempoView::m_lastSetFilter = -1; + + +TempoView::TempoView(RosegardenGUIDoc *doc, QWidget *parent, timeT openTime): + EditViewBase(doc, std::vector(), 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 selection = m_list->selectedItems(); + + if (selection.count()) { + QPtrListIterator 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 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 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("")); + 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::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 + (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 selection = m_list->selectedItems(); + if (selection.count() == 0) + return ; + + RG_DEBUG << "TempoView::slotEditDelete - deleting " + << selection.count() << " items" << endl; + + QPtrListIterator 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 commands; + + while ((listItem = it.current()) != 0) { + item = dynamic_cast((*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::iterator i = commands.end(); + i != commands.begin();) { + command->addCommand(*--i); + } + addCommandToHistory(command); + } + + applyLayout(); + m_ignoreUpdates = false; +} + +void +TempoView::slotEditInsertTempo() +{ + timeT insertTime = 0; + QPtrList selection = m_list->selectedItems(); + + if (selection.count() > 0) { + TempoListItem *item = + dynamic_cast(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 selection = m_list->selectedItems(); + + if (selection.count() > 0) { + TempoListItem *item = + dynamic_cast(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 selection = m_list->selectedItems(); + + if (selection.count() > 0) { + TempoListItem *item = + dynamic_cast(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(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(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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 +#include +#include +#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 m_listSelection; + + bool m_ignoreUpdates; +}; + + + +} + +#endif diff --git a/src/gui/general/ActiveItem.cpp b/src/gui/general/ActiveItem.cpp new file mode 100644 index 0000000..00d967f --- /dev/null +++ b/src/gui/general/ActiveItem.cpp @@ -0,0 +1,32 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ActiveItem.h" + + + +namespace Rosegarden +{ +} diff --git a/src/gui/general/ActiveItem.h b/src/gui/general/ActiveItem.h new file mode 100644 index 0000000..f8f5339 --- /dev/null +++ b/src/gui/general/ActiveItem.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ACTIVEITEM_H_ +#define _RG_ACTIVEITEM_H_ + + + +class QMouseEvent; + + +namespace Rosegarden +{ + + + +/** + * An interface for canvas items which are capable of handling + * mouse events + */ +class ActiveItem +{ +public: + virtual void handleMousePress(QMouseEvent*) = 0; + virtual void handleMouseMove(QMouseEvent*) = 0; + virtual void handleMouseRelease(QMouseEvent*) = 0; +}; + + + +} + +#endif diff --git a/src/gui/general/BarLine.cpp b/src/gui/general/BarLine.cpp new file mode 100644 index 0000000..42bb936 --- /dev/null +++ b/src/gui/general/BarLine.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "BarLine.h" + +#include + +namespace Rosegarden { + +void +BarLine::drawShape(QPainter &painter) +{ + int bx = int(x()); + int by = int(y()); + + switch (m_style) { + + case LinedStaff::PlainBar: + painter.drawRect(bx, by, m_baseBarThickness, m_barLineHeight); + break; + + case LinedStaff::DoubleBar: + painter.drawRect(bx, by, m_baseBarThickness, m_barLineHeight); + painter.drawRect(bx + m_baseBarThickness * 3, by, + m_baseBarThickness, m_barLineHeight); + break; + + case LinedStaff::HeavyDoubleBar: + bx -= m_baseBarThickness * 5; + painter.drawRect(bx, by, m_baseBarThickness, m_barLineHeight); + painter.drawRect(bx + m_baseBarThickness * 3, by, + m_baseBarThickness * 3, m_barLineHeight); + break; + + case LinedStaff::RepeatEndBar: + bx -= m_baseBarThickness * 5 + m_lineSpacing * 2 / 3; + painter.drawEllipse(bx, by + m_barLineHeight / 2 - (m_lineSpacing * 2 / 3), + m_lineSpacing / 3, m_lineSpacing / 3); + painter.drawEllipse(bx, by + m_barLineHeight / 2 + (m_lineSpacing / 3), + m_lineSpacing / 3, m_lineSpacing / 3); + bx += m_lineSpacing * 2 / 3; + painter.drawRect(bx, by, m_baseBarThickness, m_barLineHeight); + painter.drawRect(bx + m_baseBarThickness * 3, by, + m_baseBarThickness * 3, m_barLineHeight); + break; + + case LinedStaff::RepeatStartBar: + + if (m_inset > 0) { + painter.drawRect(bx, by, m_baseBarThickness, m_barLineHeight); + bx += m_inset; + } + + painter.drawRect(bx, by, m_baseBarThickness * 3, m_barLineHeight); + painter.drawRect(bx + m_baseBarThickness * 5, by, + m_baseBarThickness, m_barLineHeight); + bx += m_baseBarThickness * 6 + (m_lineSpacing / 3); + painter.drawEllipse(bx, by + m_barLineHeight / 2 - (m_lineSpacing * 2 / 3), + m_lineSpacing / 3, m_lineSpacing / 3); + painter.drawEllipse(bx, by + m_barLineHeight / 2 + (m_lineSpacing / 3), + m_lineSpacing / 3, m_lineSpacing / 3); + break; + + case LinedStaff::RepeatBothBar: + + if (m_inset > 0) { + painter.drawRect(bx, by, m_baseBarThickness, m_barLineHeight); + bx += m_inset; + } + + bx -= m_baseBarThickness * 4 + m_lineSpacing * 2 / 3; + painter.drawEllipse(bx, by + m_barLineHeight / 2 - (m_lineSpacing * 2 / 3), + m_lineSpacing / 3, m_lineSpacing / 3); + painter.drawEllipse(bx, by + m_barLineHeight / 2 + (m_lineSpacing / 3), + m_lineSpacing / 3, m_lineSpacing / 3); + bx += m_lineSpacing * 2 / 3; + painter.drawRect(bx, by, m_baseBarThickness, m_barLineHeight); + painter.drawRect(bx + m_baseBarThickness * 3, by, + m_baseBarThickness * 3, m_barLineHeight); + painter.drawRect(bx + m_baseBarThickness * 8, by, + m_baseBarThickness, m_barLineHeight); + bx += m_baseBarThickness * 9 + (m_lineSpacing / 3); + painter.drawEllipse(bx, by + m_barLineHeight / 2 - (m_lineSpacing * 2 / 3), + m_lineSpacing / 3, m_lineSpacing / 3); + painter.drawEllipse(bx, by + m_barLineHeight / 2 + (m_lineSpacing / 3), + m_lineSpacing / 3, m_lineSpacing / 3); + + break; + + case LinedStaff::NoVisibleBar: + break; + } +} + +QPointArray +BarLine::areaPoints() const +{ + int bx = int(x()); + int by = int(y()); + int x0 = bx, y0 = by, x1, y1 = by + m_barLineHeight; + + switch (m_style) { + + case LinedStaff::PlainBar: + x1 = x0 + m_baseBarThickness; + break; + + case LinedStaff::DoubleBar: + x1 = x0 + m_baseBarThickness * 4; + break; + + case LinedStaff::HeavyDoubleBar: + x0 -= m_baseBarThickness * 6; + x1 = bx; + break; + + case LinedStaff::RepeatEndBar: + x0 -= m_baseBarThickness * 6 + m_lineSpacing * 2 / 3; + x1 = bx; + break; + + case LinedStaff::RepeatStartBar: + x1 = x0 + m_baseBarThickness * 6 + m_lineSpacing * 2 / 3 + m_inset; + break; + + case LinedStaff::RepeatBothBar: + x0 -= m_baseBarThickness * 4 + m_lineSpacing * 2 / 3; + x1 = x0 + m_baseBarThickness * 9 + m_lineSpacing * 2 / 3 + m_inset; + break; + + case LinedStaff::NoVisibleBar: + x1 = x0 + 1; + break; + } + + QPointArray p(4); + p[0] = QPoint(x0, y0); + p[1] = QPoint(x1, y0); + p[2] = QPoint(x1, y1); + p[3] = QPoint(x0, y1); + return p; +} + +} diff --git a/src/gui/general/BarLine.h b/src/gui/general/BarLine.h new file mode 100644 index 0000000..124bc06 --- /dev/null +++ b/src/gui/general/BarLine.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_BARLINE_H_ +#define _RG_BARLINE_H_ + +#include "LinedStaff.h" +#include + +namespace Rosegarden { + +class BarLine : public QCanvasPolygonalItem +{ +public: + BarLine(QCanvas *canvas, double layoutX, + int barLineHeight, int baseBarThickness, int lineSpacing, + int inset, LinedStaff::BarStyle style) : + QCanvasPolygonalItem(canvas), + m_layoutX(layoutX), + m_barLineHeight(barLineHeight), + m_baseBarThickness(baseBarThickness), + m_lineSpacing(lineSpacing), + m_inset(inset), + m_style(style) { } + + double getLayoutX() const { return m_layoutX; } + + virtual QPointArray areaPoints() const; + virtual void drawShape(QPainter &); + +protected: + double m_layoutX; + int m_barLineHeight; + int m_baseBarThickness; + int m_lineSpacing; + int m_inset; + LinedStaff::BarStyle m_style; +}; + +} + +#endif /*BARLINE_H_*/ diff --git a/src/gui/general/BaseTool.cpp b/src/gui/general/BaseTool.cpp new file mode 100644 index 0000000..4c33610 --- /dev/null +++ b/src/gui/general/BaseTool.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "BaseTool.h" + +#include "misc/Debug.h" +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +BaseTool::BaseTool(const QString& menuName, KXMLGUIFactory* factory, QObject* parent) + : QObject(parent), + m_menuName(menuName), + m_menu(0), + m_parentFactory(factory) +{} + +BaseTool::~BaseTool() +{ + RG_DEBUG << "BaseTool::~BaseTool()\n"; + + // delete m_menu; + // m_parentView->factory()->removeClient(this); + // m_instance = 0; +} + +void BaseTool::ready() +{} + +void BaseTool::stow() +{} + +void BaseTool::showMenu() +{ + if (!hasMenu()) + return ; + + if (!m_menu) + createMenu(); + + if (m_menu) + m_menu->exec(QCursor::pos()); + else + RG_DEBUG << "BaseTool::showMenu() : no menu to show\n"; +} + +QString BaseTool::getCurrentContextHelp() const +{ + return m_contextHelp; +} + +void BaseTool::setContextHelp(const QString &help) +{ + m_contextHelp = help; + emit showContextHelp(m_contextHelp); +} + +} + +#include "BaseTool.moc" + diff --git a/src/gui/general/BaseTool.h b/src/gui/general/BaseTool.h new file mode 100644 index 0000000..71e6410 --- /dev/null +++ b/src/gui/general/BaseTool.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_BASETOOL_H_ +#define _RG_BASETOOL_H_ + +#include +#include + + +class QPopupMenu; +class KXMLGUIFactory; + + +namespace Rosegarden +{ + + + +/** + * BaseTool : base tool class, just handles RMB menu creation and + * handling by a BaseToolBox + * + */ +class BaseTool : public QObject +{ + Q_OBJECT + + friend class BaseToolBox; + +public: + + virtual ~BaseTool(); + + /** + * Is called by the parent View (EditView or SegmentCanvas) when + * the tool is set as current. + * Add any setup here (e.g. setting the mouse cursor shape) + */ + virtual void ready(); + + /** + * Is called by the parent View (EditView or SegmentCanvas) after + * the tool is put away. + * Add any cleanup here + */ + virtual void stow(); + + /** + * Show the menu if there is one + */ + virtual void showMenu(); + + /** + * Retrieve current status-line type help for the tool, if any + */ + virtual QString getCurrentContextHelp() const; + +signals: + void showContextHelp(const QString &); + +protected: + /** + * Create a new BaseTool + * + * \a menuName : the name of the menu defined in the XML rc file + */ + BaseTool(const QString& menuName, KXMLGUIFactory*, QObject* parent); + + virtual void createMenu() = 0; + virtual bool hasMenu() { return false; } + + virtual void setContextHelp(const QString &help); + virtual void clearContextHelp() { setContextHelp(""); } + + //--------------- Data members --------------------------------- + + QString m_menuName; + QPopupMenu* m_menu; + + KXMLGUIFactory* m_parentFactory; + + QString m_contextHelp; +}; + + + +} + +#endif diff --git a/src/gui/general/BaseToolBox.cpp b/src/gui/general/BaseToolBox.cpp new file mode 100644 index 0000000..9e2fda9 --- /dev/null +++ b/src/gui/general/BaseToolBox.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "BaseToolBox.h" + +#include "BaseTool.h" +#include +#include +#include + + +namespace Rosegarden +{ + +BaseToolBox::BaseToolBox(QWidget* parent) + : QObject(parent), + m_tools(17, // default size, from the Qt docs + false) // but we want it to be case insensitive +{ + //m_tools.setAutoDelete(true); +} + +BaseTool* BaseToolBox::getTool(const QString& toolName) +{ + BaseTool* tool = m_tools[toolName]; + + if (!tool) tool = createTool(toolName); + + connect(tool, SIGNAL(showContextHelp(const QString &)), + this, SIGNAL(showContextHelp(const QString &))); + + return tool; +} + +} +#include "BaseToolBox.moc" diff --git a/src/gui/general/BaseToolBox.h b/src/gui/general/BaseToolBox.h new file mode 100644 index 0000000..2a0242f --- /dev/null +++ b/src/gui/general/BaseToolBox.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_BASETOOLBOX_H_ +#define _RG_BASETOOLBOX_H_ + +#include +#include + + +class QWidget; +class QString; + + +namespace Rosegarden +{ + +class BaseTool; + + +/** + * BaseToolBox : maintains a single instance of each registered tool + * + * Tools are fetched from a name + */ +class BaseToolBox : public QObject +{ + Q_OBJECT + +public: + BaseToolBox(QWidget* parent); + + virtual BaseTool* getTool(const QString& toolName); + +signals: + void showContextHelp(const QString &); + +protected: + virtual BaseTool* createTool(const QString& toolName) = 0; + + QDict m_tools; +}; + + +} + +#endif diff --git a/src/gui/general/CanvasCursor.cpp b/src/gui/general/CanvasCursor.cpp new file mode 100644 index 0000000..5f04794 --- /dev/null +++ b/src/gui/general/CanvasCursor.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CanvasCursor.h" + +#include "GUIPalette.h" +#include +#include + + +namespace Rosegarden +{ + +CanvasCursor::CanvasCursor(QCanvas* c, int width) + : QCanvasRectangle(c), + m_width(width) +{ + QPen pen(GUIPalette::getColour(GUIPalette::Pointer)); + // pen.setWidth(width); + setPen(pen); + setBrush(GUIPalette::getColour(GUIPalette::Pointer)); +} + +void CanvasCursor::updateHeight() +{ + setSize(m_width, canvas()->height()); + // setPoints(0, 0, 0, canvas()->height()); +} + +} diff --git a/src/gui/general/CanvasCursor.h b/src/gui/general/CanvasCursor.h new file mode 100644 index 0000000..694e9df --- /dev/null +++ b/src/gui/general/CanvasCursor.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CANVASCURSOR_H_ +#define _RG_CANVASCURSOR_H_ + + +#include + +class QCanvas; + + +namespace Rosegarden +{ + + + +class CanvasCursor : public QCanvasRectangle +{ +public: + CanvasCursor(QCanvas*, int width); + void updateHeight(); +// virtual QRect boundingRect() const; +protected: + int m_width; +}; + + + + +} + +#endif diff --git a/src/gui/general/CanvasItemGC.cpp b/src/gui/general/CanvasItemGC.cpp new file mode 100644 index 0000000..6e6afb2 --- /dev/null +++ b/src/gui/general/CanvasItemGC.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CanvasItemGC.h" + +#include "misc/Debug.h" + +#include + +namespace Rosegarden +{ + +void CanvasItemGC::mark(QCanvasItem* item) +{ + if (!item) + return ; + + item->hide(); + // RG_DEBUG << "CanvasItemGC::mark() : " + // << item << std::endl; + m_garbage.push_back(item); +} + +void CanvasItemGC::gc() +{ + for (unsigned int i = 0; i < m_garbage.size(); ++i) { + // RG_DEBUG << "CanvasItemGC::gc() : delete " + // << m_garbage[i] << "\n"; + delete m_garbage[i]; + } + + m_garbage.clear(); +} + +void CanvasItemGC::flush() +{ + m_garbage.clear(); +} + +std::vector CanvasItemGC::m_garbage; + +} diff --git a/src/gui/general/CanvasItemGC.h b/src/gui/general/CanvasItemGC.h new file mode 100644 index 0000000..db1833b --- /dev/null +++ b/src/gui/general/CanvasItemGC.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CANVASITEMGC_H_ +#define _RG_CANVASITEMGC_H_ + +#include + + +class QCanvasItem; + + +namespace Rosegarden +{ + + + +/** + * A pseudo GC in which CanvasItems whose ownership isn't clear cut + * can be put for periodical removal. + * + * This is especially for SegmentItems which can put their repeat + * rectangles when they're being deleted. + * + * The problem this solves is a classic ownership/double deletion + * case. The SegmentCanvas deletes all its items on destruction. But + * the SegmentItems have an auxiliary "repeat rectangle" which is a + * QCanvasRectangle, that needs to be deleted when the SegmentItem is + * itself deleted. + * + * However, if the SegmentItem deletes its repeat rectangle, then when + * the SegmentCanvas destruction occurs, the SegmentCanvas dtor will + * get a list of all its children (QCanvas::allItems()), containing + * both SegmentItems and their repeat rectangles. Deleting a + * SegmentItem will delete its repeat rectangle, which will still be + * present in the all children list which the SegmentCanvas dtor is + * iterating over. + * + * So a solution is simply to push to-be-deleted repeat rectangles on + * this GC, which should be processed on canvas updates, for instance. + * + */ +class CanvasItemGC +{ +public: + /// mark the given item for GC + static void mark(QCanvasItem*); + + /// GC all marked items + static void gc(); + + /// Forget all marked items - don't delete them + static void flush(); + +protected: + static std::vector m_garbage; +}; + + + +} + +#endif diff --git a/src/gui/general/CategoryElement.cpp b/src/gui/general/CategoryElement.cpp new file mode 100644 index 0000000..2199ce6 --- /dev/null +++ b/src/gui/general/CategoryElement.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "CategoryElement.h" + +#include "misc/Debug.h" +#include "PresetElement.h" +#include + + +namespace Rosegarden +{ + +CategoryElement::CategoryElement(QString name) : + m_name(name) +{} + +CategoryElement::~CategoryElement() +{ + // nothing to do +} + +void +CategoryElement::addPreset(QString name, + int clef, + int transpose, + int highAm, + int lowAm, + int highPro, + int lowPro) +{ + RG_DEBUG << "CategoryElement::addPreset(...): adding new PresetElement" << endl; + + PresetElement e(name, clef, transpose, highAm, lowAm, + highPro, lowPro); + m_categoryPresets.push_back(e); +} + +} diff --git a/src/gui/general/CategoryElement.h b/src/gui/general/CategoryElement.h new file mode 100644 index 0000000..eeb88a5 --- /dev/null +++ b/src/gui/general/CategoryElement.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CATEGORYELEMENT_H_ +#define _RG_CATEGORYELEMENT_H_ + +#include "PresetElement.h" +#include + + + + +namespace Rosegarden +{ + + +/* + * A container class for storing a collection of PresetElement objects grouped + * into the same musical category (eg. Flutes, Clarinets, Strings) + */ +class CategoryElement +{ +public: + CategoryElement(QString name); + ~CategoryElement(); + + void addPreset(QString name, + int clef, + int transpose, + int highAm, + int lowAm, + int highPro, + int lowPro); + + QString getName() { return m_name; } + + ElementContainer getPresets() { return m_categoryPresets; } + PresetElement getPresetByIndex(int index) { return m_categoryPresets [index]; } + +private: + QString m_name; + ElementContainer m_categoryPresets; +}; // CategoryElement + +typedef std::vector CategoriesContainer; + +} + +#endif diff --git a/src/gui/general/ClefIndex.cpp b/src/gui/general/ClefIndex.cpp new file mode 100644 index 0000000..68ad488 --- /dev/null +++ b/src/gui/general/ClefIndex.cpp @@ -0,0 +1,100 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 "gui/general/ClefIndex.h" +#include "misc/Debug.h" + +namespace Rosegarden +{ + +const Rosegarden::Clef clefIndexToClef(int index) +{ + Rosegarden::Clef clef; + + // insert an initial clef from track parameters + switch (index) { + case TrebleClef: clef = Clef(Clef::Treble); break; + case BassClef: clef = Clef(Clef::Bass); break; + case CrotalesClef: clef = Clef(Clef::Treble, 2); break; + case XylophoneClef: clef = Clef(Clef::Treble, 1); break; + case GuitarClef: clef = Clef(Clef::Treble, -1); break; + case ContrabassClef: clef = Clef(Clef::Bass, -1); break; + case CelestaClef: clef = Clef(Clef::Bass, 2); break; + case OldCelestaClef: clef = Clef(Clef::Bass, 1); break; + case FrenchClef: clef = Clef(Clef::French); break; + case SopranoClef: clef = Clef(Clef::Soprano); break; + case MezzosopranoClef: clef = Clef(Clef::Mezzosoprano); break; + case AltoClef: clef = Clef(Clef::Alto); break; + case TenorClef: clef = Clef(Clef::Tenor); break; + case BaritoneClef: clef = Clef(Clef::Baritone); break; + case VarbaritoneClef: clef = Clef(Clef::Varbaritone); break; + case SubbassClef: clef = Clef(Clef::Subbass); break; + default: clef = Clef(Clef::Treble); break; + } + return clef; +} + +const int clefNameToClefIndex(QString s) +{ + int m_elClef = 0; + if (s) { + if (s == "treble") + m_elClef = TrebleClef; + else if (s == "bass") + m_elClef = BassClef; + else if (s == "crotales") + m_elClef = CrotalesClef; + else if (s == "xylophone") + m_elClef = XylophoneClef; + else if (s == "guitar") + m_elClef = GuitarClef; + else if (s == "contrabass") + m_elClef = ContrabassClef; + else if (s == "celesta") + m_elClef = CelestaClef; + else if (s == "oldCelesta") + m_elClef = OldCelestaClef; + else if (s == "french") + m_elClef = FrenchClef; + else if (s == "soprano") + m_elClef = SopranoClef; + else if (s == "mezzosoprano") + m_elClef = MezzosopranoClef; + else if (s == "alto") + m_elClef = AltoClef; + else if (s == "tenor") + m_elClef = TenorClef; + else if (s == "baritone") + m_elClef = BaritoneClef; + else if (s == "varbaritone") + m_elClef = VarbaritoneClef; + else if (s == "subbass") + m_elClef = SubbassClef; + else if (s == "two-bar") + m_elClef = TwoBarClef; + else { + RG_DEBUG << "startElement: processed unrecognized clef type: " << s << endl; + } + } + return m_elClef; +} + +} \ No newline at end of file diff --git a/src/gui/general/ClefIndex.h b/src/gui/general/ClefIndex.h new file mode 100644 index 0000000..74e3fc8 --- /dev/null +++ b/src/gui/general/ClefIndex.h @@ -0,0 +1,59 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 _CLEF_INDEX_H_ +#define _CLEF_INDEX_H_ + +#include "base/NotationTypes.h" +#include + +// used variously by TPB, SPB, PresetHandler to correlate combo box indices to +// clef types +enum { TrebleClef = 0, // G clef, line 2 + BassClef, // F clef, line 4 + CrotalesClef, // G clef, line 2, 15 above + XylophoneClef, // G clef, line 2, 8 above + GuitarClef, // G clef, line 2, 8 below + ContrabassClef, // F clef, line 4, 8 below + CelestaClef, // F clef, line 4, 15 above + OldCelestaClef, // F clef, line 4, 8 above + FrenchClef, // G clef, line 1 + SopranoClef, // C clef, line 1 + MezzosopranoClef, // C clef, line 2 + AltoClef, // C clef, line 3 + TenorClef, // C clef, line 4 + BaritoneClef, // C clef, line 5 + VarbaritoneClef, // F clef, line 3 + SubbassClef, // F clef, line 5 + TwoBarClef // percussion clef //!!! doesn't exist yet! + }; + +namespace Rosegarden +{ + +const Clef clefIndexToClef(int index); + +const int clefNameToClefIndex(QString s); + +} + +#endif // _CLEF_INDEX_H_ diff --git a/src/gui/general/EditTool.cpp b/src/gui/general/EditTool.cpp new file mode 100644 index 0000000..52e7b11 --- /dev/null +++ b/src/gui/general/EditTool.cpp @@ -0,0 +1,143 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EditTool.h" + +#include "misc/Debug.h" +#include "base/Event.h" +#include "BaseTool.h" +#include "base/ViewElement.h" +#include "EditView.h" +#include "RosegardenCanvasView.h" +#include +#include +#include +#include + + +namespace Rosegarden +{ + +EditTool::EditTool(const QString& menuName, EditView* view) + : BaseTool(menuName, view->factory(), view), + m_parentView(view) +{} + +void EditTool::handleMousePress(timeT time, + int height, + int staffNo, + QMouseEvent* e, + ViewElement* el) +{ + RG_DEBUG << "EditTool::handleMousePress : mouse button = " + << e->button() << endl; + + switch (e->button()) { + + case Qt::LeftButton: + if (e->type() == QEvent::MouseButtonDblClick) { + RG_DEBUG << "EditTool::handleMousePress: it's a double-click" + << endl; + handleMouseDoubleClick(time, height, staffNo, e, el); + } else { + RG_DEBUG << "EditTool::handleMousePress: it's a single-click" + << endl; + handleLeftButtonPress(time, height, staffNo, e, el); + } + break; + + case Qt::RightButton: + handleRightButtonPress(time, height, staffNo, e, el); + break; + + case Qt::MidButton: + handleMidButtonPress(time, height, staffNo, e, el); + break; + + default: + RG_DEBUG << "EditTool::handleMousePress : no button mouse press\n"; + break; + } +} + +void EditTool::handleMidButtonPress(timeT, + int, int, + QMouseEvent*, + ViewElement*) +{} + +void EditTool::handleRightButtonPress(timeT, + int, int, + QMouseEvent*, + ViewElement*) +{ + showMenu(); +} + +void EditTool::handleMouseDoubleClick(timeT, + int, int, + QMouseEvent*, + ViewElement*) +{ + // nothing +} + +int EditTool::handleMouseMove(timeT, int, QMouseEvent*) +{ + return RosegardenCanvasView::NoFollow; +} + +void EditTool::handleMouseRelease(timeT, int, QMouseEvent*) +{} + +void EditTool::createMenu(QString rcFileName) +{ + setRCFileName(rcFileName); + createMenu(); +} + +void EditTool::createMenu() +{ + RG_DEBUG << "BaseTool::createMenu() " << m_rcFileName << " - " << m_menuName << endl; + + setXMLFile(m_rcFileName); + m_parentFactory->addClient(this); + + QWidget* tmp = m_parentFactory->container(m_menuName, this); + + if (!tmp) + RG_DEBUG << "BaseTool::createMenu(" << m_rcFileName + << ") : menu creation failed (name : " + << m_menuName << ")\n"; + + m_menu = dynamic_cast(tmp); +} + +bool EditTool::hasMenu() +{ + return !m_rcFileName.isEmpty(); +} + +} diff --git a/src/gui/general/EditTool.h b/src/gui/general/EditTool.h new file mode 100644 index 0000000..17937d1 --- /dev/null +++ b/src/gui/general/EditTool.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EDITTOOL_H_ +#define _RG_EDITTOOL_H_ + +#include "BaseTool.h" +#include +#include +#include "base/Event.h" + + +class QMouseEvent; + + +namespace Rosegarden +{ + +class ViewElement; +class Event; +class EditView; + + +/** + * Edit tool base class. + * + * A EditTool represents one of the items on an edition view + * toolbar. It handles mouse click events for the EditView ('State' + * design pattern). + * + * A EditTool 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 + * EditView 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 + * EditToolBox 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 EditView#setTool() + * @see EditToolBox + */ +class EditTool : public BaseTool, public KXMLGUIClient +{ + friend class EditToolBox; + +public: + + /** + * Dispatch the event to Left/Middle/Right MousePress + */ + virtual void handleMousePress(timeT time, + int height, + int staffNo, + QMouseEvent *event, + ViewElement*); + + /** + * Main operation of the tool + */ + virtual void handleLeftButtonPress(timeT time, + int height, + int staffNo, + QMouseEvent *event, + ViewElement*) = 0; + + /** + * Do nothing + */ + virtual void handleMidButtonPress(timeT time, + int height, + int staffNo, + QMouseEvent*, + ViewElement*); + + /** + * Show option menu + */ + virtual void handleRightButtonPress(timeT time, + int height, + int staffNo, + QMouseEvent*, + ViewElement*); + + /** + * Do nothing + */ + virtual void handleMouseDoubleClick(timeT time, + int height, + int staffNo, + QMouseEvent*, + ViewElement*); + + /** + * Do nothing. + * Implementations of handleMouseMove should return true if + * they want the canvas to scroll to the position the mouse + * moved to following the method's return. + */ + virtual int handleMouseMove(timeT time, + int height, + QMouseEvent*); + + /** + * Do nothing + */ + virtual void handleMouseRelease(timeT time, + int height, + QMouseEvent*); + + /** + * Respond to an event being deleted -- it may be the one the tool + * is remembering as the current event. + */ + virtual void handleEventRemoved(Event *) { } + + +protected: + /** + * Create a new EditTool + * + * \a menuName : the name of the menu defined in the XML rc file + */ + EditTool(const QString& menuName, EditView*); + + void setRCFileName(QString rcfilename) { m_rcFileName = rcfilename; } + + virtual void createMenu(); + virtual void createMenu(QString rcFileName); + virtual bool hasMenu(); + + //--------------- Data members --------------------------------- + QString m_rcFileName; + + EditView* m_parentView; +}; + + +} + +#endif diff --git a/src/gui/general/EditToolBox.cpp b/src/gui/general/EditToolBox.cpp new file mode 100644 index 0000000..c2e24a9 --- /dev/null +++ b/src/gui/general/EditToolBox.cpp @@ -0,0 +1,56 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EditToolBox.h" + +#include "BaseToolBox.h" +#include "EditTool.h" +#include "EditView.h" +#include +#include +#include + +namespace Rosegarden +{ + +EditToolBox::EditToolBox(EditView *parent) + : BaseToolBox(parent), + m_parentView(parent) +{ +} + +EditTool* EditToolBox::getTool(const QString& toolName) +{ + return dynamic_cast(BaseToolBox::getTool(toolName)); +} + +EditTool* EditToolBox::createTool(const QString&) +{ + KMessageBox::error(0, "EditToolBox::createTool called - this should never happen"); + return 0; +} + +} +#include "EditToolBox.moc" diff --git a/src/gui/general/EditToolBox.h b/src/gui/general/EditToolBox.h new file mode 100644 index 0000000..0115558 --- /dev/null +++ b/src/gui/general/EditToolBox.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EDITTOOLBOX_H_ +#define _RG_EDITTOOLBOX_H_ + +#include "BaseToolBox.h" +#include "EditTool.h" + + +class QString; + + +namespace Rosegarden +{ + +class EditView; + + +/** + * EditToolBox : specialized toolbox for EditViews (notation, matrix...) + * + */ +class EditToolBox : public BaseToolBox +{ + Q_OBJECT +public: + EditToolBox(EditView* parent); + + virtual EditTool* getTool(const QString& toolName); + +protected: + virtual EditTool* createTool(const QString& toolName); + + //--------------- Data members --------------------------------- + + EditView* m_parentView; +}; + + +} + +#endif diff --git a/src/gui/general/EditView.cpp b/src/gui/general/EditView.cpp new file mode 100644 index 0000000..a36b385 --- /dev/null +++ b/src/gui/general/EditView.cpp @@ -0,0 +1,1717 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EditView.h" +#include + +#include "base/BaseProperties.h" +#include +#include +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "ActiveItem.h" +#include "base/AnalysisTypes.h" +#include "base/Composition.h" +#include "base/CompositionTimeSliceAdapter.h" +#include "base/Controllable.h" +#include "base/ControlParameter.h" +#include "base/Device.h" +#include "base/Event.h" +#include "base/Exception.h" +#include "base/Instrument.h" +#include "base/MidiDevice.h" +#include "base/MidiProgram.h" +#include "base/MidiTypes.h" +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "base/Property.h" +#include "base/PropertyName.h" +#include "base/RulerScale.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/SoftSynthDevice.h" +#include "base/Staff.h" +#include "base/Studio.h" +#include "base/ViewElement.h" +#include "commands/edit/InvertCommand.h" +#include "commands/edit/MoveCommand.h" +#include "commands/edit/RescaleCommand.h" +#include "commands/edit/RetrogradeCommand.h" +#include "commands/edit/RetrogradeInvertCommand.h" +#include "commands/edit/TransposeCommand.h" +#include "commands/segment/AddTempoChangeCommand.h" +#include "commands/segment/AddTimeSignatureAndNormalizeCommand.h" +#include "commands/segment/AddTimeSignatureCommand.h" +#include "document/MultiViewCommandHistory.h" +#include "document/RosegardenGUIDoc.h" +#include "document/ConfigGroups.h" +#include "EditViewBase.h" +#include "gui/dialogs/RescaleDialog.h" +#include "gui/dialogs/TempoDialog.h" +#include "gui/dialogs/IntervalDialog.h" +#include "gui/dialogs/TimeSignatureDialog.h" +#include "gui/rulers/StandardRuler.h" +#include "gui/kdeext/KTmpStatusMsg.h" +#include "gui/kdeext/QCanvasGroupableItem.h" +#include "gui/rulers/ControllerEventsRuler.h" +#include "gui/rulers/ControlRuler.h" +#include "gui/rulers/PropertyControlRuler.h" +#include "RosegardenCanvasView.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +const unsigned int EditView::CONTROLS_ROW = 0; +const unsigned int EditView::RULERS_ROW = CONTROLS_ROW + 1; +const unsigned int EditView::TOPBARBUTTONS_ROW = RULERS_ROW + 1; +const unsigned int EditView::CANVASVIEW_ROW = TOPBARBUTTONS_ROW + 1; +const unsigned int EditView::CONTROLRULER_ROW = CANVASVIEW_ROW + 1; + +// Just some simple features we might want to show - make them bit maskable +// +static int FeatureShowVelocity = 0x00001; // show the velocity ruler + +EditView::EditView(RosegardenGUIDoc *doc, + std::vector segments, + unsigned int cols, + QWidget *parent, const char *name) : + EditViewBase(doc, segments, cols, parent, name), + m_currentEventSelection(0), + m_activeItem(0), + m_canvasView(0), + m_rulerBox(new QVBoxLayout), // top ruler box - added to grid later on + m_rulerBoxFiller(0), // On the left of m_rulerBox + m_controlBox(new QVBoxLayout), // top control ruler box - added to grid later on + m_bottomBox(new QVBox(this, "bottomframe")), // bottom box - added to bottom of canvas view by setCanvasView() + m_topStandardRuler(0), + m_bottomStandardRuler(0), + m_controlRuler(0), + m_controlRulers(new KTabWidget(getBottomWidget(), "controlrulers")) +{ + m_controlRulers->setHoverCloseButton(true); + m_controlRulers->setHoverCloseButtonDelayed(false); + connect(m_controlRulers, SIGNAL(closeRequest(QWidget*)), + this, SLOT(slotRemoveControlRuler(QWidget*))); + + (dynamic_cast(m_bottomBox->layout()))->setDirection(QBoxLayout::BottomToTop); + + // m_rulerBoxFiller is a white label used to keep m_rulerBox exactly + // above the scrolling part of the view (and never above the + // RosegardenCanvasView::m_leftWidget). + QGridLayout * gl = new QGridLayout(1, 2); + gl->setColStretch(0, 0); + gl->setColStretch(1, 1); + gl->addLayout(m_rulerBox, 0, 1); + m_rulerBoxFiller = new QLabel(getCentralWidget()); + gl->addWidget(m_rulerBoxFiller, 0, 0); + m_rulerBoxFiller->hide(); + + m_grid->addLayout(gl, RULERS_ROW, m_mainCol); + + m_grid->addMultiCellLayout(m_controlBox, CONTROLS_ROW, CONTROLS_ROW, 0, 1); + m_controlBox->setAlignment(AlignRight); + // m_grid->addWidget(m_controlRulers, CONTROLRULER_ROW, 2); + + m_controlRulers->hide(); + m_controlRulers->setTabPosition(QTabWidget::Bottom); +} + +EditView::~EditView() +{ + delete m_currentEventSelection; + m_currentEventSelection = 0; +} + +void EditView::updateBottomWidgetGeometry() +{ + getBottomWidget()->layout()->invalidate(); + getBottomWidget()->updateGeometry(); + getCanvasView()->updateBottomWidgetGeometry(); +} + +void EditView::paintEvent(QPaintEvent* e) +{ + RG_DEBUG << "EditView::paintEvent()\n"; + EditViewBase::paintEvent(e); + + if (m_needUpdate) { + RG_DEBUG << "EditView::paintEvent() - calling updateView\n"; + updateView(); + getCanvasView()->slotUpdate(); + + // update rulers + QLayoutIterator it = m_rulerBox->iterator(); + QLayoutItem *child; + while ( (child = it.current()) != 0 ) { + if (child->widget()) + child->widget()->update(); + ++it; + } + + updateControlRulers(); + + } else { + + getCanvasView()->slotUpdate(); + updateControlRulers(); + + } + + m_needUpdate = false; +} + +void EditView::updateControlRulers(bool updateHPos) +{ + for (int i = 0; i < m_controlRulers->count(); ++i) { + ControlRuler* ruler = dynamic_cast(m_controlRulers->page(i)); + if (ruler) { + if (updateHPos) + ruler->slotUpdateElementsHPos(); + else + ruler->slotUpdate(); + } + } +} + +void EditView::setControlRulersZoom(QWMatrix zoomMatrix) +{ + m_currentRulerZoomMatrix = zoomMatrix; + + for (int i = 0; i < m_controlRulers->count(); ++i) { + ControlRuler* ruler = dynamic_cast(m_controlRulers->page(i)); + if (ruler) + ruler->setWorldMatrix(zoomMatrix); + } +} + +void EditView::setControlRulersCurrentSegment() +{ + RG_DEBUG << "EditView::setControlRulersCurrentSegment: visible is " << m_controlRulers->isVisible() << endl; + + bool visible = m_controlRulers->isVisible(); + + delete m_controlRulers; + m_controlRulers = new KTabWidget(getBottomWidget(), "controlrulers"); + + bool haveTabs = setupControllerTabs(); + setupAddControlRulerMenu(); + + if (haveTabs) + m_controlRulers->show(); + else + m_controlRulers->hide(); + + updateBottomWidgetGeometry(); + + /* + for (int i = 0; i < m_controlRulers->count(); ++i) { + + PropertyControlRuler *pcr = dynamic_cast + (m_controlRulers->page(i)); + + if (pcr) pcr->setStaff(getCurrentStaff()); + else { + + ControllerEventsRuler *cer = dynamic_cast + (m_controlRulers->page(i)); + + if (cer) cer->setSegment(getCurrentSegment()); + } + } + */ +} + +void EditView::setTopStandardRuler(StandardRuler* w, QWidget *leftBox) +{ + delete m_topStandardRuler; + m_topStandardRuler = w; + + QGridLayout * gl = new QGridLayout(1, 2); + gl->setColStretch(0, 0); + gl->setColStretch(1, 1); + + gl->addWidget(w, 0, 1); + if (leftBox) { + gl->addWidget(leftBox, 0, 0); + } + + m_grid->addLayout(gl, TOPBARBUTTONS_ROW, m_mainCol); + + if (m_canvasView) { + connect(m_canvasView->horizontalScrollBar(), SIGNAL(valueChanged(int)), + m_topStandardRuler, SLOT(slotScrollHoriz(int))); + connect(m_canvasView->horizontalScrollBar(), SIGNAL(sliderMoved(int)), + m_topStandardRuler, SLOT(slotScrollHoriz(int))); + } +} + +void EditView::setBottomStandardRuler(StandardRuler* w) +{ + delete m_bottomStandardRuler; + m_bottomStandardRuler = w; + + // m_bottomBox->insertWidget(0, w); + + if (m_canvasView) { + connect(m_canvasView->horizontalScrollBar(), SIGNAL(valueChanged(int)), + m_bottomStandardRuler, SLOT(slotScrollHoriz(int))); + connect(m_canvasView->horizontalScrollBar(), SIGNAL(sliderMoved(int)), + m_bottomStandardRuler, SLOT(slotScrollHoriz(int))); + } +} + +void EditView::setRewFFwdToAutoRepeat() +{ + QWidget* transportToolbar = factory()->container("Transport Toolbar", this); + + if (transportToolbar) { + QObjectList *l = transportToolbar->queryList(); + QObjectListIt it(*l); // iterate over the buttons + QObject *obj; + + while ( (obj = it.current()) != 0 ) { + // for each found object... + ++it; + // RG_DEBUG << "EditView::setRewFFwdToAutoRepeat() : obj name : " << obj->name() << endl; + QString objName = obj->name(); + + if (objName.endsWith("playback_pointer_back_bar") || objName.endsWith("playback_pointer_forward_bar")) { + QButton* btn = dynamic_cast(obj); + if (!btn) { + RG_DEBUG << "Very strange - found widgets in Transport Toolbar which aren't buttons\n"; + + continue; + } + btn->setAutoRepeat(true); + } + + + } + delete l; + + } else { + RG_DEBUG << "transportToolbar == 0\n"; + } + +} + +void EditView::addRuler(QWidget* w) +{ + m_rulerBox->addWidget(w); + + if (m_canvasView) { + connect(m_canvasView->horizontalScrollBar(), SIGNAL(valueChanged(int)), + w, SLOT(slotScrollHoriz(int))); + connect(m_canvasView->horizontalScrollBar(), SIGNAL(sliderMoved(int)), + w, SLOT(slotScrollHoriz(int))); + } +} + +void EditView::addPropertyBox(QWidget *w) +{ + m_controlBox->addWidget(w); +} + +void EditView::addControlRuler(ControlRuler* ruler) +{ + ruler->setWorldMatrix(m_currentRulerZoomMatrix); + m_controlRulers->addTab(ruler, KGlobal::iconLoader()->loadIconSet("fileclose", KIcon::Small), + ruler->getName()); + m_controlRulers->showPage(ruler); + + if (m_canvasView) { + connect(m_canvasView->horizontalScrollBar(), SIGNAL(valueChanged(int)), + ruler->horizontalScrollBar(), SIGNAL(valueChanged(int))); + connect(m_canvasView->horizontalScrollBar(), SIGNAL(sliderMoved(int)), + ruler->horizontalScrollBar(), SIGNAL(sliderMoved(int))); + } + + connect(ruler, SIGNAL(stateChange(const QString&, bool)), + this, SLOT(slotStateChanged(const QString&, bool))); + + stateChanged("have_control_ruler", KXMLGUIClient::StateReverse); +} + +void EditView::readjustViewSize(QSize requestedSize, bool exact) +{ + Profiler profiler("EditView::readjustViewSize", true); + + if (exact) { + RG_DEBUG << "EditView::readjustViewSize: exact size requested (" + << requestedSize.width() << ", " << requestedSize.height() + << ")\n"; + + setViewSize(requestedSize); + getCanvasView()->slotUpdate(); + return ; + } + + int requestedWidth = requestedSize.width(), + requestedHeight = requestedSize.height(), + windowWidth = width(), + windowHeight = height(); + + QSize newSize; + + newSize.setWidth(((requestedWidth / windowWidth) + 1) * windowWidth); + newSize.setHeight(((requestedHeight / windowHeight) + 1) * windowHeight); + + RG_DEBUG << "EditView::readjustViewSize: requested (" + << requestedSize.width() << ", " << requestedSize.height() + << "), getting (" << newSize.width() << ", " + << newSize.height() << ")" << endl; + + setViewSize(newSize); + + getCanvasView()->slotUpdate(); +} + +void EditView::setCanvasView(RosegardenCanvasView *canvasView) +{ + delete m_canvasView; + m_canvasView = canvasView; + m_grid->addWidget(m_canvasView, CANVASVIEW_ROW, m_mainCol); + m_canvasView->setBottomFixedWidget(m_bottomBox); + + // TODO : connect canvas view's horiz. scrollbar to top/bottom bars and rulers + + // m_horizontalScrollBar->setRange(m_canvasView->horizontalScrollBar()->minValue(), + // m_canvasView->horizontalScrollBar()->maxValue()); + + // m_horizontalScrollBar->setSteps(m_canvasView->horizontalScrollBar()->lineStep(), + // m_canvasView->horizontalScrollBar()->pageStep()); + + // connect(m_horizontalScrollBar, SIGNAL(valueChanged(int)), + // m_canvasView->horizontalScrollBar(), SIGNAL(valueChanged(int))); + // connect(m_horizontalScrollBar, SIGNAL(sliderMoved(int)), + // m_canvasView->horizontalScrollBar(), SIGNAL(sliderMoved(int))); + +} + +Device * +EditView::getCurrentDevice() +{ + Segment *segment = getCurrentSegment(); + if (!segment) + return 0; + + Studio &studio = getDocument()->getStudio(); + Instrument *instrument = + studio.getInstrumentById + (segment->getComposition()->getTrackById(segment->getTrack())-> + getInstrument()); + if (!instrument) + return 0; + + return instrument->getDevice(); +} + +timeT +EditView::getInsertionTime(Clef &clef, + Rosegarden::Key &key) +{ + timeT t = getInsertionTime(); + Segment *segment = getCurrentSegment(); + + if (segment) { + clef = segment->getClefAtTime(t); + key = segment->getKeyAtTime(t); + } else { + clef = Clef(); + key = ::Rosegarden::Key(); + } + + return t; +} + +void EditView::slotActiveItemPressed(QMouseEvent* e, + QCanvasItem* item) +{ + if (!item) + return ; + + // Check if it's a groupable item, if so get its group + // + QCanvasGroupableItem *gitem = dynamic_cast(item); + if (gitem) + item = gitem->group(); + + // Check if it's an active item + // + ActiveItem *activeItem = dynamic_cast(item); + + if (activeItem) { + + setActiveItem(activeItem); + activeItem->handleMousePress(e); + updateView(); + + } +} + +void +EditView::slotStepBackward() +{ + Staff *staff = getCurrentStaff(); + if (!staff) + return ; + ViewElementList *vel = staff->getViewElementList(); + + timeT time = getInsertionTime(); + ViewElementList::iterator i = vel->findTime(time); + + while (i != vel->begin() && + (i == vel->end() || (*i)->getViewAbsoluteTime() >= time)) + --i; + + if (i != vel->end()) + slotSetInsertCursorPosition((*i)->getViewAbsoluteTime()); +} + +void +EditView::slotStepForward() +{ + Staff *staff = getCurrentStaff(); + if (!staff) + return ; + ViewElementList *vel = staff->getViewElementList(); + + timeT time = getInsertionTime(); + ViewElementList::iterator i = vel->findTime(time); + + while (i != vel->end() && + (*i)->getViewAbsoluteTime() <= time) + ++i; + + if (i == vel->end()) { + slotSetInsertCursorPosition(staff->getSegment().getEndMarkerTime()); + } else { + slotSetInsertCursorPosition((*i)->getViewAbsoluteTime()); + } +} + +void +EditView::slotJumpBackward() +{ + Segment *segment = getCurrentSegment(); + if (!segment) + return ; + timeT time = getInsertionTime(); + time = segment->getBarStartForTime(time - 1); + slotSetInsertCursorPosition(time); +} + +void +EditView::slotJumpForward() +{ + Segment *segment = getCurrentSegment(); + if (!segment) + return ; + timeT time = getInsertionTime(); + time = segment->getBarEndForTime(time); + slotSetInsertCursorPosition(time); +} + +void +EditView::slotJumpToStart() +{ + Segment *segment = getCurrentSegment(); + if (!segment) + return ; + timeT time = segment->getStartTime(); + slotSetInsertCursorPosition(time); +} + +void +EditView::slotJumpToEnd() +{ + Segment *segment = getCurrentSegment(); + if (!segment) + return ; + timeT time = segment->getEndMarkerTime(); + slotSetInsertCursorPosition(time); +} + +void EditView::slotExtendSelectionBackward() +{ + slotExtendSelectionBackward(false); +} + +void EditView::slotExtendSelectionBackwardBar() +{ + slotExtendSelectionBackward(true); +} + +void EditView::slotExtendSelectionBackward(bool bar) +{ + // If there is no current selection, or the selection is entirely + // to the right of the cursor, move the cursor left and add to the + // selection + + timeT oldTime = getInsertionTime(); + if (bar) + slotJumpBackward(); + else + slotStepBackward(); + timeT newTime = getInsertionTime(); + + Staff *staff = getCurrentStaff(); + if (!staff) + return ; + Segment *segment = &staff->getSegment(); + ViewElementList *vel = staff->getViewElementList(); + + EventSelection *es = new EventSelection(*segment); + if (m_currentEventSelection && + &m_currentEventSelection->getSegment() == segment) + es->addFromSelection(m_currentEventSelection); + + if (!m_currentEventSelection || + &m_currentEventSelection->getSegment() != segment || + m_currentEventSelection->getSegmentEvents().size() == 0 || + m_currentEventSelection->getStartTime() >= oldTime) { + + ViewElementList::iterator extendFrom = vel->findTime(oldTime); + + while (extendFrom != vel->begin() && + (*--extendFrom)->getViewAbsoluteTime() >= newTime) { + if ((*extendFrom)->event()->isa(Note::EventType)) { + es->addEvent((*extendFrom)->event()); + } + } + + } else { // remove an event + + EventSelection::eventcontainer::iterator i = + es->getSegmentEvents().end(); + + std::vector toErase; + + while (i != es->getSegmentEvents().begin() && + (*--i)->getAbsoluteTime() >= newTime) { + toErase.push_back(*i); + } + + for (unsigned int j = 0; j < toErase.size(); ++j) { + es->removeEvent(toErase[j]); + } + } + + setCurrentSelection(es); +} + +void EditView::slotExtendSelectionForward() +{ + slotExtendSelectionForward(false); +} + +void EditView::slotExtendSelectionForwardBar() +{ + slotExtendSelectionForward(true); +} + +void EditView::slotExtendSelectionForward(bool bar) +{ + // If there is no current selection, or the selection is entirely + // to the left of the cursor, move the cursor right and add to the + // selection + + timeT oldTime = getInsertionTime(); + if (bar) + slotJumpForward(); + else + slotStepForward(); + timeT newTime = getInsertionTime(); + + Staff *staff = getCurrentStaff(); + if (!staff) + return ; + Segment *segment = &staff->getSegment(); + ViewElementList *vel = staff->getViewElementList(); + + EventSelection *es = new EventSelection(*segment); + if (m_currentEventSelection && + &m_currentEventSelection->getSegment() == segment) + es->addFromSelection(m_currentEventSelection); + + if (!m_currentEventSelection || + &m_currentEventSelection->getSegment() != segment || + m_currentEventSelection->getSegmentEvents().size() == 0 || + m_currentEventSelection->getEndTime() <= oldTime) { + + ViewElementList::iterator extendFrom = vel->findTime(oldTime); + + while (extendFrom != vel->end() && + (*extendFrom)->getViewAbsoluteTime() < newTime) { + if ((*extendFrom)->event()->isa(Note::EventType)) { + es->addEvent((*extendFrom)->event()); + } + ++extendFrom; + } + + } else { // remove an event + + EventSelection::eventcontainer::iterator i = + es->getSegmentEvents().begin(); + + std::vector toErase; + + while (i != es->getSegmentEvents().end() && + (*i)->getAbsoluteTime() < newTime) { + toErase.push_back(*i); + ++i; + } + + for (unsigned int j = 0; j < toErase.size(); ++j) { + es->removeEvent(toErase[j]); + } + } + + setCurrentSelection(es); +} + +void +EditView::setupActions() +{ + createInsertPitchActionMenu(); + + // + // Tempo and time signature changes + // + QString pixmapDir = KGlobal::dirs()->findResource("appdata", "pixmaps/"); + QCanvasPixmap pixmap(pixmapDir + "/toolbar/event-insert-tempo.png"); + QIconSet icon = QIconSet(pixmap); + new KAction(AddTempoChangeCommand::getGlobalName(), + icon, 0, + this, SLOT(slotAddTempo()), + actionCollection(), "add_tempo"); + + pixmap.load(pixmapDir + "/toolbar/event-insert-timesig.png"); + icon = QIconSet(pixmap); + new KAction(AddTimeSignatureCommand::getGlobalName(), + icon, 0, + this, SLOT(slotAddTimeSignature()), + actionCollection(), "add_time_signature"); + + // + // Transforms + // + new KAction(i18n("&Halve Durations"), Key_H + CTRL, this, + SLOT(slotHalveDurations()), actionCollection(), + "halve_durations"); + + new KAction(i18n("&Double Durations"), Key_H + CTRL + SHIFT, this, + SLOT(slotDoubleDurations()), actionCollection(), + "double_durations"); + + new KAction(RescaleCommand::getGlobalName(), 0, this, + SLOT(slotRescale()), actionCollection(), + "rescale"); + + new KAction(TransposeCommand::getGlobalName(1), 0, + Key_Up, this, + SLOT(slotTransposeUp()), actionCollection(), + "transpose_up"); + + new KAction(TransposeCommand::getGlobalName(12), 0, + Key_Up + CTRL, this, + SLOT(slotTransposeUpOctave()), actionCollection(), + "transpose_up_octave"); + + new KAction(TransposeCommand::getGlobalName( -1), 0, + Key_Down, this, + SLOT(slotTransposeDown()), actionCollection(), + "transpose_down"); + + new KAction(TransposeCommand::getGlobalName( -12), 0, + Key_Down + CTRL, this, + SLOT(slotTransposeDownOctave()), actionCollection(), + "transpose_down_octave"); + + new KAction(TransposeCommand::getGlobalName(0), 0, this, + SLOT(slotTranspose()), actionCollection(), + "general_transpose"); + + new KAction(TransposeCommand::getDiatonicGlobalName(0,0), 0, this, + SLOT(slotDiatonicTranspose()), actionCollection(), + "general_diatonic_transpose"); + + new KAction(InvertCommand::getGlobalName(0), 0, this, + SLOT(slotInvert()), actionCollection(), + "invert"); + + new KAction(RetrogradeCommand::getGlobalName(0), 0, this, + SLOT(slotRetrograde()), actionCollection(), + "retrograde"); + + new KAction(RetrogradeInvertCommand::getGlobalName(0), 0, this, + SLOT(slotRetrogradeInvert()), actionCollection(), + "retrograde_invert"); + + new KAction(i18n("Jog &Left"), Key_Left + ALT, this, + SLOT(slotJogLeft()), actionCollection(), + "jog_left"); + + new KAction(i18n("Jog &Right"), Key_Right + ALT, this, + SLOT(slotJogRight()), actionCollection(), + "jog_right"); + + // Control rulers + // + new KAction(i18n("Show Velocity Property Ruler"), 0, this, + SLOT(slotShowVelocityControlRuler()), actionCollection(), + "show_velocity_control_ruler"); + + /* + new KAction(i18n("Show Controllers Events Ruler"), 0, this, + SLOT(slotShowControllerEventsRuler()), actionCollection(), + "show_controller_events_ruler"); + */ + + // Disabled for now + // + // new KAction(i18n("Add Control Ruler..."), 0, this, + // SLOT(slotShowPropertyControlRuler()), actionCollection(), + // "add_control_ruler"); + + // + // Control Ruler context menu + // + new KAction(i18n("Insert item"), 0, this, + SLOT(slotInsertControlRulerItem()), actionCollection(), + "insert_control_ruler_item"); + + // This was on Key_Delete, but that conflicts with existing Delete commands + // on individual edit views + new KAction(i18n("Erase selected items"), 0, this, + SLOT(slotEraseControlRulerItem()), actionCollection(), + "erase_control_ruler_item"); + + new KAction(i18n("Clear ruler"), 0, this, + SLOT(slotClearControlRulerItem()), actionCollection(), + "clear_control_ruler_item"); + + new KAction(i18n("Insert line of controllers"), 0, this, + SLOT(slotStartControlLineItem()), actionCollection(), + "start_control_line_item"); + + new KAction(i18n("Flip forward"), Key_BracketRight, this, + SLOT(slotFlipForwards()), actionCollection(), + "flip_control_events_forward"); + + new KAction(i18n("Flip backwards"), Key_BracketLeft, this, + SLOT(slotFlipBackwards()), actionCollection(), + "flip_control_events_back"); + + new KAction(i18n("Draw property line"), 0, this, + SLOT(slotDrawPropertyLine()), actionCollection(), + "draw_property_line"); + + new KAction(i18n("Select all property values"), 0, this, + SLOT(slotSelectAllProperties()), actionCollection(), + "select_all_properties"); +} + +void +EditView::setupAddControlRulerMenu() +{ + RG_DEBUG << "EditView::setupAddControlRulerMenu" << endl; + + QPopupMenu* addControlRulerMenu = dynamic_cast + (factory()->container("add_control_ruler", this)); + + if (addControlRulerMenu) { + + addControlRulerMenu->clear(); + + //!!! problem here with notation view -- current segment can + // change after construction, but this function isn't used again + + Controllable *c = + dynamic_cast(getCurrentDevice()); + if (!c) { + c = dynamic_cast(getCurrentDevice()); + if (!c) + return ; + } + + const ControlList &list = c->getControlParameters(); + + int i = 0; + QString itemStr; + + for (ControlList::const_iterator it = list.begin(); + it != list.end(); ++it) { + if (it->getType() == Controller::EventType) { + QString hexValue; + hexValue.sprintf("(0x%x)", it->getControllerValue()); + + itemStr = i18n("%1 Controller %2 %3").arg(strtoqstr(it->getName())) + .arg(it->getControllerValue()) + .arg(hexValue); + + } else if (it->getType() == PitchBend::EventType) + itemStr = i18n("Pitch Bend"); + else + itemStr = i18n("Unsupported Event Type"); + + addControlRulerMenu->insertItem(itemStr, i++); + } + + connect(addControlRulerMenu, SIGNAL(activated(int)), + SLOT(slotAddControlRuler(int))); + } + +} + +bool +EditView::setupControllerTabs() +{ + bool have = false; + + // Setup control rulers the Segment already has some stored against it. + // + Segment *segment = getCurrentSegment(); + Segment::EventRulerList list = segment->getEventRulerList(); + + RG_DEBUG << "EditView::setupControllerTabs - got " << list.size() << " EventRulers" << endl; + + RG_DEBUG << "Segment view features: " << segment->getViewFeatures() << endl; + if (segment->getViewFeatures() & FeatureShowVelocity) { + showPropertyControlRuler(BaseProperties::VELOCITY); + have = true; + } + + if (list.size()) { + Controllable *c = + dynamic_cast(getCurrentDevice()); + if (!c) { + c = dynamic_cast(getCurrentDevice()); + if (!c) + return have; + } + + have = true; + + Segment::EventRulerListIterator it; + + for (it = list.begin(); it != list.end(); ++it) { + // Get ControlParameter object from controller value + // + const ControlParameter *controlParameter = + c->getControlParameter((*it)->m_type, + MidiByte((*it)->m_controllerValue)); + + RG_DEBUG << "EditView::setupControllerTabs - " + << "Control Parameter type = " << (*it)->m_type << endl; + + if (controlParameter) { + ControllerEventsRuler* controlRuler = makeControllerEventRuler(controlParameter); + addControlRuler(controlRuler); + RG_DEBUG << "EditView::setupControllerTabs - adding Ruler" << endl; + } + } + + if (!m_controlRulers->isVisible()) + m_controlRulers->show(); + + updateBottomWidgetGeometry(); + } + + return have; +} + +void +EditView::slotAddControlRuler(int controller) +{ + RG_DEBUG << "EditView::slotAddControlRuler - item = " + << controller << endl; + + Controllable *c = + dynamic_cast(getCurrentDevice()); + if (!c) { + c = dynamic_cast(getCurrentDevice()); + if (!c) + return ; + } + + const ControlList &list = c->getControlParameters(); + ControlParameter control = list[controller]; + + int index = 0; + + ControlRuler* existingRuler = findRuler(control, index); + + if (existingRuler) { + + m_controlRulers->setCurrentPage(index); + + } else { + + // Create control ruler to a specific controller. This duplicates + // the control parameter in the supplied pointer. + ControllerEventsRuler* controlRuler = makeControllerEventRuler(&control); + + addControlRuler(controlRuler); + } + + if (!m_controlRulers->isVisible()) { + m_controlRulers->show(); + } + + updateBottomWidgetGeometry(); + + // Add the controller to the segment so the views can + // remember what we've opened against it. + // + Staff *staff = getCurrentStaff(); + staff->getSegment().addEventRuler(control.getType(), control.getControllerValue()); + + getDocument()->slotDocumentModified(); +} + +void EditView::slotRemoveControlRuler(QWidget* w) +{ + ControllerEventsRuler* ruler = dynamic_cast(w); + + if (ruler) { + ControlParameter *controller = ruler->getControlParameter(); + + // remove the control parameter from the "showing controllers" list on the segment + // + if (controller) { + Staff *staff = getCurrentStaff(); + bool value = staff->getSegment(). + deleteEventRuler(controller->getType(), controller->getControllerValue()); + + if (value) + RG_DEBUG << "slotRemoveControlRuler : removed controller from segment\n"; + else + RG_DEBUG << "slotRemoveControlRuler : couldn't remove controller from segment - " + << int(controller->getControllerValue()) + << endl; + + } + } else { // else it's probably a velocity ruler + PropertyControlRuler *propertyRuler = dynamic_cast(w); + + if (propertyRuler) { + Segment &seg = getCurrentStaff()->getSegment(); + seg.setViewFeatures(0); // for the moment we only have one view feature so + // we can just blank it out + + RG_DEBUG << "slotRemoveControlRuler : removed velocity ruler" << endl; + } + } + + delete w; + + if (m_controlRulers->count() == 0) { + m_controlRulers->hide(); + updateBottomWidgetGeometry(); + } + + getDocument()->slotDocumentModified(); +} + +void +EditView::createInsertPitchActionMenu() +{ + QString notePitchNames[] = { + i18n("I"), i18n("II"), i18n("III"), i18n("IV"), + i18n("V"), i18n("VI"), i18n("VII"), i18n("VIII") + }; + QString flat = i18n("%1 flat"); + QString sharp = i18n("%1 sharp"); + + const Key notePitchKeys[3][7] = { + { + Key_A, Key_S, Key_D, Key_F, Key_J, Key_K, Key_L, + }, + { + Key_Q, Key_W, Key_E, Key_R, Key_U, Key_I, Key_O, + }, + { + Key_Z, Key_X, Key_C, Key_V, Key_B, Key_N, Key_M, + }, + }; + + KActionMenu *insertPitchActionMenu = + new KActionMenu(i18n("&Insert Note"), this, "insert_note_actionmenu"); + + for (int octave = 0; octave <= 2; ++octave) { + + KActionMenu *menu = insertPitchActionMenu; + if (octave == 1) { + menu = new KActionMenu(i18n("&Upper Octave"), this, + "insert_note_actionmenu_upper_octave"); + insertPitchActionMenu->insert(new KActionSeparator(this)); + insertPitchActionMenu->insert(menu); + } else if (octave == 2) { + menu = new KActionMenu(i18n("&Lower Octave"), this, + "insert_note_actionmenu_lower_octave"); + insertPitchActionMenu->insert(menu); + } + + for (unsigned int i = 0; i < 7; ++i) { + + KAction *insertPitchAction = 0; + + QString octaveSuffix; + if (octave == 1) + octaveSuffix = "_high"; + else if (octave == 2) + octaveSuffix = "_low"; + + // do and fa lack a flat + + if (i != 0 && i != 3) { + + insertPitchAction = + new KAction + (flat.arg(notePitchNames[i]), + CTRL + SHIFT + notePitchKeys[octave][i], + this, SLOT(slotInsertNoteFromAction()), actionCollection(), + QString("insert_%1_flat%2").arg(i).arg(octaveSuffix)); + + menu->insert(insertPitchAction); + } + + insertPitchAction = + new KAction + (notePitchNames[i], + notePitchKeys[octave][i], + this, SLOT(slotInsertNoteFromAction()), actionCollection(), + QString("insert_%1%2").arg(i).arg(octaveSuffix)); + + menu->insert(insertPitchAction); + + // and mi and ti lack a sharp + + if (i != 2 && i != 6) { + + insertPitchAction = + new KAction + (sharp.arg(notePitchNames[i]), + SHIFT + notePitchKeys[octave][i], + this, SLOT(slotInsertNoteFromAction()), actionCollection(), + QString("insert_%1_sharp%2").arg(i).arg(octaveSuffix)); + + menu->insert(insertPitchAction); + } + + if (i < 6) + menu->insert(new KActionSeparator(this)); + } + } + + actionCollection()->insert(insertPitchActionMenu); +} + +int +EditView::getPitchFromNoteInsertAction(QString name, + Accidental &accidental, + const Clef &clef, + const ::Rosegarden::Key &key) +{ + using namespace Accidentals; + + accidental = NoAccidental; + + if (name.left(7) == "insert_") { + + name = name.right(name.length() - 7); + + int modify = 0; + int octave = 0; + + if (name.right(5) == "_high") { + + octave = 1; + name = name.left(name.length() - 5); + + } else if (name.right(4) == "_low") { + + octave = -1; + name = name.left(name.length() - 4); + } + + if (name.right(6) == "_sharp") { + + modify = 1; + accidental = Sharp; + name = name.left(name.length() - 6); + + } else if (name.right(5) == "_flat") { + + modify = -1; + accidental = Flat; + name = name.left(name.length() - 5); + } + + int scalePitch = name.toInt(); + + if (scalePitch < 0 || scalePitch > 7) { + NOTATION_DEBUG << "EditView::getPitchFromNoteInsertAction: pitch " + << scalePitch << " out of range, using 0" << endl; + scalePitch = 0; + } + + Pitch pitch + (scalePitch, 4 + octave + clef.getOctave(), key, accidental); + return pitch.getPerformancePitch(); + + } else { + + throw Exception("Not an insert action", + __FILE__, __LINE__); + } +} + +void EditView::slotAddTempo() +{ + timeT insertionTime = getInsertionTime(); + + TempoDialog tempoDlg(this, getDocument()); + + connect(&tempoDlg, + SIGNAL(changeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction)), + this, + SIGNAL(changeTempo(timeT, + tempoT, + tempoT, + TempoDialog::TempoDialogAction))); + + tempoDlg.setTempoPosition(insertionTime); + tempoDlg.exec(); +} + +void EditView::slotAddTimeSignature() +{ + Segment *segment = getCurrentSegment(); + if (!segment) + return ; + Composition *composition = segment->getComposition(); + timeT insertionTime = getInsertionTime(); + + TimeSignatureDialog *dialog = 0; + int timeSigNo = composition->getTimeSignatureNumberAt(insertionTime); + + if (timeSigNo >= 0) { + + dialog = new TimeSignatureDialog + (this, composition, insertionTime, + composition->getTimeSignatureAt(insertionTime)); + + } else { + + timeT endTime = composition->getDuration(); + if (composition->getTimeSignatureCount() > 0) { + endTime = composition->getTimeSignatureChange(0).first; + } + + CompositionTimeSliceAdapter adapter + (composition, insertionTime, endTime); + AnalysisHelper helper; + TimeSignature timeSig = helper.guessTimeSignature(adapter); + + dialog = new TimeSignatureDialog + (this, composition, insertionTime, timeSig, false, + i18n("Estimated time signature shown")); + } + + if (dialog->exec() == QDialog::Accepted) { + + insertionTime = dialog->getTime(); + + if (dialog->shouldNormalizeRests()) { + + addCommandToHistory(new AddTimeSignatureAndNormalizeCommand + (composition, insertionTime, + dialog->getTimeSignature())); + + } else { + + addCommandToHistory(new AddTimeSignatureCommand + (composition, insertionTime, + dialog->getTimeSignature())); + } + } + + delete dialog; +} + +void EditView::showPropertyControlRuler(PropertyName propertyName) +{ + int index = 0; + + ControlRuler* existingRuler = findRuler(propertyName, index); + + if (existingRuler) { + + m_controlRulers->setCurrentPage(index); + + } else { + + PropertyControlRuler* controlRuler = makePropertyControlRuler(propertyName); + addControlRuler(controlRuler); + } + + if (!m_controlRulers->isVisible()) { + m_controlRulers->show(); + } + + updateBottomWidgetGeometry(); +} + +void EditView::slotShowVelocityControlRuler() +{ + showPropertyControlRuler(BaseProperties::VELOCITY); + Segment &seg = getCurrentStaff()->getSegment(); + seg.setViewFeatures(seg.getViewFeatures() | FeatureShowVelocity); + getDocument()->slotDocumentModified(); +} + +void EditView::slotShowControllerEventsRuler() +{ + + // int index = 0; + + // ControlRuler* existingRuler = findRuler(propertyName, index); + + // if (existingRuler) { + + // m_controlRulers->setCurrentPage(index); + + // } else { + + // ControllerEventsRuler* controlRuler = makeControllerEventRuler(); + // addControlRuler(controlRuler); + // } + + // if (!m_controlRulers->isVisible()) { + // m_controlRulers->show(); + // } + + // updateBottomWidgetGeometry(); +} + +void EditView::slotShowPropertyControlRuler() +{ + /* + KDialogBase propChooserDialog(this, "propertychooserdialog", true, i18n("Select event property"), + KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok); + + KListBox* propList = new KListBox(propChooserDialog.makeVBoxMainWidget()); + new QListBoxRGProperty(propList, BaseProperties::VELOCITY.c_str()); + + int rc = propChooserDialog.exec(); + if (rc == QDialog::Accepted) { + // fix for KDE 3.0 + //QListBoxRGProperty* item = dynamic_cast(propList->selectedItem()); + QListBoxRGProperty* item = dynamic_cast + (propList->item(propList->currentItem())); + + if (item) { + PropertyName property = item->getPropertyName(); + showPropertyControlRuler(property); + } + } + */ +} + +void +EditView::slotInsertControlRulerItem() +{ + ControllerEventsRuler* ruler = dynamic_cast(getCurrentControlRuler()); + if (ruler) + ruler->insertControllerEvent(); +} + +void +EditView::slotEraseControlRulerItem() +{ + ControllerEventsRuler* ruler = dynamic_cast(getCurrentControlRuler()); + if (ruler) + ruler->eraseControllerEvent(); +} + +void +EditView::slotStartControlLineItem() +{ + ControllerEventsRuler* ruler = dynamic_cast(getCurrentControlRuler()); + if (ruler) + ruler->startControlLine(); +} + +void +EditView::slotDrawPropertyLine() +{ + int index = 0; + PropertyControlRuler* ruler = dynamic_cast + (findRuler(BaseProperties::VELOCITY, index)); + + if (ruler) + ruler->startPropertyLine(); +} + +void +EditView::slotSelectAllProperties() +{ + int index = 0; + PropertyControlRuler* ruler = dynamic_cast + (findRuler(BaseProperties::VELOCITY, index)); + + if (ruler) + ruler->selectAllProperties(); +} + +void +EditView::slotClearControlRulerItem() +{ + ControllerEventsRuler* ruler = dynamic_cast(getCurrentControlRuler()); + if (ruler) + ruler->clearControllerEvents(); +} + +void +EditView::slotHalveDurations() +{ + if (!m_currentEventSelection) + return ; + + KTmpStatusMsg msg(i18n("Halving durations..."), this); + + addCommandToHistory( + new RescaleCommand(*m_currentEventSelection, + m_currentEventSelection->getTotalDuration() / 2, + false)); +} + +void +EditView::slotDoubleDurations() +{ + if (!m_currentEventSelection) + return ; + + KTmpStatusMsg msg(i18n("Doubling durations..."), this); + + addCommandToHistory( + new RescaleCommand(*m_currentEventSelection, + m_currentEventSelection->getTotalDuration() * 2, + false)); +} + +void +EditView::slotRescale() +{ + if (!m_currentEventSelection) + return ; + + RescaleDialog dialog + (this, + &getDocument()->getComposition(), + m_currentEventSelection->getStartTime(), + m_currentEventSelection->getEndTime() - + m_currentEventSelection->getStartTime(), + true, + true); + + if (dialog.exec() == QDialog::Accepted) { + KTmpStatusMsg msg(i18n("Rescaling..."), this); + addCommandToHistory(new RescaleCommand + (*m_currentEventSelection, + dialog.getNewDuration(), + dialog.shouldCloseGap())); + } +} + +void EditView::slotTranspose() +{ + if (!m_currentEventSelection) + return ; + + m_config->setGroup(EditViewConfigGroup); + + int dialogDefault = m_config->readNumEntry("lasttransposition", 0); + + bool ok = false; + int semitones = QInputDialog::getInteger + (i18n("Transpose"), + i18n("By number of semitones: "), + dialogDefault, -127, 127, 1, &ok, this); + if (!ok || semitones == 0) return; + + m_config->setGroup(EditViewConfigGroup); + m_config->writeEntry("lasttransposition", semitones); + + KTmpStatusMsg msg(i18n("Transposing..."), this); + addCommandToHistory(new TransposeCommand + (semitones, *m_currentEventSelection)); +} + +void EditView::slotDiatonicTranspose() +{ + if (!m_currentEventSelection) + return ; + + m_config->setGroup(EditViewConfigGroup); + + IntervalDialog intervalDialog(this); + int ok = intervalDialog.exec(); + //int dialogDefault = m_config->readNumEntry("lasttransposition", 0); + int semitones = intervalDialog.getChromaticDistance(); + int steps = intervalDialog.getDiatonicDistance(); + + if (!ok || (semitones == 0 && steps == 0)) return; + + m_config->setGroup(EditViewConfigGroup); + + KTmpStatusMsg msg(i18n("Transposing..."), this); + if (intervalDialog.getChangeKey()) + { + std::cout << "Transposing changing keys is not currently supported on selections" << std::endl; + } + else + { + // Transpose within key + //std::cout << "Transposing semitones, steps: " << semitones << ", " << steps << std::endl; + addCommandToHistory(new TransposeCommand + (semitones, steps, *m_currentEventSelection)); + } +} + +void EditView::slotTransposeUp() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Transposing up one semitone..."), this); + + addCommandToHistory(new TransposeCommand(1, *m_currentEventSelection)); +} + +void EditView::slotTransposeUpOctave() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Transposing up one octave..."), this); + + addCommandToHistory(new TransposeCommand(12, *m_currentEventSelection)); +} + +void EditView::slotTransposeDown() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Transposing down one semitone..."), this); + + addCommandToHistory(new TransposeCommand( -1, *m_currentEventSelection)); +} + +void EditView::slotTransposeDownOctave() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Transposing down one octave..."), this); + + addCommandToHistory(new TransposeCommand( -12, *m_currentEventSelection)); +} + +void EditView::slotInvert() +{ + if (!m_currentEventSelection) + return ; + + int semitones = 0; + + KTmpStatusMsg msg(i18n("Inverting..."), this); + addCommandToHistory(new InvertCommand + (semitones, *m_currentEventSelection)); +} + +void EditView::slotRetrograde() +{ + if (!m_currentEventSelection) + return ; + + int semitones = 0; + + KTmpStatusMsg msg(i18n("Retrograding..."), this); + addCommandToHistory(new RetrogradeCommand + (semitones, *m_currentEventSelection)); +} + +void EditView::slotRetrogradeInvert() +{ + if (!m_currentEventSelection) + return ; + + int semitones = 0; + + KTmpStatusMsg msg(i18n("Retrograde inverting..."), this); + addCommandToHistory(new RetrogradeInvertCommand + (semitones, *m_currentEventSelection)); +} + +void EditView::slotJogLeft() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Jogging left..."), this); + + RG_DEBUG << "EditView::slotJogLeft" << endl; + + addCommandToHistory( + new MoveCommand(*getCurrentSegment(), + -Note(Note::Demisemiquaver).getDuration(), + false, // don't use notation timings + *m_currentEventSelection)); +} + +void EditView::slotJogRight() +{ + if (!m_currentEventSelection) + return ; + KTmpStatusMsg msg(i18n("Jogging right..."), this); + + RG_DEBUG << "EditView::slotJogRight" << endl; + + addCommandToHistory( + new MoveCommand(*getCurrentSegment(), + Note(Note::Demisemiquaver).getDuration(), + false, // don't use notation timings + *m_currentEventSelection)); +} + +void +EditView::slotFlipForwards() +{ + RG_DEBUG << "EditView::slotFlipForwards" << endl; + ControlRuler* ruler = getCurrentControlRuler(); + if (ruler) ruler->flipForwards(); +} + +void +EditView::slotFlipBackwards() +{ + RG_DEBUG << "EditView::slotFlipBackwards" << endl; + ControlRuler* ruler = getCurrentControlRuler(); + if (ruler) ruler->flipBackwards(); +} + +ControlRuler* EditView::getCurrentControlRuler() +{ + return dynamic_cast(m_controlRulers->currentPage()); +} + +ControlRuler* EditView::findRuler(PropertyName propertyName, int &index) +{ + for(index = 0; index < m_controlRulers->count(); ++index) { + PropertyControlRuler* ruler = dynamic_cast(m_controlRulers->page(index)); + if (ruler && ruler->getPropertyName() == propertyName) return ruler; + } + + return 0; +} + +ControlRuler* EditView::findRuler(const ControlParameter& controller, int &index) +{ + for(index = 0; index < m_controlRulers->count(); ++index) { + ControllerEventsRuler* ruler = dynamic_cast(m_controlRulers->page(index)); + if (ruler && *(ruler->getControlParameter()) == controller) return ruler; + } + + return 0; +} + +PropertyControlRuler* EditView::makePropertyControlRuler(PropertyName propertyName) +{ + QCanvas* controlRulerCanvas = new QCanvas(this); + QSize viewSize = getViewSize(); + controlRulerCanvas->resize(viewSize.width(), ControlRuler::DefaultRulerHeight); // TODO - keep it in sync with main canvas size + +// QCanvas* controlRulerCanvas = ControlRulerCanvasRepository::getCanvas(getCurrentSegment(), propertyName, +// getViewSize()); + + PropertyControlRuler* controlRuler = new PropertyControlRuler + (propertyName, getCurrentStaff(), getHLayout(), this, + controlRulerCanvas, m_controlRulers); + + controlRuler->setMainHorizontalScrollBar(m_canvasView->horizontalScrollBar()); + + return controlRuler; +} + +ControllerEventsRuler* EditView::makeControllerEventRuler(const ControlParameter *controller) +{ + QCanvas* controlRulerCanvas = new QCanvas(this); + QSize viewSize = getViewSize(); + controlRulerCanvas->resize(viewSize.width(), ControlRuler::DefaultRulerHeight); // TODO - keep it in sync with main canvas size +// QCanvas* controlRulerCanvas = ControlRulerCanvasRepository::getCanvas(getCurrentSegment(), controller, +// getViewSize()); + + + ControllerEventsRuler* controlRuler = new ControllerEventsRuler + (getCurrentSegment(), getHLayout(), this, + controlRulerCanvas, m_controlRulers, controller); + + controlRuler->setMainHorizontalScrollBar(m_canvasView->horizontalScrollBar()); + + return controlRuler; +} + +RosegardenCanvasView* EditView::getCanvasView() +{ + return m_canvasView; +} + +} +#include "EditView.moc" diff --git a/src/gui/general/EditView.h b/src/gui/general/EditView.h new file mode 100644 index 0000000..da18982 --- /dev/null +++ b/src/gui/general/EditView.h @@ -0,0 +1,405 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EDITVIEW_H_ +#define _RG_EDITVIEW_H_ + +#include "base/PropertyName.h" +#include "EditViewBase.h" +#include "gui/dialogs/TempoDialog.h" +#include +#include +#include +#include +#include "base/Event.h" + + +class QWidget; +class QVBoxLayout; +class QVBox; +class QPaintEvent; +class QMouseEvent; +class QCanvasItem; +class KTabWidget; +class Accidental; + + +namespace Rosegarden +{ + +class Staff; +class Segment; +class RulerScale; +class RosegardenGUIDoc; +class RosegardenCanvasView; +class PropertyControlRuler; +class Key; +class EventSelection; +class Device; +class ControlRuler; +class ControlParameter; +class ControllerEventsRuler; +class Clef; +class StandardRuler; +class ActiveItem; + + +class EditView : public EditViewBase +{ + Q_OBJECT + +public: + + /** + * Create an EditView for the segments \a segments from document \a doc. + * + * \arg cols : number of columns, main column is always rightmost + * + */ + EditView(RosegardenGUIDoc *doc, + std::vector segments, + unsigned int cols, + QWidget *parent, + const char *name = 0); + + virtual ~EditView(); + + /** + * "Clever" readjustment of the view size + * If the new size is larger, enlarge to that size plus a margin + * If it is smaller, only shrink if the reduction is significant + * (e.g. new size is less than 75% of the old one) + * + * @arg exact if true, then set to newSize exactly + */ + virtual void readjustViewSize(QSize newSize, bool exact = false); + + /** + * Return the active item + */ + ActiveItem* activeItem() { return m_activeItem; } + + /** + * Set the active item + */ + void setActiveItem(ActiveItem* i) { m_activeItem = i; } + + /** + * 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) = 0; + + EventSelection* getCurrentSelection() + { return m_currentEventSelection; } + + RosegardenCanvasView* getRawCanvasView() { return m_canvasView; } + virtual RosegardenCanvasView* getCanvasView(); + +signals: + void changeTempo(timeT, // tempo change time + tempoT, // tempo value + tempoT, // target value + TempoDialog::TempoDialogAction); // tempo action + +public slots: + /** + * Called when a mouse press occurred on an active canvas item + * + * @see ActiveItem + * @see QCanvasItem#setActive + */ + virtual void slotActiveItemPressed(QMouseEvent*, QCanvasItem*); + + virtual void slotSetInsertCursorPosition(timeT position) = 0; + + void slotExtendSelectionBackward(); + void slotExtendSelectionForward(); + void slotExtendSelectionBackwardBar(); + void slotExtendSelectionForwardBar(); + void slotExtendSelectionBackward(bool bar); + void slotExtendSelectionForward(bool bar); + + virtual void slotStepBackward(); // default is event-by-event + virtual void slotStepForward(); // default is event-by-event + void slotJumpBackward(); + void slotJumpForward(); + void slotJumpToStart(); + void slotJumpToEnd(); + + void slotAddTempo(); + void slotAddTimeSignature(); + + virtual void slotShowVelocityControlRuler(); + virtual void slotShowControllerEventsRuler(); + virtual void slotShowPropertyControlRuler(); + + // rescale + void slotHalveDurations(); + void slotDoubleDurations(); + void slotRescale(); + + // transpose + void slotTransposeUp(); + void slotTransposeUpOctave(); + void slotTransposeDown(); + void slotTransposeDownOctave(); + void slotTranspose(); + void slotDiatonicTranspose(); + + // invert + void slotInvert(); + void slotRetrograde(); + void slotRetrogradeInvert(); + + // jog events + void slotJogLeft(); + void slotJogRight(); + + // Control ruler actions + // + void slotInsertControlRulerItem(); + void slotEraseControlRulerItem(); + void slotClearControlRulerItem(); + void slotStartControlLineItem(); + void slotFlipForwards(); + void slotFlipBackwards(); + + // Property ruler actions + // + void slotDrawPropertyLine(); + void slotSelectAllProperties(); + + // add control ruler + void slotAddControlRuler(int); + void slotRemoveControlRuler(QWidget*); + +protected: + virtual RulerScale* getHLayout() = 0; + + QVBox* getBottomWidget() { return m_bottomBox; } + + virtual void updateBottomWidgetGeometry(); + + virtual void paintEvent(QPaintEvent* e); + + /** + * Locate the given widgets in the top bar-buttons position and + * connect up its scrolling signals. + */ + void setTopStandardRuler(StandardRuler*, QWidget *leftBox = NULL); + + /** + * Locate the given widget in the bottom bar-buttons position and + * connect up its scrolling signals. + */ + void setBottomStandardRuler(StandardRuler*); + + /** + * Set the 'Rewind' and 'Fast Forward' buttons in the transport + * toolbar to AutoRepeat + */ + void setRewFFwdToAutoRepeat(); + + /** + * Locate the given widget right above the top bar-buttons and + * connect up its scrolling signals. + * The widget has to have a slotScrollHoriz(int) slot + */ + void addRuler(QWidget*); + + /** + * Add a ruler control box + */ + void addPropertyBox(QWidget*); + + /** + * Make a control ruler for the given property, + */ + PropertyControlRuler* makePropertyControlRuler(PropertyName propertyName); + + /** + * Make a ruler for controller events + */ + ControllerEventsRuler* makeControllerEventRuler(const ControlParameter *controller = 0); + + /** + * Add control ruler + */ + void addControlRuler(ControlRuler* ruler); + + /** + * Update all control rulers + */ + void updateControlRulers(bool updateHPos=false); + + /** + * Set zoom factor of control rulers + */ + void setControlRulersZoom(QWMatrix); + + /** + * Set current segment for control rulers + */ + void setControlRulersCurrentSegment(); + + /** + * Find the control ruler for the given property name + * if it's already been created, return 0 otherwise + */ + ControlRuler* findRuler(PropertyName propertyName, int &index); + + /** + * Find the control ruler for the given controller + * if it's already been created, return 0 otherwise + */ + ControlRuler* findRuler(const ControlParameter& controller, int &index); + + /** + * Show a control ruler for the given property + * If the ruler already exists, activate the tab it's in, + * otherwise create the ruler and add it to the control rulers tab + * widget + */ + void showPropertyControlRuler(PropertyName propertyName); + + /** + * Return the control ruler currently displayed, or 0 if none exist + */ + ControlRuler* getCurrentControlRuler(); + + /** + * Set up those actions common to any EditView (e.g. note insertion, + * time signatures etc) + */ + void setupActions(); + + /** + * Set up the 'Add control ruler' sub-menu + */ + void setupAddControlRulerMenu(); + + /** + * Do this after any other segment setup in a subordinate view. + * Returns true if there were any tabs to set up. + */ + bool setupControllerTabs(); + + /** + * Create an action menu for inserting notes from the PC keyboard, + * and add it to the action collection. This is one of the methods + * called by setupActions(). + */ + void createInsertPitchActionMenu(); + + /** + * Get a note pitch from an action name (where the action is one of + * those created by createInsertPitchActionMenu). Can throw an + * Exception to mean that the action is not an insert one. Also + * returns any specified accidental through the reference arg. + */ + int getPitchFromNoteInsertAction(QString actionName, + Accidental &acc, + const Clef &clef, + const ::Rosegarden::Key &key); + + /** + * Abstract method to get the view size + * Typically implemented as canvas()->size(). + */ + virtual QSize getViewSize() = 0; + + /** + * Abstract method to set the view size + * Typically implemented as canvas()->resize(). + */ + virtual void setViewSize(QSize) = 0; + + /** + * Abstract method to get current insert-pointer time + */ + virtual timeT getInsertionTime() = 0; + + /** + * Return the time at which the insert cursor may be found, + * and the time signature, clef and key at that time. Default + * implementation is okay but slow. + */ + virtual timeT getInsertionTime(Clef &clef, ::Rosegarden::Key &key); + + /** + * Abstract method to get current staff (the returned staff will be + * that representing the segment of getCurrentSegment()) + */ + virtual Staff *getCurrentStaff() = 0; + + /** + * Return the device of the current segment, if any + */ + Device *getCurrentDevice(); + + virtual void setCanvasView(RosegardenCanvasView *cv); + + //--------------- Data members --------------------------------- + + /// The current selection of Events (for cut/copy/paste) + EventSelection* m_currentEventSelection; + + ActiveItem* m_activeItem; + + RosegardenCanvasView *m_canvasView; + + QVBoxLayout *m_rulerBox; + QLabel *m_rulerBoxFiller; + QVBoxLayout *m_controlBox; + QVBox *m_bottomBox; + StandardRuler *m_topStandardRuler; + StandardRuler *m_bottomStandardRuler; + ControlRuler *m_controlRuler; + KTabWidget *m_controlRulers; + QWMatrix m_currentRulerZoomMatrix; + + static const unsigned int RULERS_ROW; + static const unsigned int CONTROLS_ROW; + static const unsigned int TOPBARBUTTONS_ROW; + static const unsigned int CANVASVIEW_ROW; + static const unsigned int CONTROLRULER_ROW; +}; + + +} + +#endif diff --git a/src/gui/general/EditViewBase.cpp b/src/gui/general/EditViewBase.cpp new file mode 100644 index 0000000..0193beb --- /dev/null +++ b/src/gui/general/EditViewBase.cpp @@ -0,0 +1,711 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "EditViewBase.h" +#include +#include + +#include +#include +#include "misc/Debug.h" +#include "base/Clipboard.h" +#include "base/Event.h" +#include "base/NotationTypes.h" +#include "base/Segment.h" +#include "commands/segment/SegmentReconfigureCommand.h" +#include "document/MultiViewCommandHistory.h" +#include "document/RosegardenGUIDoc.h" +#include "EditToolBox.h" +#include "EditTool.h" +#include "EditView.h" +#include "gui/dialogs/ConfigureDialog.h" +#include "gui/dialogs/TimeDialog.h" +#include "gui/general/EditViewTimeSigNotifier.h" +#include "gui/kdeext/KTmpStatusMsg.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +bool EditViewBase::m_inPaintEvent = false; +const unsigned int EditViewBase::ID_STATUS_MSG = 1; +const unsigned int EditViewBase::NbLayoutRows = 6; + +EditViewBase::EditViewBase(RosegardenGUIDoc *doc, + std::vector segments, + unsigned int cols, + QWidget *parent, const char *name) : + KDockMainWindow(parent, name), + m_viewNumber( -1), + m_viewLocalPropertyPrefix(makeViewLocalPropertyPrefix()), + m_config(kapp->config()), + m_doc(doc), + m_segments(segments), + m_tool(0), + m_toolBox(0), + m_mainDockWidget(0), + m_centralFrame(0), + m_grid(0), + m_mainCol(cols - 1), + m_compositionRefreshStatusId(doc->getComposition().getNewRefreshStatusId()), + m_needUpdate(false), + m_pendingPaintEvent(0), + m_havePendingPaintEvent(false), + m_accelerators(0), + m_configDialogPageIndex(0), + m_inCtor(true), + m_timeSigNotifier(new EditViewTimeSigNotifier(doc)) +{ + + QPixmap dummyPixmap; // any icon will do + m_mainDockWidget = createDockWidget("Rosegarden EditView DockWidget", dummyPixmap, + 0L, "editview_dock_widget"); + // allow others to dock to the left and right sides only + m_mainDockWidget->setDockSite(KDockWidget::DockLeft | KDockWidget::DockRight); + // forbit docking abilities of m_mainDockWidget itself + m_mainDockWidget->setEnableDocking(KDockWidget::DockNone); + setView(m_mainDockWidget); // central widget in a KDE mainwindow + setMainDockWidget(m_mainDockWidget); // master dockwidget + + m_centralFrame = new QFrame(m_mainDockWidget, "centralframe"); + m_grid = new QGridLayout(m_centralFrame, NbLayoutRows, cols); + + m_mainDockWidget->setWidget(m_centralFrame); + + initSegmentRefreshStatusIds(); + + m_doc->attachEditView(this); + + QObject::connect + (getCommandHistory(), SIGNAL(commandExecuted()), + this, SLOT(update())); + + QObject::connect + (getCommandHistory(), SIGNAL(commandExecuted()), + this, SLOT(slotTestClipboard())); + + // create accelerators + // + m_accelerators = new QAccel(this); +} + +EditViewBase::~EditViewBase() +{ + delete m_timeSigNotifier; + + m_doc->detachEditView(this); + + getCommandHistory()->detachView(actionCollection()); + m_viewNumberPool.erase(m_viewNumber); + slotSaveOptions(); +} + +void EditViewBase::slotSaveOptions() +{} + +void EditViewBase::readOptions() +{ + getToggleAction("options_show_statusbar")->setChecked(!statusBar()->isHidden()); + getToggleAction("options_show_toolbar")->setChecked(!toolBar()->isHidden()); +} + +void EditViewBase::setupActions(QString rcFileName, bool haveClipboard) +{ + setRCFileName(rcFileName); + + // Actions all edit views will have + + KStdAction::showToolbar(this, SLOT(slotToggleToolBar()), + actionCollection(), "options_show_toolbar"); + + KStdAction::showStatusbar(this, SLOT(slotToggleStatusBar()), + actionCollection(), "options_show_statusbar"); + + KStdAction::preferences(this, + SLOT(slotConfigure()), + actionCollection()); + + KStdAction::keyBindings(this, + SLOT(slotEditKeys()), + actionCollection()); + + KStdAction::configureToolbars(this, + SLOT(slotEditToolbars()), + actionCollection()); + + + // File menu + KStdAction::save (this, SIGNAL(saveFile()), actionCollection()); + KStdAction::close(this, SLOT(slotCloseWindow()), actionCollection()); + + if (haveClipboard) { + KStdAction::cut (this, SLOT(slotEditCut()), actionCollection()); + KStdAction::copy (this, SLOT(slotEditCopy()), actionCollection()); + KStdAction::paste (this, SLOT(slotEditPaste()), actionCollection()); + } + + 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/"); + + QCanvasPixmap pixmap(pixmapDir + "/toolbar/matrix.png"); + QIconSet icon = QIconSet(pixmap); + new KAction(i18n("Open in Matri&x Editor"), icon, 0, this, + SLOT(slotOpenInMatrix()), actionCollection(), + "open_in_matrix"); + + pixmap.load(pixmapDir + "/toolbar/matrix-percussion.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Open in &Percussion Matrix Editor"), icon, 0, this, + SLOT(slotOpenInPercussionMatrix()), actionCollection(), + "open_in_percussion_matrix"); + + pixmap.load(pixmapDir + "/toolbar/notation.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Open in &Notation Editor"), icon, 0, this, + SLOT(slotOpenInNotation()), actionCollection(), + "open_in_notation"); + + pixmap.load(pixmapDir + "/toolbar/eventlist.png"); + icon = QIconSet(pixmap); + new KAction(i18n("Open in &Event List Editor"), icon, 0, this, + SLOT(slotOpenInEventList()), actionCollection(), + "open_in_event_list"); + + new KAction(i18n("Set Segment Start Time..."), 0, this, + SLOT(slotSetSegmentStartTime()), actionCollection(), + "set_segment_start"); + + new KAction(i18n("Set Segment Duration..."), 0, this, + SLOT(slotSetSegmentDuration()), actionCollection(), + "set_segment_duration"); + + // add undo and redo to edit menu and toolbar + getCommandHistory()->attachView(actionCollection()); + +} + +void EditViewBase::slotConfigure() +{ + ConfigureDialog *configDlg = + new ConfigureDialog(getDocument(), m_config, this); + + configDlg->showPage(getConfigDialogPageIndex()); + configDlg->show(); +} + +void EditViewBase::slotEditKeys() +{ + KKeyDialog::configure(actionCollection()); +} + +void EditViewBase::slotEditToolbars() +{ + KEditToolbar dlg(actionCollection(), getRCFileName()); + + connect(&dlg, SIGNAL(newToolbarConfig()), + SLOT(slotUpdateToolbars())); + + dlg.exec(); +} + +void EditViewBase::slotUpdateToolbars() +{ + createGUI(getRCFileName()); + //m_viewToolBar->setChecked(!toolBar()->isHidden()); +} + +void +EditViewBase::slotOpenInNotation() +{ + + emit openInNotation(m_segments); +} + +void +EditViewBase::slotOpenInMatrix() +{ + emit openInMatrix(m_segments); +} + +void +EditViewBase::slotOpenInPercussionMatrix() +{ + emit openInPercussionMatrix(m_segments); +} + +void +EditViewBase::slotOpenInEventList() +{ + emit openInEventList(m_segments); +} + +std::set EditViewBase::m_viewNumberPool; + +std::string +EditViewBase::makeViewLocalPropertyPrefix() +{ + static char buffer[100]; + int i = 0; + while (m_viewNumberPool.find(i) != m_viewNumberPool.end()) + ++i; + m_viewNumber = i; + m_viewNumberPool.insert(i); + sprintf(buffer, "View%d::", i); + return buffer; +} + +void EditViewBase::paintEvent(QPaintEvent* e) +{ + // It is possible for this function to be called re-entrantly, + // because a re-layout procedure may deliberately ask the event + // loop to process some more events so as to keep the GUI looking + // responsive. If that happens, we remember the events that came + // in in the middle of one paintEvent call and process their union + // again at the end of the call. + /* + if (m_inPaintEvent) { + NOTATION_DEBUG << "EditViewBase::paintEvent: in paint event already" << endl; + if (e) { + if (m_havePendingPaintEvent) { + if (m_pendingPaintEvent) { + QRect r = m_pendingPaintEvent->rect().unite(e->rect()); + *m_pendingPaintEvent = QPaintEvent(r); + } else { + m_pendingPaintEvent = new QPaintEvent(*e); + } + } else { + m_pendingPaintEvent = new QPaintEvent(*e); + } + } + m_havePendingPaintEvent = true; + return; + } + */ + //!!! m_inPaintEvent = true; + + 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 ; + } + } + } + + + m_needUpdate = false; + + // Scan all segments and check if they've been modified. + // + // If we have more than one segment modified, we need to update + // them all at once with the same time range, otherwise we can run + // into problems when the layout of one depends on the others. So + // we use updateStart/End to calculate a bounding range for all + // modifications. + + timeT updateStart = 0, updateEnd = 0; + int segmentsToUpdate = 0; + Segment *singleSegment = 0; + + for (unsigned int i = 0; i < m_segments.size(); ++i) { + + Segment* segment = m_segments[i]; + unsigned int refreshStatusId = m_segmentsRefreshStatusIds[i]; + SegmentRefreshStatus &refreshStatus = + segment->getRefreshStatus(refreshStatusId); + + if (refreshStatus.needsRefresh() && isCompositionModified()) { + + // if composition is also modified, relayout everything + refreshSegment(0); + segmentsToUpdate = 0; + break; + + } else if (m_timeSigNotifier->hasTimeSigChanged()) { + + // not exactly optimal! + refreshSegment(0); + segmentsToUpdate = 0; + m_timeSigNotifier->reset(); + break; + + } else if (refreshStatus.needsRefresh()) { + + timeT startTime = refreshStatus.from(), + endTime = refreshStatus.to(); + + if (segmentsToUpdate == 0 || startTime < updateStart) { + updateStart = startTime; + } + if (segmentsToUpdate == 0 || endTime > updateEnd) { + updateEnd = endTime; + } + singleSegment = segment; + ++segmentsToUpdate; + + refreshStatus.setNeedsRefresh(false); + m_needUpdate = true; + } + } + + if (segmentsToUpdate > 1) { + refreshSegment(0, updateStart, updateEnd); + } else if (segmentsToUpdate > 0) { + refreshSegment(singleSegment, updateStart, updateEnd); + } + + if (e) + KMainWindow::paintEvent(e); + + // moved this to the end of the method so that things called + // from this method can still test whether the composition had + // been modified (it's sometimes useful to know whether e.g. + // any time signatures have changed) + setCompositionModified(false); + + //!!! m_inPaintEvent = false; + /* + if (m_havePendingPaintEvent) { + e = m_pendingPaintEvent; + m_havePendingPaintEvent = false; + m_pendingPaintEvent = 0; + paintEvent(e); + delete e; + } + */ +} + +void EditViewBase::closeEvent(QCloseEvent* e) +{ + RG_DEBUG << "EditViewBase::closeEvent()\n"; + + if (isInCtor()) { + RG_DEBUG << "EditViewBase::closeEvent() : is in ctor, ignoring close event\n"; + e->ignore(); + } else { + KMainWindow::closeEvent(e); + } +} + +void EditViewBase::addCommandToHistory(KCommand *command) +{ + getCommandHistory()->addCommand(command); +} + +void EditViewBase::setTool(EditTool* tool) +{ + if (m_tool) + m_tool->stow(); + + m_tool = tool; + + if (m_tool) + m_tool->ready(); + +} + +void EditViewBase::slotCloseWindow() +{ + close(); +} + +void EditViewBase::slotToggleToolBar() +{ + KTmpStatusMsg msg(i18n("Toggle the toolbar..."), this); + + if (toolBar()->isVisible()) + toolBar()->hide(); + else + toolBar()->show(); +} + +void EditViewBase::slotToggleStatusBar() +{ + KTmpStatusMsg msg(i18n("Toggle the statusbar..."), this); + + if (statusBar()->isVisible()) + statusBar()->hide(); + else + statusBar()->show(); +} + +void EditViewBase::slotStatusMsg(const QString &text) +{ + /////////////////////////////////////////////////////////////////// + // change status message permanently + statusBar()->clear(); + statusBar()->changeItem(text, ID_STATUS_MSG); +} + +void EditViewBase::slotStatusHelpMsg(const QString &text) +{ + /////////////////////////////////////////////////////////////////// + // change status message of whole statusbar temporary (text, msec) + statusBar()->message(text, 2000); +} + +void EditViewBase::initSegmentRefreshStatusIds() +{ + for (unsigned int i = 0; i < m_segments.size(); ++i) { + + unsigned int rid = m_segments[i]->getNewRefreshStatusId(); + m_segmentsRefreshStatusIds.push_back(rid); + } +} + +bool EditViewBase::isCompositionModified() +{ + return getDocument()->getComposition().getRefreshStatus + (m_compositionRefreshStatusId).needsRefresh(); +} + +void EditViewBase::setCompositionModified(bool c) +{ + getDocument()->getComposition().getRefreshStatus + (m_compositionRefreshStatusId).setNeedsRefresh(c); +} + +bool EditViewBase::getSegmentsOnlyRestsAndClefs() +{ + using Rosegarden::Segment; + + for (unsigned int i = 0; i < m_segments.size(); ++i) { + + Segment* segment = m_segments[i]; + + for (Segment::iterator iter = segment->begin(); + iter != segment->end(); ++iter) { + + if (((*iter)->getType() != Note::EventRestType) + && ((*iter)->getType() != Clef::EventType)) + return false; + } + + } + + return true; + +} + +void EditViewBase::toggleWidget(QWidget* widget, + const QString& toggleActionName) +{ + KToggleAction* toggleAction = getToggleAction(toggleActionName); + + if (!toggleAction) { + RG_DEBUG << "!!! Unknown toggle action : " << toggleActionName << endl; + return ; + } + + widget->setShown(toggleAction->isChecked()); +} + +void +EditViewBase::slotTestClipboard() +{ + if (getDocument()->getClipboard()->isEmpty()) { + RG_DEBUG << "EditViewBase::slotTestClipboard(): empty" << endl; + + stateChanged("have_clipboard", KXMLGUIClient::StateReverse); + stateChanged("have_clipboard_single_segment", + KXMLGUIClient::StateReverse); + } else { + RG_DEBUG << "EditViewBase::slotTestClipboard(): not empty" << endl; + + stateChanged("have_clipboard", KXMLGUIClient::StateNoReverse); + stateChanged("have_clipboard_single_segment", + (getDocument()->getClipboard()->isSingleSegment() ? + KXMLGUIClient::StateNoReverse : + KXMLGUIClient::StateReverse)); + } +} + +void +EditViewBase::slotToggleSolo() +{ + KToggleAction* toggleSoloAction = getToggleAction("toggle_solo"); + if (!toggleSoloAction) + return ; + + bool newSoloState = toggleSoloAction->isChecked(); + + RG_DEBUG << "EditViewBase::slotToggleSolo() : solo = " << newSoloState << endl; + emit toggleSolo(newSoloState); + + if (newSoloState) { + emit selectTrack(getCurrentSegment()->getTrack()); + } + +} + +void +EditViewBase::slotStateChanged(const QString& s, + bool noReverse) +{ + RG_DEBUG << "EditViewBase::slotStateChanged " << s << ", " << noReverse << endl; + stateChanged(s, noReverse ? KXMLGUIClient::StateNoReverse : KXMLGUIClient::StateReverse); +} + +void +EditViewBase::slotSetSegmentStartTime() +{ + Segment *s = getCurrentSegment(); + if (!s) + return ; + + TimeDialog dialog(this, i18n("Segment Start Time"), + &getDocument()->getComposition(), + s->getStartTime(), false); + + if (dialog.exec() == QDialog::Accepted) { + + SegmentReconfigureCommand *command = + new SegmentReconfigureCommand(i18n("Set Segment Start Time")); + + command->addSegment + (s, dialog.getTime(), + s->getEndMarkerTime() - s->getStartTime() + dialog.getTime(), + s->getTrack()); + + addCommandToHistory(command); + } +} + +void +EditViewBase::slotSetSegmentDuration() +{ + Segment *s = getCurrentSegment(); + if (!s) + return ; + + TimeDialog dialog(this, i18n("Segment Duration"), + &getDocument()->getComposition(), + s->getStartTime(), + s->getEndMarkerTime() - s->getStartTime(), false); + + if (dialog.exec() == QDialog::Accepted) { + + SegmentReconfigureCommand *command = + new SegmentReconfigureCommand(i18n("Set Segment Duration")); + + command->addSegment + (s, s->getStartTime(), + s->getStartTime() + dialog.getTime(), + s->getTrack()); + + addCommandToHistory(command); + } +} + +void EditViewBase::slotCompositionStateUpdate() +{ + // update state of 'solo' toggle + // + KToggleAction* toggleSolo = getToggleAction("toggle_solo"); + if (!toggleSolo) + return ; + + if (getDocument()->getComposition().isSolo()) { + bool s = m_segments[0]->getTrack() == getDocument()->getComposition().getSelectedTrack(); + RG_DEBUG << "EditViewBase::slotCompositionStateUpdate() : set solo to " << s << endl; + toggleSolo->setChecked(s); + } else { + toggleSolo->setChecked(false); + RG_DEBUG << "EditViewBase::slotCompositionStateUpdate() : set solo to false\n"; + } + + // update the window caption + // + updateViewCaption(); +} + +void +EditViewBase::windowActivationChange(bool oldState) +{ + if (isActiveWindow()) { + emit windowActivated(); + } +} + +void +EditViewBase::handleEventRemoved(Event *event) +{ + if (m_tool) + m_tool->handleEventRemoved(event); +} + +MultiViewCommandHistory* EditViewBase::getCommandHistory() +{ + return getDocument()->getCommandHistory(); +} + +KToggleAction* EditViewBase::getToggleAction(const QString& actionName) +{ + return dynamic_cast(actionCollection()->action(actionName)); +} + +} +#include "EditViewBase.moc" diff --git a/src/gui/general/EditViewBase.h b/src/gui/general/EditViewBase.h new file mode 100644 index 0000000..03784cb --- /dev/null +++ b/src/gui/general/EditViewBase.h @@ -0,0 +1,396 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EDITVIEWBASE_H_ +#define _RG_EDITVIEWBASE_H_ + +#include +#include +#include +#include +#include +#include "base/Event.h" + + +class QWidget; +class QPaintEvent; +class QGridLayout; +class QFrame; +class QCloseEvent; +class QAccel; +class KToggleAction; +class KConfig; +class KCommand; +namespace Rosegarden { class EditViewTimeSigNotifier; } + + +namespace Rosegarden +{ + +class Segment; +class RosegardenGUIDoc; +class MultiViewCommandHistory; +class Event; +class EditToolBox; +class EditTool; + + +class EditViewBase : public KDockMainWindow +{ + + Q_OBJECT + +public: + + /** + * Create an EditViewBase for the segments \a segments from document \a doc. + * + * \arg cols : number of columns, main column is always rightmost + * + */ + EditViewBase(RosegardenGUIDoc *doc, + std::vector segments, + unsigned int cols, + QWidget *parent, + const char *name = 0); + + virtual ~EditViewBase(); + + const RosegardenGUIDoc *getDocument() const { return m_doc; } + RosegardenGUIDoc *getDocument() { return m_doc; } + + /** + * Refresh part of a Segment following a modification made in this + * or another view. The startTime and endTime give the extents of + * the modified region. This method is called following a + * modification to any Segment; no attempt has been made to check + * that the given Segment is actually shown in this view, so take + * care. + * + * If segment is null, refresh all segments. + * If the startTime and endTime are equal, refresh the whole of + * the relevant segments. + */ + virtual void refreshSegment(Segment *segment, + timeT startTime = 0, + timeT endTime = 0) = 0; + + /** + * Get the document's global command history + */ + virtual MultiViewCommandHistory *getCommandHistory(); + + /** + * Add a Command to the history + */ + virtual void addCommandToHistory(KCommand *); + + /** + * Update the view + */ + virtual void updateView() = 0; + + /** + * Return our local accelerator object + */ + QAccel* getAccelerators() { return m_accelerators; } + + /** + * Return a string unique to this view (amongst views currently + * extant) that can be used (e.g. as a prefix) to distinguish + * view-local properties. It's up to the subclass or other user + * of this string to manage the properties correctly, for example + * by deleting them from the events when the view closes. + */ + std::string getViewLocalPropertyPrefix() { + return m_viewLocalPropertyPrefix; + } + + /* + * So that other people can create tools against our view + * + */ + EditToolBox* getToolBox() { return m_toolBox; } + + /** + * Let tools know if their current element has gone + */ + virtual void handleEventRemoved(Event *event); + + static const unsigned int ID_STATUS_MSG; + static const unsigned int NbLayoutRows; + + +signals: + /** + * Tell the app to save the file. + */ + void saveFile(); + + /** + * Reopen the given segments in another sort of editor. + */ + void openInNotation(std::vector); + void openInMatrix(std::vector); + void openInPercussionMatrix(std::vector); + void openInEventList(std::vector); + + /** + * Tell the main view that the track being edited is the + * current selected track + * This is used by #slotToggleSolo + */ + void selectTrack(int); + + /** + * Tell the main view that the solo status has changed (the user clicked on the 'solo' toggle) + */ + void toggleSolo(bool); + + void windowActivated(); + +public slots: + /** + * close window + */ + virtual void slotCloseWindow(); + + /** + * put the indicationed text/object into the clipboard and remove * it + * from the document + */ + virtual void slotEditCut() = 0; + + /** + * put the indicationed text/object into the clipboard + */ + virtual void slotEditCopy() = 0; + + /** + * paste the clipboard into the document + */ + virtual void slotEditPaste() = 0; + + /** + * toggles the main toolbar + */ + virtual void slotToggleToolBar(); + + /** + * toggles the statusbar + */ + virtual void slotToggleStatusBar(); + + /** + * Changes the statusbar contents for the standard label permanently, + * used to indicate current actions. + * + * @param text the text that is displayed in the statusbar + */ + virtual void slotStatusMsg(const QString &text); + + /** + * Changes the status message of the whole statusbar for two + * seconds, then restores the last status. This is used to display + * statusbar messages that give information about actions for + * toolbar icons and menuentries. + * + * @param text the text that is displayed in the statusbar + */ + virtual void slotStatusHelpMsg(const QString &text); + + /** + * A command has happened; check the clipboard in case we + * need to change state + */ + virtual void slotTestClipboard(); + + /** + * Connected to this view's toolbar 'solo' button + */ + virtual void slotToggleSolo(); + + void slotStateChanged(const QString&, bool noReverse); + + virtual void slotOpenInMatrix(); + virtual void slotOpenInPercussionMatrix(); + virtual void slotOpenInNotation(); + virtual void slotOpenInEventList(); + + /** + * Set the start time of the current segment + */ + void slotSetSegmentStartTime(); + + /** + * Set the duration of the current segment + */ + void slotSetSegmentDuration(); + + /** + * Global composition updates from the main view (track selection, solo, etc...) + */ + virtual void slotCompositionStateUpdate(); + +protected: + + virtual void windowActivationChange(bool); + + virtual void paintEvent(QPaintEvent* e); + + /** + * @see #setInCtor + */ + virtual void closeEvent(QCloseEvent* e); + + /** + * ignore close events while we're in ctor + */ + void setOutOfCtor() { m_inCtor = false; } + + /** + * Check if we're still in ctor + */ + bool isInCtor() { return m_inCtor; } + + /** + * Set the current Notation tool (note inserter, rest inserter, eraser...) + * + * Called when the user selects a new item on one of the notation toolbars + * (notes toolbars, rests toolbars...) + */ + void setTool(EditTool*); + + /** + * read general Options again and initialize all variables like the recent file list + */ + virtual void readOptions(); + + /** + * create menus and toolbars + */ + virtual void setupActions(QString rcFileName, bool haveClipboard = true); + + /** + * setup status bar + */ + virtual void initStatusBar() = 0; + + /** + * Abstract method to get current segment + */ + virtual Segment *getCurrentSegment() = 0; + + /** + * Set the caption of the view's window + */ + virtual void updateViewCaption() = 0; + +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(); + virtual void slotConfigure(); + virtual void slotEditKeys(); + virtual void slotEditToolbars(); + virtual void slotUpdateToolbars(); + +protected: + QWidget* getCentralWidget() { return m_centralFrame; } + + void initSegmentRefreshStatusIds(); + + bool isCompositionModified(); + void setCompositionModified(bool); + + /** + * Returns true if all of the segments contain + * only rests and clefs events + */ + bool getSegmentsOnlyRestsAndClefs(); + + /// Convenience function around actionCollection()->action() + KToggleAction* getToggleAction(const QString& actionName); + + /** + * Make a widget visible depending on the state of a + * KToggleAction + */ + virtual void toggleWidget(QWidget* widget, const QString& toggleActionName); + + void setRCFileName(QString s) { m_rcFileName = s; } + QString getRCFileName() { return m_rcFileName; } + + /** + * Set the page index of the config dialog which corresponds to + * this editview + */ + void setConfigDialogPageIndex(int p) { m_configDialogPageIndex = p; } + int getConfigDialogPageIndex() { return m_configDialogPageIndex; } + + //--------------- Data members --------------------------------- + QString m_rcFileName; + + static std::set m_viewNumberPool; + std::string makeViewLocalPropertyPrefix(); + int m_viewNumber; + std::string m_viewLocalPropertyPrefix; + + KConfig* m_config; + + RosegardenGUIDoc* m_doc; + std::vector m_segments; + std::vector m_segmentsRefreshStatusIds; + + EditTool* m_tool; + EditToolBox* m_toolBox; + + KDockWidget *m_mainDockWidget; + QFrame *m_centralFrame; + QGridLayout *m_grid; + + unsigned int m_mainCol; + unsigned int m_compositionRefreshStatusId; + bool m_needUpdate; + + QPaintEvent *m_pendingPaintEvent; + bool m_havePendingPaintEvent; + static bool m_inPaintEvent; // true if _any_ edit view is in a paint event + + QAccel *m_accelerators; + + int m_configDialogPageIndex; + + bool m_inCtor; + + EditViewTimeSigNotifier *m_timeSigNotifier; +}; + + +} + +#endif diff --git a/src/gui/general/EditViewTimeSigNotifier.h b/src/gui/general/EditViewTimeSigNotifier.h new file mode 100644 index 0000000..679494d --- /dev/null +++ b/src/gui/general/EditViewTimeSigNotifier.h @@ -0,0 +1,56 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_EDITVIEWTIMESIGNOTIFIER_H_ +#define _RG_EDITVIEWTIMESIGNOTIFIER_H_ + +namespace Rosegarden { + +class EditViewTimeSigNotifier : public Rosegarden::CompositionObserver +{ +public: + EditViewTimeSigNotifier(RosegardenGUIDoc *doc) : + m_composition(&doc->getComposition()), + m_timeSigChanged(false) { + m_composition->addObserver(this); + } + virtual ~EditViewTimeSigNotifier() { + if (!isCompositionDeleted()) m_composition->removeObserver(this); + } + virtual void timeSignatureChanged(const Rosegarden::Composition *c) { + if (c == m_composition) m_timeSigChanged = true; + } + + bool hasTimeSigChanged() const { return m_timeSigChanged; } + void reset() { m_timeSigChanged = false; } + +protected: + Rosegarden::Composition *m_composition; + bool m_timeSigChanged; +}; + +} + +#endif /*EDITVIEWTIMESIGNOTIFIER_H_*/ diff --git a/src/gui/general/GUIPalette.cpp b/src/gui/general/GUIPalette.cpp new file mode 100644 index 0000000..4705c21 --- /dev/null +++ b/src/gui/general/GUIPalette.cpp @@ -0,0 +1,311 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "GUIPalette.h" +#include + +#include "base/Colour.h" +#include "document/ConfigGroups.h" +#include +#include + + +namespace Rosegarden +{ + +QColor GUIPalette::getColour(const char* const colourName) +{ + KConfig* config = kapp->config(); + config->setGroup(ColoursConfigGroup); + + QColor res = getInstance()->m_defaultsMap[colourName]; + config->readColorEntry(colourName, &res); + return res; +} + +Colour GUIPalette::convertColour(const QColor &input) +{ + int r, g, b; + input.rgb(&r, &g, &b); + return Colour(r, g, b); +} + +QColor GUIPalette::convertColour(const Colour& input) +{ + return QColor(input.getRed(), input.getGreen(), input.getBlue()); +} + +GUIPalette::GUIPalette() +{ + m_defaultsMap[ActiveRecordTrack] = Qt::red; + + m_defaultsMap[SegmentCanvas] = QColor(230, 230, 230); + m_defaultsMap[SegmentBorder] = Qt::black; + + // 1.0 colors + // m_defaultsMap[RecordingInternalSegmentBlock] = QColor(255, 182, 193); + // m_defaultsMap[RecordingAudioSegmentBlock] = QColor(182, 222, 255); + + // MIDI recording preview (pale yellow) + m_defaultsMap[RecordingInternalSegmentBlock] = QColor(255, 234, 182); + + // audio recording preview (pale red) + m_defaultsMap[RecordingAudioSegmentBlock] = QColor(255, 182, 193); + + m_defaultsMap[RecordingSegmentBorder] = Qt::black; + + m_defaultsMap[RepeatSegmentBorder] = QColor(130, 133, 170); + + m_defaultsMap[SegmentAudioPreview] = QColor(39, 71, 22); + m_defaultsMap[SegmentInternalPreview] = Qt::white; + m_defaultsMap[SegmentLabel] = Qt::black; + m_defaultsMap[SegmentSplitLine] = Qt::black; + + m_defaultsMap[MatrixElementBorder] = Qt::black; + m_defaultsMap[MatrixElementBlock] = QColor(98, 128, 232); + m_defaultsMap[MatrixOverlapBlock] = Qt::black; + + m_defaultsMap[LoopRulerBackground] = QColor(120, 120, 120); + m_defaultsMap[LoopRulerForeground] = Qt::white; + m_defaultsMap[LoopHighlight] = Qt::white; + + m_defaultsMap[TempoBase] = QColor(197, 211, 125); + + //m_defaultsMap[TextRulerBackground] = QColor(60, 205, 230, QColor::Hsv); + // m_defaultsMap[TextRulerBackground] = QColor(120, 90, 238, QColor::Hsv); + // m_defaultsMap[TextRulerBackground] = QColor(210, 220, 140); + m_defaultsMap[TextRulerBackground] = QColor(226, 232, 187); + m_defaultsMap[TextRulerForeground] = Qt::white; + + m_defaultsMap[ChordNameRulerBackground] = QColor(230, 230, 230); + m_defaultsMap[ChordNameRulerForeground] = Qt::black; + + m_defaultsMap[RawNoteRulerBackground] = QColor(240, 240, 240); + m_defaultsMap[RawNoteRulerForeground] = Qt::black; + + m_defaultsMap[LevelMeterGreen] = QColor(0, 200, 0); + m_defaultsMap[LevelMeterOrange] = QColor(255, 165, 0); + m_defaultsMap[LevelMeterRed] = QColor(200, 0, 0); + + // m_defaultsMap[LevelMeterSolidGreen] = QColor(0, 140, 0); + m_defaultsMap[LevelMeterSolidGreen] = QColor(84, 177, 248); // blue! + // m_defaultsMap[LevelMeterSolidOrange] = QColor(220, 120, 0); + m_defaultsMap[LevelMeterSolidOrange] = QColor(255, 225, 0); + // m_defaultsMap[LevelMeterSolidRed] = QColor(255, 50, 50); + m_defaultsMap[LevelMeterSolidRed] = QColor(255, 0, 0); + + m_defaultsMap[BarLine] = Qt::black; + m_defaultsMap[BarLineIncorrect] = QColor(211, 0, 31); + m_defaultsMap[BeatLine] = QColor(100, 100, 100); + m_defaultsMap[SubBeatLine] = QColor(212, 212, 212); + m_defaultsMap[StaffConnectingLine] = QColor(192, 192, 192); + m_defaultsMap[StaffConnectingTerminatingLine] = QColor(128, 128, 128); + + m_defaultsMap[Pointer] = Qt::darkBlue; + m_defaultsMap[PointerRuler] = QColor(100, 100, 100); + + m_defaultsMap[InsertCursor] = QColor(160, 104, 186); + m_defaultsMap[InsertCursorRuler] = QColor(160, 136, 170); + + m_defaultsMap[TrackDivider] = QColor(145, 145, 145); + //m_defaultsMap[MovementGuide] = QColor(172, 230, 139); + m_defaultsMap[MovementGuide] = QColor(62, 161, 194); + //m_defaultsMap[MovementGuide] = QColor(255, 189, 89); + m_defaultsMap[SelectionRectangle] = QColor(103, 128, 211); + m_defaultsMap[SelectedElement] = QColor(0, 54, 232); + + const int SelectedElementHue = 225; + const int SelectedElementMinValue = 220; + const int HighlightedElementHue = 25; + const int HighlightedElementMinValue = 220; + const int QuantizedNoteHue = 69; + const int QuantizedNoteMinValue = 140; + const int TriggerNoteHue = 4; + const int TriggerNoteMinValue = 140; + const int OutRangeNoteHue = 0; + const int OutRangeNoteMinValue = 200; + + m_defaultsMap[TextAnnotationBackground] = QColor(255, 255, 180); + + m_defaultsMap[TextLilyPondDirectiveBackground] = QColor(95, 157, 87); + + m_defaultsMap[AudioCountdownBackground] = Qt::darkGray; + m_defaultsMap[AudioCountdownForeground] = Qt::red; + +// m_defaultsMap[RotaryFloatBackground] = Qt::cyan; + m_defaultsMap[RotaryFloatBackground] = QColor(182, 222, 255); + m_defaultsMap[RotaryFloatForeground] = Qt::black; + + m_defaultsMap[RotaryPastelBlue] = QColor(205, 212, 255); + m_defaultsMap[RotaryPastelRed] = QColor(255, 168, 169); + m_defaultsMap[RotaryPastelGreen] = QColor(231, 255, 223); + m_defaultsMap[RotaryPastelOrange] = QColor(255, 233, 208); + m_defaultsMap[RotaryPastelYellow] = QColor(249, 255, 208); + + m_defaultsMap[MatrixKeyboardFocus] = QColor(224, 112, 8); + + // m_defaultsMap[RotaryPlugin] = QColor(185, 255, 248); + m_defaultsMap[RotaryPlugin] = QColor(185, 200, 248); + // m_defaultsMap[RotaryPlugin] = QColor(185, 185, 185); + + m_defaultsMap[RotaryMeter] = QColor(255, 100, 0); + + m_defaultsMap[MarkerBackground] = QColor(185, 255, 248); + + m_defaultsMap[QuickMarker] = Qt::red; + + // m_defaultsMap[MuteTrackLED] = QColor(218, 190, 230, QColor::Hsv); + m_defaultsMap[MuteTrackLED] = QColor(211, 194, 238, QColor::Hsv); + m_defaultsMap[RecordMIDITrackLED] = QColor(45, 250, 225, QColor::Hsv); + m_defaultsMap[RecordAudioTrackLED] = QColor(0, 250, 225, QColor::Hsv); + + m_defaultsMap[PlaybackFaderOutline] = QColor(211, 194, 238, QColor::Hsv); + m_defaultsMap[RecordFaderOutline] = QColor(0, 250, 225, QColor::Hsv); +} + +GUIPalette* GUIPalette::getInstance() +{ + if (!m_instance) m_instance = new GUIPalette(); + return m_instance; +} + +const char* const GUIPalette::ActiveRecordTrack = "activerecordtrack"; + + +const char* const GUIPalette::SegmentCanvas = "segmentcanvas"; +const char* const GUIPalette::SegmentBorder = "segmentborder"; +const char* const GUIPalette::RecordingInternalSegmentBlock = "recordinginternalsegmentblock"; +const char* const GUIPalette::RecordingAudioSegmentBlock = "recordingaudiosegmentblock"; +const char* const GUIPalette::RecordingSegmentBorder = "recordingsegmentborder"; + +const char* const GUIPalette::RepeatSegmentBorder = "repeatsegmentborder"; + +const char* const GUIPalette::SegmentAudioPreview = "segmentaudiopreview"; +const char* const GUIPalette::SegmentInternalPreview = "segmentinternalpreview"; +const char* const GUIPalette::SegmentLabel = "segmentlabel"; +const char* const GUIPalette::SegmentSplitLine = "segmentsplitline"; + +const char* const GUIPalette::MatrixElementBorder = "matrixelementborder"; +const char* const GUIPalette::MatrixElementBlock = "matrixelementblock"; +const char* const GUIPalette::MatrixOverlapBlock = "matrixoverlapblock"; + +const char* const GUIPalette::LoopRulerBackground = "looprulerbackground"; +const char* const GUIPalette::LoopRulerForeground = "looprulerforeground"; +const char* const GUIPalette::LoopHighlight = "loophighlight"; + +const char* const GUIPalette::TempoBase = "tempobase"; + +const char* const GUIPalette::TextRulerBackground = "textrulerbackground"; +const char* const GUIPalette::TextRulerForeground = "textrulerforeground"; + +const char* const GUIPalette::ChordNameRulerBackground = "chordnamerulerbackground"; +const char* const GUIPalette::ChordNameRulerForeground = "chordnamerulerforeground"; + +const char* const GUIPalette::RawNoteRulerBackground = "rawnoterulerbackground"; +const char* const GUIPalette::RawNoteRulerForeground = "rawnoterulerforeground"; + +const char* const GUIPalette::LevelMeterGreen = "levelmetergreen"; +const char* const GUIPalette::LevelMeterOrange = "levelmeterorange"; +const char* const GUIPalette::LevelMeterRed = "levelmeterred"; + +const char* const GUIPalette::LevelMeterSolidGreen = "levelmetersolidgreen"; +const char* const GUIPalette::LevelMeterSolidOrange = "levelmetersolidorange"; +const char* const GUIPalette::LevelMeterSolidRed = "levelmetersolidred"; + +const char* const GUIPalette::BarLine = "barline"; +const char* const GUIPalette::BarLineIncorrect = "barlineincorrect"; +const char* const GUIPalette::BeatLine = "beatline"; +const char* const GUIPalette::SubBeatLine = "subbeatline"; +const char* const GUIPalette::StaffConnectingLine = "staffconnectingline"; +const char* const GUIPalette::StaffConnectingTerminatingLine = "staffconnectingterminatingline"; + +const char* const GUIPalette::Pointer = "pointer"; +const char* const GUIPalette::PointerRuler = "pointerruler"; + +const char* const GUIPalette::InsertCursor = "insertcursor"; +const char* const GUIPalette::InsertCursorRuler = "insertcursorruler"; + +const char* const GUIPalette::TrackDivider = "trackdivider"; +const char* const GUIPalette::MovementGuide = "movementguide"; +const char* const GUIPalette::SelectionRectangle = "selectionrectangle"; +const char* const GUIPalette::SelectedElement = "selectedelement"; + +const int GUIPalette::SelectedElementHue = 225; +const int GUIPalette::SelectedElementMinValue = 220; +const int GUIPalette::HighlightedElementHue = 25; +const int GUIPalette::HighlightedElementMinValue = 220; +const int GUIPalette::QuantizedNoteHue = 69; +const int GUIPalette::QuantizedNoteMinValue = 140; +const int GUIPalette::TriggerNoteHue = 4; +const int GUIPalette::TriggerNoteMinValue = 140; +const int GUIPalette::OutRangeNoteHue = 0; +const int GUIPalette::OutRangeNoteMinValue = 200; + +const int GUIPalette::CollisionHaloHue = 42; +const int GUIPalette::CollisionHaloSaturation = 200; + +const char* const GUIPalette::TextAnnotationBackground = "textannotationbackground"; + +const char* const GUIPalette::TextLilyPondDirectiveBackground = "textlilyponddirectivebackground"; + +const char* const GUIPalette::AudioCountdownBackground = "audiocountdownbackground"; +const char* const GUIPalette::AudioCountdownForeground = "audiocountdownforeground"; + +const char* const GUIPalette::RotaryFloatBackground = "rotaryfloatbackground"; +const char* const GUIPalette::RotaryFloatForeground = "rotaryfloatforeground"; + +const char* const GUIPalette::RotaryPastelBlue = "rotarypastelblue"; +const char* const GUIPalette::RotaryPastelRed = "rotarypastelred"; +const char* const GUIPalette::RotaryPastelGreen = "rotarypastelgreen"; +const char* const GUIPalette::RotaryPastelOrange = "rotarypastelorange"; +const char* const GUIPalette::RotaryPastelYellow = "rotarypastelyellow"; + +const char* const GUIPalette::MatrixKeyboardFocus = "matrixkeyboardfocus"; + +const char* const GUIPalette::RotaryPlugin = "rotaryplugin"; + +const char* const GUIPalette::RotaryMeter = "rotarymeter"; + +const char* const GUIPalette::MarkerBackground = "markerbackground"; + +const char* const GUIPalette::QuickMarker = "quickmarker"; + +const char* const GUIPalette::MuteTrackLED = "mutetrackled"; +const char* const GUIPalette::RecordMIDITrackLED = "recordmiditrackled"; +const char* const GUIPalette::RecordAudioTrackLED = "recordaudiotrackled"; + +const char* const GUIPalette::PlaybackFaderOutline = "playbackfaderoutline"; +const char* const GUIPalette::RecordFaderOutline = "recordfaderoutline"; + + +GUIPalette* GUIPalette::m_instance = 0; + +// defines which index in the document's colourmap should be used as the color +// when creating new audio segments from recordings, or inserting from the +// audio file manager (presumes a file derived from the updated autoload.rg +// that shipped along with this change) +const int GUIPalette::AudioDefaultIndex = 1; + +} diff --git a/src/gui/general/GUIPalette.h b/src/gui/general/GUIPalette.h new file mode 100644 index 0000000..c8760fb --- /dev/null +++ b/src/gui/general/GUIPalette.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_GUIPALETTE_H_ +#define _RG_GUIPALETTE_H_ + +#include "base/Colour.h" +#include +#include + + + + +namespace Rosegarden +{ + + + +/** + * Definitions of colours to be used throughout the Rosegarden GUI. + * + * They're in this file not so much to allow them to be easily + * changed, as to ensure a certain amount of consistency between + * colours for different functions that might end up being seen + * at the same time. + */ + +class GUIPalette +{ +public: + static QColor getColour(const char* const colourName); + + static Colour convertColour(const QColor &input); + static QColor convertColour(const Colour &input); + + static const char* const ActiveRecordTrack; + + static const char* const SegmentCanvas; + static const char* const SegmentBorder; + + static const char* const RecordingInternalSegmentBlock; + static const char* const RecordingAudioSegmentBlock; + static const char* const RecordingSegmentBorder; + + static const char* const RepeatSegmentBorder; + + static const char* const SegmentAudioPreview; + static const char* const SegmentInternalPreview; + static const char* const SegmentLabel; + static const char* const SegmentSplitLine; + + static const char* const MatrixElementBorder; + static const char* const MatrixElementBlock; + static const char* const MatrixOverlapBlock; + + static const char* const LoopRulerBackground; + static const char* const LoopRulerForeground; + static const char* const LoopHighlight; + + static const char* const TempoBase; + + static const char* const TextRulerBackground; + static const char* const TextRulerForeground; + + static const char* const ChordNameRulerBackground; + static const char* const ChordNameRulerForeground; + + static const char* const RawNoteRulerBackground; + static const char* const RawNoteRulerForeground; + + static const char* const LevelMeterGreen; + static const char* const LevelMeterOrange; + static const char* const LevelMeterRed; + + static const char* const LevelMeterSolidGreen; + static const char* const LevelMeterSolidOrange; + static const char* const LevelMeterSolidRed; + + static const char* const BarLine; + static const char* const BarLineIncorrect; + static const char* const BeatLine; + static const char* const SubBeatLine; + static const char* const StaffConnectingLine; + static const char* const StaffConnectingTerminatingLine; + + static const char* const Pointer; + static const char* const PointerRuler; + + static const char* const InsertCursor; + static const char* const InsertCursorRuler; + + static const char* const TrackDivider; + static const char* const MovementGuide; + static const char* const SelectionRectangle; + static const char* const SelectedElement; + + static const int SelectedElementHue; + static const int SelectedElementMinValue; + static const int HighlightedElementHue; + static const int HighlightedElementMinValue; + static const int QuantizedNoteHue; + static const int QuantizedNoteMinValue; + static const int TriggerNoteHue; + static const int TriggerNoteMinValue; + static const int OutRangeNoteHue; + static const int OutRangeNoteMinValue; + + static const int CollisionHaloHue; + static const int CollisionHaloSaturation; + + static const char* const TextAnnotationBackground; + + static const char* const TextLilyPondDirectiveBackground; + + static const char* const AudioCountdownBackground; + static const char* const AudioCountdownForeground; + + static const char* const RotaryFloatBackground; + static const char* const RotaryFloatForeground; + + static const char* const RotaryPastelBlue; + static const char* const RotaryPastelRed; + static const char* const RotaryPastelGreen; + static const char* const RotaryPastelOrange; + static const char* const RotaryPastelYellow; + + static const char* const MatrixKeyboardFocus; + + static const char* const RotaryPlugin; + + static const char* const RotaryMeter; + + static const char* const MarkerBackground; + + static const char* const QuickMarker; + + static const char* const MuteTrackLED; + static const char* const RecordMIDITrackLED; + static const char* const RecordAudioTrackLED; + + static const char* const PlaybackFaderOutline; + static const char* const RecordFaderOutline; + + static const int AudioDefaultIndex; + +protected: + GUIPalette(); + QColor getDefaultColour(const char* const colourName); + + //--------------- Data members --------------------------------- + static GUIPalette* getInstance(); + static GUIPalette* m_instance; + + typedef std::map colourmap; + + colourmap m_defaultsMap; +}; + + + +} + +#endif diff --git a/src/gui/general/HZoomable.cpp b/src/gui/general/HZoomable.cpp new file mode 100644 index 0000000..ff81f6c --- /dev/null +++ b/src/gui/general/HZoomable.cpp @@ -0,0 +1,32 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "HZoomable.h" + + + +namespace Rosegarden +{ +} diff --git a/src/gui/general/HZoomable.h b/src/gui/general/HZoomable.h new file mode 100644 index 0000000..82e9aa9 --- /dev/null +++ b/src/gui/general/HZoomable.h @@ -0,0 +1,53 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_HZOOMABLE_H_ +#define _RG_HZOOMABLE_H_ + + + + + +namespace Rosegarden +{ + + + +class HZoomable +{ +public: + HZoomable() : m_hScaleFactor(1.0) {} + + void setHScaleFactor(double dy) { m_hScaleFactor = dy; } + double getHScaleFactor() const { return m_hScaleFactor; } + +protected: + double m_hScaleFactor; +}; + + +} + +#endif diff --git a/src/gui/general/LinedStaff.cpp b/src/gui/general/LinedStaff.cpp new file mode 100644 index 0000000..e2e5d12 --- /dev/null +++ b/src/gui/general/LinedStaff.cpp @@ -0,0 +1,1217 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "LinedStaff.h" + +#include "misc/Debug.h" +#include "base/Event.h" +#include "base/LayoutEngine.h" +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "base/Segment.h" +#include "base/SnapGrid.h" +#include "base/Staff.h" +#include "base/ViewElement.h" +#include "GUIPalette.h" +#include "BarLine.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +// width of pointer +// +const int pointerWidth = 3; + + +LinedStaff::LinedStaff(QCanvas *canvas, Segment *segment, + SnapGrid *snapGrid, int id, + int resolution, int lineThickness) : + Staff(*segment), + m_canvas(canvas), + m_snapGrid(snapGrid), + m_id(id), + m_x(0.0), + m_y(0), + m_margin(0.0), + m_titleHeight(0), + m_resolution(resolution), + m_lineThickness(lineThickness), + m_pageMode(LinearMode), + m_pageWidth(2000.0), // fairly arbitrary, but we need something non-zero + m_rowsPerPage(0), + m_rowSpacing(0), + m_connectingLineLength(0), + m_startLayoutX(0), + m_endLayoutX(0), + m_current(false), + m_pointer(new QCanvasLine(canvas)), + m_insertCursor(new QCanvasLine(canvas)), + m_insertCursorTime(segment->getStartTime()), + m_insertCursorTimeValid(false) +{ + initCursors(); +} + +LinedStaff::LinedStaff(QCanvas *canvas, Segment *segment, + SnapGrid *snapGrid, + int id, int resolution, int lineThickness, + double pageWidth, int rowsPerPage, int rowSpacing) : + Staff(*segment), + m_canvas(canvas), + m_snapGrid(snapGrid), + m_id(id), + m_x(0.0), + m_y(0), + m_margin(0.0), + m_titleHeight(0), + m_resolution(resolution), + m_lineThickness(lineThickness), + m_pageMode(rowsPerPage ? MultiPageMode : ContinuousPageMode), + m_pageWidth(pageWidth), + m_rowsPerPage(rowsPerPage), + m_rowSpacing(rowSpacing), + m_connectingLineLength(0), + m_startLayoutX(0), + m_endLayoutX(0), + m_current(false), + m_pointer(new QCanvasLine(canvas)), + m_insertCursor(new QCanvasLine(canvas)), + m_insertCursorTime(segment->getStartTime()), + m_insertCursorTimeValid(false) +{ + initCursors(); +} + +LinedStaff::LinedStaff(QCanvas *canvas, Segment *segment, + SnapGrid *snapGrid, + int id, int resolution, int lineThickness, + PageMode pageMode, double pageWidth, int rowsPerPage, + int rowSpacing) : + Staff(*segment), + m_canvas(canvas), + m_snapGrid(snapGrid), + m_id(id), + m_x(0.0), + m_y(0), + m_margin(0.0), + m_titleHeight(0), + m_resolution(resolution), + m_lineThickness(lineThickness), + m_pageMode(pageMode), + m_pageWidth(pageWidth), + m_rowsPerPage(rowsPerPage), + m_rowSpacing(rowSpacing), + m_connectingLineLength(0), + m_startLayoutX(0), + m_endLayoutX(0), + m_current(false), + m_pointer(new QCanvasLine(canvas)), + m_insertCursor(new QCanvasLine(canvas)), + m_insertCursorTime(segment->getStartTime()), + m_insertCursorTimeValid(false) +{ + initCursors(); +} + +LinedStaff::~LinedStaff() +{ + /*!!! No, the canvas items are all deleted by the canvas on destruction. + + deleteBars(); + for (int i = 0; i < (int)m_staffLines.size(); ++i) clearStaffLineRow(i); + */ +} + +void +LinedStaff::initCursors() +{ + QPen pen(GUIPalette::getColour(GUIPalette::Pointer)); + pen.setWidth(pointerWidth); + + m_pointer->setPen(pen); + m_pointer->setBrush(GUIPalette::getColour(GUIPalette::Pointer)); + + pen.setColor(GUIPalette::getColour(GUIPalette::InsertCursor)); + + m_insertCursor->setPen(pen); + m_insertCursor->setBrush(GUIPalette::getColour(GUIPalette::InsertCursor)); +} + +void +LinedStaff::setResolution(int resolution) +{ + m_resolution = resolution; +} + +void +LinedStaff::setLineThickness(int lineThickness) +{ + m_lineThickness = lineThickness; +} + +void +LinedStaff::setPageMode(PageMode pageMode) +{ + m_pageMode = pageMode; +} + +void +LinedStaff::setPageWidth(double pageWidth) +{ + m_pageWidth = pageWidth; +} + +void +LinedStaff::setRowsPerPage(int rowsPerPage) +{ + m_rowsPerPage = rowsPerPage; +} + +void +LinedStaff::setRowSpacing(int rowSpacing) +{ + m_rowSpacing = rowSpacing; +} + +void +LinedStaff::setConnectingLineLength(int connectingLineLength) +{ + m_connectingLineLength = connectingLineLength; +} + +int +LinedStaff::getId() const +{ + return m_id; +} + +void +LinedStaff::setX(double x) +{ + m_x = x; +} + +double +LinedStaff::getX() const +{ + return m_x; +} + +void +LinedStaff::setY(int y) +{ + m_y = y; +} + +int +LinedStaff::getY() const +{ + return m_y; +} + +void +LinedStaff::setMargin(double margin) +{ + m_margin = margin; +} + +double +LinedStaff::getMargin() const +{ + if (m_pageMode != MultiPageMode) + return 0; + return m_margin; +} + +void +LinedStaff::setTitleHeight(int titleHeight) +{ + m_titleHeight = titleHeight; +} + +int +LinedStaff::getTitleHeight() const +{ + return m_titleHeight; +} + +double +LinedStaff::getTotalWidth() const +{ + switch (m_pageMode) { + + case ContinuousPageMode: + return getCanvasXForRightOfRow(getRowForLayoutX(m_endLayoutX)) - m_x; + + case MultiPageMode: + return getCanvasXForRightOfRow(getRowForLayoutX(m_endLayoutX)) + m_margin - m_x; + + case LinearMode: + default: + return getCanvasXForLayoutX(m_endLayoutX) - m_x; + } +} + +int +LinedStaff::getTotalHeight() const +{ + switch (m_pageMode) { + + case ContinuousPageMode: + return getCanvasYForTopOfStaff(getRowForLayoutX(m_endLayoutX)) + + getHeightOfRow() - m_y; + + case MultiPageMode: + return getCanvasYForTopOfStaff(m_rowsPerPage - 1) + + getHeightOfRow() - m_y; + + case LinearMode: + default: + return getCanvasYForTopOfStaff(0) + getHeightOfRow() - m_y; + } +} + +int +LinedStaff::getHeightOfRow() const +{ + return getTopLineOffset() + getLegerLineCount() * getLineSpacing() + + getBarLineHeight() + m_lineThickness; +} + +bool +LinedStaff::containsCanvasCoords(double x, int y) const +{ + switch (m_pageMode) { + + case ContinuousPageMode: + + for (int row = getRowForLayoutX(m_startLayoutX); + row <= getRowForLayoutX(m_endLayoutX); ++row) { + if (y >= getCanvasYForTopOfStaff(row) && + y < getCanvasYForTopOfStaff(row) + getHeightOfRow()) { + return true; + } + } + + return false; + + case MultiPageMode: + + for (int row = getRowForLayoutX(m_startLayoutX); + row <= getRowForLayoutX(m_endLayoutX); ++row) { + if (y >= getCanvasYForTopOfStaff(row) && + y < getCanvasYForTopOfStaff(row) + getHeightOfRow() && + x >= getCanvasXForLeftOfRow(row) && + x <= getCanvasXForRightOfRow(row)) { + return true; + } + } + + return false; + + case LinearMode: + default: + + return (y >= getCanvasYForTopOfStaff() && + y < getCanvasYForTopOfStaff() + getHeightOfRow()); + } +} + +int +LinedStaff::getCanvasYForHeight(int h, double baseX, int baseY) const +{ + int y; + + // NOTATION_DEBUG << "LinedStaff::getCanvasYForHeight(" << h << "," << baseY + // << ")" << endl; + + if (baseX < 0) + baseX = getX() + getMargin(); + + if (baseY >= 0) { + y = getCanvasYForTopLine(getRowForCanvasCoords(baseX, baseY)); + } else { + y = getCanvasYForTopLine(); + } + + y += getLayoutYForHeight(h); + + return y; +} + +int +LinedStaff::getLayoutYForHeight(int h) const +{ + int y = ((getTopLineHeight() - h) * getLineSpacing()) / getHeightPerLine(); + if (h < getTopLineHeight() && (h % getHeightPerLine() != 0)) + ++y; + + return y; +} + +int +LinedStaff::getHeightAtCanvasCoords(double x, int y) const +{ + //!!! the lazy route: approximate, then get the right value + // by calling getCanvasYForHeight a few times... ugh + + // RG_DEBUG << "\nNotationStaff::heightOfYCoord: y = " << y + // << ", getTopLineOffset() = " << getTopLineOffset() + // << ", getLineSpacing() = " << m_npf->getLineSpacing() + // << endl; + + if (x < 0) + x = getX() + getMargin(); + + int row = getRowForCanvasCoords(x, y); + int ph = (y - getCanvasYForTopLine(row)) * getHeightPerLine() / + getLineSpacing(); + ph = getTopLineHeight() - ph; + + int i; + int mi = -2; + int md = getLineSpacing() * 2; + + int testi = -2; + int testMd = 1000; + + for (i = -1; i <= 1; ++i) { + int d = y - getCanvasYForHeight(ph + i, x, y); + if (d < 0) + d = -d; + if (d < md) { + md = d; + mi = i; + } + if (d < testMd) { + testMd = d; + testi = i; + } + } + + if (mi > -2) { + // RG_DEBUG << "LinedStaff::getHeightAtCanvasCoords: " << y + // << " -> " << (ph + mi) << " (mi is " << mi << ", distance " + // << md << ")" << endl; + // if (mi == 0) { + // RG_DEBUG << "GOOD APPROXIMATION" << endl; + // } else { + // RG_DEBUG << "BAD APPROXIMATION" << endl; + // } + return ph + mi; + } else { + RG_DEBUG << "LinedStaff::getHeightAtCanvasCoords: heuristic got " << ph << ", nothing within range (closest was " << (ph + testi) << " which is " << testMd << " away)" << endl; + return 0; + } +} + +QRect +LinedStaff::getBarExtents(double x, int y) const +{ + int row = getRowForCanvasCoords(x, y); + + for (int i = 1; i < m_barLines.size(); ++i) { + + double layoutX = m_barLines[i]->getLayoutX(); + int barRow = getRowForLayoutX(layoutX); + + if (m_pageMode != LinearMode && (barRow < row)) + continue; + + BarLine *line = m_barLines[i]; + + if (line) { + if (line->x() <= x) + continue; + + return QRect(int(m_barLines[i -1]->x()), + getCanvasYForTopOfStaff(barRow), + int(line->x() - m_barLines[i - 1]->x()), + getHeightOfRow()); + } + } + + // failure + return QRect(int(getX() + getMargin()), getCanvasYForTopOfStaff(), 4, getHeightOfRow()); +} + +double +LinedStaff::getCanvasXForLayoutX(double x) const +{ + switch (m_pageMode) { + + case ContinuousPageMode: + return m_x + x - (m_pageWidth * getRowForLayoutX(x)); + + case MultiPageMode: { + int pageNo = getRowForLayoutX(x) / getRowsPerPage(); + double cx = m_x + x - (m_pageWidth * getRowForLayoutX(x)); + cx += m_margin + (m_margin * 2 + m_pageWidth) * pageNo; + return cx; + } + + case LinearMode: + default: + return m_x + x; + } +} + +LinedStaff::LinedStaffCoords + +LinedStaff::getLayoutCoordsForCanvasCoords(double x, int y) const +{ + int row = getRowForCanvasCoords(x, y); + return LinedStaffCoords + ((row * m_pageWidth) + x - getCanvasXForLeftOfRow(row), + y - getCanvasYForTopOfStaff(row)); +} + +LinedStaff::LinedStaffCoords + +LinedStaff::getCanvasCoordsForLayoutCoords(double x, int y) const +{ + int row = getRowForLayoutX(x); + return LinedStaffCoords + (getCanvasXForLayoutX(x), getCanvasYForTopLine(row) + y); +} + +int +LinedStaff::getRowForCanvasCoords(double x, int y) const +{ + switch (m_pageMode) { + + case ContinuousPageMode: + return ((y - m_y) / m_rowSpacing); + + case MultiPageMode: { + int px = int(x - m_x - m_margin); + int pw = int(m_margin * 2 + m_pageWidth); + if (px < pw) + y -= m_titleHeight; + return (getRowsPerPage() * (px / pw)) + ((y - m_y) / m_rowSpacing); + } + + case LinearMode: + default: + return (int)((x - m_x) / m_pageWidth); + } +} + +int +LinedStaff::getCanvasYForTopOfStaff(int row) const +{ + switch (m_pageMode) { + + case ContinuousPageMode: + if (row <= 0) + return m_y; + else + return m_y + (row * m_rowSpacing); + + case MultiPageMode: + if (row <= 0) + return m_y + m_titleHeight; + else if (row < getRowsPerPage()) + return m_y + ((row % getRowsPerPage()) * m_rowSpacing) + m_titleHeight; + else + return m_y + ((row % getRowsPerPage()) * m_rowSpacing); + + case LinearMode: + default: + return m_y; + } +} + +double +LinedStaff::getCanvasXForLeftOfRow(int row) const +{ + switch (m_pageMode) { + + case ContinuousPageMode: + return m_x; + + case MultiPageMode: + return m_x + m_margin + + (m_margin*2 + m_pageWidth) * (row / getRowsPerPage()); + + case LinearMode: + default: + return m_x + (row * m_pageWidth); + } +} + +void +LinedStaff::sizeStaff(HorizontalLayoutEngine &layout) +{ + Profiler profiler("LinedStaff::sizeStaff", true); + + deleteBars(); + deleteRepeatedClefsAndKeys(); + deleteTimeSignatures(); + + // RG_DEBUG << "LinedStaff::sizeStaff" << endl; + + int lastBar = layout.getLastVisibleBarOnStaff(*this); + + double xleft = 0, xright = 0; + bool haveXLeft = false; + + xright = layout.getBarPosition(lastBar) - 1; + + TimeSignature currentTimeSignature; + + for (int barNo = layout.getFirstVisibleBarOnStaff(*this); + barNo <= lastBar; ++barNo) { + + double x = layout.getBarPosition(barNo); + + if (!haveXLeft) { + xleft = x; + haveXLeft = true; + } + + double timeSigX = 0; + TimeSignature timeSig; + bool isNew = layout.getTimeSignaturePosition(*this, barNo, timeSig, timeSigX); + + if (isNew && barNo < lastBar) { + currentTimeSignature = timeSig; + insertTimeSignature(timeSigX, currentTimeSignature); + RG_DEBUG << "LinedStaff[" << this << "]::sizeStaff: bar no " << barNo << " has time signature at " << timeSigX << endl; + } + + RG_DEBUG << "LinedStaff::sizeStaff: inserting bar at " << x << " on staff " << this << " (isNew " << isNew << ", timeSigX " << timeSigX << ")" << endl; + + bool showBarNo = + (showBarNumbersEvery() > 0 && + ((barNo + 1) % showBarNumbersEvery()) == 0); + + insertBar(x, + ((barNo == lastBar) ? 0 : + (layout.getBarPosition(barNo + 1) - x)), + layout.isBarCorrectOnStaff(*this, barNo - 1), + currentTimeSignature, + barNo, + showBarNo); + } + + m_startLayoutX = xleft; + m_endLayoutX = xright; + + drawStaffName(); + resizeStaffLines(); +} + +void +LinedStaff::deleteBars() +{ + for (BarLineList::iterator i = m_barLines.begin(); + i != m_barLines.end(); ++i) { + (*i)->hide(); + delete *i; + } + + for (LineRecList::iterator i = m_beatLines.begin(); + i != m_beatLines.end(); ++i) { + i->second->hide(); + delete i->second; + } + + for (LineRecList::iterator i = m_barConnectingLines.begin(); + i != m_barConnectingLines.end(); ++i) { + i->second->hide(); + delete i->second; + } + + for (ItemList::iterator i = m_barNumbers.begin(); + i != m_barNumbers.end(); ++i) { + (*i)->hide(); + delete *i; + } + + m_barLines.clear(); + m_beatLines.clear(); + m_barConnectingLines.clear(); + m_barNumbers.clear(); +} + +void +LinedStaff::insertBar(double layoutX, double width, bool isCorrect, + const TimeSignature &timeSig, + int barNo, bool showBarNo) +{ + // RG_DEBUG << "insertBar: " << layoutX << ", " << width + // << ", " << isCorrect << endl; + + int barThickness = m_lineThickness * 5 / 4; + + // hack to ensure the bar line appears on the correct row in + // notation page layouts, with a conditional to prevent us from + // moving the bar and beat lines in the matrix + if (!showBeatLines()) { + if (width > 0.01) { // not final bar in staff + layoutX += 1; + } else { + layoutX -= 1; + } + } + + int row = getRowForLayoutX(layoutX); + double x = getCanvasXForLayoutX(layoutX); + int y = getCanvasYForTopLine(row); + + bool firstBarInRow = false, lastBarInRow = false; + + if (m_pageMode != LinearMode && + (getRowForLayoutX(layoutX) > + getRowForLayoutX(layoutX - getMargin() - 2))) + firstBarInRow = true; + + if (m_pageMode != LinearMode && + width > 0.01 && // width == 0 for final bar in staff + (getRowForLayoutX(layoutX) < + getRowForLayoutX(layoutX + width + getMargin() + 2))) + lastBarInRow = true; + + BarStyle style = getBarStyle(barNo); + + if (style == RepeatBothBar && firstBarInRow) + style = RepeatStartBar; + + if (firstBarInRow) + insertRepeatedClefAndKey(layoutX, barNo); + + // If we're supposed to be hiding bar lines, we do just that -- + // create them as normal, then hide them. We can't simply not + // create them because we rely on this to find bar extents for + // things like double-click selection in notation. + bool hidden = false; + if (style == PlainBar && timeSig.hasHiddenBars()) + hidden = true; + + double inset = 0.0; + if (style == RepeatStartBar || style == RepeatBothBar) { + inset = getBarInset(barNo, firstBarInRow); + } + + BarLine *line = new BarLine(m_canvas, layoutX, + getBarLineHeight(), barThickness, getLineSpacing(), + (int)inset, style); + + line->moveBy(x, y); + + if (isCorrect) { + line->setPen(GUIPalette::getColour(GUIPalette::BarLine)); + line->setBrush(GUIPalette::getColour(GUIPalette::BarLine)); + } else { + line->setPen(GUIPalette::getColour(GUIPalette::BarLineIncorrect)); + line->setBrush(GUIPalette::getColour(GUIPalette::BarLineIncorrect)); + } + + line->setZ( -1); + if (hidden) + line->hide(); + else + line->show(); + + // The bar lines have to be in order of layout-x (there's no + // such interesting stipulation for beat or connecting lines) + BarLineList::iterator insertPoint = lower_bound + (m_barLines.begin(), m_barLines.end(), line, compareBars); + m_barLines.insert(insertPoint, line); + + if (lastBarInRow) { + + double xe = x + width - barThickness; + style = getBarStyle(barNo + 1); + if (style == RepeatBothBar) + style = RepeatEndBar; + + BarLine *eline = new BarLine(m_canvas, layoutX, + getBarLineHeight(), barThickness, getLineSpacing(), + 0, style); + eline->moveBy(xe, y); + + eline->setPen(GUIPalette::getColour(GUIPalette::BarLine)); + eline->setBrush(GUIPalette::getColour(GUIPalette::BarLine)); + + eline->setZ( -1); + if (hidden) + eline->hide(); + else + eline->show(); + + BarLineList::iterator insertPoint = lower_bound + (m_barLines.begin(), m_barLines.end(), eline, compareBars); + m_barLines.insert(insertPoint, eline); + } + + if (showBarNo) { + + QFont font; + font.setPixelSize(m_resolution * 3 / 2); + QFontMetrics metrics(font); + QString text = QString("%1").arg(barNo + 1); + + QCanvasItem *barNoText = new QCanvasText(text, font, m_canvas); + barNoText->setX(x); + barNoText->setY(y - metrics.height() - m_resolution * 2); + barNoText->setZ( -1); + if (hidden) + barNoText->hide(); + else + barNoText->show(); + + m_barNumbers.push_back(barNoText); + } + + QCanvasRectangle *rect = 0; + + if (showBeatLines()) { + + double gridLines; // number of grid lines per bar may be fractional + + // If the snap time is zero we default to beat markers + // + if (m_snapGrid && m_snapGrid->getSnapTime(x)) + gridLines = double(timeSig.getBarDuration()) / + double(m_snapGrid->getSnapTime(x)); + else + gridLines = timeSig.getBeatsPerBar(); + + double dx = width / gridLines; + + for (int gridLine = hidden ? 0 : 1; gridLine < gridLines; ++gridLine) { + + rect = new QCanvasRectangle + (0, 0, barThickness, getBarLineHeight(), m_canvas); + + rect->moveBy(x + gridLine * dx, y); + + double currentGrid = gridLines / double(timeSig.getBeatsPerBar()); + + rect->setPen(GUIPalette::getColour(GUIPalette::BeatLine)); + rect->setBrush(GUIPalette::getColour(GUIPalette::BeatLine)); + + // Reset to SubBeatLine colour if we're not a beat line - avoid div by zero! + // + if (currentGrid > 1.0 && double(gridLine) / currentGrid != gridLine / int(currentGrid)) { + rect->setPen(GUIPalette::getColour(GUIPalette::SubBeatLine)); + rect->setBrush(GUIPalette::getColour(GUIPalette::SubBeatLine)); + } + + rect->setZ( -1); + rect->show(); // show beat lines even if the bar lines are hidden + + LineRec beatLine(layoutX + gridLine * dx, rect); + m_beatLines.push_back(beatLine); + } + } + + if (m_connectingLineLength > 0) { + + rect = new QCanvasRectangle + (0, 0, barThickness, m_connectingLineLength, m_canvas); + + rect->moveBy(x, y); + + rect->setPen(GUIPalette::getColour(GUIPalette::StaffConnectingLine)); + rect->setBrush(GUIPalette::getColour(GUIPalette::StaffConnectingLine)); + rect->setZ( -3); + if (hidden) + rect->hide(); + else + rect->show(); + + LineRec connectingLine(layoutX, rect); + m_barConnectingLines.push_back(connectingLine); + } +} + +bool +LinedStaff::compareBars(const BarLine *barLine1, const BarLine *barLine2) +{ + return (barLine1->getLayoutX() < barLine2->getLayoutX()); +} + +bool +LinedStaff::compareBarToLayoutX(const BarLine *barLine1, int x) +{ + return (barLine1->getLayoutX() < x); +} + +void +LinedStaff::deleteTimeSignatures() +{ + // default implementation is empty +} + +void +LinedStaff::insertTimeSignature(double, const TimeSignature &) +{ + // default implementation is empty +} + +void +LinedStaff::deleteRepeatedClefsAndKeys() +{ + // default implementation is empty +} + +void +LinedStaff::insertRepeatedClefAndKey(double, int) +{ + // default implementation is empty +} + +void +LinedStaff::drawStaffName() +{ + // default implementation is empty +} + +void +LinedStaff::resizeStaffLines() +{ + int firstRow = getRowForLayoutX(m_startLayoutX); + int lastRow = getRowForLayoutX(m_endLayoutX); + + RG_DEBUG << "LinedStaff::resizeStaffLines: firstRow " + << firstRow << ", lastRow " << lastRow + << " (startLayoutX " << m_startLayoutX + << ", endLayoutX " << m_endLayoutX << ")" << endl; + + assert(lastRow >= firstRow); + + int i; + while ((int)m_staffLines.size() <= lastRow) { + m_staffLines.push_back(ItemList()); + m_staffConnectingLines.push_back(0); + } + + // Remove all the staff lines that precede the start of the staff + + for (i = 0; i < firstRow; ++i) + clearStaffLineRow(i); + + // now i == firstRow + + while (i <= lastRow) { + + double x0; + double x1; + + if (i == firstRow) { + x0 = getCanvasXForLayoutX(m_startLayoutX); + } else { + x0 = getCanvasXForLeftOfRow(i); + } + + if (i == lastRow) { + x1 = getCanvasXForLayoutX(m_endLayoutX); + } else { + x1 = getCanvasXForRightOfRow(i); + } + + resizeStaffLineRow(i, x0, x1 - x0); + + ++i; + } + + // now i == lastRow + 1 + + while (i < (int)m_staffLines.size()) + clearStaffLineRow(i++); +} + +void +LinedStaff::clearStaffLineRow(int row) +{ + for (int h = 0; h < (int)m_staffLines[row].size(); ++h) { + delete m_staffLines[row][h]; + } + m_staffLines[row].clear(); + + delete m_staffConnectingLines[row]; + m_staffConnectingLines[row] = 0; +} + +void +LinedStaff::resizeStaffLineRow(int row, double x, double length) +{ + // RG_DEBUG << "LinedStaff::resizeStaffLineRow: row " + // << row << ", x " << x << ", length " + // << length << endl; + + + // If the resolution is 8 or less, we want to reduce the blackness + // of the staff lines somewhat to make them less intrusive + + int level = 0; + int z = 2; + if (m_resolution < 6) { + z = -1; + level = (9 - m_resolution) * 32; + if (level > 200) + level = 200; + } + + QColor lineColour(level, level, level); + + int h; + + /*!!! No longer really good enough. But we could potentially use the + bar positions to sort this out + + if (m_pageMode && row > 0 && offset == 0.0) { + offset = (double)m_npf->getBarMargin() / 2; + length -= offset; + } + */ + + int y; + + delete m_staffConnectingLines[row]; + + if (m_pageMode != LinearMode && m_connectingLineLength > 0.1) { + + // rather arbitrary (dup in insertBar) + int barThickness = m_resolution / 12 + 1; + y = getCanvasYForTopLine(row); + QCanvasRectangle *line = new QCanvasRectangle + (int(x + length), y, barThickness, m_connectingLineLength, m_canvas); + line->setPen(GUIPalette::getColour(GUIPalette::StaffConnectingTerminatingLine)); + line->setBrush(GUIPalette::getColour(GUIPalette::StaffConnectingTerminatingLine)); + line->setZ( -2); + line->show(); + m_staffConnectingLines[row] = line; + + } else { + m_staffConnectingLines[row] = 0; + } + + while ((int)m_staffLines[row].size() <= getLineCount() * m_lineThickness) { + m_staffLines[row].push_back(0); + } + + int lineIndex = 0; + + for (h = 0; h < getLineCount(); ++h) { + + y = getCanvasYForHeight + (getBottomLineHeight() + getHeightPerLine() * h, + x, getCanvasYForTopLine(row)); + + if (elementsInSpaces()) { + y -= getLineSpacing() / 2 + 1; + } + + // RG_DEBUG << "LinedStaff: drawing line from (" + // << x << "," << y << ") to (" << (x+length-1) + // << "," << y << ")" << endl; + + QCanvasItem *line; + delete m_staffLines[row][lineIndex]; + m_staffLines[row][lineIndex] = 0; + + if (m_lineThickness > 1) { + QCanvasRectangle *rline = new QCanvasRectangle + (int(x), y, int(length), m_lineThickness, m_canvas); + rline->setPen(lineColour); + rline->setBrush(lineColour); + line = rline; + } else { + QCanvasLine *lline = new QCanvasLine(m_canvas); + lline->setPoints(int(x), y, int(x + length), y); + lline->setPen(lineColour); + line = lline; + } + + // if (j > 0) line->setSignificant(false); + + line->setZ(z); + m_staffLines[row][lineIndex] = line; + line->show(); + + ++lineIndex; + } + + while (lineIndex < (int)m_staffLines[row].size()) { + delete m_staffLines[row][lineIndex]; + m_staffLines[row][lineIndex] = 0; + ++lineIndex; + } +} + +void +LinedStaff::setCurrent(bool current) +{ + m_current = current; + if (m_current) { + m_insertCursor->show(); + } else { + m_insertCursor->hide(); + } +} + +double +LinedStaff::getLayoutXOfPointer() const +{ + double x = m_pointer->x(); + int row = getRowForCanvasCoords(x, int(m_pointer->y())); + return getLayoutCoordsForCanvasCoords(x, getCanvasYForTopLine(row)).first; +} + +void +LinedStaff::getPointerPosition(double &cx, int &cy) const +{ + cx = m_pointer->x(); + cy = getCanvasYForTopOfStaff(getRowForCanvasCoords(cx, int(m_pointer->y()))); +} + +double +LinedStaff::getLayoutXOfInsertCursor() const +{ + if (!m_current) return -1; + double x = m_insertCursor->x(); + int row = getRowForCanvasCoords(x, int(m_insertCursor->y())); + return getLayoutCoordsForCanvasCoords(x, getCanvasYForTopLine(row)).first; +} + +timeT +LinedStaff::getInsertCursorTime(HorizontalLayoutEngine &layout) const +{ + if (m_insertCursorTimeValid) return m_insertCursorTime; + return layout.getTimeForX(getLayoutXOfInsertCursor()); +} + +void +LinedStaff::getInsertCursorPosition(double &cx, int &cy) const +{ + if (!m_current) { + cx = -1; + cy = -1; + return ; + } + cx = m_insertCursor->x(); + cy = getCanvasYForTopOfStaff(getRowForCanvasCoords(cx, int(m_insertCursor->y()))); +} + +void +LinedStaff::setPointerPosition(double canvasX, int canvasY) +{ + int row = getRowForCanvasCoords(canvasX, canvasY); + canvasY = getCanvasYForTopOfStaff(row); + m_pointer->setX(int(canvasX)); + m_pointer->setY(int(canvasY)); + m_pointer->setZ( -30); // behind everything else + m_pointer->setPoints(0, 0, 0, getHeightOfRow() /* - 1 */); + m_pointer->show(); +} + +void +LinedStaff::setPointerPosition(HorizontalLayoutEngine &layout, + timeT time) +{ + setPointerPosition(layout.getXForTime(time)); +} + +void +LinedStaff::setPointerPosition(double layoutX) +{ + LinedStaffCoords coords = getCanvasCoordsForLayoutCoords(layoutX, 0); + setPointerPosition(coords.first, coords.second); +} + +void +LinedStaff::hidePointer() +{ + m_pointer->hide(); +} + +void +LinedStaff::setInsertCursorPosition(double canvasX, int canvasY) +{ + if (!m_current) return; + + int row = getRowForCanvasCoords(canvasX, canvasY); + canvasY = getCanvasYForTopOfStaff(row); + m_insertCursor->setX(canvasX); + m_insertCursor->setY(canvasY); + m_insertCursor->setZ( -28); // behind everything else except playback pointer + m_insertCursor->setPoints(0, 0, 0, getHeightOfRow() - 1); + m_insertCursor->show(); + m_insertCursorTimeValid = false; +} + +void +LinedStaff::setInsertCursorPosition(HorizontalLayoutEngine &layout, + timeT time) +{ + double x = layout.getXForTime(time); + LinedStaffCoords coords = getCanvasCoordsForLayoutCoords(x, 0); + setInsertCursorPosition(coords.first, coords.second); + m_insertCursorTime = time; + m_insertCursorTimeValid = true; +} + +void +LinedStaff::hideInsertCursor() +{ + m_insertCursor->hide(); +} + +void +LinedStaff::renderElements(ViewElementList::iterator, + ViewElementList::iterator) +{ + // nothing -- we assume rendering will be done by the implementation + // of positionElements +} + +void +LinedStaff::renderAllElements() +{ + renderElements(getViewElementList()->begin(), + getViewElementList()->end()); +} + +void +LinedStaff::positionAllElements() +{ + positionElements(getSegment().getStartTime(), + getSegment().getEndTime()); +} + +} diff --git a/src/gui/general/LinedStaff.h b/src/gui/general/LinedStaff.h new file mode 100644 index 0000000..1444bd2 --- /dev/null +++ b/src/gui/general/LinedStaff.h @@ -0,0 +1,759 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_LINEDSTAFF_H_ +#define _RG_LINEDSTAFF_H_ + +#include "base/Event.h" +#include "base/FastVector.h" +#include "base/Staff.h" +#include "base/ViewElement.h" +#include +#include +#include + + +class QCanvasLine; +class QCanvasItem; +class QCanvas; +class isFirstBarInRow; +class barNo; + + +namespace Rosegarden +{ + +class BarLine; +class TimeSignature; +class SnapGrid; +class Segment; +class HorizontalLayoutEngine; +class Event; + + +/** + * LinedStaff is a base class for implementations of Staff that + * display the contents of a Segment on a set of horizontal lines + * with optional vertical bar lines. + * Likely subclasses include the notation and piano-roll staffs. + * + * In general, this class handles x coordinates in floating-point, + * but y-coordinates as integers because of the requirement that + * staff lines be a precise integral distance apart. + */ + +class LinedStaff : public Staff +{ +public: + typedef std::pair LinedStaffCoords; + + enum PageMode { + LinearMode = 0, + ContinuousPageMode, + MultiPageMode + }; + + enum BarStyle { + PlainBar = 0, + DoubleBar, + HeavyDoubleBar, + RepeatEndBar, + RepeatStartBar, + RepeatBothBar, + NoVisibleBar + }; + +protected: + /** + * Create a new LinedStaff for the given Segment, with a + * linear layout. + * + * \a id is an arbitrary id for the staff in its view, + * not used within the LinedStaff implementation but + * queryable via getId + * + * \a resolution is the number of blank pixels between + * staff lines + * + * \a lineThickness is the number of pixels thick a + * staff line should be + */ + LinedStaff(QCanvas *, Segment *, SnapGrid *, + int id, int resolution, int lineThickness); + + /** + * Create a new LinedStaff for the given Segment, with a + * page layout. + * + * \a id is an arbitrary id for the staff in its view, + * not used within the LinedStaff implementation but + * queryable via getId + * + * \a resolution is the number of blank pixels between + * staff lines + * + * \a lineThickness is the number of pixels thick a + * staff line should be + * + * \a pageWidth is the width of a page, to determine + * when to break lines for page layout + * + * \a rowsPerPage is the number of rows to a page, or zero + * for a single continuous page + * + * \a rowSpacing is the distance in pixels between + * the tops of consecutive rows on this staff + */ + LinedStaff(QCanvas *, Segment *, SnapGrid *, + int id, int resolution, int lineThickness, + double pageWidth, int rowsPerPage, int rowSpacing); + + /** + * Create a new LinedStaff for the given Segment, with + * either page or linear layout. + */ + LinedStaff(QCanvas *, Segment *, SnapGrid *, + int id, int resolution, int lineThickness, PageMode pageMode, + double pageWidth, int rowsPerPage, int rowSpacing); + +public: + virtual ~LinedStaff(); + +protected: + // Methods required to define the type of staff this is + + /** + * Returns the number of visible staff lines + */ + virtual int getLineCount() const = 0; + + /** + * Returns the number of invisible staff lines + * to leave space for above (and below) the visible staff + */ + virtual int getLegerLineCount() const = 0; + + /** + * Returns the height-on-staff value for + * the bottom visible staff line (a shorthand means for + * referring to staff lines) + */ + virtual int getBottomLineHeight() const = 0; + + /** + * Returns the difference between the height-on- + * staff value of one visible staff line and the next one + * above it + */ + virtual int getHeightPerLine() const = 0; + + /** + * Returns the height-on-staff value for the top visible + * staff line. This is deliberately not virtual. + */ + int getTopLineHeight() const { + return getBottomLineHeight() + + (getLineCount() - 1) * getHeightPerLine(); + } + + /** + * Returns true if elements fill the spaces between lines, + * false if elements can fall on lines. If true, the lines + * will be displaced vertically by half a line spacing. + */ + virtual bool elementsInSpaces() const { + return false; + } + + /** + * Returns true if the staff should draw a faint vertical line at + * each beat, in between the (darker) bar lines. + */ + virtual bool showBeatLines() const { + return false; + } + + /** + * Returns the number of bars between bar-line numbers, or zero if + * bar lines should not be numbered. For example, if this + * function returns 5, every 5th bar (starting at bar 5) will be + * numbered. + */ + virtual int showBarNumbersEvery() const { + return 0; + } + + /** + * Returns the bar line / repeat style for the start of the given bar. + */ + virtual BarStyle getBarStyle(int /* barNo */) const { + return PlainBar; + } + + /** + * Returns the distance the opening (repeat) bar is inset from the + * nominal barline position. This is to accommodate the situation + * where a repeat bar has to appear after the clef and key. + */ + virtual double getBarInset(int /* barNo */, bool /* isFirstBarInRow */) const { + return 0; + } + +protected: + /// Subclass may wish to expose this + virtual void setResolution(int resolution); + + /// Subclass may wish to expose this + virtual void setLineThickness(int lineThickness); + + /// Subclass may wish to expose this + virtual void setPageMode(PageMode pageMode); + + /// Subclass may wish to expose this + virtual void setPageWidth(double pageWidth); + + /// Subclass may wish to expose this + virtual void setRowsPerPage(int rowsPerPage); + + /// Subclass may wish to expose this + virtual void setRowSpacing(int rowSpacing); + + /// Subclass may wish to expose this. Default is zero + virtual void setConnectingLineLength(int length); + +public: + /** + * Return the id of the staff. This is only useful to external + * agents, it isn't used by the LinedStaff itself. + */ + virtual int getId() const; + + /** + * Set the canvas x-coordinate of the left-hand end of the staff. + * This does not move any canvas items that have already been + * created; it should be called before the sizeStaff/positionElements + * procedure begins. + */ + virtual void setX(double x); + + /** + * Get the canvas x-coordinate of the left-hand end of the staff. + */ + virtual double getX() const; + + /** + * Set the canvas y-coordinate of the top of the first staff row. + * This does not move any canvas items that have already been + * created; it should be called before the sizeStaff/positionElements + * procedure begins. + */ + virtual void setY(int y); + + /** + * Get the canvas y-coordinate of the top of the first staff row. + */ + virtual int getY() const; + + /** + * Set the canvas width of the margin to left and right of the + * staff on each page (used only in MultiPageMode). Each staff + * row will still be pageWidth wide (that is, the margin is in + * addition to the pageWidth, not included in it). This does not + * move any canvas items that have already been created; it should + * be called before the sizeStaff/positionElements procedure + * begins. + */ + virtual void setMargin(double m); + + /** + * Get the canvas width of the left and right margins. + */ + virtual double getMargin() const; + + /** + * Set the canvas height of the area at the top of the first page + * reserved for the composition title and composer's name (used + * only in MultiPageMode). + */ + virtual void setTitleHeight(int h); + + /** + * Get the canvas height of the title area. + */ + virtual int getTitleHeight() const; + + /** + * Returns the width of the entire staff after layout. Call + * this only after you've done the full sizeStaff/positionElements + * procedure. + */ + virtual double getTotalWidth() const; + + /** + * Returns the height of the entire staff after layout. Call + * this only after you've done the full sizeStaff/positionElements + * procedure. If there are multiple rows, this will be the + * height of all rows, including any space between rows that + * is used to display other staffs. + */ + virtual int getTotalHeight() const; + + /** + * Returns the total number of pages used by the staff. + */ + int getPageCount() const { + if (m_pageMode != MultiPageMode) return 1; + else return 1 + (getRowForLayoutX(m_endLayoutX) / getRowsPerPage()); + } + + /** + * Returns the difference between the y coordinates of + * neighbouring visible staff lines. Deliberately non-virtual + */ + int getLineSpacing() const { + return m_resolution + m_lineThickness; + } + + /** + * Returns the total height of a single staff row, including ruler + */ + virtual int getHeightOfRow() const; + + /** + * Returns true if the given canvas coordinates fall within + * (any of the rows of) this staff. False if they fall in the + * gap between two rows. + */ + virtual bool containsCanvasCoords(double canvasX, int canvasY) const; + + /** + * Returns the canvas y coordinate of the specified line on the + * staff. baseX/baseY are a canvas coordinates somewhere on the + * correct row, or -1 for the default row. + */ + virtual int getCanvasYForHeight(int height, double baseX = -1, int baseY = -1) const; + + /** + * Returns the y coordinate of the specified line on the + * staff, relative to the top of the row. + */ + virtual int getLayoutYForHeight(int height) const; + + /** + * Returns the height-on-staff value nearest to the given + * canvas coordinates. + */ + virtual int getHeightAtCanvasCoords(double x, int y) const; + + /** + * Return the full width, height and origin of the bar containing + * the given canvas cooordinates. + */ + virtual QRect getBarExtents(double x, int y) const; + + /** + * Set whether this is the current staff or not. A staff that is + * current will differ visually from non-current staffs. + * + * The owner of the staffs should normally ensure that one staff + * is current (the default is non-current, even if there only is + * one staff) and that only one staff is current at once. + */ + virtual void setCurrent(bool current); + + /** + * Move the playback pointer to the layout-X coordinate + * corresponding to the given time, and show it. + */ + virtual void setPointerPosition + (HorizontalLayoutEngine&, timeT); + + /** + * Move the playback pointer to the layout-X coordinate + * corresponding to the given canvas coordinates, and show it. + */ + virtual void setPointerPosition(double x, int y); + + /** + * Move the playback pointer to the given layout-X + * coordinate, and show it. + */ + virtual void setPointerPosition(double x); + + /** + * Returns the layout-X coordinate corresponding to the current + * position of the playback pointer. + */ + virtual double getLayoutXOfPointer() const; + + /** + * Returns the canvas coordinates of the top of the playback + * pointer. + */ + virtual void getPointerPosition(double &x, int &y) const; + + /** + * Hide the playback pointer. + */ + virtual void hidePointer(); + + /** + * Move the insertion cursor to the layout-X coordinate + * corresponding to the given time, and show it. + */ + virtual void setInsertCursorPosition(HorizontalLayoutEngine&, timeT); + + /** + * Move the insertion cursor to the layout-X coordinate + * corresponding to the given canvas coordinates, and show it. + */ + virtual void setInsertCursorPosition(double x, int y); + + /** + * Returns the layout-X coordinate corresponding to the current + * position of the insertion cursor. Returns -1 if this staff + * is not current or there is some other problem. + */ + virtual double getLayoutXOfInsertCursor() const; + + /** + * Return the time of the insert cursor. + */ + virtual timeT getInsertCursorTime(HorizontalLayoutEngine&) const; + + /** + * Return the canvas coordinates of the top of the insert + * cursor. + */ + virtual void getInsertCursorPosition(double &x, int &y) const; + + /** + * Hide the insert cursor. + */ + virtual void hideInsertCursor(); + + /** + * Query the given horizontal layout object (which is assumed to + * have just completed its layout procedure) to determine the + * required extents of the staff and the positions of the bars, + * and create the bars and staff lines accordingly. It may be + * called either before or after renderElements and/or + * positionElements. + * + * No bars or staff lines will appear unless this method has + * been called. + */ + virtual void sizeStaff(HorizontalLayoutEngine& layout); + + /** + * Generate or re-generate sprites for all the elements between + * from and to. See subclasses for specific detailed comments. + * + * A very simplistic staff subclass may choose not to + * implement this (the default implementation is empty) and to + * do all the rendering work in positionElements. If rendering + * elements is slow, however, it makes sense to do it here + * because this method may be called less often. + */ + virtual void renderElements(ViewElementList::iterator from, + ViewElementList::iterator to); + + /** + * Call renderElements(from, to) on the whole staff. + */ + virtual void renderAllElements(); + + /** + * Assign suitable coordinates to the elements on the staff + * between the start and end times, based entirely on the layout + * X and Y coordinates they were given by the horizontal and + * vertical layout processes. + * + * The implementation is free to render any elements it + * chooses in this method as well. + */ + virtual void positionElements(timeT from, + timeT to) = 0; + + /** + * Call positionElements(from, to) on the whole staff. + */ + virtual void positionAllElements(); + + + /* Some optional methods for the subclass. */ + + + /** + * Return an iterator pointing to the nearest view element to the + * given canvas coordinates. + * + * If notesAndRestsOnly is true, do not return any view element + * other than a note or rest. + * + * If the closest view element is further away than + * proximityThreshold pixels in either x or y axis, return end(). + * If proximityThreshold is less than zero, treat it as infinite. + * + * Also return the clef and key in force at these coordinates. + * + * The default implementation should suit for subclasses that only + * show a single element per layout X coordinate. + */ + virtual ViewElementList::iterator getClosestElementToCanvasCoords + (double x, int y, + Event *&clef, Event *&key, + bool notesAndRestsOnly = false, int proximityThreshold = 10) { + LinedStaffCoords layoutCoords = getLayoutCoordsForCanvasCoords(x, y); + return getClosestElementToLayoutX + (layoutCoords.first, clef, key, + notesAndRestsOnly, proximityThreshold); + } + + /** + * Return an iterator pointing to the nearest view element to the + * given layout x-coordinate. + * + * If notesAndRestsOnly is true, do not return any view element + * other than a note or rest. + * + * If the closest view element is further away than + * proximityThreshold pixels in either x or y axis, return end(). + * If proximityThreshold is less than zero, treat it as infinite. + * + * Also return the clef and key in force at these coordinates. + * + * The subclass may decide whether to implement this method or not + * based on the semantics and intended usage of the class. + */ + virtual ViewElementList::iterator getClosestElementToLayoutX + (double x, + Event *&clef, Event *&key, + bool notesAndRestsOnly = false, int proximityThreshold = 10) { + return getViewElementList()->end(); + } + + /** + * Return an iterator pointing to the element "under" the given + * canvas coordinates. + * + * Return end() if there is no such element. + * + * Also return the clef and key in force at these coordinates. + * + * + * The default implementation should suit for subclasses that only + * show a single element per layout X coordinate. + */ + virtual ViewElementList::iterator getElementUnderCanvasCoords + (double x, int y, Event *&clef, Event *&key) { + LinedStaffCoords layoutCoords = getLayoutCoordsForCanvasCoords(x, y); + return getElementUnderLayoutX(layoutCoords.first, clef, key); + } + + /** + * Return an iterator pointing to the element "under" the given + * canvas coordinates. + * + * Return end() if there is no such element. + * + * Also return the clef and key in force at these coordinates. + * + * The subclass may decide whether to implement this method or not + * based on the semantics and intended usage of the class. + */ + virtual ViewElementList::iterator getElementUnderLayoutX + (double x, Event *&clef, Event *&key) { + return getViewElementList()->end(); + } + + // The default implementation of the following is empty. The + // subclass is presumed to know what the staff's name is and + // where to put it; this is simply called at some point during + // the staff-drawing process. + virtual void drawStaffName(); + + +public: + // This should not really be public -- it should be one of the + // protected methods below -- but we have some code that needs + // it and hasn't been supplied with a proper way to do without. + // Please try to avoid calling this method. + //!!! fix NotationView::doDeferredCursorMove + + // This should not really be public -- it should be one of the + // protected methods below -- but we have some code that needs + // it and hasn't been supplied with a proper way to do without. + // Please try to avoid calling this method. + //!!! fix NotationView::getStaffForCanvasCoords + LinedStaffCoords + getLayoutCoordsForCanvasCoords(double x, int y) const; + + // This should not really be public -- it should be one of the + // protected methods below -- but we have some code that needs + // it and hasn't been supplied with a proper way to do without. + // Please try to avoid calling this method. + //!!! fix NotationView::scrollToTime + LinedStaffCoords + getCanvasCoordsForLayoutCoords(double x, int y) const;//!!! + + // This should not really be public -- it should be one of the + // protected methods below -- but we have some code that needs + // it and hasn't been supplied with a proper way to do without. + // Please try to avoid calling this method. + //!!! fix NotationView::print etc + int getRowSpacing() { return m_rowSpacing; } + +protected: + // Methods that the subclass may (indeed, should) use to convert + // between the layout coordinates of elements and their canvas + // coordinates. These are deliberately not virtual. + + // Note that even linear-layout staffs have multiple rows; their + // rows all have the same y coordinate but increasing x + // coordinates, instead of the other way around. (The only reason + // for this is that it seems to be more efficient from the QCanvas + // perspective to create and manipulate many relatively short + // canvas lines rather than a smaller number of very long ones.) + + int getTopLineOffset() const { + return getLineSpacing() * getLegerLineCount(); + } + + int getBarLineHeight() const { + return getLineSpacing() * (getLineCount() - 1) + m_lineThickness; + } + + int getRowForLayoutX(double x) const { + return (int)(x / m_pageWidth); + } + + int getRowForCanvasCoords(double x, int y) const; + + int getCanvasYForTopOfStaff(int row = -1) const; + + int getCanvasYForTopLine(int row = -1) const { + return getCanvasYForTopOfStaff(row) + getTopLineOffset(); + } + + double getCanvasXForLeftOfRow(int row) const; + + double getCanvasXForRightOfRow(int row) const { + return getCanvasXForLeftOfRow(row) + m_pageWidth; + } + + LinedStaffCoords + getCanvasOffsetsForLayoutCoords(double x, int y) const { + LinedStaffCoords cc = getCanvasCoordsForLayoutCoords(x, y); + return LinedStaffCoords(cc.first - x, cc.second - y); + } + + double getCanvasXForLayoutX(double x) const; + + int getRowsPerPage() const { + return m_rowsPerPage; + } + +protected: + // Actual implementation methods. The default implementation + // shows staff lines, connecting lines (where appropriate) and bar + // lines, but does not show time signatures. To see time + // signatures, override the deleteTimeSignatures and + // insertTimeSignature methods. For repeated clefs and keys at + // the start of each row, override deleteRepeatedClefsAndKeys + // and insertRepeatedClefAndKey, but note that your layout class + // will need to allot the space for them separately. + + virtual void resizeStaffLines(); + virtual void clearStaffLineRow(int row); + virtual void resizeStaffLineRow(int row, double offset, double length); + + virtual void deleteBars(); + virtual void insertBar(double layoutX, double width, bool isCorrect, + const TimeSignature &, + int barNo, bool showBarNo); + + // The default implementations of the following two are empty. + virtual void deleteTimeSignatures(); + virtual void insertTimeSignature(double layoutX, + const TimeSignature &); + + // The default implementations of the following two are empty. + virtual void deleteRepeatedClefsAndKeys(); + virtual void insertRepeatedClefAndKey(double layoutX, int barNo); + + void initCursors(); + +protected: + + //--------------- Data members --------------------------------- + + QCanvas *m_canvas; + SnapGrid *m_snapGrid; + + int m_id; + + double m_x; + int m_y; + double m_margin; + int m_titleHeight; + int m_resolution; + int m_lineThickness; + + PageMode m_pageMode; + double m_pageWidth; + int m_rowsPerPage; + int m_rowSpacing; + int m_connectingLineLength; + + double m_startLayoutX; + double m_endLayoutX; + + bool m_current; + + typedef std::vector ItemList; + typedef std::vector ItemMatrix; + ItemMatrix m_staffLines; + ItemList m_staffConnectingLines; + + typedef std::pair LineRec; // layout-x, line + typedef FastVector LineRecList; + typedef FastVector BarLineList;//!!! should be multiset I reckon + static bool compareBars(const BarLine *, const BarLine *); + static bool compareBarToLayoutX(const BarLine *, int); + BarLineList m_barLines; + LineRecList m_beatLines; + LineRecList m_barConnectingLines; + ItemList m_barNumbers; + + QCanvasLine *m_pointer; + QCanvasLine *m_insertCursor; + timeT m_insertCursorTime; + bool m_insertCursorTimeValid; +}; + + +} + +#endif diff --git a/src/gui/general/LinedStaffManager.cpp b/src/gui/general/LinedStaffManager.cpp new file mode 100644 index 0000000..b1b92d2 --- /dev/null +++ b/src/gui/general/LinedStaffManager.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "LinedStaffManager.h" + +#include "LinedStaff.h" + + +namespace Rosegarden +{ +} diff --git a/src/gui/general/LinedStaffManager.h b/src/gui/general/LinedStaffManager.h new file mode 100644 index 0000000..44338f1 --- /dev/null +++ b/src/gui/general/LinedStaffManager.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_LINEDSTAFFMANAGER_H_ +#define _RG_LINEDSTAFFMANAGER_H_ + + + + + +namespace Rosegarden +{ + +class LinedStaff; + + +/** + * LinedStaffManager is a trivial abstract base for classes that own + * and position sets of LinedStaffs, as a convenient API to permit + * clients (such as canvas implementations) to discover which staff + * lies where. + * + * LinedStaffManager is not used by LinedStaff. + */ + +class LinedStaff; + +class LinedStaffManager +{ +public: + virtual ~LinedStaffManager() {} + virtual LinedStaff *getStaffForCanvasCoords(int x, int y) const = 0; +}; + + + +} + +#endif diff --git a/src/gui/general/MidiPitchLabel.cpp b/src/gui/general/MidiPitchLabel.cpp new file mode 100644 index 0000000..47a748b --- /dev/null +++ b/src/gui/general/MidiPitchLabel.cpp @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "MidiPitchLabel.h" +#include + +#include "document/ConfigGroups.h" +#include +#include +#include + + +namespace Rosegarden +{ + +static QString notes[] = { + i18n("C%1"), i18n("C#%1"), i18n("D%1"), i18n("D#%1"), + i18n("E%1"), i18n("F%1"), i18n("F#%1"), i18n("G%1"), + i18n("G#%1"), i18n("A%1"), i18n("A#%1"), i18n("B%1") +}; + + +MidiPitchLabel::MidiPitchLabel(int pitch) +{ + if (pitch < 0 || pitch > 127) { + + m_midiNote = ""; + + } else { + + KConfig *config = kapp->config(); + config->setGroup(GeneralOptionsConfigGroup); + int baseOctave = config->readNumEntry("midipitchoctave", -2); + + int octave = (int)(((float)pitch) / 12.0) + baseOctave; + m_midiNote = notes[pitch % 12].arg(octave); + } +} + +std::string +MidiPitchLabel::getString() const +{ + return std::string(m_midiNote.utf8().data()); +} + +QString +MidiPitchLabel::getQString() const +{ + return m_midiNote; +} + +} diff --git a/src/gui/general/MidiPitchLabel.h b/src/gui/general/MidiPitchLabel.h new file mode 100644 index 0000000..9abcc11 --- /dev/null +++ b/src/gui/general/MidiPitchLabel.h @@ -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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_MIDIPITCHLABEL_H_ +#define _RG_MIDIPITCHLABEL_H_ + +#include +#include + + + + +namespace Rosegarden +{ + + + +class MidiPitchLabel +{ +public: + MidiPitchLabel(int pitch); + + std::string getString() const; + QString getQString() const; + +private: + QString m_midiNote; + +}; + + + +} + +#endif diff --git a/src/gui/general/PixmapFunctions.cpp b/src/gui/general/PixmapFunctions.cpp new file mode 100644 index 0000000..d297dad --- /dev/null +++ b/src/gui/general/PixmapFunctions.cpp @@ -0,0 +1,271 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "PixmapFunctions.h" + +#include +#include +#include +#include +#include + +#include + +namespace Rosegarden +{ + +QBitmap +PixmapFunctions::generateMask(const QPixmap &map, const QRgb &px) +{ + QImage i(map.convertToImage()); + QImage im(i.width(), i.height(), 1, 2, QImage::LittleEndian); + + for (int y = 0; y < i.height(); ++y) { + for (int x = 0; x < i.width(); ++x) { + if (i.pixel(x, y) != px) { + im.setPixel(x, y, 1); + } else { + im.setPixel(x, y, 0); + } + } + } + + QBitmap m; + m.convertFromImage(im); + return m; +} + +QBitmap +PixmapFunctions::generateMask(const QPixmap &map) +{ + QImage i(map.convertToImage()); + QImage im(i.width(), i.height(), 1, 2, QImage::LittleEndian); + + QRgb px0(i.pixel(0, 0)); + QRgb px1(i.pixel(i.width() - 1, 0)); + QRgb px2(i.pixel(i.width() - 1, i.height() - 1)); + QRgb px3(i.pixel(0, i.height() - 1)); + + QRgb px(px0); + if (px0 != px2 && px1 == px3) + px = px1; + + for (int y = 0; y < i.height(); ++y) { + for (int x = 0; x < i.width(); ++x) { + if (i.pixel(x, y) != px) { + im.setPixel(x, y, 1); + } else { + im.setPixel(x, y, 0); + } + } + } + + QBitmap m; + m.convertFromImage(im); + return m; +} + +QPixmap +PixmapFunctions::colourPixmap(const QPixmap &map, int hue, int minValue) +{ + // assumes pixmap is currently in shades of grey; maps black -> + // solid colour and greys -> shades of colour + + QImage image = map.convertToImage(); + + int s, v; + + bool warned = false; + + for (int y = 0; y < image.height(); ++y) { + for (int x = 0; x < image.width(); ++x) { + + QColor pixel(image.pixel(x, y)); + + int oldHue; + pixel.hsv(&oldHue, &s, &v); + + if (oldHue >= 0) { + if (!warned) { + std::cerr << "PixmapFunctions::recolour: Not a greyscale pixmap " + << "(found rgb value " << pixel.red() << "," + << pixel.green() << "," << pixel.blue() + << "), hoping for the best" << std::endl; + warned = true; + } + } + + image.setPixel + (x, y, QColor(hue, + 255 - v, + v > minValue ? v : minValue, + QColor::Hsv).rgb()); + } + } + + QPixmap rmap; + rmap.convertFromImage(image); + if (map.mask()) + rmap.setMask(*map.mask()); + return rmap; +} + +QPixmap +PixmapFunctions::shadePixmap(const QPixmap &map) +{ + QImage image = map.convertToImage(); + + int h, s, v; + + for (int y = 0; y < image.height(); ++y) { + for (int x = 0; x < image.width(); ++x) { + + QColor pixel(image.pixel(x, y)); + + pixel.hsv(&h, &s, &v); + + image.setPixel + (x, y, QColor(h, + s, + 255 - ((255 - v) / 2), + QColor::Hsv).rgb()); + } + } + + QPixmap rmap; + rmap.convertFromImage(image); + if (map.mask()) + rmap.setMask(*map.mask()); + return rmap; +} + +QPixmap +PixmapFunctions::flipVertical(const QPixmap &map) +{ + QPixmap rmap; + QImage i(map.convertToImage()); + rmap.convertFromImage(i.mirror(false, true)); + + if (map.mask()) { + QImage im(map.mask()->convertToImage()); + QBitmap newMask; + newMask.convertFromImage(im.mirror(false, true)); + rmap.setMask(newMask); + } + + return rmap; +} + +QPixmap +PixmapFunctions::flipHorizontal(const QPixmap &map) +{ + QPixmap rmap; + QImage i(map.convertToImage()); + rmap.convertFromImage(i.mirror(true, false)); + + if (map.mask()) { + QImage im(map.mask()->convertToImage()); + QBitmap newMask; + newMask.convertFromImage(im.mirror(true, false)); + rmap.setMask(newMask); + } + + return rmap; +} + +std::pair +PixmapFunctions::splitPixmap(const QPixmap &pixmap, int x) +{ + QPixmap left(x, pixmap.height(), pixmap.depth()); + QBitmap leftMask(left.width(), left.height()); + + QPixmap right(pixmap.width() - x, pixmap.height(), pixmap.depth()); + QBitmap rightMask(right.width(), right.height()); + + QPainter paint; + + paint.begin(&left); + paint.drawPixmap(0, 0, pixmap, 0, 0, left.width(), left.height()); + paint.end(); + + paint.begin(&leftMask); + paint.drawPixmap(0, 0, *pixmap.mask(), 0, 0, left.width(), left.height()); + paint.end(); + + left.setMask(leftMask); + + paint.begin(&right); + paint.drawPixmap(0, 0, pixmap, left.width(), 0, right.width(), right.height()); + paint.end(); + + paint.begin(&rightMask); + paint.drawPixmap(0, 0, *pixmap.mask(), left.width(), 0, right.width(), right.height()); + paint.end(); + + right.setMask(rightMask); + + return std::pair(left, right); +} + +void +PixmapFunctions::drawPixmapMasked(QPixmap &dest, QBitmap &destMask, + int x0, int y0, + const QPixmap &src) +{ + QImage idp(dest.convertToImage()); + QImage idm(destMask.convertToImage()); + QImage isp(src.convertToImage()); + QImage ism(src.mask()->convertToImage()); + + for (int y = 0; y < isp.height(); ++y) { + for (int x = 0; x < isp.width(); ++x) { + + if (x >= ism.width()) + continue; + if (y >= ism.height()) + continue; + + if (ism.depth() == 1 && ism.pixel(x, y) == 0) + continue; + if (ism.pixel(x, y) == Qt::white.rgb()) + continue; + + int x1 = x + x0; + int y1 = y + y0; + if (x1 < 0 || x1 >= idp.width()) + continue; + if (y1 < 0 || y1 >= idp.height()) + continue; + + idp.setPixel(x1, y1, isp.pixel(x, y)); + idm.setPixel(x1, y1, 1); + } + } + + dest.convertFromImage(idp); + destMask.convertFromImage(idm); +} + +} diff --git a/src/gui/general/PixmapFunctions.h b/src/gui/general/PixmapFunctions.h new file mode 100644 index 0000000..22da0f0 --- /dev/null +++ b/src/gui/general/PixmapFunctions.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PIXMAPFUNCTIONS_H_ +#define _RG_PIXMAPFUNCTIONS_H_ + +#include +#include +#include + + +namespace Rosegarden +{ + + + +class PixmapFunctions +{ +public: + /** + * Generate a heuristic mask for the given pixmap. Unlike + * QPixmap::createHeuristicMask, this removes from the mask all + * pixels that are apparently "background" even if they appear in + * holes in the middle of the image. This is more usually what we + * want than the default behaviour of createHeuristicMask. + * + * The rgb value specifies the colour to treat as background. + * + * This function is slow. + */ + static QBitmap generateMask(const QPixmap &map, const QRgb &rgb); + + /** + * Generate a heuristic mask for the given pixmap. Unlike + * QPixmap::createHeuristicMask, this removes from the mask all + * pixels that are apparently "background" even if they appear in + * holes in the middle of the image. This is more usually what we + * want than the default behaviour of createHeuristicMask. + * + * This function calculates its own estimated colour to match as + * background. + * + * This function is slow. + */ + static QBitmap generateMask(const QPixmap &map); + + /** + * Colour a greyscale pixmap with the given hue. + * minValue specifies the minimum value (in the HSV sense) that + * will be used for any recoloured pixel. + */ + static QPixmap colourPixmap(const QPixmap &map, int hue, int minValue); + + /** + * Make a pixmap grey, or otherwise reduce its intensity. + */ + static QPixmap shadePixmap(const QPixmap &map); + + /// Return a QPixmap that is a mirror image of map (including mask) + static QPixmap flipVertical(const QPixmap &map); + + /// Return a QPixmap that is a mirror image of map (including mask) + static QPixmap flipHorizontal(const QPixmap &map); + + /// Return left and right parts of the QPixmap + static std::pair splitPixmap(const QPixmap &original, int x); + + /** + * Using QPainter::drawPixmap to draw one pixmap on another does + * not appear to take the mask into account properly. Background + * pixels in the second pixmap erase foreground pixels in the + * first one, regardless of whether they're masked or not. This + * function does what I expect. + * + * Note that the source pixmap _must_ have a mask. + */ + static void drawPixmapMasked(QPixmap &dest, QBitmap &destMask, + int x, int y, + const QPixmap &source); +}; + + +} + +#endif diff --git a/src/gui/general/PresetElement.cpp b/src/gui/general/PresetElement.cpp new file mode 100644 index 0000000..4158d69 --- /dev/null +++ b/src/gui/general/PresetElement.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 , + Chris Cannam , + Richard Bown + + This file is Copyright 2006 + D. Michael McIntyre + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "PresetElement.h" + +#include "misc/Debug.h" +#include + + +namespace Rosegarden +{ + +PresetElement::PresetElement(QString name, + int clef, + int transpose, + int highAm, + int lowAm, + int highPro, + int lowPro) : + m_name (name), + m_clef (clef), + m_transpose (transpose), + m_highAm (highAm), + m_lowAm (lowAm), + m_highPro (highPro), + m_lowPro (lowPro) +{ + RG_DEBUG << "PresetElement::PresetElement(" << endl + << " name = " << name << endl + << " clef = " << clef << endl + << " trns.= " << transpose << endl + << " higH = " << highAm << endl + << " lowA = " << lowAm << endl + << " higP = " << highPro << endl + << " lowP = " << lowPro << ")" << endl; +} + +PresetElement::~PresetElement() +{ + // nothing to do +} + +} diff --git a/src/gui/general/PresetElement.h b/src/gui/general/PresetElement.h new file mode 100644 index 0000000..24d3ee4 --- /dev/null +++ b/src/gui/general/PresetElement.h @@ -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 , + Chris Cannam , + Richard Bown + + This file is Copyright 2006 + D. Michael McIntyre + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PRESETELEMENT_H_ +#define _RG_PRESETELEMENT_H_ + +#include + +#include + + + +namespace Rosegarden +{ + +/* + * A container class for storing a set of data describing a real world + * instrument for which one is writing musical notation + */ +class PresetElement +{ +public: + + PresetElement(QString name, + int clef, + int transpose, + int highAm, + int lowAm, + int highPro, + int lowPro); + + ~PresetElement(); + + // accessors + QString getName() { return m_name; } + int getClef() { return m_clef; } + int getTranspose() { return m_transpose; } + int getHighAm() { return m_highAm; } + int getLowAm() { return m_lowAm; } + int getHighPro() { return m_highPro; } + int getLowPro() { return m_lowPro; } + +private: + QString m_name; + int m_clef; + int m_transpose; + int m_highAm; + int m_lowAm; + int m_highPro; + int m_lowPro; +}; // PresetElement + +typedef std::vector ElementContainer; + +} + +#endif diff --git a/src/gui/general/PresetGroup.cpp b/src/gui/general/PresetGroup.cpp new file mode 100644 index 0000000..4a457a9 --- /dev/null +++ b/src/gui/general/PresetGroup.cpp @@ -0,0 +1,269 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2006 + D. Michael McIntyre + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "PresetGroup.h" + +#include "misc/Debug.h" +#include "misc/Strings.h" +#include "gui/general/ClefIndex.h" +#include "base/Exception.h" +#include "CategoryElement.h" +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +PresetGroup::PresetGroup() : + m_errorString(i18n("unknown error")), + m_elCategoryName(""), + m_elInstrumentName(""), + m_elClef(0), + m_elTranspose(0), + m_elLowAm(0), + m_elHighAm(0), + m_elLowPro(0), + m_elHighPro(0), + m_lastCategory( -1), + m_currentCategory( -1), + m_lastInstrument( -1), + m_currentInstrument( -1), + m_name(false), + m_clef(false), + m_transpose(false), + m_amateur(false), + m_pro(false) +{ + m_presetDirectory = KGlobal::dirs()->findResource("appdata", "presets/"); + + QString language = KGlobal::locale()->language(); + + QString presetFileName = QString("%1/presets-%2.xml") + .arg(m_presetDirectory).arg(language); + + if (!QFileInfo(presetFileName).isReadable()) { + + RG_DEBUG << "Failed to open " << presetFileName << endl; + + language.replace(QRegExp("_.*$"), ""); + presetFileName = QString("%1/presets-%2.xml") + .arg(m_presetDirectory).arg(language); + + if (!QFileInfo(presetFileName).isReadable()) { + + RG_DEBUG << "Failed to open " << presetFileName << endl; + + presetFileName = QString("%1/presets.xml") + .arg(m_presetDirectory); + + if (!QFileInfo(presetFileName).isReadable()) { + + RG_DEBUG << "Failed to open " << presetFileName << endl; + + throw PresetFileReadFailed + (qstrtostr(i18n("Can't open preset file %1"). + arg(presetFileName))); + } + } + } + + QFile presetFile(presetFileName); + + QXmlInputSource source(presetFile); + QXmlSimpleReader reader; + reader.setContentHandler(this); + reader.setErrorHandler(this); + bool ok = reader.parse(source); + presetFile.close(); + + if (!ok) { + throw PresetFileReadFailed(qstrtostr(m_errorString)); + } +} + +PresetGroup::~PresetGroup() +{ + //!!! do I have anything to do here? +} + +bool +PresetGroup::startElement(const QString &, const QString &, + const QString &qName, + const QXmlAttributes &attributes) +{ + QString lcName = qName.lower(); + + // RG_DEBUG << "PresetGroup::startElement: processing starting element: " << lcName << endl; + + if (lcName == "category") { + + QString s = attributes.value("name"); + if (s) { + m_elCategoryName = s; + // increment the current category number + m_lastCategory = m_currentCategory; + m_currentCategory++; + + // reset the instrument counter going into the new category + m_lastInstrument = -1; + m_currentInstrument = -1; + + RG_DEBUG << "PresetGroup::startElement: adding category " << m_elCategoryName << " last: " + << m_lastCategory << " curr: " << m_currentCategory << endl; + + // add new CategoryElement to m_categories, in order to contain + // subsequent PresetElements + CategoryElement ce(m_elCategoryName); + m_categories.push_back(ce); + } + + } else if (lcName == "instrument") { + + QString s = attributes.value("name"); + if (s) { + m_elInstrumentName = s; + m_name = true; + + // increment the current instrument number + m_lastInstrument = m_currentInstrument; + m_currentInstrument++; + } + + } else if (lcName == "clef") { + QString s = attributes.value("type"); + if (s) { + m_elClef = clefNameToClefIndex(s); + m_clef = true; + } + } else if (lcName == "transpose") { + QString s = attributes.value("value"); + if (s) { + m_elTranspose = s.toInt(); + m_transpose = true; + } + + } else if (lcName == "range") { + QString s = attributes.value("class"); + + if (s == "amateur") { + s = attributes.value("low"); + if (s) { + m_elLowAm = s.toInt(); + m_amateur = true; + } + + s = attributes.value("high"); + if (s && m_amateur) { + m_elHighAm = s.toInt(); + } else { + return false; + } + + } else if (s == "professional") { + s = attributes.value("low"); + if (s) { + m_pro = true; + m_elLowPro = s.toInt(); + } + + s = attributes.value("high"); + if (s && m_pro) { + m_elHighPro = s.toInt(); + } else { + return false; + } + } else { + return false; + } + } + + // RG_DEBUG << "PresetGroup::startElement(): accumulating flags:" << endl + // << " name: " << (m_name ? "true" : "false") << endl + // << " clef: " << (m_clef ? "true" : "false") << endl + // << "transpose: " << (m_transpose ? "true" : "false") << endl + // << " am. rng: " << (m_amateur ? "true" : "false") << endl + // << " pro rng: " << (m_pro ? "true" : "false") << endl; + + // once we have assembled all the bits, create a new PresetElement + if (m_name && m_clef && m_transpose && m_amateur && m_pro) { + m_categories[m_currentCategory].addPreset(m_elInstrumentName, + m_elClef, + m_elTranspose, + m_elHighAm, + m_elLowAm, + m_elHighPro, + m_elLowPro); + // increment the current instrument + //!!! (is this ever going to be needed?) + m_lastInstrument = m_currentInstrument; + m_currentInstrument++; + + // reset the "do we have a whole preset yet?" flags + m_name = false; + m_clef = false; + m_transpose = false; + m_amateur = false; + m_pro = false; + } + + return true; + +} // startElement + +bool +PresetGroup::error(const QXmlParseException& exception) +{ + RG_DEBUG << "PresetGroup::error(): jubilation and glee, we have an error, whee!" << endl; + + 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 +PresetGroup::fatalError(const QXmlParseException& exception) +{ + RG_DEBUG << "PresetGroup::fatalError(): double your jubilation, and triple your glee, a fatal error doth it be!" << endl; + 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); +} + +} diff --git a/src/gui/general/PresetGroup.h b/src/gui/general/PresetGroup.h new file mode 100644 index 0000000..476a878 --- /dev/null +++ b/src/gui/general/PresetGroup.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 , + Chris Cannam , + Richard Bown + + This file is Copyright 2006 + D. Michael McIntyre + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PRESETGROUP_H_ +#define _RG_PRESETGROUP_H_ + +#include "base/Exception.h" +#include "CategoryElement.h" +#include +#include + + +class QXmlParseException; +class QXmlAttributes; + + +namespace Rosegarden +{ + +/* + * Read presets.xml from disk and store a collection of PresetElement objects + * which can then be used to populate and run the chooser GUI + */ +class PresetGroup : public QXmlDefaultHandler +{ +public: + typedef Exception PresetFileReadFailed; + + PresetGroup(); // load and parse the XML mapping file + ~PresetGroup(); + + CategoriesContainer getCategories() { return m_categories; } + //CategoryElement getCategoryByIndex(int index) { return m_categories [index]; } + + // Xml handler methods: + + virtual bool startElement (const QString& namespaceURI, const QString& localName, + const QString& qName, const QXmlAttributes& atts); + + bool error(const QXmlParseException& exception); + bool fatalError(const QXmlParseException& exception); + + // I don't think I have anything to do with this, but it must return true? +// bool characters(const QString &) { return true; } + +private: + + //--------------- Data members --------------------------------- + CategoriesContainer m_categories; + + // For use when reading the XML file: + QString m_errorString; + QString m_presetDirectory; + + QString m_elCategoryName; + QString m_elInstrumentName; + int m_elClef; + int m_elTranspose; + int m_elLowAm; + int m_elHighAm; + int m_elLowPro; + int m_elHighPro; + + int m_lastCategory; + int m_currentCategory; + int m_lastInstrument; + int m_currentInstrument; + + bool m_name; + bool m_clef; + bool m_transpose; + bool m_amateur; + bool m_pro; + +}; // PresetGroup + + +} + +#endif diff --git a/src/gui/general/PresetHandlerDialog.cpp b/src/gui/general/PresetHandlerDialog.cpp new file mode 100644 index 0000000..6081f85 --- /dev/null +++ b/src/gui/general/PresetHandlerDialog.cpp @@ -0,0 +1,281 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file is Copyright 2006 + D. Michael McIntyre + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "PresetHandlerDialog.h" +#include +#include + +#include +#include "misc/Debug.h" +#include "document/ConfigGroups.h" +#include "CategoryElement.h" +#include "PresetElement.h" +#include "PresetGroup.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +PresetHandlerDialog::PresetHandlerDialog(QWidget *parent, bool fromNotation) + : KDialogBase(parent, "presethandlerdialog", true, i18n("Load track parameters preset"), Ok | Cancel, Ok), + m_config(kapp->config()), + m_fromNotation(fromNotation) +{ + m_presets = new PresetGroup(); + m_categories = m_presets->getCategories(); + if (m_fromNotation) setCaption(i18n("Convert notation for...")); + + initDialog(); +} + +PresetHandlerDialog::~PresetHandlerDialog() +{ + // delete m_presets + if (m_presets != NULL) { + delete m_presets; + } +} + +void +PresetHandlerDialog::initDialog() +{ + RG_DEBUG << "PresetHandlerDialog::initDialog()" << endl; + + QVBox *vBox = makeVBoxMainWidget(); + + QFrame *frame = new QFrame(vBox); + + QGridLayout *layout = new QGridLayout(frame, 6, 5, 10, 5); + + QLabel *title = new QLabel(i18n("Select preset track parameters for:"), frame); + if (m_fromNotation) title->setText(i18n("Create appropriate notation for:")); + + QLabel *catlabel = new QLabel(i18n("Category"), frame); + m_categoryCombo = new KComboBox(frame); + + QLabel *inslabel = new QLabel(i18n("Instrument"), frame); + m_instrumentCombo = new KComboBox(frame); + + QLabel *plylabel = new QLabel(i18n("Player Ability"), frame); + m_playerCombo = new KComboBox(frame); + m_playerCombo->insertItem(i18n("Amateur")); + m_playerCombo->insertItem(i18n("Professional")); + + QGroupBox *scopeBox = new QButtonGroup + (1, Horizontal, i18n("Scope"), frame); + if (m_fromNotation) { + QRadioButton *onlySelectedSegments = new + QRadioButton(i18n("Only selected segments"), scopeBox); + m_convertAllSegments = new + QRadioButton(i18n("All segments in this track"), scopeBox); + onlySelectedSegments->setChecked(true); + } + else { + QRadioButton *onlyNewSegments = new + QRadioButton(i18n("Only for new segments"), scopeBox); + m_convertSegments = new + QRadioButton(i18n("Convert existing segments"), scopeBox); + onlyNewSegments->setChecked(true); + } + + layout->addMultiCellWidget(title, 0, 0, 0, 1, AlignLeft); + layout->addWidget(catlabel, 1, 0, AlignRight); + layout->addWidget(m_categoryCombo, 1, 1); + layout->addWidget(inslabel, 2, 0, AlignRight); + layout->addWidget(m_instrumentCombo, 2, 1); + layout->addWidget(plylabel, 3, 0, AlignRight); + layout->addWidget(m_playerCombo, 3, 1); + layout->addMultiCellWidget(scopeBox, 4, 4, 0, 1, AlignLeft); + + populateCategoryCombo(); + // try to set to same category used previously + m_config->setGroup(GeneralOptionsConfigGroup); + m_categoryCombo->setCurrentItem(m_config->readNumEntry("category_combo_index", 0)); + + // populate the instrument combo + slotCategoryIndexChanged(m_categoryCombo->currentItem()); + + // try to set to same instrument used previously + m_config->setGroup(GeneralOptionsConfigGroup); + m_instrumentCombo->setCurrentItem(m_config->readNumEntry("instrument_combo_index", 0)); + + // set to same player used previously (this one can't fail, unlike the + // others, because the contents of this combo are static) + m_playerCombo->setCurrentItem(m_config->readNumEntry("player_combo_index", 0)); + + if (m_fromNotation){ + m_convertAllSegments->setChecked(m_config->readBoolEntry("convert_all_segments", 0)); + } + else { + m_convertSegments->setChecked(m_config->readBoolEntry("convert_segments", 0)); + } + + + connect(m_categoryCombo, SIGNAL(activated(int)), + SLOT(slotCategoryIndexChanged(int))); +} + +QString +PresetHandlerDialog::getName() +{ + return m_instrumentCombo->currentText(); +} + +int +PresetHandlerDialog::getClef() +{ + PresetElement p = m_categories[m_categoryCombo->currentItem()]. + getPresetByIndex(m_instrumentCombo->currentItem()); + + return p.getClef(); +} + +int +PresetHandlerDialog::getTranspose() +{ + PresetElement p = m_categories[m_categoryCombo->currentItem()]. + getPresetByIndex(m_instrumentCombo->currentItem()); + + return p.getTranspose(); +} + +int +PresetHandlerDialog::getLowRange() +{ + PresetElement p = m_categories[m_categoryCombo->currentItem()]. + getPresetByIndex(m_instrumentCombo->currentItem()); + // 0 == amateur + // 1 == pro + if (m_playerCombo->currentItem() == 0) { + return p.getLowAm(); + } else { + return p.getLowPro(); + } +} + +int +PresetHandlerDialog::getHighRange() +{ + PresetElement p = m_categories[m_categoryCombo->currentItem()]. + getPresetByIndex(m_instrumentCombo->currentItem()); + // 0 == amateur + // 1 == pro + if (m_playerCombo->currentItem() == 0) { + return p.getHighAm(); + } else { + return p.getHighPro(); + } +} + +bool +PresetHandlerDialog::getConvertAllSegments() +{ + if (m_fromNotation) { + return m_convertAllSegments && m_convertAllSegments->isChecked(); + } + else { + return m_convertSegments && m_convertSegments->isChecked(); + } +} + +bool +PresetHandlerDialog::getConvertOnlySelectedSegments() +{ + if (m_fromNotation) { + return m_convertAllSegments && !m_convertAllSegments->isChecked(); + } + else { + return false; + } +} + +void +PresetHandlerDialog::populateCategoryCombo() +{ + RG_DEBUG << "PresetHandlerDialog::populateCategoryCombo()" << endl; + + for (CategoriesContainer::iterator i = m_categories.begin(); + i != m_categories.end(); ++i) { + + RG_DEBUG << " adding category: " << (*i).getName() << endl; + + m_categoryCombo->insertItem((*i).getName()); + } +} + +void +PresetHandlerDialog::slotCategoryIndexChanged(int index) +{ + RG_DEBUG << "PresetHandlerDialog::slotCategoryIndexChanged(" << index << ")" << endl; + + CategoryElement e = m_categories[index]; + ElementContainer c = e.getPresets(); + + m_instrumentCombo->clear(); + + for (ElementContainer::iterator i = c.begin(); + i != c.end(); ++i) { + + RG_DEBUG << " adding instrument: " << (*i).getName() << endl; + + m_instrumentCombo->insertItem((*i).getName()); + } + +} + +void +PresetHandlerDialog::slotOk() +{ + m_config->setGroup(GeneralOptionsConfigGroup); + m_config->writeEntry("category_combo_index", m_categoryCombo->currentItem()); + m_config->writeEntry("instrument_combo_index", m_instrumentCombo->currentItem()); + m_config->writeEntry("player_combo_index", m_playerCombo->currentItem()); + + if (m_fromNotation) { + m_config->writeEntry("convert_all_segments", m_convertAllSegments->isChecked()); + } + else { + m_config->writeEntry("convert_segments", m_convertSegments->isChecked()); + } + + QDialog::accept(); +} + +} +#include "PresetHandlerDialog.moc" diff --git a/src/gui/general/PresetHandlerDialog.h b/src/gui/general/PresetHandlerDialog.h new file mode 100644 index 0000000..879ddca --- /dev/null +++ b/src/gui/general/PresetHandlerDialog.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 , + Chris Cannam , + Richard Bown + + This file is Copyright 2006 + D. Michael McIntyre + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PRESETHANDLERDIALOG_H_ +#define _RG_PRESETHANDLERDIALOG_H_ + +#include +#include +#include +#include "CategoryElement.h" + +class QWidget; +class KConfig; +class KComboBox; + + +namespace Rosegarden +{ + +class PresetGroup; + + +class PresetHandlerDialog : public KDialogBase +{ + Q_OBJECT + +public: + + PresetHandlerDialog(QWidget* parent, bool fromNotation = false); + ~PresetHandlerDialog(); + + PresetGroup *m_presets; + CategoriesContainer m_categories; + + KConfig *m_config; + bool m_fromNotation; + + //-------[ accessor functions ]------------------------ + + QString getName(); + + int getClef(); + int getTranspose(); + int getLowRange(); + int getHighRange(); + bool getConvertAllSegments(); + bool getConvertOnlySelectedSegments(); + +protected: + + //--------[ member functions ]------------------------- + + // initialize the dialog + void initDialog(); + + // populate the category combo + void populateCategoryCombo(); + + + //---------[ data members ]----------------------------- + + KComboBox *m_categoryCombo; + KComboBox *m_instrumentCombo; + KComboBox *m_playerCombo; + QRadioButton *m_convertSegments; + QRadioButton *m_convertAllSegments; + +protected slots: + + // de-populate and re-populate the Instrument combo when the category + // changes. + void slotCategoryIndexChanged(int index); + + // write out settings to kconfig data for next time and call accept() + void slotOk(); + +}; // PresetHandlerDialog + + +} + +#endif diff --git a/src/gui/general/ProgressReporter.cpp b/src/gui/general/ProgressReporter.cpp new file mode 100644 index 0000000..0d9e896 --- /dev/null +++ b/src/gui/general/ProgressReporter.cpp @@ -0,0 +1,53 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ProgressReporter.h" + +#include + + +namespace Rosegarden +{ + +ProgressReporter::ProgressReporter(QObject* parent, const char* name) + : QObject(parent, name), m_isCancelled(false) +{} + + +void ProgressReporter::throwIfCancelled() +{ + if (m_isCancelled) { + m_isCancelled = false; + throw Cancelled(); + } +} + +void ProgressReporter::slotCancel() +{ + m_isCancelled = true; +}; + +} +#include "ProgressReporter.moc" diff --git a/src/gui/general/ProgressReporter.h b/src/gui/general/ProgressReporter.h new file mode 100644 index 0000000..d8aa306 --- /dev/null +++ b/src/gui/general/ProgressReporter.h @@ -0,0 +1,80 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_PROGRESSREPORTER_H_ +#define _RG_PROGRESSREPORTER_H_ + +#include + + + + +namespace Rosegarden +{ + + + +class ProgressReporter : public QObject +{ + Q_OBJECT +public: + ProgressReporter(QObject* parent, const char* name = 0); + + // exception class for cancellations + class Cancelled { }; + +protected: + /** + * Call this at appropriate times if you know Qt isn't in the stack + */ + void throwIfCancelled(); + + /* + We have to use these accessors rather than throwing directly + from slotCancel() because Qt is generally compiled without + exception support, so we can't throw from a slot. + */ + bool isOperationCancelled() const { return m_isCancelled; } +// void resetOperationCancelledState() { m_isCancelled = false; } + +protected slots: + virtual void slotCancel(); + +signals: + /// Report progress + void setProgress(int); + void incrementProgress(int); + void setOperationName(QString); + +protected: + //--------------- Data members --------------------------------- + bool m_isCancelled; +}; + + + +} + +#endif diff --git a/src/gui/general/RosegardenCanvasView.cpp b/src/gui/general/RosegardenCanvasView.cpp new file mode 100644 index 0000000..a829aac --- /dev/null +++ b/src/gui/general/RosegardenCanvasView.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RosegardenCanvasView.h" + +#include "misc/Debug.h" +#include "gui/general/CanvasItemGC.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +RosegardenCanvasView::RosegardenCanvasView(QCanvas* canvas, + QWidget* parent, + const char* name, WFlags f) + : QCanvasView(canvas, parent, name, f), + m_bottomWidget(0), + m_currentBottomWidgetHeight( -1), + m_leftWidget(0), + m_smoothScroll(true), + m_smoothScrollTimeInterval(DefaultSmoothScrollTimeInterval), + m_minDeltaScroll(DefaultMinDeltaScroll), + m_autoScrollTime(InitialScrollTime), + m_autoScrollAccel(InitialScrollAccel), + m_autoScrollXMargin(0), + m_autoScrollYMargin(0), + m_currentScrollDirection(None), + m_scrollDirectionConstraint(NoFollow), + m_autoScrolling(false) +{ + setDragAutoScroll(true); + connect( &m_autoScrollTimer, SIGNAL( timeout() ), + this, SLOT( doAutoScroll() ) ); +} + +void RosegardenCanvasView::fitWidthToContents() +{ + QRect allItemsBoundingRect; + + QCanvasItemList items = canvas()->allItems(); + + QCanvasItemList::Iterator it; + + for (it = items.begin(); it != items.end(); ++it) { + allItemsBoundingRect |= (*it)->boundingRect(); + } + + QSize currentSize = canvas()->size(); + resizeContents(allItemsBoundingRect.width(), currentSize.height()); +} + +void RosegardenCanvasView::setBottomFixedWidget(QWidget* w) +{ + m_bottomWidget = w; + if (m_bottomWidget) { + int lww = m_leftWidget ? m_leftWidget->sizeHint().width() : 0; + m_bottomWidget->reparent(this, 0, QPoint(0, 0)); + m_bottomWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); + setMargins(lww, 0, 0, m_bottomWidget->sizeHint().height()); + } +} + +void RosegardenCanvasView::slotUpdate() +{ + CanvasItemGC::gc(); + canvas()->update(); +} + +// Smooth scroll checks +// + +const int RosegardenCanvasView::AutoscrollMargin = 16; +const int RosegardenCanvasView::InitialScrollTime = 30; +const int RosegardenCanvasView::InitialScrollAccel = 5; +const int RosegardenCanvasView::MaxScrollDelta = 100; // max a.scroll speed +const double RosegardenCanvasView::ScrollAccelValue = 1.04;// acceleration rate + +const int RosegardenCanvasView::DefaultSmoothScrollTimeInterval = 10; +const double RosegardenCanvasView::DefaultMinDeltaScroll = 1.2; + +void RosegardenCanvasView::startAutoScroll() +{ + // RG_DEBUG << "RosegardenCanvasView::startAutoScroll()\n"; + + if ( !m_autoScrollTimer.isActive() ) { + m_autoScrollTime = InitialScrollTime; + m_autoScrollAccel = InitialScrollAccel; + m_autoScrollTimer.start( m_autoScrollTime ); + } + + QPoint autoScrollStartPoint = viewport()->mapFromGlobal( QCursor::pos() ); + m_autoScrollYMargin = autoScrollStartPoint.y() / 10; + m_autoScrollXMargin = autoScrollStartPoint.x() / 10; + + m_autoScrolling = true; +} + +void RosegardenCanvasView::startAutoScroll(int directionConstraint) +{ + setScrollDirectionConstraint(directionConstraint); + startAutoScroll(); +} + +void RosegardenCanvasView::stopAutoScroll() +{ + // RG_DEBUG << "RosegardenCanvasView::stopAutoScroll()\n"; + + m_autoScrollTimer.stop(); + m_minDeltaScroll = DefaultMinDeltaScroll; + m_currentScrollDirection = None; + + m_autoScrolling = false; +} + +void RosegardenCanvasView::doAutoScroll() +{ + // RG_DEBUG << "RosegardenCanvasView::doAutoScroll()\n"; + + QPoint p = viewport()->mapFromGlobal( QCursor::pos() ); + QPoint dp = p - m_previousP; + m_previousP = p; + + m_autoScrollTimer.start( m_autoScrollTime ); + ScrollDirection scrollDirection = None; + + int dx = 0, dy = 0; + if (m_scrollDirectionConstraint & FollowVertical) { + if ( p.y() < m_autoScrollYMargin ) { + dy = -(int(m_minDeltaScroll)); + scrollDirection = Top; + } else if ( p.y() > visibleHeight() - m_autoScrollYMargin ) { + dy = + (int(m_minDeltaScroll)); + scrollDirection = Bottom; + } + } + bool startDecelerating = false; + if (m_scrollDirectionConstraint & FollowHorizontal) { + + // RG_DEBUG << "p.x() : " << p.x() << " - visibleWidth : " << visibleWidth() << " - autoScrollXMargin : " << m_autoScrollXMargin << endl; + + if ( p.x() < m_autoScrollXMargin ) { + if ( dp.x() > 0 ) { + startDecelerating = true; + m_minDeltaScroll /= ScrollAccelValue; + } + dx = -(int(m_minDeltaScroll)); + scrollDirection = Left; + } else if ( p.x() > visibleWidth() - m_autoScrollXMargin ) { + if ( dp.x() < 0 ) { + startDecelerating = true; + m_minDeltaScroll /= ScrollAccelValue; + } + dx = + (int(m_minDeltaScroll)); + scrollDirection = Right; + } + } + + // RG_DEBUG << "dx: " << dx << ", dy: " << dy << endl; + + if ( (dx || dy) && + ((scrollDirection == m_currentScrollDirection) || (m_currentScrollDirection == None)) ) { + scrollBy(dx, dy); + if ( startDecelerating ) + m_minDeltaScroll /= ScrollAccelValue; + else + m_minDeltaScroll *= ScrollAccelValue; + if (m_minDeltaScroll > MaxScrollDelta ) + m_minDeltaScroll = MaxScrollDelta; + m_currentScrollDirection = scrollDirection; + + } else { + // Don't automatically stopAutoScroll() here, the mouse button + // is presumably still pressed. + m_minDeltaScroll = DefaultMinDeltaScroll; + m_currentScrollDirection = None; + } + +} + +bool RosegardenCanvasView::isTimeForSmoothScroll() +{ + if (m_smoothScroll) { + int ta = m_scrollAccelerationTimer.elapsed(); + int t = m_scrollTimer.elapsed(); + + // RG_DEBUG << "t = " << t << ", ta = " << ta << ", int " << m_smoothScrollTimeInterval << ", delta " << m_minDeltaScroll << endl; + + if (t < m_smoothScrollTimeInterval) { + + return false; + + } else { + + if (ta > 300) { + // reset smoothScrollTimeInterval + m_smoothScrollTimeInterval = DefaultSmoothScrollTimeInterval; + m_minDeltaScroll = DefaultMinDeltaScroll; + m_scrollAccelerationTimer.restart(); + } else if (ta > 50) { + // m_smoothScrollTimeInterval /= 2; + m_minDeltaScroll *= 1.08; + m_scrollAccelerationTimer.restart(); + } + + m_scrollTimer.restart(); + return true; + } + } + + return true; +} + +void RosegardenCanvasView::slotScrollHoriz(int hpos) +{ + QScrollBar* hbar = getMainHorizontalScrollBar(); + int currentContentYPos = contentsY(); + + /* Lots of performance hitting debug + RG_DEBUG << "RosegardenCanvasView::slotScrollHoriz: hpos is " << hpos + << ", contentsX is " << contentsX() << ", visibleWidth is " + << visibleWidth() << endl; + */ + + if (hpos == 0) { + + // returning to zero + // hbar->setValue(0); + setContentsPos(0, currentContentYPos); + + } else if (hpos > (contentsX() + + visibleWidth() * 1.6) || + hpos < (contentsX() - + visibleWidth() * 0.7)) { + + // miles off one side or the other + // hbar->setValue(hpos - int(visibleWidth() * 0.4)); + setContentsPos(hpos - int(visibleWidth() * 0.4), currentContentYPos); + + } else if (hpos > (contentsX() + + visibleWidth() * 0.9)) { + + // moving off the right hand side of the view + // hbar->setValue(hbar->value() + int(visibleWidth() * 0.6)); + setContentsPos(hbar->value() + int(visibleWidth() * 0.6), currentContentYPos); + + } else if (hpos < (contentsX() + + visibleWidth() * 0.1)) { + + // moving off the left + // hbar->setValue(hbar->value() - int(visibleWidth() * 0.6)); + setContentsPos(hbar->value() - int(visibleWidth() * 0.6), currentContentYPos); + } +} + +void RosegardenCanvasView::slotScrollHorizSmallSteps(int hpos) +{ + QScrollBar* hbar = getMainHorizontalScrollBar(); + int currentContentYPos = contentsY(); + + int diff = 0; + + if (hpos == 0) { + + // returning to zero + // hbar->setValue(0); + setContentsPos(0, currentContentYPos); + + } else if ((diff = int(hpos - (contentsX() + + visibleWidth() * 0.90))) > 0) { + + // moving off the right hand side of the view + + int delta = diff / 6; + int diff10 = std::min(diff, (int)m_minDeltaScroll); + delta = std::max(delta, diff10); + + // hbar->setValue(hbar->value() + delta); + setContentsPos(hbar->value() + delta, currentContentYPos); + + } else if ((diff = int(hpos - (contentsX() + + visibleWidth() * 0.10))) < 0) { + // moving off the left + + int delta = -diff / 6; + int diff10 = std::min( -diff, (int)m_minDeltaScroll); + delta = std::max(delta, diff10); + + // hbar->setValue(hbar->value() - delta); + setContentsPos(hbar->value() - delta, currentContentYPos); + + } +} + +void RosegardenCanvasView::slotScrollVertSmallSteps(int vpos) +{ + QScrollBar* vbar = verticalScrollBar(); + + // RG_DEBUG << "RosegardenCanvasView::slotScrollVertSmallSteps: vpos is " << vpos << ", contentsY is " << contentsY() << ", visibleHeight is " << visibleHeight() << endl; + + // As a special case (or hack), ignore any request made before we've + // actually been rendered and sized + if (visibleHeight() <= 1) + return ; + + int diff = 0; + + if (vpos == 0) { + + // returning to zero + vbar->setValue(0); + + } else if ((diff = int(vpos - (contentsY() + + visibleHeight() * 0.90))) > 0) { + + // moving off up + + int delta = diff / 6; + int diff10 = std::min(diff, (int)m_minDeltaScroll); + delta = std::max(delta, diff10); + + vbar->setValue(vbar->value() + diff); + + } else if ((diff = int(vpos - (contentsY() + + visibleHeight() * 0.10))) < 0) { + + // moving off down + + int delta = -diff / 6; + int diff10 = std::min( -diff, (int)m_minDeltaScroll); + delta = std::max(delta, diff10); + + vbar->setValue(vbar->value() - delta); + + } +} + +void RosegardenCanvasView::slotScrollVertToTop(int vpos) +{ + QScrollBar* vbar = verticalScrollBar(); + if (vpos < visibleHeight() / 3) + vbar->setValue(0); + else + vbar->setValue(vpos - visibleHeight() / 5); +} + +void RosegardenCanvasView::slotSetScrollPos(const QPoint &pos) +{ + getMainHorizontalScrollBar()->setValue(pos.x()); + verticalScrollBar()->setValue(pos.y()); +} + +void RosegardenCanvasView::resizeEvent(QResizeEvent* e) +{ + QCanvasView::resizeEvent(e); + if (!horizontalScrollBar()->isVisible()) + updateBottomWidgetGeometry(); + updateLeftWidgetGeometry(); +} + +void RosegardenCanvasView::setHBarGeometry(QScrollBar &hbar, int x, int y, int w, int h) +{ + QCanvasView::setHBarGeometry(hbar, x, y, w, h); + updateBottomWidgetGeometry(); +} + +void RosegardenCanvasView::updateBottomWidgetGeometry() +{ + if (!m_bottomWidget) + return ; + + int bottomWidgetHeight = m_bottomWidget->sizeHint().height(); + + int leftWidgetWidth = 0; + if (m_leftWidget && m_leftWidget->isVisible()) { + QScrollView * qsv = dynamic_cast(m_leftWidget); + leftWidgetWidth = qsv->contentsWidth()+2; + qsv->setFixedWidth(leftWidgetWidth); + } + + setMargins(leftWidgetWidth, 0, 0, bottomWidgetHeight); + + QRect r = frameRect(); + int hScrollBarHeight = 0; + if (horizontalScrollBar()->isVisible()) + hScrollBarHeight = horizontalScrollBar()->height() + 2; + // + 2 offset : needed to preserve border shadow + + int vScrollBarWidth = 0; + if (verticalScrollBar()->isVisible()) + vScrollBarWidth = verticalScrollBar()->width(); + + m_bottomWidget->setGeometry(r.x() + leftWidgetWidth, + r.y() + r.height() - bottomWidgetHeight - hScrollBarHeight, + r.width() - vScrollBarWidth - leftWidgetWidth, + bottomWidgetHeight); + + if (bottomWidgetHeight != m_currentBottomWidgetHeight) { + emit bottomWidgetHeightChanged(bottomWidgetHeight); + m_currentBottomWidgetHeight = bottomWidgetHeight; + } +} + +void RosegardenCanvasView::wheelEvent(QWheelEvent *e) +{ + if (e->state() & ControlButton) { + if (e->delta() > 0) + emit zoomIn(); + else if (e->delta() < 0) + emit zoomOut(); + return ; + } + QCanvasView::wheelEvent(e); +} + +void RosegardenCanvasView::setLeftFixedWidget(QWidget* w) +{ + m_leftWidget = w; + if (m_leftWidget) { + int bwh = m_bottomWidget ? m_bottomWidget->sizeHint().height() : 0; + m_leftWidget->reparent(this, 0, QPoint(0, 0)); + m_leftWidget->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred)); + setMargins(m_leftWidget->sizeHint().width(), 0, 0, bwh); + } +} + +void RosegardenCanvasView::updateLeftWidgetGeometry() +{ + if (!m_leftWidget) + return ; + + int leftWidgetWidth = 0; + if (m_leftWidget->isVisible()) { + QScrollView * qsv = dynamic_cast(m_leftWidget); + leftWidgetWidth = qsv->contentsWidth() + 2; + } + m_leftWidget->setFixedWidth(leftWidgetWidth); + + int bottomWidgetHeight = m_bottomWidget ? + m_bottomWidget->sizeHint().height() : 0; + + setMargins(leftWidgetWidth, 0, 0, bottomWidgetHeight); + + QRect r = frameRect(); + int hScrollBarHeight = 0; + if (horizontalScrollBar()->isVisible()) + hScrollBarHeight = horizontalScrollBar()->height() + 2; + // + 2 offset : needed to preserve border shadow + + m_leftWidget->setFixedHeight(r.height() - bottomWidgetHeight - hScrollBarHeight); +} + + +} +#include "RosegardenCanvasView.moc" diff --git a/src/gui/general/RosegardenCanvasView.h b/src/gui/general/RosegardenCanvasView.h new file mode 100644 index 0000000..509c1aa --- /dev/null +++ b/src/gui/general/RosegardenCanvasView.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ROSEGARDENCANVASVIEW_H_ +#define _RG_ROSEGARDENCANVASVIEW_H_ + +#include +#include +#include +#include +#include + +class QWidget; +class QWheelEvent; +class QScrollBar; +class QResizeEvent; + + +namespace Rosegarden +{ + +/** + * A QCanvasView with an auxiliary horiz. scrollbar + * That scrollbar should be provided by the parent widget + * (typically an EditView). The RosegardenCanvasView keeps + * the auxilliary horiz. scrollbar range in sync with the + * one of its own scrollbar with slotUpdate(). + */ + +class RosegardenCanvasView : public QCanvasView +{ + Q_OBJECT +public: + RosegardenCanvasView(QCanvas*, + QWidget* parent=0, const char* name=0, WFlags f=0); + + /** + * EditTool::handleMouseMove() returns a OR-ed combination of these + * to indicate which direction to scroll to + */ + enum { + NoFollow = 0x0, + FollowHorizontal = 0x1, + FollowVertical = 0x2 + }; + + /** + * Sets the canvas width to be exactly the width needed to show + * all the items + */ + void fitWidthToContents(); + + /** + * Sets the widget which will be between the scrollable part of the view + * and the horizontal scrollbar + */ + void setBottomFixedWidget(QWidget*); + + void updateBottomWidgetGeometry(); + + /** + * Sets the widget which will be between the scrollable part of the view + * and the left edge of the view. + */ + void setLeftFixedWidget(QWidget*); + + void updateLeftWidgetGeometry(); + + /// Map a point with the inverse world matrix + QPoint inverseMapPoint(const QPoint& p) { return inverseWorldMatrix().map(p); } + + void setSmoothScroll(bool s) { m_smoothScroll = s; } + + bool isTimeForSmoothScroll(); + + void setScrollDirectionConstraint(int d) { m_scrollDirectionConstraint = d; } + + bool isAutoScrolling() const { return m_autoScrolling; } + + virtual void wheelEvent(QWheelEvent *); + +public slots: + /// Update the RosegardenCanvasView after a change of content + virtual void slotUpdate(); + + /** + * Scroll horizontally to make the given position visible, + * paging to as to get some visibility of the next screenful + * (for playback etc) + */ + void slotScrollHoriz(int hpos); + + /** + * Scroll horizontally to make the given position somewhat + * nearer to visible, scrolling by only "a small distance" + * at a time + */ + void slotScrollHorizSmallSteps(int hpos); + + /** + * Scroll vertically to make the given position somewhat + * nearer to visible, scrolling by only "a small distance" + * at a time + */ + void slotScrollVertSmallSteps(int vpos); + + /** + * Scroll vertically so as to place the given position + * somewhere near the top of the viewport. + */ + void slotScrollVertToTop(int vpos); + + /** + * Set the x and y scrollbars to a particular position + */ + void slotSetScrollPos(const QPoint &); + + void startAutoScroll(); + void startAutoScroll(int directionConstraint); + void stopAutoScroll(); + void doAutoScroll(); + +signals: + void bottomWidgetHeightChanged(int); + + void zoomIn(); + void zoomOut(); + +protected: + + virtual void resizeEvent(QResizeEvent*); + virtual void setHBarGeometry(QScrollBar &hbar, int x, int y, int w, int h); + + virtual QScrollBar* getMainHorizontalScrollBar() { return horizontalScrollBar(); } + + //--------------- Data members --------------------------------- + enum ScrollDirection { None, Top, Bottom, Left, Right }; + + + QWidget* m_bottomWidget; + int m_currentBottomWidgetHeight; + + QWidget* m_leftWidget; + + bool m_smoothScroll; + int m_smoothScrollTimeInterval; + float m_minDeltaScroll; + QTime m_scrollTimer; + QTime m_scrollAccelerationTimer; + + QTimer m_autoScrollTimer; + int m_autoScrollTime; + int m_autoScrollAccel; + QPoint m_previousP; + int m_autoScrollXMargin; + int m_autoScrollYMargin; + ScrollDirection m_currentScrollDirection; + int m_scrollDirectionConstraint; + bool m_autoScrolling; + + static const int DefaultSmoothScrollTimeInterval; + static const double DefaultMinDeltaScroll; + + static const int AutoscrollMargin; + static const int InitialScrollTime; + static const int InitialScrollAccel; + static const int MaxScrollDelta; + static const double ScrollAccelValue; + +}; + + +} + +#endif diff --git a/src/gui/general/RosegardenScrollView.cpp b/src/gui/general/RosegardenScrollView.cpp new file mode 100644 index 0000000..fbcaf79 --- /dev/null +++ b/src/gui/general/RosegardenScrollView.cpp @@ -0,0 +1,416 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "RosegardenScrollView.h" + +#include "misc/Debug.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +// Smooth scroll checks +// + +const int RosegardenScrollView::AutoscrollMargin = 16; +const int RosegardenScrollView::InitialScrollTime = 30; +const int RosegardenScrollView::InitialScrollAccel = 5; +const int RosegardenScrollView::MaxScrollDelta = 100; // max a.scroll speed +const double RosegardenScrollView::ScrollAccelValue = 1.04;// acceleration rate + +RosegardenScrollView::RosegardenScrollView(QWidget* parent, + const char* name, WFlags f) + : QScrollView(parent, name, f), + m_bottomWidget(0), + m_currentBottomWidgetHeight( -1), + m_smoothScroll(true), + m_smoothScrollTimeInterval(DefaultSmoothScrollTimeInterval), + m_minDeltaScroll(DefaultMinDeltaScroll), + m_autoScrollTime(InitialScrollTime), + m_autoScrollAccel(InitialScrollAccel), + m_autoScrollXMargin(0), + m_autoScrollYMargin(0), + m_currentScrollDirection(None), + m_scrollDirectionConstraint(NoFollow), + m_autoScrolling(false) +{ + setDragAutoScroll(true); + connect( &m_autoScrollTimer, SIGNAL( timeout() ), + this, SLOT( doAutoScroll() ) ); +} + +void RosegardenScrollView::setBottomFixedWidget(QWidget* w) +{ + m_bottomWidget = w; + if (m_bottomWidget) { + m_bottomWidget->reparent(this, 0, QPoint(0, 0)); + m_bottomWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); + setMargins(0, 0, 0, m_bottomWidget->sizeHint().height()); + } +} + +void RosegardenScrollView::startAutoScroll() +{ + // RG_DEBUG << "RosegardenScrollView::startAutoScroll()\n"; + + if ( !m_autoScrollTimer.isActive() ) { + m_autoScrollTime = InitialScrollTime; + m_autoScrollAccel = InitialScrollAccel; + m_autoScrollTimer.start( m_autoScrollTime ); + } + + QPoint autoScrollStartPoint = viewport()->mapFromGlobal( QCursor::pos() ); + m_autoScrollYMargin = autoScrollStartPoint.y() / 10; + m_autoScrollXMargin = autoScrollStartPoint.x() / 10; + + m_autoScrolling = true; +} + +void RosegardenScrollView::startAutoScroll(int directionConstraint) +{ + setScrollDirectionConstraint(directionConstraint); + startAutoScroll(); +} + +void RosegardenScrollView::stopAutoScroll() +{ + // RG_DEBUG << "RosegardenScrollView::stopAutoScroll()\n"; + + m_autoScrollTimer.stop(); + m_minDeltaScroll = DefaultMinDeltaScroll; + m_currentScrollDirection = None; + + m_autoScrolling = false; +} + +void RosegardenScrollView::doAutoScroll() +{ + // RG_DEBUG << "RosegardenScrollView::doAutoScroll()\n"; + + QPoint p = viewport()->mapFromGlobal( QCursor::pos() ); + QPoint dp = p - m_previousP; + m_previousP = p; + + m_autoScrollTimer.start( m_autoScrollTime ); + ScrollDirection scrollDirection = None; + + int dx = 0, dy = 0; + if (m_scrollDirectionConstraint & FollowVertical) { + if ( p.y() < m_autoScrollYMargin ) { + dy = -(int(m_minDeltaScroll)); + scrollDirection = Top; + } else if ( p.y() > visibleHeight() - m_autoScrollYMargin ) { + dy = + (int(m_minDeltaScroll)); + scrollDirection = Bottom; + } + } + bool startDecelerating = false; + if (m_scrollDirectionConstraint & FollowHorizontal) { + + // RG_DEBUG << "p.x() : " << p.x() << " - visibleWidth : " << visibleWidth() << " - autoScrollXMargin : " << m_autoScrollXMargin << endl; + + if ( p.x() < m_autoScrollXMargin ) { + if ( dp.x() > 0 ) { + startDecelerating = true; + m_minDeltaScroll /= ScrollAccelValue; + } + dx = -(int(m_minDeltaScroll)); + scrollDirection = Left; + } else if ( p.x() > visibleWidth() - m_autoScrollXMargin ) { + if ( dp.x() < 0 ) { + startDecelerating = true; + m_minDeltaScroll /= ScrollAccelValue; + } + dx = + (int(m_minDeltaScroll)); + scrollDirection = Right; + } + } + + // RG_DEBUG << "dx: " << dx << ", dy: " << dy << endl; + + if ( (dx || dy) && + ((scrollDirection == m_currentScrollDirection) || (m_currentScrollDirection == None)) ) { + scrollBy(dx, dy); + if ( startDecelerating ) + m_minDeltaScroll /= ScrollAccelValue; + else + m_minDeltaScroll *= ScrollAccelValue; + if (m_minDeltaScroll > MaxScrollDelta ) + m_minDeltaScroll = MaxScrollDelta; + m_currentScrollDirection = scrollDirection; + + } else { + // Don't automatically stopAutoScroll() here, the mouse button + // is presumably still pressed. + m_minDeltaScroll = DefaultMinDeltaScroll; + m_currentScrollDirection = None; + } + +} + +const int RosegardenScrollView::DefaultSmoothScrollTimeInterval = 10; +const double RosegardenScrollView::DefaultMinDeltaScroll = 1.2; + +bool RosegardenScrollView::isTimeForSmoothScroll() +{ + static int desktopWidth = QApplication::desktop()->width(), + desktopHeight = QApplication::desktop()->height(); + + if (m_smoothScroll) { + int ta = m_scrollAccelerationTimer.elapsed(); + int t = m_scrollTimer.elapsed(); + + RG_DEBUG << "t = " << t << ", ta = " << ta << ", int " << m_smoothScrollTimeInterval << ", delta " << m_minDeltaScroll << endl; + + if (t < m_smoothScrollTimeInterval) { + + return false; + + } else { + + if (ta > 300) { + // reset smoothScrollTimeInterval + m_smoothScrollTimeInterval = DefaultSmoothScrollTimeInterval; + m_minDeltaScroll = DefaultMinDeltaScroll; + m_scrollAccelerationTimer.restart(); + } else if (ta > 50) { + // m_smoothScrollTimeInterval /= 2; + m_minDeltaScroll *= 1.08; + m_scrollAccelerationTimer.restart(); + } + + m_scrollTimer.restart(); + return true; + } + } + + return true; +} + +void RosegardenScrollView::slotScrollHoriz(int hpos) +{ + QScrollBar* hbar = getMainHorizontalScrollBar(); + int currentContentYPos = contentsY(); + + /* Lots of performance hitting debug + RG_DEBUG << "RosegardenCanvasView::slotScrollHoriz: hpos is " << hpos + << ", contentsX is " << contentsX() << ", visibleWidth is " + << visibleWidth() << endl; + */ + + if (hpos == 0) { + + // returning to zero + // hbar->setValue(0); + setContentsPos(0, currentContentYPos); + + } else if (hpos > (contentsX() + + visibleWidth() * 1.6) || + hpos < (contentsX() - + visibleWidth() * 0.7)) { + + // miles off one side or the other + // hbar->setValue(hpos - int(visibleWidth() * 0.4)); + setContentsPos(hpos - int(visibleWidth() * 0.4), currentContentYPos); + + } else if (hpos > (contentsX() + + visibleWidth() * 0.9)) { + + // moving off the right hand side of the view + // hbar->setValue(hbar->value() + int(visibleWidth() * 0.6)); + setContentsPos(hbar->value() + int(visibleWidth() * 0.6), currentContentYPos); + + } else if (hpos < (contentsX() + + visibleWidth() * 0.1)) { + + // moving off the left + // hbar->setValue(hbar->value() - int(visibleWidth() * 0.6)); + setContentsPos(hbar->value() - int(visibleWidth() * 0.6), currentContentYPos); + } +} + +void RosegardenScrollView::slotScrollHorizSmallSteps(int hpos) +{ + QScrollBar* hbar = getMainHorizontalScrollBar(); + int currentContentYPos = contentsY(); + + int diff = 0; + + if (hpos == 0) { + + // returning to zero + // hbar->setValue(0); + setContentsPos(0, currentContentYPos); + + } else if ((diff = int(hpos - (contentsX() + + visibleWidth() * 0.90))) > 0) { + + // moving off the right hand side of the view + + int delta = diff / 6; + int diff10 = std::min(diff, (int)m_minDeltaScroll); + delta = std::max(delta, diff10); + + // hbar->setValue(hbar->value() + delta); + setContentsPos(hbar->value() + delta, currentContentYPos); + + } else if ((diff = int(hpos - (contentsX() + + visibleWidth() * 0.10))) < 0) { + // moving off the left + + int delta = -diff / 6; + int diff10 = std::min( -diff, (int)m_minDeltaScroll); + delta = std::max(delta, diff10); + + // hbar->setValue(hbar->value() - delta); + setContentsPos(hbar->value() - delta, currentContentYPos); + + } +} + +void RosegardenScrollView::slotScrollVertSmallSteps(int vpos) +{ + QScrollBar* vbar = verticalScrollBar(); + + // RG_DEBUG << "RosegardenCanvasView::slotScrollVertSmallSteps: vpos is " << vpos << ", contentsY is " << contentsY() << ", visibleHeight is " << visibleHeight() << endl; + + // As a special case (or hack), ignore any request made before we've + // actually been rendered and sized + if (visibleHeight() <= 1) + return ; + + int diff = 0; + + if (vpos == 0) { + + // returning to zero + vbar->setValue(0); + + } else if ((diff = int(vpos - (contentsY() + + visibleHeight() * 0.90))) > 0) { + + // moving off up + + int delta = diff / 6; + int diff10 = std::min(diff, (int)m_minDeltaScroll); + delta = std::max(delta, diff10); + + vbar->setValue(vbar->value() + diff); + + } else if ((diff = int(vpos - (contentsY() + + visibleHeight() * 0.10))) < 0) { + + // moving off down + + int delta = -diff / 6; + int diff10 = std::min( -diff, (int)m_minDeltaScroll); + delta = std::max(delta, diff10); + + vbar->setValue(vbar->value() - delta); + + } +} + +void RosegardenScrollView::slotScrollVertToTop(int vpos) +{ + QScrollBar* vbar = verticalScrollBar(); + if (vpos < visibleHeight() / 3) + vbar->setValue(0); + else + vbar->setValue(vpos - visibleHeight() / 5); +} + +void RosegardenScrollView::slotSetScrollPos(const QPoint &pos) +{ + horizontalScrollBar()->setValue(pos.x()); + verticalScrollBar()->setValue(pos.y()); +} + +void RosegardenScrollView::resizeEvent(QResizeEvent* e) +{ + QScrollView::resizeEvent(e); + if (!horizontalScrollBar()->isVisible()) + updateBottomWidgetGeometry(); + +} + +void RosegardenScrollView::setHBarGeometry(QScrollBar &hbar, int x, int y, int w, int h) +{ + QScrollView::setHBarGeometry(hbar, x, y, w, h); + updateBottomWidgetGeometry(); +} + +void RosegardenScrollView::updateBottomWidgetGeometry() +{ + if (!m_bottomWidget) + return ; + + int bottomWidgetHeight = m_bottomWidget->sizeHint().height(); + + setMargins(0, 0, 0, bottomWidgetHeight); + QRect r = frameRect(); + int hScrollBarHeight = 0; + if (horizontalScrollBar()->isVisible()) + hScrollBarHeight = horizontalScrollBar()->height() + 2; // + 2 offset needed to preserve border shadow + + int vScrollBarWidth = 0; + if (verticalScrollBar()->isVisible()) + vScrollBarWidth = verticalScrollBar()->width(); + + m_bottomWidget->setGeometry(r.x(), + r.y() + r.height() - bottomWidgetHeight - hScrollBarHeight, + r.width() - vScrollBarWidth, + bottomWidgetHeight); + + if (bottomWidgetHeight != m_currentBottomWidgetHeight) { + emit bottomWidgetHeightChanged(bottomWidgetHeight); + m_currentBottomWidgetHeight = bottomWidgetHeight; + } + +} + +void RosegardenScrollView::wheelEvent(QWheelEvent *e) +{ + if (e->state() & ControlButton) { + if (e->delta() > 0) + emit zoomIn(); + else if (e->delta() < 0) + emit zoomOut(); + return ; + } + QScrollView::wheelEvent(e); +} + +} +#include "RosegardenScrollView.moc" diff --git a/src/gui/general/RosegardenScrollView.h b/src/gui/general/RosegardenScrollView.h new file mode 100644 index 0000000..6a0dab7 --- /dev/null +++ b/src/gui/general/RosegardenScrollView.h @@ -0,0 +1,183 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_ROSEGARDENSCROLLVIEW_H_ +#define _RG_ROSEGARDENSCROLLVIEW_H_ + +#include +#include +#include +#include + + +class QWidget; +class QWheelEvent; +class QScrollBar; +class QResizeEvent; + + +namespace Rosegarden +{ + + + +/** + * A QScrollView with more elaborate auto-scrolling capabilities + * and the ability to have a "fixed" (non-scrolling) widget at its bottom, + * just above the bottom scrollbar. + */ +class RosegardenScrollView : public QScrollView +{ + Q_OBJECT +public: + RosegardenScrollView(QWidget* parent=0, const char* name=0, WFlags f=0); + + /** + * EditTool::handleMouseMove() returns a OR-ed combination of these + * to indicate which direction to scroll to + */ + enum { + NoFollow = 0x0, + FollowHorizontal = 0x1, + FollowVertical = 0x2 + }; + + /** + * Sets the canvas width to be exactly the width needed to show + * all the items + */ +// void fitWidthToContents(); + + /** + * Sets the widget which will be between the scrollable part of the view + * and the horizontal scrollbar + */ + void setBottomFixedWidget(QWidget*); + + void updateBottomWidgetGeometry(); + + /// Map a point with the inverse world matrix +// QPoint inverseMapPoint(const QPoint& p) { return inverseWorldMatrix().map(p); } + + void setSmoothScroll(bool s) { m_smoothScroll = s; } + + bool isTimeForSmoothScroll(); + + void setScrollDirectionConstraint(int d) { m_scrollDirectionConstraint = d; } + + int getDeltaScroll() { return m_minDeltaScroll; } + + virtual void wheelEvent(QWheelEvent *); + +public slots: + /** + * Scroll horizontally to make the given position visible, + * paging to as to get some visibility of the next screenful + * (for playback etc) + */ + void slotScrollHoriz(int hpos); + + /** + * Scroll horizontally to make the given position somewhat + * nearer to visible, scrolling by only "a small distance" + * at a time + */ + void slotScrollHorizSmallSteps(int hpos); + + /** + * Scroll vertically to make the given position somewhat + * nearer to visible, scrolling by only "a small distance" + * at a time + */ + void slotScrollVertSmallSteps(int vpos); + + /** + * Scroll vertically so as to place the given position + * somewhere near the top of the viewport. + */ + void slotScrollVertToTop(int vpos); + + /** + * Set the x and y scrollbars to a particular position + */ + void slotSetScrollPos(const QPoint &); + + void startAutoScroll(); + void startAutoScroll(int directionConstraint); + void stopAutoScroll(); + void doAutoScroll(); + + bool isAutoScrolling() const { return m_autoScrolling; } + +signals: + void bottomWidgetHeightChanged(int); + + void zoomIn(); + void zoomOut(); + +protected: + + virtual void resizeEvent(QResizeEvent*); + virtual void setHBarGeometry(QScrollBar &hbar, int x, int y, int w, int h); + + virtual QScrollBar* getMainHorizontalScrollBar() { return horizontalScrollBar(); } + + //--------------- Data members --------------------------------- + enum ScrollDirection { None, Top, Bottom, Left, Right }; + + QWidget* m_bottomWidget; + int m_currentBottomWidgetHeight; + + bool m_smoothScroll; + int m_smoothScrollTimeInterval; + float m_minDeltaScroll; + QTime m_scrollTimer; + QTime m_scrollAccelerationTimer; + + QTimer m_autoScrollTimer; + int m_autoScrollTime; + int m_autoScrollAccel; + QPoint m_previousP; + int m_autoScrollXMargin; + int m_autoScrollYMargin; + ScrollDirection m_currentScrollDirection; + int m_scrollDirectionConstraint; + bool m_autoScrolling; + + static const int DefaultSmoothScrollTimeInterval; + static const double DefaultMinDeltaScroll; + + static const int AutoscrollMargin; + static const int InitialScrollTime; + static const int InitialScrollAccel; + static const int MaxScrollDelta; + static const double ScrollAccelValue; + +}; + + +} + +#endif diff --git a/src/gui/general/Spline.cpp b/src/gui/general/Spline.cpp new file mode 100644 index 0000000..455cca5 --- /dev/null +++ b/src/gui/general/Spline.cpp @@ -0,0 +1,130 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "Spline.h" + +#include + + +namespace Rosegarden +{ + +Spline::PointList * + +Spline::calculate(const QPoint &s, const QPoint &f, const PointList &cp, + QPoint &topLeft, QPoint &bottomRight) +{ + if (cp.size() < 2) + return 0; + + int i; + PointList *acc = new PointList(); + QPoint p(s); + + topLeft = bottomRight = QPoint(0, 0); + + for (i = 1; i < cp.size(); ++i) { + + QPoint c(cp[i - 1]); + + int x = (c.x() + cp[i].x()) / 2; + int y = (c.y() + cp[i].y()) / 2; + QPoint n(x, y); + + calculateSegment(acc, p, n, c, topLeft, bottomRight); + + p = n; + } + + calculateSegment(acc, p, f, cp[i - 1], topLeft, bottomRight); + + return acc; +} + +void +Spline::calculateSegment(PointList *acc, + const QPoint &s, const QPoint &f, const QPoint &c, + QPoint &topLeft, QPoint &bottomRight) +{ + int x, y, n; + + x = c.x() - s.x(); + y = c.y() - s.y(); + + if (x < 0) + x = -x; + if (y < 0) + y = -y; + if (x > y) + n = x; + else + n = y; + + x = f.x() - c.x(); + y = f.y() - c.y(); + + if (x < 0) + x = -x; + if (y < 0) + y = -y; + if (x > y) + n += x; + else + n += y; + + calculateSegmentSub(acc, s, f, c, n, topLeft, bottomRight); +} + +void +Spline::calculateSegmentSub(PointList *acc, + const QPoint &s, const QPoint &f, const QPoint &c, + int n, QPoint &topLeft, QPoint &bottomRight) +{ + double ax = (double)(f.x() + s.x() - 2 * c.x()) / (double)n; + double ay = (double)(f.y() + s.y() - 2 * c.y()) / (double)n; + + double bx = 2.0 * (double)(c.x() - s.x()); + double by = 2.0 * (double)(c.y() - s.y()); + + for (int m = 0; m <= n; ++m) { + + int x = s.x() + (int)((m * ((double)m * ax + bx)) / n); + int y = s.y() + (int)((m * ((double)m * ay + by)) / n); + + if (x < topLeft.x()) + topLeft.setX(x); + if (y < topLeft.y()) + topLeft.setY(y); + + if (x > bottomRight.x()) + bottomRight.setX(x); + if (y > bottomRight.y()) + bottomRight.setY(y); + + acc->push_back(QPoint(x, y)); + } +} + +} diff --git a/src/gui/general/Spline.h b/src/gui/general/Spline.h new file mode 100644 index 0000000..63946a5 --- /dev/null +++ b/src/gui/general/Spline.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_SPLINE_H_ +#define _RG_SPLINE_H_ + +#include "base/FastVector.h" + + +class QPoint; +class PointList; + + +namespace Rosegarden +{ + + + +class Spline +{ +public: + typedef FastVector PointList; + + /** + * Calculate a set of polyline points to approximate + * a Bezier spline. Caller takes ownership of returned + * heap-allocated container. + */ + static PointList *calculate(const QPoint &start, const QPoint &finish, + const PointList &controlPoints, + QPoint &topLeft, QPoint &bottomRight); + +private: + static void calculateSegment + (PointList *acc, + const QPoint &start, const QPoint &finish, const QPoint &control, + QPoint &topLeft, QPoint &bottomRight); + + static void calculateSegmentSub + (PointList *acc, + const QPoint &start, const QPoint &finish, const QPoint &control, int n, + QPoint &topLeft, QPoint &bottomRight); +}; + + + +} + +#endif diff --git a/src/gui/general/StaffLine.cpp b/src/gui/general/StaffLine.cpp new file mode 100644 index 0000000..ab5d5ff --- /dev/null +++ b/src/gui/general/StaffLine.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "StaffLine.h" + +#include "misc/Debug.h" +#include +#include + + +namespace Rosegarden +{ + +StaffLine::StaffLine(QCanvas *c, QCanvasItemGroup *g, int height) : + QCanvasLineGroupable(c, g), + m_height(height), + m_significant(true) +{ + setZ(1); +} + +void +StaffLine::setHighlighted(bool highlighted) +{ + // RG_DEBUG << "StaffLine::setHighlighted(" + // << highlighted << ")\n"; + + if (highlighted) { + + m_normalPen = pen(); + QPen newPen = m_normalPen; + newPen.setColor(red); + setPen(newPen); + + } else { + + setPen(m_normalPen); + + } +} + +} diff --git a/src/gui/general/StaffLine.h b/src/gui/general/StaffLine.h new file mode 100644 index 0000000..7d01ff4 --- /dev/null +++ b/src/gui/general/StaffLine.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_STAFFLINE_H_ +#define _RG_STAFFLINE_H_ + +#include "gui/kdeext/QCanvasGroupableItem.h" +#include + + +class QCanvasItemGroup; +class QCanvas; + + +namespace Rosegarden +{ + + + +/** + * A staff line + * + * A groupable line which can be "highlighted" + * (drawn with a different color) + */ +class StaffLine : public QCanvasLineGroupable +{ +public: + StaffLine(QCanvas *c, QCanvasItemGroup *g, int height); + + enum { NoHeight = -150 }; + + void setHeight(int h) { m_height = h; } + int getHeight() const { return m_height; } + + void setSignificant(bool s) { m_significant = s; } + bool isSignificant() const { return m_significant; } + + /** + * "highlight" the line (set its pen to red) + */ + void setHighlighted(bool); + +protected: + //--------------- Data members --------------------------------- + + int m_height; + bool m_significant; + + QPen m_normalPen; +}; + + +} + +#endif diff --git a/src/gui/kdeext/KLedButton.cpp b/src/gui/kdeext/KLedButton.cpp new file mode 100644 index 0000000..f4e2a95 --- /dev/null +++ b/src/gui/kdeext/KLedButton.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + This file taken from KMix + Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de>. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "KLedButton.h" + +#include +#include +#include + + +namespace Rosegarden +{ + +KLedButton::KLedButton(const QColor &col, QWidget *parent, const char *name) + : KLed( col, parent, name ) +{} + +KLedButton::KLedButton(const QColor& col, KLed::State st, KLed::Look look, + KLed::Shape shape, QWidget *parent, const char *name) + : KLed( col, st, look, shape, parent, name ) +{} + +KLedButton::~KLedButton() +{} + +void KLedButton::mousePressEvent( QMouseEvent *e ) +{ + if (e->button() == LeftButton) { + toggle(); + emit stateChanged( state() ); + } +} + +} +#include "KLedButton.moc" diff --git a/src/gui/kdeext/KLedButton.h b/src/gui/kdeext/KLedButton.h new file mode 100644 index 0000000..e17ecdb --- /dev/null +++ b/src/gui/kdeext/KLedButton.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + This file taken from KMix + Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de>. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_KLEDBUTTON_H_ +#define _RG_KLEDBUTTON_H_ + +#include + + +class QWidget; +class QMouseEvent; +class QColor; + + +namespace Rosegarden +{ + + + +/** + * @author Stefan Schimanski + * Taken from KMix code, + * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de> + */ +class KLedButton : public KLed { + Q_OBJECT + public: + KLedButton(const QColor &col=Qt::green, QWidget *parent=0, const char *name=0); + KLedButton(const QColor& col, KLed::State st, KLed::Look look, KLed::Shape shape, + QWidget *parent=0, const char *name=0); + ~KLedButton(); + + signals: + void stateChanged( bool newState ); + + protected: + void mousePressEvent ( QMouseEvent *e ); +}; + + +// This class creates a list of mute and record buttons +// based on the rosegarden document and a specialisation +// of the Vertical Box widget. +// +// +// + + +} + +#endif diff --git a/src/gui/kdeext/KStartupLogo.cpp b/src/gui/kdeext/KStartupLogo.cpp new file mode 100644 index 0000000..9a04d8f --- /dev/null +++ b/src/gui/kdeext/KStartupLogo.cpp @@ -0,0 +1,159 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file contains code borrowed from KDevelop 2.0 + Copyright (c) The KDevelop Development Team. + + 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 +#include + +#include +#include + +#include +#include +#include +#include + +#include "KStartupLogo.h" +#include "misc/Debug.h" + +KStartupLogo::KStartupLogo(QWidget * parent, const char *name) + : QWidget(parent, name, + WStyle_Customize | +#if QT_VERSION >= 0x030100 + WStyle_Splash +#else + WStyle_NoBorder | WStyle_StaysOnTop | WStyle_Tool | WX11BypassWM | WWinOwnDC +#endif + ), + m_readyToHide(false), + m_showTip(true) +{ + QString pixmapFile = locate("appdata", "pixmaps/splash.png"); + if (!pixmapFile) + return ; + m_pixmap.load(pixmapFile); + setBackgroundPixmap(m_pixmap); + setGeometry(QApplication::desktop()->width() / 2 - m_pixmap.width() / 2, + QApplication::desktop()->height() / 2 - m_pixmap.height() / 2, + m_pixmap.width(), m_pixmap.height()); +} + +KStartupLogo::~KStartupLogo() +{ + m_wasClosed = true; + m_instance = 0; +} + +void KStartupLogo::paintEvent(QPaintEvent*) +{ + // Print version number + QPainter paint(this); + + QFont defaultFont; + defaultFont.setPixelSize(12); + paint.setFont(defaultFont); + + QFontMetrics metrics(defaultFont); + int width = metrics.width(m_statusMessage) + 6; + if (width > 200) + width = 200; + + int y = m_pixmap.height() - 12; + + // grep me: splash color + // QColor bg(49, 94, 19); // color for 2006 splash + QColor bg(19, 19, 19); // color for the 2008 splash + paint.setPen(bg); + paint.setBrush(bg); + paint.drawRect(QRect(m_pixmap.width() - 220, m_pixmap.height() - 43, + 220, (y + 8) - (m_pixmap.height() - 43))); + + // paint.setPen(Qt::black); + // paint.setBrush(Qt::black); + paint.setPen(Qt::white); + paint.setBrush(Qt::white); + + //QString version(VERSION); + //int sepIdx = version.find("-"); + QString versionLabel(VERSION); + //QString("R%1 v%2").arg(version.left(sepIdx)).arg(version.mid(sepIdx + 1)); + int versionWidth = metrics.width(versionLabel); + + paint.drawText(m_pixmap.width() - versionWidth - 18, + m_pixmap.height() - 28, + versionLabel); + + paint.drawText(m_pixmap.width() - (width + 10), y, m_statusMessage); +} + +void KStartupLogo::slotShowStatusMessage(QString message) +{ + m_statusMessage = message; + paintEvent(0); + QApplication::flushX(); +} + +void KStartupLogo::close() +{ + if (!m_wasClosed && isVisible()) { + + if (m_showTip) { + RG_DEBUG << "KStartupLogo::close: Showing Tips\n"; + KTipDialog::showTip(locate("data", "rosegarden/tips")); + } + } + + QWidget::close(); +} + + +void KStartupLogo::mousePressEvent(QMouseEvent*) +{ + // for the haters of raising startlogos + if (m_readyToHide) + hide(); // don't close, main() sets up a QTimer for that +} + +KStartupLogo* KStartupLogo::getInstance() +{ + if (m_wasClosed) + return 0; + + if (!m_instance) + m_instance = new KStartupLogo; + + return m_instance; +} + +void KStartupLogo::hideIfStillThere() +{ + if (m_instance) + m_instance->hide(); + // don't close, main() sets up a QTimer for that +} + + +KStartupLogo* KStartupLogo::m_instance = 0; +bool KStartupLogo::m_wasClosed = false; + +#include "KStartupLogo.moc" diff --git a/src/gui/kdeext/KStartupLogo.h b/src/gui/kdeext/KStartupLogo.h new file mode 100644 index 0000000..1af80fa --- /dev/null +++ b/src/gui/kdeext/KStartupLogo.h @@ -0,0 +1,70 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + This file contains code borrowed from KDevelop 2.0 + Copyright (c) The KDevelop Development Team. + + 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 KSTARTUPLOGO_H +#define KSTARTUPLOGO_H + +#include +#include + +class KStartupLogo : public QWidget +{ + Q_OBJECT + +public: + static KStartupLogo* getInstance(); + + static void hideIfStillThere(); + + void setHideEnabled(bool enabled) { m_readyToHide = enabled; }; + void setShowTip(bool showTip) { m_showTip = showTip; }; + +public slots: + void slotShowStatusMessage(QString); + virtual void close(); + +protected: + + KStartupLogo(QWidget *parent=0, const char *name=0); + ~KStartupLogo(); + + virtual void paintEvent(QPaintEvent*); + virtual void mousePressEvent( QMouseEvent*); + + bool m_readyToHide; + bool m_showTip; + + QPixmap m_pixmap; + + static KStartupLogo* m_instance; + static bool m_wasClosed; + QString m_statusMessage; +}; + +#endif + + + + + diff --git a/src/gui/kdeext/KTmpStatusMsg.cpp b/src/gui/kdeext/KTmpStatusMsg.cpp new file mode 100644 index 0000000..81142d2 --- /dev/null +++ b/src/gui/kdeext/KTmpStatusMsg.cpp @@ -0,0 +1,70 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 +#include + +#include +#include +#include + +#include "KTmpStatusMsg.h" +#include "gui/application/RosegardenApplication.h" + +KTmpStatusMsg::KTmpStatusMsg(const QString& msg, KMainWindow* window, int id) + : m_mainWindow(window), + m_id(id) +{ + m_mainWindow->statusBar()->changeItem(QString(" %1").arg(msg), m_id); + Rosegarden::rgapp->refreshGUI(50); +} + +KTmpStatusMsg::~KTmpStatusMsg() +{ + m_mainWindow->statusBar()->clear(); + m_mainWindow->statusBar()->changeItem(m_defaultMsg, m_id); + Rosegarden::rgapp->refreshGUI(50); +} + + +void KTmpStatusMsg::setDefaultMsg(const QString& m) +{ + m_defaultMsg = m; +} + +const QString& KTmpStatusMsg::getDefaultMsg() +{ + return m_defaultMsg; +} + +void KTmpStatusMsg::setDefaultId(int id) +{ + m_defaultId = id; +} + +int KTmpStatusMsg::getDefaultId() +{ + return m_defaultId; +} + + +int KTmpStatusMsg::m_defaultId = 1; +QString KTmpStatusMsg::m_defaultMsg = ""; diff --git a/src/gui/kdeext/KTmpStatusMsg.h b/src/gui/kdeext/KTmpStatusMsg.h new file mode 100644 index 0000000..6fd512c --- /dev/null +++ b/src/gui/kdeext/KTmpStatusMsg.h @@ -0,0 +1,88 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 KTMPSTATUSMSG_H +#define KTMPSTATUSMSG_H + +class KMainWindow; + +/** + * A class to create a temporary message on KMainWindow's status bar + * + * Use as follows : + * { // some block of code starts here + * KTmpStatusMsg tmpMsg("doing something...", mainWindow); + * + * // do something + * + * } // the message goes away + * + */ +class KTmpStatusMsg +{ +public: + + /** + * Creates a new temporary status message on the status bar + * of the specified KMainWindow. + * The id of the text widget in the status bar can be specified + */ + KTmpStatusMsg(const QString& msg, KMainWindow*, int id = m_defaultId); + + ~KTmpStatusMsg(); + + /** + * Sets the message which will replace the temporary one in the + * status bar + */ + static void setDefaultMsg(const QString&); + + /** + * Returns the default message which will replace the temporary + * one in the status bar + */ + static const QString& getDefaultMsg(); + + /** + * Sets the default id which will be used as the id of the text + * widget in the status bar + */ + static void setDefaultId(int); + + /** + * Returns the default id which will be used as id of the text + * widget in the status bar + */ + static int getDefaultId(); + +protected: + + //--------------- Data members --------------------------------- + + KMainWindow* m_mainWindow; + int m_id; + + static int m_defaultId; + static QString m_defaultMsg; +}; + +#endif + diff --git a/src/gui/kdeext/QCanvasGroupableItem.cpp b/src/gui/kdeext/QCanvasGroupableItem.cpp new file mode 100644 index 0000000..1fc2f2d --- /dev/null +++ b/src/gui/kdeext/QCanvasGroupableItem.cpp @@ -0,0 +1,279 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 +#include "misc/Debug.h" + +#include "QCanvasGroupableItem.h" + +QCanvasGroupableItem::QCanvasGroupableItem(QCanvasItem *i, + QCanvasItemGroup *g, + bool withRelativeCoords) + : m_group(g), + m_item(i) +{ + // RG_DEBUG << "QCanvasGroupableItem() - this : " << this + // << " - group : " << g + // << " - item : " << i << endl; + + if (withRelativeCoords) + group()->addItemWithRelativeCoords(item()); + else + group()->addItem(item()); +} + +QCanvasGroupableItem::~QCanvasGroupableItem() +{ + // RG_DEBUG << "~QCanvasGroupableItem() - this : " << this + // << " - group : " << group() + // << " - item : " << item() << endl; + + // remove from the item group if we're still attached to one + if (group()) + group()->removeItem(item()); +} + +void +QCanvasGroupableItem::relativeMoveBy(double dx, double dy) +{ + m_item->moveBy(dx + m_group->x(), + dy + m_group->y()); +} + +void +QCanvasGroupableItem::detach() +{ + m_group = 0; +} + +////////////////////////////////////////////////////////////////////// + +QCanvasItemGroup::QCanvasItemGroup(QCanvas *c) + : QCanvasItem(c) +{ + // RG_DEBUG << "QCanvasItemGroup() - this : " << this << endl; +} + +QCanvasItemGroup::~QCanvasItemGroup() +{ + // RG_DEBUG << "~QCanvasItemGroup() - this : " << this << endl; + + // Tell all our items that we're being destroyed + QCanvasItemList::Iterator i; + for (i = m_items.begin(); i != m_items.end(); ++i) { + QCanvasGroupableItem *t = dynamic_cast(*i); + if (t) + t->detach(); + } +} + +void +QCanvasItemGroup::moveBy(double dx, double dy) +{ + QCanvasItem::moveBy(dx, dy); // move ourselves + + QCanvasItemList::Iterator i; // move group items + for (i = m_items.begin(); i != m_items.end(); ++i) + (*i)->moveBy(dx, dy); +} + +void +QCanvasItemGroup::advance(int stage) +{ + QCanvasItemList::Iterator i; + for (i = m_items.begin(); i != m_items.end(); ++i) + (*i)->advance(stage); +} + +bool +QCanvasItemGroup::collidesWith(const QCanvasItem *item) const +{ + QCanvasItemList::ConstIterator i; + for (i = m_items.begin(); i != m_items.end(); ++i) + if ((*i)->collidesWith(item)) + return true; + + return false; +} + +void +QCanvasItemGroup::draw(QPainter&) +{ + // There isn't anything to do - all the items will be drawn + // seperately by the canvas anyway. However the function has to be + // implemented because it's an abstract virtual in QCanvasItem. + + // QCanvasItemList::Iterator i; + // for(i = m_items.begin(); i != m_items.end(); ++i) + // (*i)->draw(p); +} + +void +QCanvasItemGroup::setVisible(bool yes) +{ + QCanvasItemList::Iterator i; + for (i = m_items.begin(); i != m_items.end(); ++i) + (*i)->setVisible(yes); +} + +void +QCanvasItemGroup::setSelected(bool yes) +{ + QCanvasItem::setSelected(yes); + + QCanvasItemList::Iterator i; + for (i = m_items.begin(); i != m_items.end(); ++i) + (*i)->setVisible(yes); +} + +void +QCanvasItemGroup::setEnabled(bool yes) +{ + QCanvasItem::setEnabled(yes); + + QCanvasItemList::Iterator i; + for (i = m_items.begin(); i != m_items.end(); ++i) + (*i)->setEnabled(yes); +} + +void +QCanvasItemGroup::setActive(bool yes) +{ + QCanvasItem::setActive(yes); + + QCanvasItemList::Iterator i; + for (i = m_items.begin(); i != m_items.end(); ++i) + (*i)->setActive(yes); +} + +int +QCanvasItemGroup::rtti() const +{ + return 10002; +} + +QRect +QCanvasItemGroup::boundingRect() const +{ + QRect r; + + QCanvasItemList::ConstIterator i; + for (i = m_items.begin(); i != m_items.end(); ++i) + r.unite((*i)->boundingRect()); + + return r; +} + +QRect +QCanvasItemGroup::boundingRectAdvanced() const +{ + QRect r; + + QCanvasItemList::ConstIterator i; + for (i = m_items.begin(); i != m_items.end(); ++i) + r.unite((*i)->boundingRectAdvanced()); + + return r; +} + +bool +QCanvasItemGroup::collidesWith(const QCanvasSprite *s, + const QCanvasPolygonalItem *p, + const QCanvasRectangle *r, + const QCanvasEllipse *e, + const QCanvasText *t) const +{ + if (s) + return collidesWith(s); + else if (p) + return collidesWith(p); + else if (r) + return collidesWith(r); + else if (e) + return collidesWith(e); + else if (t) + return collidesWith(t); + + return false; + +} + +void +QCanvasItemGroup::addItem(QCanvasItem *i) +{ + m_items.append(i); +} + +void +QCanvasItemGroup::addItemWithRelativeCoords(QCanvasItem *i) +{ + i->moveBy(x(), y()); + addItem(i); +} + +void +QCanvasItemGroup::removeItem(QCanvasItem *i) +{ + // RG_DEBUG << "QCanvasItemGroup::removeItem() - this : " + // << this << " - item : " << i << endl; + m_items.remove(i); +} + +////////////////////////////////////////////////////////////////////// + + +QCanvasLineGroupable::QCanvasLineGroupable(QCanvas *c, + QCanvasItemGroup *g) + : QCanvasLine(c), + QCanvasGroupableItem(this, g) +{} + +////////////////////////////////////////////////////////////////////// + +QCanvasRectangleGroupable::QCanvasRectangleGroupable(QCanvas *c, + QCanvasItemGroup *g) + : QCanvasRectangle(c), + QCanvasGroupableItem(this, g) +{} + +////////////////////////////////////////////////////////////////////// + +QCanvasTextGroupable::QCanvasTextGroupable(const QString& label, + QCanvas *c, + QCanvasItemGroup *g) + : QCanvasText(label, c), + QCanvasGroupableItem(this, g) +{} + +QCanvasTextGroupable::QCanvasTextGroupable(QCanvas *c, + QCanvasItemGroup *g) + : QCanvasText(c), + QCanvasGroupableItem(this, g) +{} + +////////////////////////////////////////////////////////////////////// + +QCanvasSpriteGroupable::QCanvasSpriteGroupable(QCanvasPixmapArray *pa, + QCanvas *c, + QCanvasItemGroup *g) + : QCanvasSprite(pa, c), + QCanvasGroupableItem(this, g) +{} diff --git a/src/gui/kdeext/QCanvasGroupableItem.h b/src/gui/kdeext/QCanvasGroupableItem.h new file mode 100644 index 0000000..97d1917 --- /dev/null +++ b/src/gui/kdeext/QCanvasGroupableItem.h @@ -0,0 +1,201 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 QCANVASGROUPABLEITEM_H +#define QCANVASGROUPABLEITEM_H + +#include + +class QCanvasItemGroup; + +/** + * This class is meant to be inherited by QCanvasItem children to make + * them groupable. + * + * On destruction, the item will remove itself from the group it's + * attached to. + * + * @see QCanvasSpriteGroupable + * @see QCanvasLineGroupable + */ +class QCanvasGroupableItem +{ + friend class QCanvasItemGroup; + +public: + + /** + * Create a groupable item, e.g. put the item in the specified + * QCanvasItemGroup. If withRelativeCoords is true, the item's + * position will be translated so that it's coordinates are + * relative to those of the item group. + * + * @see QCanvasItemGroup#addItemWithRelativeCoords() + */ + QCanvasGroupableItem(QCanvasItem*, QCanvasItemGroup*, + bool withRelativeCoords = false); + + virtual ~QCanvasGroupableItem(); + + /// Returns the QCanvasItemGroup this groupable item belongs to + QCanvasItemGroup* group() { return m_group; } + + /// Returns the QCanvasItemGroup this groupable item belongs to + const QCanvasItemGroup* group() const { return m_group; } + + /// Returns the QCanvasItem which this groupable item wraps + QCanvasItem *item() { return m_item; } + + /** + * Same as moveBy(), except that the move is done relative to the + * item group's coordinates + */ + virtual void relativeMoveBy(double dx, double dy); + +protected: + /** + * Detach item from the item group - called by QCanvasItemGroup only + * + * Set m_group to 0, so that on destruction the item won't try to + * remove itself from the group + */ + void detach(); + +private: + //--------------- Data members --------------------------------- + + QCanvasItemGroup* m_group; + QCanvasItem* m_item; + +}; + + +/** + * This class implements QCanvasItem groups + * + * An item group will keep its items in a fixed relative position when + * moved, just like in a drawing program where you can "bind" several + * items together so that they'll behave as a single item. + * + * Proper behavior requires collaboration from the QCanvasView, + * though. When about to move an item, the QCanvasView object should + * first check if it's not a groupable item, and if so fetch its + * QCanvasItemGroup and move it instead. + */ +class QCanvasItemGroup : public QCanvasItem +{ +public: + QCanvasItemGroup(QCanvas *); + virtual ~QCanvasItemGroup(); + + virtual void moveBy(double dx, double dy); + virtual void advance(int stage); + virtual bool collidesWith(const QCanvasItem*) const; + virtual void draw(QPainter&); + virtual void setVisible(bool yes); + virtual void setSelected(bool yes); + virtual void setEnabled(bool yes); + virtual void setActive(bool yes); + virtual int rtti() const; + virtual QRect boundingRect() const; + virtual QRect boundingRectAdvanced() const; + + /** + * Add a new item to this group. + * + * The item's coordinates are kept as is. + * + * @see addItemWithRelativeCoords() + */ + virtual void addItem(QCanvasItem *); + + /** + * Add a new item to this group. + * + * The item's coordinates are considered relative to the group. + * For example, suppose you have a QCanvasItemGroup whose coords + * are 10,10. If you call addItemWithRelativeCoords() with an item + * whose coords are 5,5, the item is moved so that its coords + * will be 5,5 relative to the group (e.g. 15,15). + * + * @see addItem() + */ + virtual void addItemWithRelativeCoords(QCanvasItem *); + + /** + * Remove the specified item from the group + */ + virtual void removeItem(QCanvasItem*); + +private: + virtual bool collidesWith(const QCanvasSprite*, + const QCanvasPolygonalItem*, + const QCanvasRectangle*, + const QCanvasEllipse*, + const QCanvasText* ) const; + +protected: + //--------------- Data members --------------------------------- + + QCanvasItemList m_items; +}; + + +/** + * A QCanvasLine which can be put in a QCanvasGroup + */ +class QCanvasLineGroupable : public QCanvasLine, public QCanvasGroupableItem +{ +public: + QCanvasLineGroupable(QCanvas *c, QCanvasItemGroup *g); +}; + +/** + * A QCanvasRectangle which can be put in a QCanvasGroup + */ +class QCanvasRectangleGroupable : public QCanvasRectangle, public QCanvasGroupableItem +{ +public: + QCanvasRectangleGroupable(QCanvas *c, QCanvasItemGroup *g); +}; + +/** + * A QCanvasText which can be put in a QCanvasGroup + */ +class QCanvasTextGroupable : public QCanvasText, public QCanvasGroupableItem +{ +public: + QCanvasTextGroupable(QCanvas *c, QCanvasItemGroup *g); + QCanvasTextGroupable(const QString&, QCanvas *c, QCanvasItemGroup *g); +}; + +/** + * A QCanvasSprite that can be put in a QCanvasGroup + */ +class QCanvasSpriteGroupable : public QCanvasSprite, public QCanvasGroupableItem +{ +public: + QCanvasSpriteGroupable(QCanvasPixmapArray*, + QCanvas*, + QCanvasItemGroup*); +}; + +#endif diff --git a/src/gui/kdeext/QCanvasSimpleSprite.cpp b/src/gui/kdeext/QCanvasSimpleSprite.cpp new file mode 100644 index 0000000..537cc62 --- /dev/null +++ b/src/gui/kdeext/QCanvasSimpleSprite.cpp @@ -0,0 +1,217 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 +#include "misc/Debug.h" + +#include + +#include "QCanvasSimpleSprite.h" + +namespace Rosegarden { + +QCanvasSimpleSprite::QCanvasSimpleSprite(QPixmap *pixmap, QCanvas *canvas) + : QCanvasSprite(0, canvas), + m_pixmapArray(0) +{ + m_pixmapArray = makePixmapArray(pixmap); + setSequence(m_pixmapArray); +} + +QCanvasSimpleSprite::QCanvasSimpleSprite(QCanvasPixmap *pixmap, QCanvas *canvas) + : QCanvasSprite(0, canvas), + m_pixmapArray(0) +{ + m_pixmapArray = makePixmapArray(pixmap); + setSequence(m_pixmapArray); +} + +QCanvasSimpleSprite::QCanvasSimpleSprite(const QString &pixmapfile, + QCanvas *canvas) + : QCanvasSprite(0, canvas), + m_pixmapArray(0) +{ + m_pixmapArray = makePixmapArray(pixmapfile); + setSequence(m_pixmapArray); +} + +QCanvasSimpleSprite::QCanvasSimpleSprite(QCanvas *canvas) + : QCanvasSprite(0, canvas), + m_pixmapArray(0) +{ + QCanvasPixmapArray* tmpArray = makePixmapArray(new QPixmap()); + setSequence(tmpArray); +} + + +QCanvasSimpleSprite::~QCanvasSimpleSprite() +{ + PixmapArrayGC::registerForDeletion(m_pixmapArray); + m_pixmapArray = 0; + + // We can't delete m_pixmapArray or we get a core dump. + // + // The reason I think is that after the QCanvasSprite is deleted, + // it is removed from the QCanvas, which therefore needs the + // pixmaps to know how to update itself (the crash is in + // QCanvas::removeChunks(), usually). + // + // So instead we have to do this GCish + // thingy. PixmapArrayGC::deleteAll() is called by + // NotationView::redoLayout +} + +QCanvasPixmapArray* +QCanvasSimpleSprite::makePixmapArray(QPixmap *pixmap) +{ + QList pixlist; + pixlist.setAutoDelete(true); // the QCanvasPixmapArray creates its + // own copies of the pixmaps, so we + // can delete the one we're passed + pixlist.append(pixmap); + + QList spotlist; + spotlist.setAutoDelete(true); + spotlist.append(new QPoint(0, 0)); + + return new QCanvasPixmapArray(pixlist, spotlist); +} + +QCanvasPixmapArray* +QCanvasSimpleSprite::makePixmapArray(QCanvasPixmap *pixmap) +{ + QList pixlist; + pixlist.setAutoDelete(true); // the QCanvasPixmapArray creates its + // own copies of the pixmaps, so we + // can delete the one we're passed + pixlist.append(pixmap); + + QList spotlist; + spotlist.setAutoDelete(true); + spotlist.append(new QPoint(pixmap->offsetX(), pixmap->offsetY())); + + return new QCanvasPixmapArray(pixlist, spotlist); +} + +QCanvasPixmapArray* +QCanvasSimpleSprite::makePixmapArray(const QString &pixmapfile) +{ + return new QCanvasPixmapArray(pixmapfile); +} + +////////////////////////////////////////////////////////////////////// + +QCanvasNotationSprite::QCanvasNotationSprite(NotationElement& n, + QPixmap* pixmap, + QCanvas* canvas) + : QCanvasSimpleSprite(pixmap, canvas), + m_notationElement(n) +{} + +QCanvasNotationSprite::QCanvasNotationSprite(NotationElement& n, + QCanvasPixmap* pixmap, + QCanvas* canvas) + : QCanvasSimpleSprite(pixmap, canvas), + m_notationElement(n) + +{} + +QCanvasNotationSprite::~QCanvasNotationSprite() +{} + + +QCanvasNonElementSprite::QCanvasNonElementSprite(QPixmap *pixmap, + QCanvas *canvas) : + QCanvasSimpleSprite(pixmap, canvas) +{} + +QCanvasNonElementSprite::QCanvasNonElementSprite(QCanvasPixmap *pixmap, + QCanvas *canvas) : + QCanvasSimpleSprite(pixmap, canvas) +{} + +QCanvasNonElementSprite::~QCanvasNonElementSprite() +{} + +QCanvasTimeSigSprite::QCanvasTimeSigSprite(double layoutX, + QPixmap *pixmap, + QCanvas *canvas) : + QCanvasNonElementSprite(pixmap, canvas), + m_layoutX(layoutX) +{} + +QCanvasTimeSigSprite::QCanvasTimeSigSprite(double layoutX, + QCanvasPixmap *pixmap, + QCanvas *canvas) : + QCanvasNonElementSprite(pixmap, canvas), + m_layoutX(layoutX) +{} + +QCanvasTimeSigSprite::~QCanvasTimeSigSprite() +{} + + +QCanvasStaffNameSprite::QCanvasStaffNameSprite(QPixmap *pixmap, + QCanvas *canvas) : + QCanvasNonElementSprite(pixmap, canvas) +{} + +QCanvasStaffNameSprite::QCanvasStaffNameSprite(QCanvasPixmap *pixmap, + QCanvas *canvas) : + QCanvasNonElementSprite(pixmap, canvas) +{} + +QCanvasStaffNameSprite::~QCanvasStaffNameSprite() +{} + + +////////////////////////////////////////////////////////////////////// + +void PixmapArrayGC::registerForDeletion(QCanvasPixmapArray* array) +{ + m_pixmapArrays.push_back(array); +} + +void PixmapArrayGC::deleteAll() +{ + RG_DEBUG << "PixmapArrayGC::deleteAll() : " + << m_pixmapArrays.size() << " pixmap arrays to delete\n"; + + static unsigned long total = 0; + + for (unsigned int i = 0; i < m_pixmapArrays.size(); ++i) { + QCanvasPixmapArray *array = m_pixmapArrays[i]; + QPixmap *pixmap = array->image(0); + if (pixmap) { + total += pixmap->width() * pixmap->height(); + // NOTATION_DEBUG << "PixmapArrayGC::deleteAll(): " << pixmap->width() << "x" << pixmap->height() << " (" << (pixmap->width()*pixmap->height()) << " px, " << total << " total)" << endl; + } + delete m_pixmapArrays[i]; + } + + m_pixmapArrays.clear(); +} + +std::vector PixmapArrayGC::m_pixmapArrays; + +} + +////////////////////////////////////////////////////////////////////// diff --git a/src/gui/kdeext/QCanvasSimpleSprite.h b/src/gui/kdeext/QCanvasSimpleSprite.h new file mode 100644 index 0000000..15a02f9 --- /dev/null +++ b/src/gui/kdeext/QCanvasSimpleSprite.h @@ -0,0 +1,133 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + 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 QCANVASSIMPLESPRITE_H +#define QCANVASSIMPLESPRITE_H + +#include +#include + +#include "gui/editors/notation/NotePixmapFactory.h" + +namespace Rosegarden { + +/** + * A QCanvasSprite with 1 frame only + */ +class QCanvasSimpleSprite : public QCanvasSprite +{ +public: + QCanvasSimpleSprite(QPixmap*, QCanvas*); + QCanvasSimpleSprite(QCanvasPixmap*, QCanvas*); + QCanvasSimpleSprite(const QString &pixmapfile, QCanvas*); + + // For lazy pixmap rendering, when we get around looking at it + QCanvasSimpleSprite(QCanvas*); + + virtual ~QCanvasSimpleSprite(); + +protected: + static QCanvasPixmapArray* makePixmapArray(QPixmap *pixmap); + + static QCanvasPixmapArray* makePixmapArray(QCanvasPixmap *pixmap); + + static QCanvasPixmapArray* makePixmapArray(const QString &pixmapfile); + + //--------------- Data members --------------------------------- + + QCanvasPixmapArray* m_pixmapArray; +}; + +class NotationElement; + +/** + * A QCanvasSprite referencing a NotationElement + */ +class QCanvasNotationSprite : public QCanvasSimpleSprite +{ +public: + QCanvasNotationSprite(Rosegarden::NotationElement&, QPixmap*, QCanvas*); + QCanvasNotationSprite(Rosegarden::NotationElement&, QCanvasPixmap*, QCanvas*); + + virtual ~QCanvasNotationSprite(); + + Rosegarden::NotationElement& getNotationElement() { return m_notationElement; } + +protected: + //--------------- Data members --------------------------------- + + Rosegarden::NotationElement& m_notationElement; +}; + +class QCanvasNonElementSprite : public QCanvasSimpleSprite +{ +public: + QCanvasNonElementSprite(QPixmap *, QCanvas *); + QCanvasNonElementSprite(QCanvasPixmap *, QCanvas *); + virtual ~QCanvasNonElementSprite(); +}; + +/** + * A QCanvasSprite used for a time signature + */ +class QCanvasTimeSigSprite : public QCanvasNonElementSprite +{ +public: + QCanvasTimeSigSprite(double layoutX, QPixmap *, QCanvas *); + QCanvasTimeSigSprite(double layoutX, QCanvasPixmap *, QCanvas *); + virtual ~QCanvasTimeSigSprite(); + + void setLayoutX(double layoutX) { m_layoutX = layoutX; } + double getLayoutX() const { return m_layoutX; } + +protected: + double m_layoutX; +}; + +/** + * A QCanvasSprite used for a staff name + */ +class QCanvasStaffNameSprite : public QCanvasNonElementSprite +{ +public: + QCanvasStaffNameSprite(QPixmap *, QCanvas *); + QCanvasStaffNameSprite(QCanvasPixmap *, QCanvas *); + virtual ~QCanvasStaffNameSprite(); +}; + +/** + * A GC for QCanvasPixmapArray which have to be deleted seperatly + */ +class PixmapArrayGC +{ +public: + static void registerForDeletion(QCanvasPixmapArray*); + static void deleteAll(); + +protected: + //--------------- Data members --------------------------------- + + static std::vector m_pixmapArrays; +}; + +} + +#endif diff --git a/src/gui/kdeext/RGLed.cpp b/src/gui/kdeext/RGLed.cpp new file mode 100644 index 0000000..54b91b2 --- /dev/null +++ b/src/gui/kdeext/RGLed.cpp @@ -0,0 +1,729 @@ +// -*- c-basic-offset: 2 -*- + +/* This file is a modified version of kled.cpp, which is part of the + KDE libraries. The modifications (for "brute-force" antialiasing) + were carried out by Chris Cannam, April 2004. + + Copyright (C) 1998 Jörg Habenicht (j.habenicht@europemail.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/************************************************************************* + * $Id: rgled.cpp 6694 2005-07-17 11:42:36Z cannam $ + * + * $Log$ + * Revision 1.2 2005/07/17 11:42:36 cannam + * * fixes from stg branch: + * - fix failure to recolour led when switching track from audio to midi or vice versa + * - fix colour number / menu index confusion in spb colour menu + * + * Revision 1.1.10.1 2005/07/17 00:00:30 cannam + * + * * merge across from HEAD to 17/07/2005 + * * appearance fixes + * + * Revision 1.1 2004/04/05 09:54:05 cannam + * + * * antialiased LEDs + * + * Revision 1.24 2002/12/16 15:10:03 mkretz + * don't use a gray background but rather take the color from the current + * colorscheme + * + * Revision 1.23 2002/03/04 00:51:49 lunakl + * Keep BC changes (the patch is almost 100KiB of boring stuff + * ... anybody willing to review? ;) ). + * + * Revision 1.22 2002/02/27 23:15:45 pfeiffer + * kapp-- + * + * Revision 1.21 2002/01/22 10:23:55 hausmann + * - minor API fix (don't return a const QColor object) + * + * Revision 1.20 2001/10/10 17:40:39 mueller + * CVS_SILENT: fixincludes + * + * Revision 1.19 2001/08/08 14:35:12 hausmann + * - removed empty KActionCollection::childEvent + * - added sizeHint() and minimumSizeHint() methods to KLed, as advised by + * Rik in the comment in kled.h + * - removed unused mousePressEvent, mouseMoveEvent and mouseReleaseEvent + * handlers from KPassDlg + * - merged KToolBar::insertSeparator() and KToolBar::insertLineSeparator() + * with their overloaded variants + * + * Revision 1.18 2001/04/16 22:08:43 pfeiffer + * don't assume that the first item of an enum is 0 + * + * Revision 1.17 2000/09/12 19:15:53 pfeiffer + * Stefan Hellwig in cooperation with Joerg Habenicht: + * Draw nicer LEDs, especially sunken ones. + * + * Revision 1.16 2000/08/24 12:44:48 porten + * "friend class" patches from Thomas Kunert + * + * Revision 1.15 2000/08/17 16:44:44 reggie + * Don't crash + * + * Revision 1.14 2000/06/03 01:04:42 gehrmab + * * Made drawing routines available for overriding + * * Added a parent/name constructor + * * Propertyfication + * + * Revision 1.13 2000/05/08 19:38:49 sschiman + * Calling setColor before setting up the private data is a bad idea ;-) + * + * Revision 1.12 2000/05/07 09:49:57 habenich + * provided method to set the factor to dark the LED + * precalculated the dark color, is just retrieved at paint events + * + * Revision 1.11 2000/04/09 16:08:33 habenich + * fixed nasty bug #70 disappearing led + * reenabled flat and raised led painting + * + * Revision 1.10 1999/12/25 17:12:18 mirko + * Modified Look "round" to "raised", as the others are flat and + * sunken. All enums start with uppercase letters now to fit the overall + * KDE style. + * Implemented raised rectangluar look. + * --Mirko. + * + * Revision 1.9 1999/11/12 21:17:09 antlarr + * Fixed some bugs. + * Added the possibility to draw a sunk rectangle as the "old" KLedLamp did. + * + * Revision 1.9 1999/11/11 16:08:15 antlarr + * Fixed some bugs. + * Added the possibility to draw a sunk rectangle as the "old" KLedLamp did. + * + * Revision 1.8 1999/11/01 22:03:15 dmuell + * fixing all kinds of compile warnings + * (unused var, unused argument etc) + * + * Revision 1.7 1999/10/10 13:34:14 mirko + * First merge with KLedLamp that shows a rectangular LED. + * It does not yet work reliably. + * + * Revision 1.6 1999/03/01 23:34:49 kulow + * CVS_SILENT ported to Qt 2.0 + * + * Revision 1.5 1999/02/19 08:52:42 habenich + * ID und LOG tags included + * + * + *************************************************************************/ + +#define PAINT_BENCH +#undef PAINT_BENCH + +#ifdef PAINT_BENCH +#include +#include +#endif + + +#include +#include +#include +#include +#include +#include "kled.h" + + +class KLed::KLedPrivate +{ + friend class KLed; + + int dark_factor; + QColor offcolor; + QPixmap *off_map; + QPixmap *on_map; +}; + + + +KLed::KLed(QWidget *parent, const char *name) + : QWidget( parent, name), + led_state(On), + led_look(Raised), + led_shape(Circular) +{ + QColor col(green); + d = new KLed::KLedPrivate; + d->dark_factor = 300; + d->offcolor = col.dark(300); + d->off_map = 0; + d->on_map = 0; + + setColor(col); +} + + +KLed::KLed(const QColor& col, QWidget *parent, const char *name) + : QWidget( parent, name), + led_state(On), + led_look(Raised), + led_shape(Circular) +{ + d = new KLed::KLedPrivate; + d->dark_factor = 300; + d->offcolor = col.dark(300); + d->off_map = 0; + d->on_map = 0; + + setColor(col); + //setShape(Circular); +} + +KLed::KLed(const QColor& col, KLed::State state, + KLed::Look look, KLed::Shape shape, QWidget *parent, const char *name ) + : QWidget(parent, name), + led_state(state), + led_look(look), + led_shape(shape) +{ + d = new KLed::KLedPrivate; + d->dark_factor = 300; + d->offcolor = col.dark(300); + d->off_map = 0; + d->on_map = 0; + + //setShape(shape); + setColor(col); +} + + +KLed::~KLed() +{ + delete d->off_map; + delete d->on_map; + delete d; +} + +void +KLed::paintEvent(QPaintEvent *) +{ +#ifdef PAINT_BENCH + const int rounds = 1000; + QTime t; + t.start(); + for (int i = 0; i < rounds; i++) { +#endif + switch (led_shape) { + case Rectangular: + switch (led_look) { + case Sunken : + paintRectFrame(false); + break; + case Raised : + paintRectFrame(true); + break; + case Flat : + paintRect(); + break; + default : + qWarning("%s: in class KLed: no KLed::Look set", qApp->argv()[0]); + } + break; + case Circular: + switch (led_look) { + case Flat : + paintFlat(); + break; + case Raised : + paintRound(); + break; + case Sunken : + paintSunken(); + break; + default: + qWarning("%s: in class KLed: no KLed::Look set", qApp->argv()[0]); + } + break; + default: + qWarning("%s: in class KLed: no KLed::Shape set", qApp->argv()[0]); + break; + } +#ifdef PAINT_BENCH + + } + int ready = t.elapsed(); + qWarning("elapsed: %d msec. for %d rounds", ready, rounds); +#endif +} + +void +KLed::paintFlat() // paint a ROUND FLAT led lamp +{ + QPainter paint; + QColor color; + QBrush brush; + QPen pen; + + // Initialize coordinates, width, and height of the LED + // + int width = this->width(); + // Make sure the LED is round! + if (width > this->height()) + width = this->height(); + width -= 2; // leave one pixel border + if (width < 0) + width = 0; + + + // start painting widget + // + paint.begin( this ); + + // Set the color of the LED according to given parameters + color = ( led_state ) ? led_color : d->offcolor; + + // Set the brush to SolidPattern, this fills the entire area + // of the ellipse which is drawn with a thin grey "border" (pen) + brush.setStyle( QBrush::SolidPattern ); + brush.setColor( color ); + + pen.setWidth( 1 ); + color = colorGroup().dark(); + pen.setColor( color ); // Set the pen accordingly + + paint.setPen( pen ); // Select pen for drawing + paint.setBrush( brush ); // Assign the brush to the painter + + // Draws a "flat" LED with the given color: + paint.drawEllipse( 1, 1, width, width ); + + paint.end(); + // + // painting done +} + +void +KLed::paintRound() // paint a ROUND RAISED led lamp +{ + QPainter paint; + QColor color; + QBrush brush; + QPen pen; + + // Initialize coordinates, width, and height of the LED + int width = this->width(); + + // Make sure the LED is round! + if (width > this->height()) + width = this->height(); + width -= 2; // leave one pixel border + if (width < 0) + width = 0; + + // start painting widget + // + paint.begin( this ); + + // Set the color of the LED according to given parameters + color = ( led_state ) ? led_color : d->offcolor; + + // Set the brush to SolidPattern, this fills the entire area + // of the ellipse which is drawn first + brush.setStyle( QBrush::SolidPattern ); + brush.setColor( color ); + paint.setBrush( brush ); // Assign the brush to the painter + + // Draws a "flat" LED with the given color: + paint.drawEllipse( 1, 1, width, width ); + + // Draw the bright light spot of the LED now, using modified "old" + // painter routine taken from KDEUI´s KLed widget: + + // Setting the new width of the pen is essential to avoid "pixelized" + // shadow like it can be observed with the old LED code + pen.setWidth( 2 ); + + // shrink the light on the LED to a size about 2/3 of the complete LED + int pos = width / 5 + 1; + int light_width = width; + light_width *= 2; + light_width /= 3; + + // Calculate the LED´s "light factor": + int light_quote = (130 * 2 / (light_width ? light_width : 1)) + 100; + + // Now draw the bright spot on the LED: + while (light_width) + { + color = color.light( light_quote ); // make color lighter + pen.setColor( color ); // set color as pen color + paint.setPen( pen ); // select the pen for drawing + paint.drawEllipse( pos, pos, light_width, light_width ); // draw the ellipse (circle) + light_width--; + if (!light_width) + break; + paint.drawEllipse( pos, pos, light_width, light_width ); + light_width--; + if (!light_width) + break; + paint.drawEllipse( pos, pos, light_width, light_width ); + pos++; + light_width--; + } + + // Drawing of bright spot finished, now draw a thin grey border + // around the LED; it looks nicer that way. We do this here to + // avoid that the border can be erased by the bright spot of the LED + + pen.setWidth( 1 ); + color = colorGroup().dark(); + pen.setColor( color ); // Set the pen accordingly + paint.setPen( pen ); // Select pen for drawing + brush.setStyle( QBrush::NoBrush ); // Switch off the brush + paint.setBrush( brush ); // This avoids filling of the ellipse + + paint.drawEllipse( 1, 1, width, width ); + + paint.end(); + // + // painting done +} + +void +KLed::paintSunken() // paint a ROUND SUNKEN led lamp +{ + QPainter paint; + QColor color; + QBrush brush; + QPen pen; + + // First of all we want to know what area should be updated + // Initialize coordinates, width, and height of the LED + int width = this->width(); + + // Make sure the LED is round! + if (width > this->height()) + width = this->height(); + width -= 2; // leave one pixel border + if (width < 0) + width = 0; + + // maybe we could stop HERE, if width <=0 ? + + int scale = 1; + QPixmap *tmpMap = 0; + bool smooth = true; + + if (smooth) + { + if (led_state) { + if (d->on_map) { + paint.begin(this); + paint.drawPixmap(0, 0, *d->on_map); + paint.end(); + return ; + } + } else { + if (d->off_map) { + paint.begin(this); + paint.drawPixmap(0, 0, *d->off_map); + paint.end(); + return ; + } + } + + scale = 3; + width *= scale; + + tmpMap = new QPixmap(width, width); + tmpMap->fill(paletteBackgroundColor()); + paint.begin(tmpMap); + + } else + { + paint.begin(this); + } + + // Set the color of the LED according to given parameters + color = ( led_state ) ? led_color : d->offcolor; + + // Set the brush to SolidPattern, this fills the entire area + // of the ellipse which is drawn first + brush.setStyle( QBrush::SolidPattern ); + brush.setColor( color ); + paint.setBrush( brush ); // Assign the brush to the painter + + // Draws a "flat" LED with the given color: + paint.drawEllipse( scale, scale, width - scale*2, width - scale*2 ); + + // Draw the bright light spot of the LED now, using modified "old" + // painter routine taken from KDEUI´s KLed widget: + + // Setting the new width of the pen is essential to avoid "pixelized" + // shadow like it can be observed with the old LED code + pen.setWidth( 2 * scale ); + + // shrink the light on the LED to a size about 2/3 of the complete LED + int pos = width / 5 + 1; + int light_width = width; + light_width *= 2; + light_width /= 3; + + // Calculate the LED´s "light factor": + int light_quote = (130 * 2 / (light_width ? light_width : 1)) + 100; + + // Now draw the bright spot on the LED: + while (light_width) + { + color = color.light( light_quote ); // make color lighter + pen.setColor( color ); // set color as pen color + paint.setPen( pen ); // select the pen for drawing + paint.drawEllipse( pos, pos, light_width, light_width ); // draw the ellipse (circle) + light_width--; + if (!light_width) + break; + paint.drawEllipse( pos, pos, light_width, light_width ); + light_width--; + if (!light_width) + break; + paint.drawEllipse( pos, pos, light_width, light_width ); + pos++; + light_width--; + } + + // Drawing of bright spot finished, now draw a thin border + // around the LED which resembles a shadow with light coming + // from the upper left. + + pen.setWidth( 2 * scale + 1 ); // ### shouldn't this value be smaller for smaller LEDs? + brush.setStyle( QBrush::NoBrush ); // Switch off the brush + paint.setBrush( brush ); // This avoids filling of the ellipse + + // Set the initial color value to colorGroup().light() (bright) and start + // drawing the shadow border at 45° (45*16 = 720). + + int angle = -720; + color = colorGroup().light(); + + for ( int arc = 120; arc < 2880; arc += 240 ) + { + pen.setColor( color ); + paint.setPen( pen ); + int w = width - pen.width() / 2 - scale + 1; + paint.drawArc( pen.width() / 2, pen.width() / 2, w, w, angle + arc, 240 ); + paint.drawArc( pen.width() / 2, pen.width() / 2, w, w, angle - arc, 240 ); + color = color.dark( 110 ); //FIXME: this should somehow use the contrast value + } // end for ( angle = 720; angle < 6480; angle += 160 ) + + paint.end(); + // + // painting done + + if (smooth) + { + QPixmap *&dest = led_state ? d->on_map : d->off_map; + QImage i = tmpMap->convertToImage(); + width /= 3; + i = i.smoothScale(width, width); + delete tmpMap; + dest = new QPixmap(i); + paint.begin(this); + paint.drawPixmap(0, 0, *dest); + paint.end(); + } +} + +void +KLed::paintRect() +{ + QPainter painter(this); + QBrush lightBrush(led_color); + QBrush darkBrush(d->offcolor); + QPen pen(led_color.dark(300)); + int w = width(); + int h = height(); + // ----- + switch (led_state) { + case On: + painter.setBrush(lightBrush); + painter.drawRect(0, 0, w, h); + break; + case Off: + painter.setBrush(darkBrush); + painter.drawRect(0, 0, w, h); + painter.setPen(pen); + painter.drawLine(0, 0, w, 0); + painter.drawLine(0, h - 1, w, h - 1); + // Draw verticals + int i; + for (i = 0; i < w; i += 4 /* dx */) + painter.drawLine(i, 1, i, h - 1); + break; + default: + break; + } +} + +void +KLed::paintRectFrame(bool raised) +{ + QPainter painter(this); + QBrush lightBrush(led_color); + QBrush darkBrush(d->offcolor); + int w = width(); + int h = height(); + QColor black = Qt::black; + QColor white = Qt::white; + // ----- + if (raised) { + painter.setPen(white); + painter.drawLine(0, 0, 0, h - 1); + painter.drawLine(1, 0, w - 1, 0); + painter.setPen(black); + painter.drawLine(1, h - 1, w - 1, h - 1); + painter.drawLine(w - 1, 1, w - 1, h - 1); + painter.fillRect(1, 1, w - 2, h - 2, + (led_state == On) ? lightBrush : darkBrush); + } else { + painter.setPen(black); + painter.drawRect(0, 0, w, h); + painter.drawRect(0, 0, w - 1, h - 1); + painter.setPen(white); + painter.drawRect(1, 1, w - 1, h - 1); + painter.fillRect(2, 2, w - 4, h - 4, + (led_state == On) ? lightBrush : darkBrush); + } +} + +KLed::State +KLed::state() const +{ + return led_state; +} + +KLed::Shape +KLed::shape() const +{ + return led_shape; +} + +QColor +KLed::color() const +{ + return led_color; +} + +KLed::Look +KLed::look() const +{ + return led_look; +} + +void +KLed::setState( State state ) +{ + if (led_state != state) { + led_state = state; + update(); + } +} + +void +KLed::toggleState() +{ + led_state = (led_state == On) ? Off : On; + // setColor(led_color); + update(); +} + +void +KLed::setShape(KLed::Shape s) +{ + if (led_shape != s) { + led_shape = s; + update(); + } +} + +void +KLed::setColor(const QColor& col) +{ + if (led_color != col) { + led_color = col; + d->offcolor = col.dark(d->dark_factor); + delete d->on_map; + d->on_map = 0; + delete d->off_map; + d->off_map = 0; + update(); + } +} + +void +KLed::setDarkFactor(int darkfactor) +{ + if (d->dark_factor != darkfactor) { + d->dark_factor = darkfactor; + d->offcolor = led_color.dark(darkfactor); + update(); + } +} + +int +KLed::darkFactor() const +{ + return d->dark_factor; +} + +void +KLed::setLook( Look look ) +{ + if (led_look != look) { + led_look = look; + update(); + } +} + +void +KLed::toggle() +{ + toggleState(); +} + +void +KLed::on() +{ + setState(On); +} + +void +KLed::off() +{ + setState(Off); +} + +QSize +KLed::sizeHint() const +{ + return QSize(16, 16); +} + +QSize +KLed::minimumSizeHint() const +{ + return QSize(16, 16 ); +} + +void KLed::virtual_hook( int, void* ) +{ /*BASE::virtual_hook( id, data );*/ +} diff --git a/src/gui/kdeext/klearlook.cpp b/src/gui/kdeext/klearlook.cpp new file mode 100644 index 0000000..3e5b986 --- /dev/null +++ b/src/gui/kdeext/klearlook.cpp @@ -0,0 +1,4095 @@ +/* $Id: klearlook.cpp,v 1.25 2006/04/26 18:55:41 jck Exp $ + +Klearlook (C) Joerg C. Koenig, 2005 jck@gmx.org + +---- + +Based upon QtCurve (C) Craig Drummond, 2003 Craig.Drummond@lycos.co.uk + Bernhard Rosenkr�zer + Preston Brown + Than Ngo + +Released under the GNU General Public License (GPL) v2. + +---- + +B???Curve is based on the KDE Light style, 2nd revision: +Copyright(c)2000-2001 Trolltech AS (info@trolltech.com) + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files(the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "klearlook.h" +#include + +#if KDE_VERSION >= 0x30200 +#include +#include +#endif + +// Uncomment the following to enable gradients in toolbars and menubars +// NOTE: Not yet complete!!! +//#define QTC_GRADIENT_TOOLBARS_AND_MENUBARS + +//static int HIGH_LIGHT_FACTORS[]={ 100, 100, 100, 101, 102, 103, 104, 105 /*def*/, 106, 107, 108 }; +//#define HIGHLIGHT_FACTOR(X) (X<0||X>10) ? 105 : HIGH_LIGHT_FACTORS[X] +#define QTC_HIGHLIGHT_FACTOR 105 +#define QTC_BORDERED_FRAME_WIDTH 2 +#define QTC_DEF_FRAME_WIDTH 1 +//#define QTC_MIN_BTN_SIZE 10 +#define QTC_NO_SECT -1 + +#define MENU_POPUP_ITEM_HIGH_HI 7 +#define MENU_POPUP_ITEM_HIGH_LO 5 +//#define MENU_POPUP_SHADOW + +#define POS_DIV(a, b) ( (a)/(b) + ( ( (a) % (b) >= (b)/2 ) ? 1 : 0 ) ) + +static const int itemHMargin = 6; +static const int itemFrame = 2; +static const int arrowHMargin = 6; +static const int rightBorder = 12; + +#if KDE_VERSION >= 0x30200 +// Try to read $KDEHOME/share/config/kickerrc to find out if kicker is transparent... + +static QString readEnvPath( const char *env ) { + QCString path = getenv( env ); + + return path.isEmpty() + ? QString::null + : QFile::decodeName( path ); +} + +static bool kickerIsTrans() { + QString kdeHome( readEnvPath( getuid() ? "KDEHOME" : "KDEROOTHOME" ) ), + cfgFileName; + bool trans = false; + + if ( kdeHome.isEmpty() ) + cfgFileName = QDir::homeDirPath() + "/.kde/share/config/kickerrc"; + else + cfgFileName = QString( kdeHome ) + "/share/config/kickerrc"; + + QFile cfgFile( cfgFileName ); + + if ( cfgFile.open( IO_ReadOnly ) ) { + QTextStream stream( &cfgFile ); + QString line; + bool stop = false, + inGen = false; + + while ( !stream.atEnd() && !stop ) { + line = stream.readLine(); + + if ( inGen ) { + if ( 0 == line.find( "Transparent=" ) ) // Found it! + { + if ( -1 != line.find( "true" ) ) + trans = true; + stop = true; + } else if ( line[ 0 ] == QChar( '[' ) ) // Then wasn't in General section... + stop = true; + } else if ( 0 == line.find( "[General]" ) ) + inGen = true; + } + cfgFile.close(); + } + + return trans; +} +#endif + +inline int limit( double c ) { + return c < 0.0 + ? 0 + : c > 255.0 + ? 255 + : ( int ) c; +} + +inline QColor midColor( const QColor &a, const QColor &b, double factor = 1.0 ) { + return QColor( ( a.red() + limit( b.red() * factor ) ) >> 1, + ( a.green() + limit( b.green() * factor ) ) >> 1, + ( a.blue() + limit( b.blue() * factor ) ) >> 1 ); +} + +// Copied from Keramik... +static bool isFormWidget( const QWidget *widget ) { + //Form widgets are in the KHTMLView, but that has 2 further inner levels + //of widgets - QClipperWidget, and outside of that, QViewportWidget + QWidget * potentialClipPort = widget->parentWidget(); + + if ( !potentialClipPort || potentialClipPort->isTopLevel() ) + return false; + + QWidget *potentialViewPort = potentialClipPort->parentWidget(); + + if ( !potentialViewPort || potentialViewPort->isTopLevel() || qstrcmp( potentialViewPort->name(), "qt_viewport" ) ) + return false; + + QWidget *potentialKHTML = potentialViewPort->parentWidget(); + + if ( !potentialKHTML || potentialKHTML->isTopLevel() || qstrcmp( potentialKHTML->className(), "KHTMLView" ) ) + return false; + + return true; +} + +static void rgb2hls( double *r, double *g, double *b ) +{ + double min; + double max; + double red; + double green; + double blue; + double h, l, s; + double delta; + + red = *r; + green = *g; + blue = *b; + + if (red > green) + { + if (red > blue) + max = red; + else + max = blue; + + if (green < blue) + min = green; + else + min = blue; + } + else + { + if (green > blue) + max = green; + else + max = blue; + + if (red < blue) + min = red; + else + min = blue; + } + + l = (max + min) / 2; + s = 0; + h = 0; + + if (max != min) + { + if (l <= 0.5) + s = (max - min) / (max + min); + else + s = (max - min) / (2 - max - min); + + delta = max -min; + if (red == max) + h = (green - blue) / delta; + else if (green == max) + h = 2 + (blue - red) / delta; + else if (blue == max) + h = 4 + (red - green) / delta; + + h *= 60; + if (h < 0.0) + h += 360; + } + + *r = h; + *g = l; + *b = s; +} + + +static void hls2rgb( double *h, double *l, double *s ) { + double hue; + double lightness; + double saturation; + double m1, m2; + double r, g, b; + lightness = *l; + saturation = *s; + + if (lightness <= 0.5) + m2 = lightness * (1 + saturation); + else + m2 = lightness + saturation - lightness * saturation; + + m1 = 2 * lightness - m2; + + if (saturation == 0) + { + *h = lightness; + *l = lightness; + *s = lightness; + } + else + { + hue = *h + 120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + r = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + r = m2; + else if (hue < 240) + r = m1 + (m2 - m1) * (240 - hue) / 60; + else + r = m1; + + hue = *h; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + g = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + g = m2; + else if (hue < 240) + g = m1 + (m2 - m1) * (240 - hue) / 60; + else + g = m1; + + hue = *h - 120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + b = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + b = m2; + else if (hue < 240) + b = m1 + (m2 - m1) * (240 - hue) / 60; + else + b = m1; + + *h = r; + *l = g; + *s = b; + } + +} + +static bool equal( double d1, double d2 ) { + return ( fabs( d1 - d2 ) < 0.0001 ); +} + +static void shade( const QColor &a, QColor *b, float k ) { + + if ( equal( k, 1.0 ) ) + * b = a; + else { + double red = a.red() / 256.0, + green = a.green() / 256.0, + blue = a.blue() / 256.0; + + rgb2hls( &red, &green, &blue ); + + green *= k; + if ( green > 1.0 ) + green = 1.0; + else if ( green < 0.0 ) + green = 0.0; + + blue *= k; + if ( blue > 1.0 ) + blue = 1.0; + else if ( blue < 0.0 ) + blue = 0.0; + + hls2rgb( &red, &green, &blue ); + + b->setRgb( limit( red * 256 ), limit( green * 256 ), limit( blue * 256 ) ); + } +} + +static void shadeGradient( const QColor &base, QColor *vals ) { + vals[ KlearlookStyle::GRADIENT_BASE ] = base; + + shade( vals[ KlearlookStyle::GRADIENT_BASE ], + &( vals[ KlearlookStyle::GRADIENT_TOP ] ), SHADE_GRADIENT_TOP ); + + shade( vals[ KlearlookStyle::GRADIENT_BASE ], + &( vals[ KlearlookStyle::GRADIENT_BOTTOM ] ), SHADE_GRADIENT_BOTTOM ); + + shade( vals[ KlearlookStyle::GRADIENT_BASE ], + &( vals[ KlearlookStyle::GRADIENT_LIGHT ] ), SHADE_GRADIENT_LIGHT ); + shade( vals[ KlearlookStyle::GRADIENT_BASE ], + &( vals[ KlearlookStyle::GRADIENT_DARK ] ), SHADE_GRADIENT_DARK ); +} + +static void drawLines( QPainter *p, const QRect &r, bool horiz, int nLines, int offset, const QColor *cols, + int startOffset, bool etched, bool lightGradient ) { + int space = ( nLines * 2 ) + ( nLines - 1 ), + x = horiz ? r.x() : r.x() + ( ( r.width() - space ) >> 1 ), + y = horiz ? r.y() + ( ( r.height() - space ) >> 1 ) : r.y(), + x2 = r.x() + r.width() - 1, + y2 = r.y() + r.height() - 1, + i, + displacement = etched ? 1 : 0; + + if ( horiz ) { + if ( startOffset && y + startOffset > 0 ) + y += startOffset; + + p->setPen( cols[ etched ? lightGradient ? 3 : 4 : 0 ] ); + for ( i = 0; i < space; i += 3 ) + p->drawLine( x + offset, y + i, x2 - ( offset + displacement ), y + i ); + + p->setPen( cols[ etched ? 0 : lightGradient ? 3 : 4 ] ); + for ( i = 1; i < space; i += 3 ) + p->drawLine( x + offset + displacement, y + i - 2, x2 - offset, y + i - 2); + } else { + if ( startOffset && x + startOffset > 0 ) + x += startOffset; + + p->setPen( cols[ etched ? lightGradient ? 3 : 4 : 0 ] ); + for ( i = 0; i < space; i += 3 ) + p->drawLine( x + i, y + offset, x + i, y2 - ( offset + displacement ) ); + + p->setPen( cols[ etched ? 0 : lightGradient ? 3 : 4 ] ); + for ( i = 1; i < space; i += 3 ) + p->drawLine( x + i -2, y + offset + displacement, x + i -2, y2 - offset ); + } +} + +inline QColor getFill( QStyle::SFlags flags, const QColor *use ) { + return !( flags & QStyle::Style_Enabled ) + ? use[ 1 ] + : flags & QStyle::Style_Down + ? use[ 3 ] + : flags & QStyle::Style_MouseOver + ? flags & ( QStyle::Style_On | QStyle::Style_Sunken ) + ? use[ 3 ].light( QTC_HIGHLIGHT_FACTOR ) + : use[ NUM_SHADES ].light( QTC_HIGHLIGHT_FACTOR ) + : flags & ( QStyle::Style_On | QStyle::Style_Sunken ) + ? use[ 3 ] + : use[ NUM_SHADES ]; +} + +#ifdef USE_SINGLE_STYLE +KlearlookStyle::KlearlookStyle() +#else +KlearlookStyle::KlearlookStyle( + bool gpm, bool bb, bool bf, bool round, EGroove st, h, + bool ge, bool va, bool bdt, bool crlh, EDefBtnIndicator dbi, ETBarBorder tbb, + ELvExpander lve, ELvLines lvl, bool lvd, bool ico, int popuplvl ) ) +#endif +: KStyle( AllowMenuTransparency, WindowsStyleScrollBar ), +themedApp( APP_OTHER ), +#ifndef USE_SINGLE_STYLE +borderButton( bb ), borderFrame( bf ), rounded( round ), etchedSlider( etched ), appearance( ge ? APPEARANCE_GRADIENT : APPEARANCE_FLAT ), +pmProfile( PROFILE_SUNKEN ), vArrow( va ), boldDefText( bdt ), crLabelHighlight( crlh ), lvDark( lvd ), +defBtnIndicator( dbi ), sliderThumbs( st ), handles( h ), toolbarBorders( tbb ), lvExpander( lve ), lvLines( lvl ), menuIcons( ico ), borderSplitter( true ), popupmenuHighlightLevel(popuplvl) +#endif +#if KDE_VERSION >= 0x30200 +isTransKicker( false ), +#endif +hover( HOVER_NONE ), +oldCursor( -1, -1 ), +formMode( false ), +hoverWidget( NULL ), +hoverSect( QTC_NO_SECT ) { + QSettings s; + + contrast = s.readNumEntry( "/Qt/KDE/contrast", 7 ); + if ( contrast < 0 || contrast > 10 ) + contrast = 7; +#ifdef USE_SINGLE_STYLE + + borderButton = borderFrame = s.readBoolEntry( "/klearlookstyle/Settings/border", true ); + rounded = borderButton ? s.readBoolEntry( "/klearlookstyle/Settings/round", true ) : false; + menuIcons = s.readBoolEntry( "/klearlookstyle/Settings/icons", true ); + darkMenubar = s.readBoolEntry( "/klearlookstyle/Settings/darkMenubar", true ); + popupmenuHighlightLevel = s.readNumEntry( "/klearlookstyle/Settings/popupmenuHighlightLevel", 3); + + QString tmp = s.readEntry( "/klearlookstyle/Settings/toolbarBorders", QString::null ); + toolbarBorders = tmp.isEmpty() + ? TB_LIGHT + : qtc_to_tbar_border( tmp.latin1() ); + + bool etched = s.readBoolEntry( "/klearlookstyle/Settings/etched", true ); + + tmp = s.readEntry( "/klearlookstyle/Settings/sliderThumbs", QString::null ); + sliderThumbs = tmp.isEmpty() + ? etched ? GROOVE_SUNKEN : GROOVE_RAISED + : qtc_to_groove( tmp.latin1() ); + + tmp = s.readEntry( "/klearlookstyle/Settings/lvExpander", QString::null ); + lvExpander = tmp.isEmpty() + ? LV_EXP_ARR + : qtc_to_lv_expander( tmp.latin1() ); + + tmp = s.readEntry( "/klearlookstyle/Settings/lvLines", QString::null ); + lvLines = tmp.isEmpty() + ? LV_LINES_SOLID + : qtc_to_lv_lines( tmp.latin1() ); + + + lvDark = s.readBoolEntry( "/klearlookstyle/Settings/lvDark", false ); + handles = qtc_to_groove( s.readEntry( "/klearlookstyle/Settings/sliderThumbs", DEF_HANDLE_STR ).latin1() ); + + if ( GROOVE_NONE == handles ) + handles = GROOVE_RAISED; + + appearance = qtc_to_appearance( + s.readEntry( "/klearlookstyle/Settings/appearance", DEF_APPEARANCE_STR ).latin1() ); + pmProfile = qtc_to_profile( s.readEntry( "/klearlookstyle/Settings/pm", DEF_PROFILE_STR ).latin1() ); + vArrow = s.readBoolEntry( "/klearlookstyle/Settings/vArrow", false ); + boldDefText = s.readBoolEntry( "/klearlookstyle/Settings/embolden", false ); + crLabelHighlight = s.readBoolEntry( "/klearlookstyle/Settings/crLabelHighlight", false ); + defBtnIndicator = qtc_to_ind( + s.readEntry( "/klearlookstyle/Settings/defBtnIndicator", DEF_IND_STR ).latin1() ); + + //if(!boldDefText && IND_NONE==defBtnIndicator) + // defBtnIndicator=IND_CORNER; + + borderSplitter = s.readBoolEntry( "/klearlookstyle/Settings/borderSplitter", false ); +#endif + + if ( PROFILE_RAISED != pmProfile ) + shadeColors( qApp->palette().active().highlight(), menuPbar ); + else + shadeGradient( qApp->palette().active().highlight(), menuPbar ); + shadeColors( qApp->palette().active().background(), gray ); + shadeColors( qApp->palette().active().button(), button ); +} + +void KlearlookStyle::polish( QApplication *app ) { + if ( !qstrcmp( app->argv() [ 0 ], "kicker" ) || !qstrcmp( app->argv() [ 0 ], "appletproxy" ) ) { + themedApp = APP_KICKER; +#if KDE_VERSION >= 0x30200 + + isTransKicker = rounded && kickerIsTrans(); +#endif + + } else if ( !qstrcmp( app->argv() [ 0 ], "korn" ) ) { + themedApp = APP_KORN; +#if KDE_VERSION >= 0x30200 + + isTransKicker = rounded && kickerIsTrans(); +#endif + + } else + themedApp = qstrcmp( qApp->argv() [ 0 ], "soffice.bin" ) ? APP_OTHER : APP_OPENOFFICE; +} + +void KlearlookStyle::polish( QPalette &pal ) { + int c = QSettings().readNumEntry( "/Qt/KDE/contrast", 7 ); + bool newContrast = false; + + if ( c < 0 || c > 10 ) + c = 7; + + if ( c != contrast ) { + contrast = c; + newContrast = true; + } + + if ( newContrast || gray[ NUM_SHADES ] != qApp->palette().active().background() ) + shadeColors( qApp->palette().active().background(), gray ); + + if ( newContrast || button[ NUM_SHADES ] != qApp->palette().active().button() ) + shadeColors( qApp->palette().active().button(), button ); + + if ( PROFILE_RAISED == pmProfile ) { + if ( newContrast || menuPbar[ NUM_SHADES ] != qApp->palette().active().highlight() ) + shadeColors( qApp->palette().active().highlight(), menuPbar ); + } else + if ( qApp->palette().active().highlight() != menuPbar[ GRADIENT_BASE ] ) + shadeGradient( qApp->palette().active().highlight(), menuPbar ); + + const QColorGroup &actGroup = pal.active(), + &inactGroup = pal.inactive(); + const QColor *use = backgroundColors( actGroup ); + QColorGroup newAct( actGroup.foreground(), actGroup.button(), + use[ 0 ], use[ 5 ], actGroup.mid(), actGroup.text(), + actGroup.brightText(), actGroup.base(), actGroup.background() ); + + newAct.setColor( QColorGroup::Highlight, actGroup.color( QColorGroup::Highlight ) ); + pal.setActive( newAct ); + + use = backgroundColors( inactGroup ); + + QColorGroup newInact( inactGroup.foreground(), inactGroup.button(), + use[ 0 ], use[ 5 ], inactGroup.mid(), inactGroup.text(), + inactGroup.brightText(), inactGroup.base(), inactGroup.background() ); + + newInact.setColor( QColorGroup::Highlight, inactGroup.color( QColorGroup::Highlight ) ); + pal.setInactive( newInact ); +} + +static const char * kdeToolbarWidget = "kde toolbar widget"; + +void KlearlookStyle::polish( QWidget *widget ) { + if ( ::qt_cast( widget ) + || ::qt_cast( widget ) + || ::qt_cast( widget ) + || widget->inherits( "QSplitterHandle" ) ) { +#if QT_VERSION >= 0x030200 + widget->setMouseTracking( true ); +#endif + + widget->installEventFilter( this ); + } else if ( ::qt_cast( widget ) || ::qt_cast( widget ) || + widget->inherits( "QToolBarExtensionWidget" ) ) { + widget->setBackgroundMode( QWidget::PaletteBackground ); + widget->installEventFilter( this ); + + } else if ( ::qt_cast( widget ) + || ::qt_cast( widget ) + || ::qt_cast( widget ) ) + widget->setBackgroundMode( QWidget::PaletteBackground ); + + else if ( widget->inherits( "KToolBarSeparator" ) ) { + widget->setBackgroundMode( QWidget::NoBackground ); + widget->installEventFilter( this ); + + } else if ( ::qt_cast( widget ) ) { + widget->setMouseTracking( true ); + widget->installEventFilter( this ); + widget->setBackgroundMode( QWidget::NoBackground ); + + } else if ( ::qt_cast( widget ) || ::qt_cast( widget ) ) { + widget->setMouseTracking( true ); + widget->installEventFilter( this ); + + } else if ( 0 == qstrcmp( widget->name(), kdeToolbarWidget ) ) { + widget->installEventFilter( this ); + widget->setBackgroundMode( QWidget::NoBackground ); // We paint the whole background. + } + + KStyle::polish( widget ); +} + +void KlearlookStyle::unPolish( QWidget *widget ) { + if ( ::qt_cast( widget ) || + ::qt_cast( widget ) || + ::qt_cast( widget ) || + widget->inherits( "QSplitterHandle" ) ) { +#if QT_VERSION >= 0x030200 + widget->setMouseTracking( false ); +#endif + + widget->removeEventFilter( this ); + } else if ( ::qt_cast( widget ) || ::qt_cast( widget ) || + widget->inherits( "QToolBarExtensionWidget" ) ) { + widget->setBackgroundMode( QWidget::PaletteButton ); + widget->removeEventFilter( this ); + + } else if ( ::qt_cast( widget ) || + ::qt_cast( widget ) || + ::qt_cast( widget ) ) + widget->setBackgroundMode( QWidget::PaletteBackground ); + + else if ( widget->inherits( "KToolBarSeparator" ) ) { + widget->setBackgroundMode( PaletteBackground ); + widget->removeEventFilter( this ); + + } else if ( ::qt_cast( widget ) ) { + widget->setMouseTracking( false ); + widget->removeEventFilter( this ); + widget->setBackgroundMode( QWidget::PaletteButton ); + + } else if ( ::qt_cast( widget ) || + ::qt_cast( widget ) ) { + widget->setMouseTracking( false ); + widget->removeEventFilter( this ); + + } else if ( 0 == qstrcmp( widget->name(), kdeToolbarWidget ) ) { + widget->removeEventFilter( this ); + widget->setBackgroundMode( PaletteBackground ); + } + + KStyle::unPolish( widget ); +} + +bool KlearlookStyle::eventFilter( QObject *object, QEvent *event ) { + if ( object->parent() && 0 == qstrcmp( object->name(), kdeToolbarWidget ) ) { + // Draw background for custom widgets in the toolbar that have specified a "kde toolbar widget" name. + if ( QEvent::Paint == event->type() ) { + QWidget * widget = static_cast( object ), + *parent = static_cast( object->parent() ); +#ifdef QTC_GRADIENT_TOOLBARS_AND_MENUBARS + // Find the top-level toolbar of this widget, since it may be nested in other + // widgets that are on the toolbar. + int x_offset = widget->x(), + y_offset = widget->y(); + + while ( parent && parent->parent() && !qstrcmp( parent->name(), kdeToolbarWidget ) ) { + x_offset += parent->x(); + y_offset += parent->y(); + parent = static_cast( parent->parent() ); + } + + QRect pr( parent->rect() ); + bool horiz_grad = pr.width() < pr.height(); + + // Check if the parent is a QToolbar, and use its orientation, else guess. + QToolBar *toolbar = dynamic_cast( parent ); + + if ( toolbar ) + horiz_grad = toolbar->orientation() == Qt::Vertical; + + drawBevelGradient( parent->colorGroup().background(), true, 1, &QPainter( widget ), + QRect( x_offset, y_offset, pr.width(), pr.height() ), + horiz_grad, SHADE_BAR_LIGHT, SHADE_BAR_DARK ); +#else + + QPainter( widget ).fillRect( widget->rect(), parent->colorGroup().background() ); +#endif + + return false; // Now draw the contents + } + } else if ( object->inherits( "KToolBarSeparator" ) && QEvent::Paint == event->type() ) { + QFrame * frame = dynamic_cast( object ); + + if ( frame && QFrame::NoFrame != frame->frameShape() ) { + QPainter painter( frame ); + if ( QFrame::VLine == frame->frameShape() ) + drawPrimitive( PE_DockWindowSeparator, &painter, + frame->rect(), frame->colorGroup(), Style_Horizontal ); + else if ( QFrame::HLine == frame->frameShape() ) + drawPrimitive( PE_DockWindowSeparator, &painter, + frame->rect(), frame->colorGroup() ); + else + return false; + return true; // been drawn! + } + } + switch ( event->type() ) { + case QEvent::Enter: + if ( object->isWidgetType() ) { + hoverWidget = ( QWidget * ) object; + if ( hoverWidget && hoverWidget->isEnabled() ) { + if ( redrawHoverWidget() ) { + hoverWidget->repaint( false ); + if ( APP_KICKER == themedApp ) + hover = HOVER_NONE; + } + oldCursor = QCursor::pos(); + } else + hoverWidget = NULL; + } + break; + case QEvent::Leave: + if ( hoverWidget && object == hoverWidget ) { + oldCursor.setX( -1 ); + oldCursor.setY( -1 ); + hoverWidget = NULL; + ( ( QWidget * ) object ) ->repaint( false ); + } + break; + case QEvent::MouseMove: + if ( hoverWidget && object->isWidgetType() ) { + if ( redrawHoverWidget() ) { + hoverWidget->repaint( false ); + if ( APP_KICKER == themedApp ) + hover = HOVER_NONE; + } + oldCursor = QCursor::pos(); + } + break; + default: + break; + } + + return KStyle::eventFilter( object, event ); +} + +void KlearlookStyle::drawLightBevelButton( + QPainter *p, + const QRect &r, + const QColorGroup &cg, + QStyle::SFlags flags, + bool useGrad, + ERound round, + const QColor &fill, + const QColor *custom, + bool light ) const +{ + QRect br( r ); + bool sunken = ( flags & ( QStyle::Style_Down | QStyle::Style_On | QStyle::Style_Sunken ) ); + int dark = borderButton ? 4 : 5, + c1 = sunken ? dark : light ? 6 : 0; + + p->save(); + + if ( !borderButton ) + br.addCoords( -1, -1, 1, 1 ); + + if ( ( sunken && !borderButton ) || ( !sunken && flags & QStyle::Style_Raised ) ) { + p->setPen( custom ? custom[ c1 ] : gray[ c1 ] ); + if ( APPEARANCE_LIGHT_GRADIENT != appearance ) { + int c2 = sunken ? 0 : dark; + + + p->drawLine( br.x() + 1, br.y() + 2, br.x() + 1, br.y() + br.height() - 3 ); // left + p->drawLine( br.x() + 1, br.y() + 1, br.x() + br.width() - 2, br.y() + 1 ); // top + + p->setPen( custom ? custom[ c2 ] : gray[ c2 ] ); + p->drawLine( br.x() + br.width() - 2, br.y() + 1, + br.x() + br.width() - 2, br.y() + br.height() - 3 ); // right + p->drawLine( br.x() + 1, br.y() + br.height() - 2, + br.x() + br.width() - 2, br.y() + br.height() - 2 ); // bottom + + br.addCoords( 2, 2, -2, -2 ); + } else { + p->drawLine( br.x() + 1, br.y() + 2, br.x() + 1, br.y() + br.height() - 2 ); // left + p->drawLine( br.x() + 1, br.y() + 1, br.x() + br.width() - 2, br.y() + 1 ); // top + + br.addCoords( 2, 2, -1, -1 ); + } + } else + br.addCoords( 1, 1, -1, -1 ); + + // fill + if ( useGrad && APPEARANCE_FLAT != appearance ) { + drawBevelGradient( fill.dark( 100 ), !sunken, 0, p, + QRect( br.left() - 1, br.top() - 1, br.width() + 2, br.height() + 2 ), flags & Style_Horizontal, + sunken ? + SHADE_BEVEL_BUTTON_GRAD_LIGHT( appearance ) : + SHADE_BEVEL_BUTTON_GRAD_LIGHT( appearance ), + sunken ? + SHADE_BEVEL_BUTTON_GRAD_DARK( appearance ) : + SHADE_BEVEL_BUTTON_GRAD_DARK( appearance ) ); + } else + p->fillRect( br, fill ); + + if ( borderButton ) + if ( rounded && ROUNDED_NONE != round ) { + bool wide = r.width() >= QTC_MIN_BTN_SIZE, + tall = r.height() >= QTC_MIN_BTN_SIZE; + QColor border( flags & Style_ButtonDefault && IND_FONT_COLOUR == defBtnIndicator & flags & Style_Enabled + ? cg.text() : custom ? custom[ 5 ] : gray[ 5 ] ); + + p->setPen( border.light(80) ); + switch ( round ) { + case ROUNDED_ALL: + p->drawLine( r.x() + 2, r.y(), r.x() + r.width() - 3, r.y() ); + p->drawLine( r.x() + 2, r.y() + r.height() - 1, r.x() + r.width() - 3, + r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y() + 2, r.x(), r.y() + r.height() - 3 ); + p->drawLine( r.x() + r.width() - 1, r.y() + 2, r.x() + r.width() - 1, + r.y() + r.height() - 3 ); + if ( tall && wide ) { + p->drawPoint( r.x() + r.width() - 2, r.y() + 1 ); + p->drawPoint( r.x() + 1, r.y() + r.height() - 2 ); + p->drawPoint( r.x() + r.width() - 2, r.y() + r.height() - 2 ); + p->drawPoint( r.x() + 1, r.y() + 1 ); + p->setPen( midColor( border, cg.background() ) ); + } + if ( !formMode || !( tall && wide ) ) { + p->drawLine( r.x(), r.y() + 1, r.x() + 1, r.y() ); + p->drawLine( r.x() + r.width() - 2, r.y(), r.x() + r.width() - 1, r.y() + 1 ); + p->drawLine( r.x(), r.y() + r.height() - 2, r.x() + 1, r.y() + r.height() - 1 ); + p->drawLine( r.x() + r.width() - 2, r.y() + r.height() - 1, + r.x() + r.width() - 1, r.y() + r.height() - 2 ); + } + if ( !formMode ) { + if ( tall && wide ) + p->setPen( cg.background() ); + else + p->setPen( midColor( custom ? custom[ 3 ] : gray[ 3 ], cg.background() ) ); + p->drawPoint( r.x(), r.y() ); + p->drawPoint( r.x() + r.width() - 1, r.y() ); + p->drawPoint( r.x(), r.y() + r.height() - 1 ); + p->drawPoint( r.x() + r.width() - 1, r.y() + r.height() - 1 ); + } + break; + case ROUNDED_TOP: + p->drawLine( r.x() + 2, r.y(), r.x() + r.width() - 3, r.y() ); + p->drawLine( r.x() + 1, r.y() + r.height() - 1, + r.x() + r.width() - 2, r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y() + 2, r.x(), r.y() + r.height() - 1 ); + p->drawLine( r.x() + r.width() - 1, r.y() + 2, + r.x() + r.width() - 1, r.y() + r.height() - 1 ); + if ( wide ) { + p->drawPoint( r.x() + r.width() - 2, r.y() + 1 ); + p->drawPoint( r.x() + 1, r.y() + 1 ); + p->setPen( midColor( border, cg.background() ) ); + } + if ( !formMode || !wide ) { + p->drawLine( r.x(), r.y() + 1, r.x() + 1, r.y() ); + p->drawLine( r.x() + r.width() - 2, r.y(), r.x() + r.width() - 1, r.y() + 1 ); + } + if ( !formMode ) { + if ( wide ) + p->setPen( cg.background() ); + else + p->setPen( midColor( custom ? custom[ 3 ] : gray[ 3 ], cg.background() ) ); + p->drawPoint( r.x(), r.y() ); + p->drawPoint( r.x() + r.width() - 1, r.y() ); + } + break; + case ROUNDED_BOTTOM: + p->drawLine( r.x() + 1, r.y(), r.x() + r.width() - 2, r.y() ); + p->drawLine( r.x() + 2, r.y() + r.height() - 1, + r.x() + r.width() - 3, r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y(), r.x(), r.y() + r.height() - 3 ); + p->drawLine( r.x() + r.width() - 1, r.y(), + r.x() + r.width() - 1, r.y() + r.height() - 3 ); + if ( wide ) { + p->drawPoint( r.x() + 1, r.y() + r.height() - 2 ); + p->drawPoint( r.x() + r.width() - 2, r.y() + r.height() - 2 ); + p->setPen( midColor( border, cg.background() ) ); + } + if ( !formMode || !wide ) { + p->drawLine( r.x(), r.y() + r.height() - 2, r.x() + 1, r.y() + r.height() - 1 ); + p->drawLine( r.x() + r.width() - 2, r.y() + r.height() - 1, + r.x() + r.width() - 1, r.y() + r.height() - 2 ); + } + if ( !formMode ) { + if ( wide ) + p->setPen( cg.background() ); + else + p->setPen( midColor( custom ? custom[ 3 ] : gray[ 3 ], cg.background() ) ); + p->drawPoint( r.x(), r.y() + r.height() - 1 ); + p->drawPoint( r.x() + r.width() - 1, r.y() + r.height() - 1 ); + } + break; + case ROUNDED_LEFT: + p->drawLine( r.x() + 2, r.y(), r.x() + r.width() - 2, r.y() ); + p->drawLine( r.x() + 2, r.y() + r.height() - 1, + r.x() + r.width() - 2, r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y() + 2, r.x(), r.y() + r.height() - 3 ); + p->drawLine( r.x() + r.width() - 1, r.y(), r.x() + r.width() - 1, + r.y() + r.height() - 1 ); + if ( tall ) { + p->drawPoint( r.x() + 1, r.y() + r.height() - 2 ); + p->drawPoint( r.x() + 1, r.y() + 1 ); + p->setPen( midColor( border, cg.background() ) ); + } + if ( !formMode || !tall ) { + p->drawLine( r.x(), r.y() + 1, r.x() + 1, r.y() ); + p->drawLine( r.x(), r.y() + r.height() - 2, + r.x() + 1, r.y() + r.height() - 1 ); + } + if ( !formMode ) { + if ( tall ) + p->setPen( cg.background() ); + else + p->setPen( midColor( custom ? custom[ 3 ] : gray[ 3 ], cg.background() ) ); + p->drawPoint( r.x(), r.y() ); + p->drawPoint( r.x(), r.y() + r.height() - 1 ); + } + break; + case ROUNDED_RIGHT: + p->drawLine( r.x() + 1, r.y(), r.x() + r.width() - 3, r.y() ); + p->drawLine( r.x() + 1, r.y() + r.height() - 1, + r.x() + r.width() - 3, r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y(), r.x(), r.y() + r.height() - 1 ); + p->drawLine( r.x() + r.width() - 1, r.y() + 2, + r.x() + r.width() - 1, r.y() + r.height() - 3 ); + if ( tall ) { + p->drawPoint( r.x() + r.width() - 2, r.y() + 1 ); + p->drawPoint( r.x() + r.width() - 2, r.y() + r.height() - 2 ); + p->setPen( midColor( border, cg.background() ) ); + } + if ( !formMode || !tall ) { + p->drawLine( r.x() + r.width() - 2, r.y(), r.x() + r.width() - 1, r.y() + 1 ); + p->drawLine( r.x() + r.width() - 2, r.y() + r.height() - 1, + r.x() + r.width() - 1, r.y() + r.height() - 2 ); + } + if ( !formMode ) { + if ( tall ) + p->setPen( cg.background() ); + else + p->setPen( midColor( custom ? custom[ 3 ] : gray[ 3 ], cg.background() ) ); + p->drawPoint( r.x() + r.width() - 1, r.y() ); + p->drawPoint( r.x() + r.width() - 1, r.y() + r.height() - 1 ); + } + break; + default: + break; + } + } else { + p->setPen( flags & Style_ButtonDefault && + IND_FONT_COLOUR == defBtnIndicator ? + cg.text() : custom ? custom[ 5 ] : gray[ 5 ] ); + p->setBrush( NoBrush ); + p->drawRect( r ); + } + + p->restore(); +} +void KlearlookStyle::drawLightBevel( + QPainter *p, + const QRect &r, + const QColorGroup &cg, + QStyle::SFlags flags, + bool useGrad, + ERound round, + const QColor &fill, + const QColor *custom, + bool light ) const +{ + QRect br( r ); + bool sunken = ( flags & ( QStyle::Style_Down | QStyle::Style_On | QStyle::Style_Sunken ) ); + int dark = borderButton ? 4 : 5, c1 = sunken ? dark : light ? 6 : 0; + + p->save(); + + if ( !borderButton ) + br.addCoords( -1, -1, 1, 1 ); + + if ( ( sunken && !borderButton ) || ( !sunken && flags & QStyle::Style_Raised ) ) { + p->setPen( custom ? custom[ c1 ] : gray[ c1 ] ); + if ( APPEARANCE_LIGHT_GRADIENT != appearance ) { + int c2 = sunken ? 0 : dark; + + p->drawLine( br.x() + 1, br.y() + 2, br.x() + 1, br.y() + br.height() - 3 ); // left + p->drawLine( br.x() + 1, br.y() + 1, br.x() + br.width() - 2, br.y() + 1 ); // top + + p->setPen( custom ? custom[ c2 ] : gray[ c2 ] ); + p->drawLine( br.x() + br.width() - 2, br.y() + 1, + br.x() + br.width() - 2, br.y() + br.height() - 3 ); // right + p->drawLine( br.x() + 1, br.y() + br.height() - 2, + br.x() + br.width() - 2, br.y() + br.height() - 2 ); // bottom + + br.addCoords( 2, 2, -2, -2 ); + } else { + p->drawLine( br.x() + 1, br.y() + 2, br.x() + 1, br.y() + br.height() - 2 ); // left + p->drawLine( br.x() + 1, br.y() + 1, br.x() + br.width() - 2, br.y() + 1 ); // top + + br.addCoords( 2, 2, -1, -1 ); + } + } else + br.addCoords( 1, 1, -1, -1 ); + + // fill + if ( useGrad && APPEARANCE_FLAT != appearance ) { + drawBevelGradient( fill, !sunken, 0, p, + QRect( br.left() - 1, br.top() - 1, br.width() + 2, br.height() + 2 ), + flags & Style_Horizontal, + sunken ? + SHADE_BEVEL_GRAD_SEL_LIGHT( appearance ) : + SHADE_BEVEL_GRAD_LIGHT( appearance ), + sunken ? + SHADE_BEVEL_GRAD_SEL_DARK( appearance ) : + SHADE_BEVEL_GRAD_DARK( appearance ) ); + } else { + p->fillRect( br, fill ); + } + + if ( borderButton ) + if ( rounded && ROUNDED_NONE != round ) { + bool wide = r.width() >= QTC_MIN_BTN_SIZE, + tall = r.height() >= QTC_MIN_BTN_SIZE; + + QColor border = menuPbar[ GRADIENT_BASE ].dark( 130 ); + + p->setPen( border ); + + switch ( round ) { + case ROUNDED_ALL: + p->drawLine( r.x() + 1, r.y(), r.x() + r.width() - 2, r.y() ); // top + p->drawLine( r.x() + 1, r.y() + r.height() - 1, r.x() + r.width() - 2, r.y() + r.height() - 1 ); // bottom + p->drawLine( r.x(),r.y() + 1, r.x(),r.y() + r.height() - 2 ); // left + p->drawLine( r.x() + r.width() - 1, r.y() + 1, r.x() + r.width() - 1, r.y() + r.height() - 2 ); // right + + //p->drawLine( r.x() + 2, r.y() + r.height() - 1, r.x() + r.width() - 3, r.y() + r.height() - 1 ); + + //p->drawLine( r.x(), r.y() + 2, r.x(), r.y() + r.height() - 3 ); + //p->drawLine( r.x() + r.width() - 1, r.y() + 2, r.x() + r.width() - 1, r.y() + r.height() - 3 ); + + if ( tall && wide ) { + //p->drawPoint( r.x() + r.width() - 2, r.y() + 1 ); + //p->drawPoint( r.x() + 1, r.y() + r.height() - 2 ); + //p->drawPoint( r.x() + r.width() - 2, r.y() + r.height() - 2 ); + //p->drawPoint( r.x() + 1, r.y() + 1 ); + p->setPen( midColor( border, cg.background() ) ); + } + if ( !formMode || !( tall && wide ) ) { + //p->drawLine( r.x(), r.y() + 1, r.x() + 1, r.y() ); + //p->drawLine( r.x() + r.width() - 2, r.y(), r.x() + r.width() - 1, r.y() + 1 ); + //p->drawLine( r.x(), r.y() + r.height() - 2, r.x() + 1, r.y() + r.height() - 1 ); + //p->drawLine( r.x() + r.width() - 2, r.y() + r.height() - 1, r.x() + r.width() - 1, r.y() + r.height() - 2 ); + } + if ( !formMode ) { + if ( tall && wide ) + p->setPen( cg.background() ); + else + p->setPen( midColor( custom ? custom[ 3 ] : gray[ 3 ], cg.background() ) ); + + //p->drawPoint( r.x(), r.y() ); + //p->drawPoint( r.x() + r.width() - 1, r.y() ); + //p->drawPoint( r.x(), r.y() + r.height() - 1 ); + //p->drawPoint( r.x() + r.width() - 1, r.y() + r.height() - 1 ); + } + break; + + case ROUNDED_TOP: + p->drawLine( r.x() + 2, r.y(), r.x() + r.width() - 3, r.y() ); + p->drawLine( r.x() + 1, r.y() + r.height() - 1, r.x() + r.width() - 2, r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y() + 2, r.x(), r.y() + r.height() - 1 ); + p->drawLine( r.x() + r.width() - 1, r.y() + 2, r.x() + r.width() - 1, r.y() + r.height() - 1 ); + + if ( wide ) { + p->drawPoint( r.x() + r.width() - 2, r.y() + 1 ); + p->drawPoint( r.x() + 1, r.y() + 1 ); + p->setPen( midColor( border, cg.background() ) ); + } + if ( !formMode || !wide ) { + p->drawLine( r.x(), r.y() + 1, r.x() + 1, r.y() ); + p->drawLine( r.x() + r.width() - 2, r.y(), r.x() + r.width() - 1, r.y() + 1 ); + } + if ( !formMode ) { + if ( wide ) + p->setPen( cg.background() ); + else + p->setPen( midColor( custom ? custom[ 3 ] : gray[ 3 ], cg.background() ) ); + p->drawPoint( r.x(), r.y() ); + p->drawPoint( r.x() + r.width() - 1, r.y() ); + } + break; + case ROUNDED_BOTTOM: + p->drawLine( r.x() + 1, r.y(), r.x() + r.width() - 2, r.y() ); + p->drawLine( r.x() + 2, r.y() + r.height() - 1, r.x() + r.width() - 3, r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y(), r.x(), r.y() + r.height() - 3 ); + p->drawLine( r.x() + r.width() - 1, r.y(), r.x() + r.width() - 1, r.y() + r.height() - 3 ); + if ( wide ) { + p->drawPoint( r.x() + 1, r.y() + r.height() - 2 ); + p->drawPoint( r.x() + r.width() - 2, r.y() + r.height() - 2 ); + p->setPen( midColor( border, cg.background() ) ); + } + if ( !formMode || !wide ) { + p->drawLine( r.x(), r.y() + r.height() - 2, r.x() + 1, r.y() + r.height() - 1 ); + p->drawLine( r.x() + r.width() - 2, r.y() + r.height() - 1, + r.x() + r.width() - 1, r.y() + r.height() - 2 ); + } + if ( !formMode ) { + if ( wide ) + p->setPen( cg.background() ); + else + p->setPen( midColor( custom ? custom[ 3 ] : gray[ 3 ], cg.background() ) ); + p->drawPoint( r.x(), r.y() + r.height() - 1 ); + p->drawPoint( r.x() + r.width() - 1, r.y() + r.height() - 1 ); + } + break; + case ROUNDED_LEFT: + p->drawLine( r.x() + 2, r.y(), r.x() + r.width() - 2, r.y() ); + p->drawLine( r.x() + 2, r.y() + r.height() - 1, + r.x() + r.width() - 2, r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y() + 2, r.x(), r.y() + r.height() - 3 ); + p->drawLine( r.x() + r.width() - 1, r.y(), r.x() + r.width() - 1, r.y() + r.height() - 1 ); + if ( tall ) { + p->drawPoint( r.x() + 1, r.y() + r.height() - 2 ); + p->drawPoint( r.x() + 1, r.y() + 1 ); + p->setPen( midColor( border, cg.background() ) ); + } + if ( !formMode || !tall ) { + p->drawLine( r.x(), r.y() + 1, r.x() + 1, r.y() ); + p->drawLine( r.x(), r.y() + r.height() - 2, r.x() + 1, r.y() + r.height() - 1 ); + } + if ( !formMode ) { + if ( tall ) + p->setPen( cg.background() ); + else + p->setPen( midColor( custom ? custom[ 3 ] : gray[ 3 ], cg.background() ) ); + p->drawPoint( r.x(), r.y() ); + p->drawPoint( r.x(), r.y() + r.height() - 1 ); + } + break; + case ROUNDED_RIGHT: + p->drawLine( r.x() + 1, r.y(), r.x() + r.width() - 3, r.y() ); + p->drawLine( r.x() + 1, r.y() + r.height() - 1, + r.x() + r.width() - 3, r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y(), r.x(), r.y() + r.height() - 1 ); + p->drawLine( r.x() + r.width() - 1, r.y() + 2, r.x() + r.width() - 1, + r.y() + r.height() - 3 ); + if ( tall ) { + p->drawPoint( r.x() + r.width() - 2, r.y() + 1 ); + p->drawPoint( r.x() + r.width() - 2, r.y() + r.height() - 2 ); + p->setPen( midColor( border, cg.background() ) ); + } + if ( !formMode || !tall ) { + p->drawLine( r.x() + r.width() - 2, r.y(), r.x() + r.width() - 1, r.y() + 1 ); + p->drawLine( r.x() + r.width() - 2, r.y() + r.height() - 1, + r.x() + r.width() - 1, r.y() + r.height() - 2 ); + } + if ( !formMode ) { + if ( tall ) + p->setPen( cg.background() ); + else + p->setPen( midColor( custom ? custom[ 3 ] : gray[ 3 ], cg.background() ) ); + p->drawPoint( r.x() + r.width() - 1, r.y() ); + p->drawPoint( r.x() + r.width() - 1, r.y() + r.height() - 1 ); + } + break; + default: + break; + } + } else { + p->setPen( flags & Style_ButtonDefault && + IND_FONT_COLOUR == defBtnIndicator ? cg.text() : custom ? custom[ 5 ] : gray[ 5 ] ); + p->setBrush( NoBrush ); + p->drawRect( r ); + } + + p->restore(); +} + +void KlearlookStyle::drawArrow( QPainter *p, const QRect &r, const QColorGroup &cg, QStyle::SFlags flags, + QStyle::PrimitiveElement pe, bool small, bool checkActive ) const { + QPointArray a; + const QColor &col = flags & Style_Enabled + ? checkActive && flags & Style_Active + ? cg.highlightedText() + : cg.text() + : cg.mid(); + + if ( vArrow ) + if ( small ) + switch ( pe ) { + case QStyle::PE_ArrowUp: + a.setPoints( 7, 2, 1, 2, 0, 0, -2, -2, 0, -2, 1, -2, 0, 2, 0 ); + break; + case QStyle::PE_ArrowDown: + a.setPoints( 7, 2, -1, 2, 0, 0, 2, -2, 0, -2, -1, -2, 0, 2, 0 ); + break; + case QStyle::PE_ArrowRight: + a.setPoints( 7, 1, -2, 0, -2, -2, 0, 0, 2, 1, 2, 0, 2, 0, -2 ); + break; + case QStyle::PE_ArrowLeft: + a.setPoints( 7, -1, -2, 0, -2, 2, 0, 0, 2, -1, 2, 0, 2, 0, -2 ); + break; + default: + return ; + } + else + switch ( pe ) { + case QStyle::PE_ArrowUp: + a.setPoints( 7, 3, 1, 0, -2, -3, 1, -2, 2, -1, 1, 1, 1, 2, 2 ); + break; + case QStyle::PE_ArrowDown: + a.setPoints( 7, 3, -1, 0, 2, -3, -1, -2, -2, -1, -1, 1, -1, 2, -2 ); + break; + case QStyle::PE_ArrowRight: + a.setPoints( 7, -1, -3, 2, 0, -1, 3, -2, 2, -1, 1, -1, -1, -2, -2 ); + break; + case QStyle::PE_ArrowLeft: + a.setPoints( 7, 1, -3, -2, 0, 1, 3, 2, 2, 1, 1, 1, -1, 2, -2 ); + break; + default: + return ; + } + else + if ( small ) + switch ( pe ) { + case QStyle::PE_ArrowUp: + a.setPoints( 4, 2, 0, 0, -2, -2, 0, 2, 0 ); + break; + case QStyle::PE_ArrowDown: + a.setPoints( 4, 2, 0, 0, 2, -2, 0, 2, 0 ); + break; + case QStyle::PE_ArrowRight: + a.setPoints( 4, 0, -2, -2, 0, 0, 2, 0, -2 ); + break; + case QStyle::PE_ArrowLeft: + a.setPoints( 4, 0, -2, 2, 0, 0, 2, 0, -2 ); + break; + default: + return ; + } + else + switch ( pe ) { + case QStyle::PE_ArrowUp: + a.setPoints( 4, 3, 1, 0, -2, -3, 1, 3, 1 ); + break; + case QStyle::PE_ArrowDown: + a.setPoints( 4, 3, -1, 0, 2, -3, -1, 3, -1 ); + break; + case QStyle::PE_ArrowRight: + a.setPoints( 4, -1, -3, 2, 0, -1, 3, -1, -3 ); + break; + case QStyle::PE_ArrowLeft: + a.setPoints( 4, 1, -3, -2, 0, 1, 3, 1, -3 ); + break; + default: + return ; + } + + if ( a.isNull() ) + return ; + + p->save(); + a.translate( ( r.x() + ( r.width() >> 1 ) ), ( r.y() + ( r.height() >> 1 ) ) ); + p->setBrush( col ); + p->setPen( col ); + p->drawPolygon( a ); + p->restore(); +} + +void KlearlookStyle::drawPrimitiveMenu( PrimitiveElement pe, QPainter *p, const QRect &r, const QColorGroup &cg, + SFlags flags, const QStyleOption &data ) const { + switch ( pe ) { + case PE_CheckMark: + if ( flags & Style_On || !( flags & Style_Off ) ) // !(flags&Style_Off) is for tri-state + { + QPointArray check; + int x = r.center().x() - 3, + y = r.center().y() - 3; + + check.setPoints( 6, + x, y + 2, + x + 2, y + 4, + x + 6, y, + x + 6, y + 2, + x + 2, y + 6, + x, y + 4 ); + + if ( flags & Style_On ) { + if ( flags & Style_Active ) { + p->setBrush( cg.highlightedText() ); + p->setPen( cg.highlightedText() ); + } else { + p->setBrush( cg.text() ); + p->setPen( cg.text() ); + } + } else { + p->setBrush( cg.text() ); + p->setPen( cg.text() ); + } + p->drawPolygon( check ); + } + break; + + default: + KStyle::drawPrimitive( pe, p, r, cg, flags, data ); + } +} + +void KlearlookStyle::drawPrimitive( PrimitiveElement pe, QPainter *p, const QRect &r, const QColorGroup &cg, + SFlags flags, const QStyleOption &data ) const { + int x, y, w, h; + + r.rect(&x, &y, &w, &h); + + switch ( pe ) { + case PE_HeaderSection: { + const QColor * use = buttonColors( cg ); + + + if ( APP_KICKER == themedApp ) { + if ( flags & Style_Down ) + flags = ( ( flags | Style_Down ) ^ Style_Down ) | Style_Sunken; + flags |= Style_Enabled; +#if KDE_VERSION >= 0x30200 +#if KDE_VERSION >= 0x30400 + + if ( HOVER_KICKER == hover && hoverWidget ) // && hoverWidget==p->device()) + flags |= Style_MouseOver; +#endif + + formMode = isTransKicker; +#endif + + drawLightBevelButton( p, r, cg, flags | Style_Horizontal, + true, ROUNDED_ALL, getFill( flags, use ), use ); +#if KDE_VERSION >= 0x30200 + + formMode = false; +#endif + + } else { + flags = ( ( flags | Style_Sunken ) ^ Style_Sunken ) | Style_Raised; + + if ( QTC_NO_SECT != hoverSect && HOVER_HEADER == hover && hoverWidget ) { + QHeader * hd = dynamic_cast( hoverWidget ); + + if ( hd && hd->isClickEnabled( hoverSect ) && r == hd->sectionRect( hoverSect ) ) + flags |= Style_MouseOver; + } + drawLightBevelButton( p, r, cg, flags | Style_Horizontal, + true, ROUNDED_NONE, getFill( flags, use ), use ); + } + break; + } + case PE_HeaderArrow: + drawArrow( p, r, cg, flags, flags & Style_Up ? PE_ArrowUp : PE_ArrowDown ); + break; + case PE_ButtonCommand: + case PE_ButtonBevel: + case PE_ButtonTool: + case PE_ButtonDropDown: { + const QColor *use = buttonColors( cg ); + + if ( !( flags & QStyle::Style_Sunken ) ) // If its not sunken, its raised-don't want flat buttons. + flags |= QStyle::Style_Raised; + + drawLightBevelButton( p, r, cg, flags | Style_Horizontal, true, + r.width() < 16 || r.height() < 16 +#if KDE_VERSION >= 0x30200 + || ( APP_KORN == themedApp && isTransKicker && PE_ButtonTool == pe ) +#endif + ? ROUNDED_NONE : ROUNDED_ALL, + getFill( flags, use ), use ); + break; + } + case PE_ButtonDefault: + switch ( defBtnIndicator ) { + case IND_BORDER: + p->setBrush( NoBrush ); + if ( rounded ) // borderButton) CPD Only use color[4] for rounded def buttons! + { + const QColor * use = buttonColors( cg ); + + p->setPen( use[ 4 ] ); + int offset = r.width() >= QTC_MIN_BTN_SIZE && r.height() >= QTC_MIN_BTN_SIZE ? 4 : 3; + + p->drawLine( r.x() + offset, r.y(), r.x() + r.width() - ( 1 + offset ), r.y() ); + p->drawLine( r.x() + offset, r.y() + r.height() - 1, + r.x() + r.width() - ( 1 + offset ), r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y() + offset, r.x(), r.y() + r.height() - ( 1 + offset ) ); + p->drawLine( r.x() + r.width() - 1, r.y() + offset, + r.x() + r.width() - 1, r.y() + r.height() - ( 1 + offset ) ); + } else { + p->setPen( cg.text() ); + p->drawRect( r ); + } + break; + case IND_CORNER: { + const QColor *use = buttonColors( cg ); + QPointArray points; + bool sunken = flags & Style_Down || flags & QStyle::Style_Sunken; + int offset = sunken ? 4 : 3; + + points.setPoints( 3, r.x() + offset, r.y() + offset, r.x() + offset + 6, r.y() + offset, + r.x() + offset, r.y() + offset + 6 ); + + p->setBrush( use[ sunken ? 0 : borderButton ? 4 : 5 ] ); + p->setPen( use[ sunken ? 0 : borderButton ? 4 : 5 ] ); + p->drawPolygon( points ); + break; + } + default: + break; + } + break; + case PE_IndicatorMask: + if ( rounded ) { + p->fillRect( r, color0 ); + p->fillRect( r.x() + 1, r.y() + 1, r.width() - 2, r.height() - 2, color1 ); + p->setPen( color1 ); + p->drawLine( r.x() + 1, r.y(), r.x() + r.width() - 2, r.y() ); + p->drawLine( r.x() + 1, r.y() + r.height() - 1, r.x() + r.width() - 2, r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y() + 1, r.x(), r.y() + r.height() - 2 ); + p->drawLine( r.x() + r.width() - 1, r.y() + 1, r.x() + r.width() - 1, r.y() + r.height() - 2 ); + } else + p->fillRect( r, color1 ); + break; + case PE_CheckMark: + if ( flags & Style_On || !( flags & Style_Off ) ) // !(flags&Style_Off) is for tri-state + { + QPointArray check; + int x = r.center().x() - 3, + y = r.center().y() - 3; + + check.setPoints( 6, + x, y + 2, + x + 2, y + 4, + x + 6, y, + x + 6, y + 2, + x + 2, y + 6, + x, y + 4 ); + p->setBrush( flags & Style_On + ? flags & Style_Enabled + ? flags & Style_Selected + ? cg.highlightedText() + : cg.text() + : cg.mid() + : cg.light() ); + p->setPen( flags & Style_Enabled + ? flags & Style_Selected + ? cg.highlightedText() + : cg.text() + : cg.mid() ); + p->drawPolygon( check ); + } + break; + case PE_CheckListController: { + QCheckListItem *item = data.checkListItem(); + + if ( item ) { + QListView * lv = item->listView(); + int x = r.x(), y = r.y(), w = r.width(), h = r.height(), marg = lv->itemMargin(); + + p->setPen( QPen( flags & Style_Enabled ? cg.text() + : lv->palette().color( QPalette::Disabled, QColorGroup::Text ) ) ); + + if ( flags & Style_Selected && !lv->rootIsDecorated() && + !( ( item->parent() && 1 == item->parent() ->rtti() && + QCheckListItem::Controller == ( ( QCheckListItem* ) item->parent() ) ->type() ) )) { + p->fillRect( 0, 0, x + marg + w + 4, item->height(), + cg.brush( QColorGroup::Highlight ) ); + if ( item->isEnabled() ) + p->setPen( QPen( cg.highlightedText() ) ); + } + + if ( flags & Style_NoChange ) + p->setBrush( cg.brush( QColorGroup::Button ) ); + p->drawRect( x + marg + 2, y + 4 + 2, w - 7, h - 8 ); + p->drawRect( x + marg, y + 4, w - 7, h - 8 ); + } + break; + } + case PE_CheckListIndicator: { + QCheckListItem *item = data.checkListItem(); + + if ( item ) { + QListView * lv = item->listView(); + + p->setPen( QPen( flags & Style_Enabled ? cg.text() + : lv->palette().color( QPalette::Disabled, QColorGroup::Text ), 2 ) ); + if ( flags & Style_Selected ) { + flags -= Style_Selected; + if ( !lv->rootIsDecorated() && + !( ( item->parent() && 1 == item->parent() ->rtti() && + QCheckListItem::Controller == + ( ( QCheckListItem* ) item->parent() ) ->type() ) ) ) { + p->fillRect( 0, 0, r.x() + lv->itemMargin() + r.width() + 4, item->height(), + cg.brush( QColorGroup::Highlight ) ); + if ( item->isEnabled() ) { + p->setPen( QPen( cg.highlightedText(), 2 ) ); + flags += Style_Selected; + } + } + } + + if ( flags & Style_NoChange ) + p->setBrush( cg.brush( QColorGroup::Button ) ); + p->drawRect( r.x() + lv->itemMargin(), r.y() + 2, r.width() - 4, r.width() - 4 ); + if ( flags & QStyle::Style_On || !( flags & Style_Off ) ) + drawPrimitive( PE_CheckMark, p, QRect( r.x() + lv->itemMargin(), + r.y() + 2, r.width() - 4, r.width() - 4 ), cg, flags ); + } + break; + } + case PE_Indicator: { + const QColor *use = buttonColors( cg ); + bool on = flags & QStyle::Style_On || !( flags & Style_Off ); + + if ( APPEARANCE_FLAT != appearance ) + drawPrimitive( PE_ButtonTool, p, r, cg, flags ); + else { + p->fillRect( r.x() + 1, r.y() + 2, QTC_CHECK_SIZE - 2, QTC_CHECK_SIZE - 2, + flags & Style_Enabled ? cg.base() : cg.background() ); + p->setPen( use[ 4 ] ); + p->drawLine( r.x() + 1, r.y() + QTC_CHECK_SIZE - 1, r.x() + 1, r.y() + 1 ); + p->drawLine( r.x() + 1, r.y() + 1, r.x() + QTC_CHECK_SIZE - 2, r.y() + 1 ); + } + p->setPen( use[ 5 ] ); + p->setBrush( NoBrush ); + if ( rounded ) { + p->drawLine( r.x() + 1, r.y(), r.x() + r.width() - 2, r.y() ); + p->drawLine( r.x() + 1, r.y() + r.height() - 1, r.x() + r.width() - 2, r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y() + 1, r.x(), r.y() + r.height() - 2 ); + p->drawLine( r.x() + r.width() - 1, r.y() + 1, r.x() + r.width() - 1, r.y() + r.height() - 2 ); + + p->setPen( midColor( use[ 3 ], cg.background() ) ); + p->drawPoint( r.x(), r.y() ); + p->drawPoint( r.x(), r.y() + r.width() - 1 ); + p->drawPoint( r.x() + r.height() - 1, r.y() ); + p->drawPoint( r.x() + r.height() - 1, r.y() + r.width() - 1 ); + } else if ( APPEARANCE_FLAT == appearance || borderButton ) + p->drawRect( r.x(), r.y(), QTC_CHECK_SIZE, QTC_CHECK_SIZE ); + + if ( on ) + drawPrimitive( PE_CheckMark, p, r, cg, flags ); + break; + } + case PE_CheckListExclusiveIndicator: { + QCheckListItem *item = data.checkListItem(); + + if ( item ) { + const QColor & bgnd = cg.background(), + &on = flags & Style_Enabled + ? cg.text() + : cg.mid(); + bool set + = flags & QStyle::Style_On; + QPointArray outer, + inner, + aa; + int x = r.x(), y = r.y() + 2; + + outer.setPoints( 24, x, y + 8, x, y + 4, x + 1, y + 3, x + 1, y + 2, + x + 2, y + 1, x + 3, y + 1, x + 4, y, x + 8, y, + x + 9, y + 1, x + 10, y + 1, x + 11, y + 2, x + 11, y + 3, + x + 12, y + 4, x + 12, y + 8, x + 11, y + 9, x + 11, y + 10, + x + 10, y + 11, x + 9, y + 11, x + 8, y + 12, x + 4, y + 12, + x + 3, y + 11, x + 2, y + 11, x + 1, y + 10, x + 1, y + 9 ); + inner.setPoints( 20, x + 1, y + 8, x + 1, y + 4, x + 2, y + 3, x + 2, y + 2, + x + 3, y + 2, x + 4, y + 1, x + 8, y + 1, x + 9, y + 2, + x + 10, y + 2, x + 10, y + 3, x + 11, y + 4, x + 11, y + 8, + x + 10, y + 9, x + 10, y + 10, x + 9, y + 10, x + 8, y + 11, + x + 4, y + 11, x + 3, y + 10, x + 2, y + 10, x + 2, y + 9 ); + aa.setPoints( 16, x + 2, y + 4, x + 4, y + 2, x + 8, y + 2, x + 10, y + 4, + x + 10, y + 8, x + 8, y + 10, x + 4, y + 10, x + 2, y + 8, + x, y + 3, x + 3, y, x + 9, y, x + 12, y + 3, + x + 12, y + 9, x + 9, y + 12, x + 3, y + 12, x, y + 9 ); + p->setBrush( on ); + p->drawPolyline( outer ); + p->drawPolyline( inner ); + p->setPen( midColor( on, bgnd, 1.5 ) ); + p->drawPoints( aa ); + + if ( set + ) { + p->setPen( midColor( on, bgnd ) ); + p->drawLine( x + 5, y + 4, x + 7, y + 4 ); + p->drawLine( x + 5, y + 8, x + 7, y + 8 ); + p->drawLine( x + 4, y + 5, x + 4, y + 7 ); + p->drawLine( x + 8, y + 5, x + 8, y + 7 ); + p->setBrush( on ); + p->setPen( NoPen ); + p->drawRect( x + 5, y + 5, 3, 3 ); + } + } + break; + } + case PE_ExclusiveIndicator: + case PE_ExclusiveIndicatorMask: { + int x = r.x(), y = r.y(); + QPointArray outer; + outer.setPoints( 24, x, y + 8, x, y + 4, x + 1, y + 3, x + 1, y + 2, + x + 2, y + 1, x + 3, y + 1, x + 4, y, x + 8, y, + x + 9, y + 1, x + 10, y + 1, x + 11, y + 2, x + 11, y + 3, + x + 12, y + 4, x + 12, y + 8, x + 11, y + 9, x + 11, y + 10, + x + 10, y + 11, x + 9, y + 11, x + 8, y + 12, x + 4, y + 12, + x + 3, y + 11, x + 2, y + 11, x + 1, y + 10, x + 1, y + 9 ); + + if ( PE_ExclusiveIndicatorMask == pe ) { + p->fillRect( r, color0 ); + p->setPen( Qt::color1 ); + p->setBrush( Qt::color1 ); + p->drawPolygon( outer ); + } else { + QPointArray shadow; + const QColor &bgnd = flags & Style_Enabled ? cg.base() : cg.background(), + &on = flags & Style_Enabled + ? flags & Style_Selected + ? cg.highlightedText() + : cg.text() + : cg.mid(); + QColor indBgnd = bgnd; + const QColor *use = buttonColors( cg ); + QColor leftShadowColor, + rightShadowColor, + outerLeftColor, + outerRightColor; + bool set + = flags & QStyle::Style_On; + + if ( APPEARANCE_FLAT != appearance && !borderButton ) + shadow.setPoints( 14, x + 1, y + 10, x + 1, y + 9, x, y + 8, x, y + 4, + x + 1, y + 3, x + 1, y + 2, x + 2, y + 1, x + 3, y + 1, + x + 4, y, x + 8, y, x + 9, y + 1, x + 10, y + 1, + x + 11, y + 2, x + 11, y + 3 ); + else + shadow.setPoints( 9, x + 2, y + 11, x + 2, y + 9, x + 1, y + 8, x + 1, y + 4, + x + 2, y + 3, x + 2, y + 2, x + 3, y + 2, x + 4, y + 1, + x + 8, y + 1 ); + + p->fillRect( r, crLabelHighlight && flags & Style_MouseOver + ? cg.background().light( QTC_HIGHLIGHT_FACTOR ) : cg.background() ); + + if ( APPEARANCE_FLAT != appearance ) { + indBgnd = getFill( flags, use ); + p->setClipRegion( QRegion( outer ) ); + drawBevelGradient( indBgnd, !set, 0, p, + QRect( x + 1, y + 1, r.width() - 2, r.height() - 2 ), true, + set ? SHADE_BEVEL_GRAD_SEL_LIGHT( appearance ) : SHADE_BEVEL_GRAD_LIGHT( appearance ), + set ? SHADE_BEVEL_GRAD_SEL_DARK( appearance ) : SHADE_BEVEL_GRAD_DARK( appearance ) ); + + p->setClipping( false ); + + if ( ( !set + && !( flags & Style_Down ) ) || !borderButton ) { + leftShadowColor = set + ? !borderButton ? use[ 5 ] : use[ 4 ] : use[ 0 ]; + p->setPen( leftShadowColor ); + p->drawPolyline( shadow ); + + if ( APPEARANCE_LIGHT_GRADIENT == appearance ) + rightShadowColor = indBgnd; + else { + if ( !borderButton ) + shadow.setPoints( 10, x + 12, y + 4, x + 12, y + 8, x + 11, y + 9, + x + 11, y + 10, x + 10, y + 11, x + 9, y + 11, + x + 8, y + 12, x + 4, y + 12, x + 3, y + 11, + x + 2, y + 11 ); + else + shadow.setPoints( 9, x + 10, y + 2, x + 10, y + 3, x + 11, y + 4, + x + 11, y + 8, x + 10, y + 9, x + 10, y + 10, + x + 9, y + 10, x + 8, y + 11, x + 4, y + 11 ); + rightShadowColor = set + ? use[ 0 ] : !borderButton ? use[ 5 ] : use[ 4 ]; + p->setPen( rightShadowColor ); + p->drawPolyline( shadow ); + } + } + else + leftShadowColor = rightShadowColor = indBgnd; + } else { + rightShadowColor = bgnd; + p->setBrush( bgnd ); + p->setPen( bgnd ); + p->drawEllipse( x, y, QTC_RADIO_SIZE, QTC_RADIO_SIZE ); + p->setPen( use[ 4 ] ); + leftShadowColor = use[ 4 ]; + p->drawPolyline( shadow ); + } + + if ( APPEARANCE_FLAT == appearance || borderButton ) { + p->setPen( use[ 5 ] ); + p->drawPolyline( outer ); + shade( use[ 5 ], &outerRightColor, 1.1 ); + } else { + shade( leftShadowColor, &outerLeftColor, 1.1 ); + shade( rightShadowColor, &outerRightColor, 1.1 ); + } + if ( set + ) { + p->setPen( midColor( on, indBgnd ) ); + p->drawLine( x + 5, y + 4, x + 7, y + 4 ); + p->drawLine( x + 5, y + 8, x + 7, y + 8 ); + p->drawLine( x + 4, y + 5, x + 4, y + 7 ); + p->drawLine( x + 8, y + 5, x + 8, y + 7 ); + p->setBrush( on ); + p->setPen( NoPen ); + p->drawRect( x + 5, y + 5, 3, 3 ); + } + + if ( !formMode ) { + QPointArray outerAaLeft, + outerAaRight; + + outerAaLeft.setPoints( 8, x, y + 3, x + 1, y + 1, x + 3, y, + x + 9, y, x + 11, y + 1, x + 12, y + 3, + x + 1, y + 11, x, y + 9 ); + outerAaRight.setPoints( 4, x + 12, y + 9, x + 11, y + 11, x + 9, y + 12, + x + 3, y + 12 ); + + p->setPen( midColor( outerRightColor, cg.background() ) ); + p->drawPoints( outerAaRight ); + if ( APPEARANCE_FLAT != appearance && !borderButton ) + p->setPen( midColor( outerLeftColor, cg.background() ) ); + p->drawPoints( outerAaLeft ); + if ( APPEARANCE_LIGHT_GRADIENT == appearance ) + p->setPen( midColor( indBgnd, use[ 5 ], 1.75 ) ); + else + p->setPen( midColor( use[ 5 ], indBgnd, 1.5 ) ); + + if ( APPEARANCE_FLAT != appearance ) { + QPointArray innerAa; + + if ( !set + && !( flags & Style_Down ) ) { + if ( borderButton ) { + innerAa.setPoints( 3, x + 1, y + 4, x + 2, y + 2, x + 4, y + 1 ); + p->drawPoints( innerAa ); + p->setPen( midColor( outerRightColor, cg.background() ) ); + p->drawPoint( x + 2, y + 10 ); + } else { + innerAa.setPoints( 4, x + 4, y + 11, x + 8, y + 11, x + 10, y + 10, + x + 11, y + 8 ); + p->drawPoints( innerAa ); + } + } + } else { + QPointArray innerAa; + + innerAa.setPoints( 6, x + 4, y + 11, x + 8, y + 11, x + 10, y + 10, + x + 11, y + 8, x + 11, y + 4, x + 10, y + 2 ); + p->drawPoints( innerAa ); + } + } + } + break; + } + case PE_DockWindowSeparator: { + QPoint p1, + p2; + //const QColor *use=backgroundColors(cg); + + if ( flags & Style_Horizontal ) { + int offset = r.height() > 18 ? 6 : r.height() > 12 ? 4 : r.height() > 6 ? 2 : 0; + + p1 = QPoint( r.width() >> 1, 0 + offset ); + p2 = QPoint( p1.x(), r.height() - offset ); + } else { + int offset = r.width() > 18 ? 6 : r.width() > 12 ? 4 : r.width() > 6 ? 2 : 0; + + p1 = QPoint( 0 + offset, r.height() >> 1 ); + p2 = QPoint( r.width() - offset, p1.y() ); + } + p->fillRect( r, cg.background() ); + p->setPen( cg.background().dark( 111 ) ); + p->drawLine( p1, p2 ); + + break; + } + case PE_Splitter: { + const QColor *use = buttonColors( cg ); + + if ( hoverWidget && hoverWidget == p->device() ) + flags |= Style_MouseOver; + + if ( borderSplitter ) + drawLightBevelButton( p, r, cg, QStyle::Style_Raised, false, + ROUNDED_NONE, getFill( flags, use ), use ); + else { + p->fillRect( r, + QColor( flags & Style_MouseOver ? + cg.background().light( QTC_HIGHLIGHT_FACTOR ) : + cg.background() ) ); + drawLines( p, r, flags & Style_Horizontal, 70, 1, use, 0, TRUE, + APPEARANCE_LIGHT_GRADIENT == appearance ); + } + break; + } + case PE_DockWindowResizeHandle: + p->fillRect( r, cg.background() ); + if ( flags & Style_Horizontal ) { + p->setPen( cg.highlight().light() ); + p->drawLine( r.left() + 1, r.top() + 1, r.right() - 1, r.top() + 1 ); + p->setPen( cg.highlight() ); + p->drawLine( r.left() + 1, r.top() + 2, r.right() - 1, r.top() + 2 ); + p->setPen( cg.highlight().dark() ); + p->drawLine( r.left() + 1, r.top() + 3, r.right() - 1, r.top() + 3 ); + } else { + p->setPen( cg.highlight().light() ); + p->drawLine( r.left() + 1, r.top() + 1, r.left() + 1, r.bottom() - 1 ); + p->setPen( cg.highlight() ); + p->drawLine( r.left() + 2, r.top() + 1, r.left() + 2, r.bottom() - 1 ); + p->setPen( cg.highlight().dark() ); + p->drawLine( r.left() + 3, r.top() + 1, r.left() + 3, r.bottom() - 1 ); + } + break; + + case PE_StatusBarSection: { + p->setPen( cg.background().dark(120) ); + p->drawRect(x-2, y-2, r.width()+3, r.height()+3); + break; + } + + case PE_PanelLineEdit: { + const QColor *use = backgroundColors( cg ); + p->setPen( use[ 4 ].light(80) ); + p->drawRect( r ); + break; + } + + case PE_PanelPopup: { + const QColor *use = backgroundColors( cg ); + + if ( borderFrame && ( data.isDefault() || data.lineWidth() > 1 ) ) { + p->setPen( use[ 4 ].light(70) ); + p->setBrush( NoBrush ); + drawPopupRect (p, r, cg); + //p->drawRect( r ); +#ifdef MENU_POPUP_SHADOW + + qDrawShadePanel( p, r.x() + 1, r.y() + 1, r.width() - 2, r.height() - 2, + QColorGroup( use[ 4 ], use[ NUM_SHADES ], use[ 0 ], use[ 4 ], use[ 2 ], + cg.text(), use[ NUM_SHADES ] ), + flags & Style_Sunken, + data.isDefault() ? QTC_BORDERED_FRAME_WIDTH - 1 : data.lineWidth() - 1 ); +#endif + + } else + qDrawShadePanel( p, r, + QColorGroup( + use[ 5 ], use[ NUM_SHADES ], use[ 0 ], use[ 5 ], use[ 2 ], + cg.text(), use[ NUM_SHADES ] + ), + flags & Style_Sunken, data.isDefault() ? QTC_DEF_FRAME_WIDTH : data.lineWidth() ); + break; + } + case PE_PanelTabWidget: { + const QColor *use = backgroundColors( cg ); + + if ( borderFrame && ( data.isDefault() || data.lineWidth() > 1 ) ) { + p->setPen( use[ 4 ] ); + p->setBrush( NoBrush ); + p->drawRect( r ); +#ifdef MENU_POPUP_SHADOW + + qDrawShadePanel( p, r.x() + 1, r.y() + 1, r.width() - 2, r.height() - 2, + QColorGroup( use[ 4 ], use[ NUM_SHADES ], use[ 0 ], use[ 4 ], use[ 2 ], + cg.text(), use[ NUM_SHADES ] ), + flags & Style_Sunken, + data.isDefault() ? QTC_BORDERED_FRAME_WIDTH - 1 : data.lineWidth() - 1 ); +#endif + + } else + qDrawShadePanel( p, r, + QColorGroup( + use[ 5 ], use[ NUM_SHADES ], use[ 0 ], use[ 5 ], use[ 2 ], + cg.text(), use[ NUM_SHADES ] + ), + flags & Style_Sunken, data.isDefault() ? QTC_DEF_FRAME_WIDTH : data.lineWidth() ); + break; + } + case PE_PanelDockWindow: + case PE_PanelMenuBar: { + const QColor *use = backgroundColors( cg ); + switch ( toolbarBorders ) { + case TB_DARK: + qDrawShadePanel( p, + r.x(), r.y(), r.width(), r.height(), + QColorGroup( + use[ 5 ].dark( 120 ), use[ NUM_SHADES ], use[ 0 ], + use[ 5 ].dark( 120 ), use[ 2 ], + cg.text(), use[ NUM_SHADES ] ), + flags & Style_Sunken, 1 + ); + +#ifdef QTC_GRADIENT_TOOLBARS_AND_MENUBARS + + if ( APPEARANCE_FLAT != appearance ) + drawBevelGradient( use[ NUM_SHADES ], + true, 1, p, r, true, + SHADE_BAR_LIGHT, SHADE_BAR_DARK + ); +#endif + + break; + case TB_LIGHT: + qDrawShadePanel( p, + r.x(), r.y(), r.width(), r.height(), + QColorGroup( + use[ 3 ], use[ NUM_SHADES ], use[ 0 ], + use[ 3 ], use[ 2 ], + cg.text(), use[ NUM_SHADES ] + ), + flags & Style_Sunken, 1 + ); + +#ifdef QTC_GRADIENT_TOOLBARS_AND_MENUBARS + + if ( APPEARANCE_FLAT != appearance ) + drawBevelGradient( use[ NUM_SHADES ], + true, 1, p, r, true, + SHADE_BAR_LIGHT, SHADE_BAR_DARK ); +#endif + + break; + case TB_NONE: + break; + + } /* switch */ + + break; + } + case PE_ScrollBarAddLine: + case PE_ScrollBarSubLine: { + bool down = ( flags & ( QStyle::Style_Down | QStyle::Style_On | QStyle::Style_Sunken ) ); + const QColor *use = buttonColors( cg ); + + pe = flags & Style_Horizontal + ? PE_ScrollBarAddLine == pe + ? PE_ArrowRight + : PE_ArrowLeft + : PE_ScrollBarAddLine == pe + ? PE_ArrowDown + : PE_ArrowUp; + + drawLightBevelButton( p, r, cg, + down ? flags : flags | ( ( ( flags & Style_Enabled ) ? Style_Raised : Style_Default ) ), + true, + PE_ArrowRight == pe ? ROUNDED_RIGHT : + PE_ArrowLeft == pe ? ROUNDED_LEFT : + PE_ArrowDown == pe ? ROUNDED_BOTTOM : + PE_ArrowUp == pe ? ROUNDED_TOP : ROUNDED_NONE, + getFill( flags, use ), use ); + drawPrimitive( pe, p, r, cg, flags ); + break; + } + case PE_ScrollBarSubPage: + case PE_ScrollBarAddPage: { + const QColor *use = backgroundColors( cg ); + + if ( borderButton ) { + if ( flags & Style_Horizontal ) { + p->fillRect( r.x(), r.y() + 1, r.width(), r.height() - 2, use[ 2 ] ); + p->setPen( use[ 5 ] ); + p->drawLine( r.left(), r.top(), r.right(), r.top() ); + p->drawLine( r.left(), r.bottom(), r.right(), r.bottom() ); + } else { + p->fillRect( r.x() + 1, r.y(), r.width() - 2, r.height(), use[ 2 ] ); + p->setPen( use[ 5 ] ); + p->drawLine( r.left(), r.top(), r.left(), r.bottom() ); + p->drawLine( r.right(), r.top(), r.right(), r.bottom() ); + } + } else + p->fillRect( r.x(), r.y(), r.width(), r.height(), use[ 2 ] ); + break; + } + case PE_ScrollBarSlider: { + const QColor *use = buttonColors( cg ); + + + + if ( flags & Style_Down ) + flags -= Style_Down; + flags |= flags & Style_Enabled ? Style_Raised : Style_Default; + + drawLightBevelButton( p, r, cg, flags, true, ROUNDED_NONE, getFill( flags, use ), use ); + + if ( GROOVE_NONE != sliderThumbs && + ( ( flags & Style_Horizontal && r.width() >= 20 ) || r.height() >= 20 ) ) + drawLines( p, r, !( flags & Style_Horizontal ), 3, 4, use, 0, GROOVE_SUNKEN == sliderThumbs, + APPEARANCE_LIGHT_GRADIENT == appearance ); + break; + } + case PE_FocusRect: { + p->drawWinFocusRect( r, cg.background() ); + break; + } + case PE_ArrowUp: + case PE_ArrowDown: + case PE_ArrowRight: + case PE_ArrowLeft: + drawArrow( p, r, cg, flags, pe ); + break; + case PE_SpinWidgetUp: + case PE_SpinWidgetDown: { + QRect sr( r ); + const QColor *use = buttonColors( cg ); + + drawLightBevelButton( p, sr, cg, + flags | Style_Horizontal, true, PE_SpinWidgetDown == pe ? ROUNDED_BOTTOM : ROUNDED_TOP, + getFill( flags, use ), use ); + + if ( vArrow ) { + if ( PE_SpinWidgetDown == pe ) + sr.setY( sr.y() - 1 ); + } else + sr.setY( sr.y() + ( PE_SpinWidgetDown == pe ? -2 : 1 ) ); + + drawArrow( p, sr, cg, flags, pe == PE_SpinWidgetUp ? PE_ArrowUp : PE_ArrowDown, true ); + break; + } + default: + KStyle::drawPrimitive( pe, p, r, cg, flags, data ); + } +} + +void KlearlookStyle::drawKStylePrimitive( KStylePrimitive kpe, QPainter *p, const QWidget *widget, const QRect &r, + const QColorGroup &cg, SFlags flags, const QStyleOption &opt ) const { + switch ( kpe ) { + case KPE_ToolBarHandle: + case KPE_GeneralHandle: + drawLines( p, r, !( flags & Style_Horizontal ), 2, + APP_KICKER == themedApp ? 1 : KPE_ToolBarHandle == kpe ? 4 : 2, gray, + APP_KICKER == themedApp ? 1 : KPE_ToolBarHandle == kpe ? -2 : 0, GROOVE_SUNKEN == handles, + APPEARANCE_LIGHT_GRADIENT == appearance ); + break; + case KPE_SliderGroove: + drawSliderGroove( p, r, flags, widget ); + break; + case KPE_SliderHandle: + drawSliderHandle( p, r, cg, flags ); + break; + case KPE_ListViewExpander: { + int lvSize = QTC_LV_SIZE( lvExpander ); + QRect ar( r.x() + ( ( r.width() - ( lvSize + 4 ) ) >> 1 ), + r.y() + ( ( r.height() - ( lvSize + 4 ) ) >> 1 ), lvSize + 4, lvSize + 4 ); + + p->setPen( /*lvDark ? cg.text() : */cg.mid() ); + + if ( LV_LINES_NONE != lvLines ) { + int lo = rounded ? 2 : 0; + + p->drawLine( ar.x() + lo, ar.y(), ( ar.x() + ar.width() - 1 ) - lo, ar.y() ); + p->drawLine( ar.x() + lo, ar.y() + ar.height() - 1, + ( ar.x() + ar.width() - 1 ) - lo, ar.y() + ar.height() - 1 ); + p->drawLine( ar.x(), ar.y() + lo, ar.x(), ( ar.y() + ar.height() - 1 ) - lo ); + p->drawLine( ar.x() + ar.width() - 1, + ar.y() + lo, ar.x() + ar.width() - 1, ( ar.y() + ar.height() - 1 ) - lo ); + + if ( rounded ) { + p->drawPoint( ar.x() + 1, ar.y() + 1 ); + p->drawPoint( ar.x() + 1, ar.y() + ar.height() - 2 ); + p->drawPoint( ar.x() + ar.width() - 2, ar.y() + 1 ); + p->drawPoint( ar.x() + ar.width() - 2, ar.y() + ar.height() - 2 ); + p->setPen( midColor( /*lvDark ? cg.text() : */cg.mid(), cg.background() ) ); + p->drawLine( ar.x(), ar.y() + 1, ar.x() + 1, ar.y() ); + p->drawLine( ar.x() + ar.width() - 2, ar.y(), ar.x() + ar.width() - 1, ar.y() + 1 ); + p->drawLine( ar.x(), ar.y() + ar.height() - 2, ar.x() + 1, ar.y() + ar.height() - 1 ); + p->drawLine( ar.x() + ar.width() - 2, ar.y() + ar.height() - 1, + ar.x() + ar.width() - 1, ar.y() + ar.height() - 2 ); + } + } + + if ( LV_EXP_ARR == lvExpander ) + drawArrow( p, ar, cg, flags | Style_Enabled, flags & Style_On // Collapsed = On + ? QApplication::reverseLayout() + ? PE_ArrowLeft + : PE_ArrowRight + : PE_ArrowDown ); + else { + int xo = ( ar.width() - lvSize ) >> 1, + yo = ( ar.height() - lvSize ) >> 1; + int mid = lvSize >> 1; + + p->setPen( cg.text() ); + p->drawLine( ar.x() + xo + ( mid - 2 ), ar.y() + yo + mid, + ar.x() + xo + lvSize - ( mid - 1 ), ar.y() + yo + mid ); + if ( flags & Style_On ) // Collapsed = On + p->drawLine( ar.x() + xo + mid, ar.y() + yo + ( mid - 2 ), + ar.x() + xo + mid, ar.y() + yo + lvSize - ( mid - 1 ) ); + } + break; + } + case KPE_ListViewBranch: + switch ( lvLines ) { + case LV_LINES_NONE: + break; + case LV_LINES_DOTTED: + // Taken and modified (colour wise) from kstyle.cpp - which in turn comes from + // qwindowsstyl.cpp + { + static QBitmap *verticalLine = 0, + *horizontalLine = 0; + static QCleanupHandler lvCleanupBitmap; + + // Create the dotline pixmaps if not already created + if ( !verticalLine ) { + // make 128*1 and 1*128 bitmaps that can be used for drawing the right sort of lines. + verticalLine = new QBitmap( 1, 129, true ); + horizontalLine = new QBitmap( 128, 1, true ); + QPointArray a( 64 ); + QPainter p2; + + p2.begin( verticalLine ); + + int i; + for ( i = 0; i < 64; i++ ) + a.setPoint( i, 0, i * 2 + 1 ); + + p2.setPen( color1 ); + p2.drawPoints( a ); + p2.end(); + QApplication::flushX(); + verticalLine->setMask( *verticalLine ); + + p2.begin( horizontalLine ); + + for ( i = 0; i < 64; i++ ) + a.setPoint( i, i * 2 + 1, 0 ); + + p2.setPen( color1 ); + p2.drawPoints( a ); + p2.end(); + QApplication::flushX(); + horizontalLine->setMask( *horizontalLine ); + + lvCleanupBitmap.add( &verticalLine ); + lvCleanupBitmap.add( &horizontalLine ); + } + + p->setPen( lvDark ? cg.text() : cg.mid() ); // Wow, my big modification... + + if ( flags & Style_Horizontal ) { + int point = r.x(), + other = r.y(), + end = r.x() + r.width(), + thickness = r.height(); + + while ( point < end ) { + int i = 128; + + if ( i + point > end ) + i = end - point; + p->drawPixmap( point, other, *horizontalLine, 0, 0, i, thickness ); + point += i; + } + } else { + int point = r.y(), + other = r.x(), + end = r.y() + r.height(), + thickness = r.width(), + pixmapoffset = ( flags & Style_NoChange ) ? 0 : 1; // ### Hackish + + while ( point < end ) { + int i = 128; + + if ( i + point > end ) + i = end - point; + p->drawPixmap( other, point, *verticalLine, 0, pixmapoffset, thickness, i ); + point += i; + } + } + break; + } + case LV_LINES_SOLID: + p->setPen( cg.mid() ); + p->drawLine( r.x(), r.y(), r.x() + r.width() - 1, r.y() + r.height() - 1 ); + break; + } + break; + default: + KStyle::drawKStylePrimitive( kpe, p, widget, r, cg, flags, opt ); + } +} + +void KlearlookStyle::drawControl( + ControlElement control, + QPainter *p, + const QWidget *widget, + const QRect &r, + const QColorGroup &cg, SFlags flags, const QStyleOption &data ) const +{ + if ( widget == hoverWidget ) + flags |= Style_MouseOver; + + switch ( control ) { + case CE_TabBarTab: { + const QTabBar * tb = ( const QTabBar * ) widget; + bool cornerWidget = false, + firstTab = 0 == tb->indexOf( data.tab() ->identifier() ); + + if ( ::qt_cast( tb->parent() ) ) { + const QTabWidget * tw = ( const QTabWidget* ) tb->parent(); + + // is there a corner widget in the (top) left edge? + if ( tw->cornerWidget( Qt::TopLeft ) ) + cornerWidget = true; + } + + if ( borderFrame ) { + QRect tr( r ), + fr( r ); + int offset = rounded ? 2 : 0; + + switch ( tb->shape() ) { + case QTabBar::TriangularAbove: + case QTabBar::RoundedAbove: + if ( flags & Style_Selected ) { + tr.addCoords( 0, 0, 0, -1 ); + fr.addCoords( 2, 2, -2, -2 ); + p->setPen( gray[ 5 ] ); + p->drawLine( tr.left(), + firstTab && !cornerWidget ? + tr.bottom() + 1 : tr.bottom(), tr.left(), + tr.top() + offset ); + p->drawLine( tr.left() + offset, tr.top(), tr.right() - offset, tr.top() ); + p->drawLine( tr.right(), tr.top() + 1, tr.right(), tr.bottom() ); + + p->setPen( gray[ 0 ] ); + p->drawLine( tr.left() + 1, tr.bottom() + 1, tr.left() + 1, tr.top() + 2 ); + p->drawLine( tr.left() + 1, tr.top() + 1, tr.right() - 1, tr.top() + 1 ); + p->drawLine( tr.right() - 1, tr.bottom() + 1, tr.right(), tr.bottom() + 1 ); + + if ( cornerWidget ) + p->drawPoint( tr.left(), tr.bottom() + 1 ); + + p->setPen( gray[ 4 ] ); + p->drawLine( tr.right() - 1, tr.top() + 1, tr.right() - 1, tr.bottom() ); + + if ( rounded ) { + p->setPen( gray[ 5 ] ); + p->drawPoint( tr.x() + 1, tr.y() + 1 ); + p->drawPoint( tr.x() + tr.width() - 2, tr.y() + 1 ); + p->setPen( gray[ 4 ] ); + p->drawLine( tr.x(), tr.y() + 1, tr.x() + 1, tr.y() ); + p->drawLine( tr.x() + tr.width() - 2, + tr.y(), tr.x() + tr.width() - 1, tr.y() + 1 ); + } + } else { + tr.addCoords( 0, 2, 0, -1 ); + fr.addCoords( 2, 4, -2, -2 ); + + p->setPen( gray[ 5 ] ); + p->drawLine( tr.left(), + firstTab && !cornerWidget ? tr.bottom() + 1 : tr.bottom(), tr.left(), + tr.top() + 1 ); + p->drawLine( rounded ? tr.left() + 1 : tr.left(), + tr.top(), rounded ? tr.right() - 1 : tr.right(), tr.top() ); + p->drawLine( tr.right(), tr.top() + 1, tr.right(), tr.bottom() ); + p->drawLine( tr.left(), tr.bottom(), tr.right(), tr.bottom() ); + p->setPen( gray[ 0 ] ); + p->drawLine( tr.left() + 1, tr.top() + 1, tr.right() - 1, tr.top() + 1 ); + + if ( cornerWidget ) + p->drawPoint( tr.left(), tr.bottom() + 1 ); + + if ( !firstTab ) + p->setPen( gray[ 2 ] ); + + p->drawLine( tr.left() + 1, tr.bottom() - 1, tr.left() + 1, tr.top() + 2 ); + p->setPen( gray[ 0 ] ); + p->drawLine( tr.left() + 1, tr.bottom() + 1, tr.right(), tr.bottom() + 1 ); + p->setPen( gray[ 4 ] ); + p->drawLine( tr.right() - 1, tr.top() + 1, tr.right() - 1, tr.bottom() - 1 ); + } + + if ( APPEARANCE_FLAT != appearance ) { + if ( flags & Style_Selected ) { + drawBevelGradient( cg.background(), true, 0, p, fr, true, + SHADE_TAB_SEL_LIGHT( appearance ), + SHADE_TAB_SEL_DARK( appearance ) ); + + p->setPen(menuPbar[ GRADIENT_BASE ]); + p->drawLine( fr.left()-1, fr.top()-1, fr.right()+1, fr.top()-1); + p->drawLine( fr.left()-1, fr.top(), fr.right()+1, fr.top()); + + p->setPen(menuPbar[ GRADIENT_DARK ].dark(118)); + p->drawLine( fr.left(), fr.top()-2, fr.right(), fr.top()-2); + p->drawPoint( tr.x() + 1, tr.y() + 1 ); + p->drawPoint( tr.x(), tr.y() + 2 ); + p->drawPoint( tr.x() + tr.width() - 2, tr.y() + 1 ); + p->drawPoint( tr.x() + tr.width() - 1, tr.y() + 2 ); + + } else + drawBevelGradient( gray[ 2 ], true, 0, p, fr, true, + SHADE_TAB_LIGHT( appearance ), SHADE_TAB_DARK( appearance ) ); + + + } else + p->fillRect( fr, flags & Style_Selected ? cg.background() : gray[ 2 ] ); + + break; + case QTabBar::TriangularBelow: + case QTabBar::RoundedBelow: + if ( flags & Style_Selected ) { + fr.addCoords( 3, 2, -3, -3 ); + p->setPen( gray[ 5 ] ); + + p->drawLine( tr.left(), tr.bottom() - offset, tr.left(), + firstTab && !cornerWidget ? tr.top() : tr.top() + 1 ); + p->drawLine( rounded ? tr.left() + 1 : tr.left(), + tr.bottom(), tr.right() - offset, tr.bottom() ); + p->drawLine( tr.right(), tr.top() + 1, tr.right(), tr.bottom() - 1 ); + p->setPen( gray[ 0 ] ); + p->drawLine( tr.left() + 1, tr.bottom() - 2, tr.left() + 1, + firstTab && !cornerWidget ? tr.top() : tr.top() - 1 ); + p->setPen( gray[ 4 ] ); + p->drawLine( tr.left() + 1, tr.bottom() - 1, tr.right() - 1, tr.bottom() - 1 ); + p->drawLine( tr.right() - 1, tr.bottom() - 2, tr.right() - 1, tr.top() - 1 ); + p->drawPoint( tr.right(), tr.top() ); + if ( cornerWidget ) + p->drawPoint( tr.left(), tr.top() ); + + + if ( rounded ) { + p->setPen( gray[ 5 ] ); + p->drawPoint( tr.x() + 1, tr.y() + tr.height() - 2 ); + p->drawPoint( tr.x() + tr.width() - 2, tr.y() + tr.height() - 2 ); + p->setPen( gray[ 4 ] ); + p->drawLine( tr.x(), tr.y() + tr.height() - 2, + tr.x() + 1, tr.y() + tr.height() - 1 ); + p->drawLine( tr.x() + tr.width() - 2, + tr.y() + tr.height() - 1, tr.x() + tr.width() - 1, + tr.y() + tr.height() - 2 ); + } + } else { + tr.addCoords( 0, 1, 0, -2 ); + fr.addCoords( 1, 2, -2, -4 ); + + p->setPen( gray[ 5 ] ); + p->drawLine( tr.left(), tr.bottom() - 1, tr.left(), + firstTab && !cornerWidget ? tr.top() : tr.top() + 1 ); + p->drawLine( rounded ? tr.left() + 1 : tr.left(), tr.bottom(), + rounded ? tr.right() - 1 : tr.right(), tr.bottom() ); + p->drawLine( tr.right(), tr.top() + 1, tr.right(), tr.bottom() - 1 ); + p->drawLine( tr.right(), tr.top(), tr.left(), tr.top() ); + p->setPen( gray[ 4 ] ); + p->drawLine( tr.left(), tr.top() - 1, tr.right(), tr.top() - 1 ); + p->drawLine( tr.left() + 1, tr.bottom() - 1, tr.right() - 1, tr.bottom() - 1 ); + p->drawLine( tr.right() - 1, tr.bottom() - 2, tr.right() - 1, tr.top() + 1 ); + } + if ( APPEARANCE_FLAT != appearance ) + if ( flags & Style_Selected ) + drawBevelGradient( cg.background(), false, -1, p, fr, true, + SHADE_BOTTOM_TAB_SEL_DARK( appearance ), + SHADE_BOTTOM_TAB_SEL_LIGHT( appearance ) ); + else + drawBevelGradient( gray[ 2 ], false, 0, p, fr, true, + SHADE_BOTTOM_TAB_DARK( appearance ), + SHADE_BOTTOM_TAB_LIGHT( appearance ) ); + else + p->fillRect( fr, flags & Style_Selected ? cg.background() : gray[ 2 ] ); + break; + default: + KStyle::drawControl( control, p, widget, r, cg, flags, data ); + } + } else { + QRect br( r ); + + switch ( tb->shape() ) { + case QTabBar::TriangularAbove: + case QTabBar::RoundedAbove: + if ( flags & Style_Selected ) { + p->setPen( cg.background() ); + p->drawLine( br.bottomLeft(), br.bottomRight() ); + p->setPen( gray[ 0 ] ); + p->drawPoint( br.bottomLeft() ); + p->setPen( gray[ 5 ] ); + p->drawPoint( br.bottomRight() ); + br.addCoords( 0, 0, 0, -1 ); + } else { + p->setPen( gray[ 0 ] ); + p->drawLine( br.left(), br.bottom(), br.right(), br.bottom() ); + br.addCoords( 0, 1, -1, -1 ); + } + + p->setPen( gray[ 0 == r.left() || flags & Style_Selected ? 0 : 5 ] ); + p->drawLine( br.bottomLeft(), br.topLeft() ); + p->setPen( gray[ 0 ] ); + p->drawLine( br.left() + 1, br.top(), br.right() - 1, br.top() ); + p->setPen( gray[ 5 ] ); + p->drawLine( br.right(), br.top(), br.right(), br.bottom() ); + br.addCoords( 1, 1, -1, 0 ); + if ( APPEARANCE_FLAT != appearance ) + if ( flags & Style_Selected ) + drawBevelGradient( cg.background(), true, 0, p, br, + true, SHADE_TAB_SEL_LIGHT( appearance ), + SHADE_TAB_SEL_DARK( appearance ) ); + else + drawBevelGradient( gray[ 2 ], true, 0, p, br, true, + SHADE_TAB_LIGHT( appearance ), SHADE_TAB_DARK( appearance ) ); + else + p->fillRect( br, flags & Style_Selected ? cg.background() : gray[ 2 ] ); + break; + case QTabBar::TriangularBelow: + case QTabBar::RoundedBelow: + if ( flags & Style_Selected ) { + p->setPen( cg.background() ); + p->drawLine( br.topLeft(), br.topRight() ); + p->setPen( gray[ 0 ] ); + p->drawPoint( br.topLeft() ); + p->setPen( gray[ 5 ] ); + p->drawPoint( br.topRight() ); + br.addCoords( 0, 1, 0, 0 ); + } else { + p->setPen( gray[ 5 ] ); + p->drawLine( br.left(), br.top(), + br.right(), br.top() ); + br.addCoords( 0, 1, -1, -1 ); + } + + if ( 0 == r.left() || flags & Style_Selected ) { + p->setPen( gray[ 0 ] ); + p->drawLine( br.bottomLeft(), br.topLeft() ); + } + p->setPen( gray[ 5 ] ); + p->drawLine( br.bottomLeft(), br.bottomRight() ); + p->drawLine( br.right(), br.top(), br.right(), br.bottom() ); + br.addCoords( 1, 0, -1, -1 ); + if ( APPEARANCE_FLAT != appearance ) + if ( flags & Style_Selected ) + drawBevelGradient( + cg.background(), false, 0, p, br, true, + SHADE_BOTTOM_TAB_SEL_DARK( appearance ), + SHADE_BOTTOM_TAB_SEL_LIGHT( appearance ) ); + else + drawBevelGradient( gray[ 2 ], false, 0, p, br, + true, SHADE_BOTTOM_TAB_DARK( appearance ), + SHADE_BOTTOM_TAB_LIGHT( appearance ) ); + else + p->fillRect( br, flags & Style_Selected ? cg.background() : gray[ 2 ] ); + } + } + break; + } +#if QT_VERSION >= 0x030200 + case CE_TabBarLabel: { + if ( data.isDefault() ) + break; + + const QTabBar *tb = ( const QTabBar * ) widget; + QTab *t = data.tab(); + QRect tr = r; + + if ( t->identifier() == tb->currentTab() ) { + if ( QTabBar::RoundedAbove == tb->shape() || QTabBar::TriangularAbove == tb->shape() ) + tr.setBottom( tr.bottom() - pixelMetric( QStyle::PM_TabBarTabShiftVertical, tb ) ); + } else + if ( QTabBar::RoundedBelow == tb->shape() || QTabBar::TriangularBelow == tb->shape() ) + tr.setTop( tr.top() + pixelMetric( QStyle::PM_TabBarTabShiftVertical, tb ) ); + + drawItem( p, tr, AlignCenter | ShowPrefix, cg, flags & Style_Enabled, 0, t->text() ); + + if ( ( flags & Style_HasFocus ) && !t->text().isEmpty() ) + drawPrimitive( PE_FocusRect, p, r, cg ); + break; + } +#endif + case CE_PushButtonLabel: // Taken from Highcolour and Plastik... + { + int x, y, w, h; + + r.rect( &x, &y, &w, &h ); + + const QPushButton *button = static_cast( widget ); + bool active = button->isOn() || button->isDown(), + cornArrow = false; + + // Shift button contents if pushed. + if ( active ) { + x += pixelMetric( PM_ButtonShiftHorizontal, widget ); + y += pixelMetric( PM_ButtonShiftVertical, widget ); + flags |= Style_Sunken; + } + + // Does the button have a popup menu? + if ( button->isMenuButton() ) { + int dx = pixelMetric( PM_MenuButtonIndicator, widget ), + margin = pixelMetric( PM_ButtonMargin, widget ); + + if ( button->iconSet() && !button->iconSet() ->isNull() && + ( dx + button->iconSet() ->pixmap ( QIconSet::Small, QIconSet::Normal, + QIconSet::Off ).width() ) >= w ) + cornArrow = true; //To little room. Draw the arrow in the corner, don't adjust the widget + else { + drawPrimitive( PE_ArrowDown, + p, visualRect( QRect( + ( x + w ) - ( dx + margin ), y, dx, + h ), r ), cg, flags, data ); + + w -= dx; + } + } + + // Draw the icon if there is one + if ( button->iconSet() && !button->iconSet() ->isNull() ) { + QIconSet::Mode mode = QIconSet::Disabled; + QIconSet::State state = QIconSet::Off; + + if ( button->isEnabled() ) + mode = button->hasFocus() ? QIconSet::Active : QIconSet::Normal; + if ( button->isToggleButton() && button->isOn() ) + state = QIconSet::On; + + QPixmap pixmap = button->iconSet() ->pixmap( QIconSet::Small, mode, state ); + + static const int constSpace = 2; + + int xo = 0, + pw = pixmap.width(), + iw = 0; + + if ( button->text().isEmpty() && !button->pixmap() ) + p->drawPixmap( x + ( w >> 1 ) - ( pixmap.width() >> 1 ), + y + ( h >> 1 ) - ( pixmap.height() >> 1 ), + pixmap ); + else { + iw = button->pixmap() ? button->pixmap() ->width() + : widget->fontMetrics().size( Qt::ShowPrefix, button->text() ).width(); + + int cw = iw + pw + constSpace; + + xo = cw < w ? ( w - cw ) >> 1 : constSpace; + p->drawPixmap( x + xo, y + ( h >> 1 ) - ( pixmap.height() >> 1 ), pixmap ); + xo += pw; + } + + if ( cornArrow ) //Draw over the icon + drawPrimitive( PE_ArrowDown, p, visualRect( QRect( x + w - 6, x + h - 6, 7, 7 ), r ), + cg, flags, data ); + + if ( xo && iw ) { + x += xo + constSpace; + w = iw; + } else { + x += pw + constSpace; + w -= pw + constSpace; + } + } + + // Make the label indicate if the button is a default button or not + int i, + j = boldDefText && button->isDefault() ? 2 : 1; + + for ( i = 0; i < j; i++ ) + drawItem( p, QRect( x + i, y, w, h ), + AlignCenter | ShowPrefix, + button->colorGroup(), + button->isEnabled(), + button->pixmap(), + button->text(), -1, + &button->colorGroup().buttonText() ); + + //Draw a focus rect if the button has focus + if ( flags & Style_HasFocus ) + drawPrimitive( PE_FocusRect, p, + QStyle::visualRect( subRect( SR_PushButtonFocusRect, widget ), widget ), cg, flags ); + + break; + } + case CE_PopupMenuItem: { + if ( !widget || data.isDefault() ) + break; + + const QPopupMenu *popupmenu = ( const QPopupMenu * ) widget; + QMenuItem *mi = data.menuItem(); + int tab = data.tabWidth(), + maxpmw = data.maxIconWidth(), + x, y, w, h; + + r.rect( &x, &y, &w, &h ); + + if ( ( flags & Style_Active ) && ( flags & Style_Enabled ) ) { + drawPBarOrMenu( p, r, true, cg, true ); + } else if ( widget->erasePixmap() && !widget->erasePixmap() ->isNull() ) + p->drawPixmap( x, y, *widget->erasePixmap(), x, y, w, h ); + else { + // lighter background in popup menu + p->fillRect( r, cg.background().light( 100 + popupmenuHighlightLevel ) ); + } + + if ( !mi ) + break; + + if ( mi->isSeparator() ) { + const QColor * use = backgroundColors( cg ); + p->setPen( cg.background().dark(105) ); + p->drawLine( r.left() + 5, r.top() + 3, r.right() - 5, r.top() + 3 ); + break; + } + + maxpmw = QMAX( maxpmw, 16 ); + + QRect cr, ir, tr, sr; + if (menuIcons) { + // check column + cr.setRect( r.left(), r.top(), maxpmw, r.height() ); + // submenu indicator column + sr.setCoords( r.right() - maxpmw, r.top(), r.right(), r.bottom() ); + // tab/accelerator column + tr.setCoords( sr.left() - tab - 4, r.top(), sr.left(), r.bottom() ); + // item column + ir.setCoords( cr.right() + 2, r.top(), tr.right() - 4, r.bottom() ); + } else { + // item column + ir.setCoords( r.left() + 4, r.top(), r.width() , r.bottom() ); + // check column + cr.setCoords( r.right() - maxpmw, r.top(), r.right(), r.bottom() ); + // submenu indicator column + sr.setCoords( r.right() - maxpmw, r.top(), r.right(), r.bottom() ); + // tab/accelerator column + tr.setCoords( sr.left() - tab - 4, r.top(), sr.left(), r.bottom() ); + } + + bool reverse = QApplication::reverseLayout(); + + if ( reverse ) { + cr = visualRect( cr, r ); + sr = visualRect( sr, r ); + tr = visualRect( tr, r ); + ir = visualRect( ir, r ); + } + + if ( mi->iconSet() && menuIcons ) { + // Select the correct icon from the iconset + QIconSet::Mode mode = flags & Style_Active + ? ( mi->isEnabled() ? QIconSet::Active : QIconSet::Disabled ) + : ( mi->isEnabled() ? QIconSet::Normal : QIconSet::Disabled ); + cr = visualRect( QRect( x, y, maxpmw, h ), r ); + + // Do we have an icon and are checked at the same time? + // Then draw a "pressed" background behind the icon + if ( popupmenu->isCheckable() && mi->isChecked() ) { + + QBrush brush( gray[ 3 ] ); + + qDrawShadePanel( p, + cr.x() + 1, cr.y() + 2, cr.width() - 2, cr.height() - 4, + QColorGroup( gray[ 5 ], gray[ NUM_SHADES ], gray[ 0 ], gray[ 5 ], + gray[ 2 ], cg.text(), gray[ NUM_SHADES ] ), + true, 1, &brush ); + } + // Draw the icon + QPixmap pixmap = mi->iconSet() ->pixmap( QIconSet::Small, mode ); + QRect pmr( 0, 0, pixmap.width(), pixmap.height() ); + + pmr.moveCenter( cr.center() ); + p->drawPixmap( pmr.topLeft(), pixmap ); + + } else if ( popupmenu->isCheckable() && mi->isChecked() ) { + + // check column + cr.setRect( r.left(), r.top(), maxpmw, r.height() ); + // submenu indicator column + sr.setCoords( r.right() - maxpmw, r.top(), r.right(), r.bottom() ); + // tab/accelerator column + tr.setCoords( sr.left() - tab - 4, r.top(), sr.left(), r.bottom() ); + // item column + ir.setCoords( cr.right() + 2, r.top(), tr.right() - 4, r.bottom() ); + + QBrush brush( mi->isEnabled() ? cg.highlightedText() : cg.background() ); + drawPrimitiveMenu( PE_CheckMark, p, cr, cg, + ( flags & ( Style_Enabled | Style_Active ) ) | Style_On ); + } + + QColor textcolor, embosscolor; + + if ( flags & Style_Active ) { + if ( !( flags & Style_Enabled ) ) { + textcolor = cg.text(); + embosscolor = cg.light(); + } else { + textcolor = cg.highlightedText(); + embosscolor = cg.midlight().light(); + } + } else if ( !( flags & Style_Enabled ) ) { + textcolor = cg.text(); + embosscolor = cg.light(); + } else + textcolor = embosscolor = cg.buttonText(); + p->setPen( textcolor ); + + if ( mi->custom() ) { + p->save(); + if ( !( flags & Style_Enabled ) ) { + p->setPen( cg.light() ); + mi->custom() ->paint( p, cg, ( flags & Style_Enabled ) ? ( flags & Style_Active ) : 0, + flags & Style_Enabled, ir.x() + 1, ir.y() + 1, ir.width() - 1, + ir.height() - 1 ); + p->setPen( textcolor ); + } + mi->custom() ->paint( p, cg, ( flags & Style_Enabled ) ? ( flags & Style_Active ) : 0, + flags & Style_Enabled, ir.x(), ir.y(), ir.width(), ir.height() ); + p->restore(); + } + + QString text = mi->text(); + + if ( !text.isNull() ) { + int t = text.find( '\t' ); + + // draw accelerator/tab-text + if ( t >= 0 ) { + int alignFlag = AlignVCenter | ShowPrefix | DontClip | SingleLine; + + alignFlag |= ( reverse ? AlignLeft : AlignRight ); + + if ( !( flags & Style_Enabled ) ) { + p->setPen( embosscolor ); + tr.moveBy( 1, 1 ); + p->drawText( tr, alignFlag, text.mid( t + 1 ) ); + tr.moveBy( -1, -1 ); + p->setPen( textcolor ); + } + + p->drawText( tr, alignFlag, text.mid( t + 1 ) ); + } + + int alignFlag = AlignVCenter | ShowPrefix | DontClip | SingleLine; + + alignFlag |= ( reverse ? AlignRight : AlignLeft ); + + if ( !( flags & Style_Enabled ) ) { + p->setPen( embosscolor ); + ir.moveBy( 1, 1 ); + p->drawText( ir, alignFlag, text, t ); + ir.moveBy( -1, -1 ); + p->setPen( textcolor ); + } + + p->drawText( ir, alignFlag, text, t ); + } else if ( mi->pixmap() ) { + QPixmap pixmap = *mi->pixmap(); + + if ( 1 == pixmap.depth() ) + p->setBackgroundMode( OpaqueMode ); + p->drawPixmap( ir.x(), ( ir.height() - pixmap.height() ) >> 1, pixmap ); + if ( pixmap.depth() == 1 ) + p->setBackgroundMode( TransparentMode ); + } + + if ( mi->popup() ) + drawArrow( p, sr, cg, flags, reverse ? PE_ArrowLeft : PE_ArrowRight, false, true ); + break; + } + case CE_MenuBarItem: { + if ( ( flags & Style_Enabled ) && + ( flags & Style_Active ) && + ( flags & Style_Down ) ) { + drawPBarOrMenu2( p, QRect(r.x(), r.y(), r.width(), r.height()), + true, cg, true ); + } else +#ifdef QTC_GRADIENT_TOOLBARS_AND_MENUBARS + if ( APPEARANCE_FLAT != appearance ) + drawBevelGradient( cg.background(), true, 0, p, + r, + true, SHADE_BAR_LIGHT, SHADE_BAR_DARK ); + else +#endif + if (darkMenubar) { + drawBevelGradient( cg.background(), true, 1, p, + QRect(r.x()-1, r.y()-1, r.width()+2, r.height()+2), + true, + SHADE_BEVEL_MENU_GRAD_LIGHT( appearance ), SHADE_BEVEL_MENU_GRAD_DARK( appearance )); + } else + p->fillRect( r, cg.background() ); + + if ( data.isDefault() ) + break; + + QMenuItem *mi = data.menuItem(); + + if ( flags & Style_Active && ( flags & Style_Down ) ) + drawItem( p, r, AlignCenter | ShowPrefix | DontClip | SingleLine, + cg, flags & Style_Enabled, mi->pixmap(), mi->text(), -1, &cg.highlightedText() ); + else + drawItem( p, r, AlignCenter | ShowPrefix | DontClip | SingleLine, cg, + flags & Style_Enabled, mi->pixmap(), mi->text(), -1, &cg.buttonText() ); + break; + } + case CE_MenuBarEmptyArea: + if (darkMenubar) { + //p->fillRect( r, cg.background().dark( 106 ) ); + QColor b; + b.setRgb(cg.background().red(), cg.background().green(), cg.background().blue()); + drawBevelGradient( b, true, 1, p, + QRect(r.x()-1, r.y()-1, r.width()+2, r.height()+2 ), + true, + SHADE_BEVEL_MENU_GRAD_LIGHT( appearance ), SHADE_BEVEL_MENU_GRAD_DARK( appearance )); + } else + p->fillRect( r, cg.background() ); + break; + + case CE_DockWindowEmptyArea: +#ifdef QTC_GRADIENT_TOOLBARS_AND_MENUBARS + + if ( APPEARANCE_FLAT != appearance ) + drawBevelGradient( cg.background(), true, 1, p, r, true, SHADE_BAR_LIGHT, SHADE_BAR_DARK ); + else +#endif + + p->fillRect( r, cg.background() ); + break; + case CE_ProgressBarGroove: + p->setBrush( gray[ NUM_SHADES ] ); + p->drawRect( r ); + qDrawShadePanel( p, r, + QColorGroup( gray[ 5 ], gray[ NUM_SHADES ], gray[ 0 ], gray[ 5 ], gray[ 2 ], + cg.text(), gray[ NUM_SHADES ] ), true, 1 ); + + break; + + case CE_ProgressBarContents: { + // ### Take into account totalSteps()for busy indicator + const QProgressBar *pb = ( const QProgressBar* ) widget; + QRect cr = subRect( SR_ProgressBarContents, widget ); + double progress = pb->progress(); + bool reverse = QApplication::reverseLayout(); + int steps = pb->totalSteps(); + + if ( cr.isValid() && ( progress > 0 || steps == 0 ) ) { + double pg = ( steps == 0 ) ? 0.1 : progress / steps; + int width = QMIN( cr.width(), ( int ) ( pg * cr.width() ) ); + + if ( 0 == steps ) //Busy indicator + { + if ( width < 1 ) + width = 1; //A busy indicator with width 0 is kind of useless + + int remWidth = cr.width() - width; //Never disappear completely + + if ( remWidth <= 0 ) + remWidth = 1; //Do something non-crashy when too small... + + int pstep = int( progress ) % ( 2 * remWidth ); + + if ( pstep > remWidth ) { + //Bounce about.. We're remWidth +some delta, we want to be remWidth-delta... + //-((remWidth +some delta)-2* remWidth)=-(some deleta-remWidth)=remWidth-some delta.. + pstep = -( pstep - 2 * remWidth ); + } + + if ( reverse ) + drawPBarOrMenu( p, QRect( cr.x() + cr.width() - width - pstep, + cr.y(), width, cr.height() ), true, cg ); + else + drawPBarOrMenu( p, QRect( cr.x() + pstep, cr.y(), width, + cr.height() ), true, cg ); + } else + if ( reverse ) + drawPBarOrMenu( p, QRect( cr.x() + ( cr.width() - width ), + cr.y(), width, cr.height() ), true, cg ); + else + drawPBarOrMenu( p, QRect( cr.x(), cr.y(), width, cr.height() ), true, cg ); + } + break; + } + case CE_PushButton: { + const QPushButton *button = static_cast( widget ); + QRect br( r ); + int dbi = pixelMetric( PM_ButtonDefaultIndicator, widget ); + + if ( rounded && isFormWidget( widget ) ) + formMode = true; + + if ( widget == hoverWidget ) + flags |= Style_MouseOver; + + if ( IND_BORDER == defBtnIndicator ) + br.setCoords( br.left() + dbi, br.top() + dbi, br.right() - dbi, br.bottom() - dbi ); + else if ( IND_FONT_COLOUR == defBtnIndicator && button->isDefault() ) + flags |= Style_ButtonDefault; + + p->save(); + p->setBrushOrigin( -widget->backgroundOffset().x(), -widget->backgroundOffset().y() ); + // draw button + drawPrimitive( PE_ButtonCommand, p, br, cg, flags ); + if ( button->isDefault() && IND_FONT_COLOUR != defBtnIndicator ) + drawPrimitive( PE_ButtonDefault, p, r, cg, flags ); + p->restore(); + formMode = false; + break; + } + case CE_CheckBox: + drawPrimitive( PE_Indicator, p, r, cg, flags, data ); + break; + case CE_CheckBoxLabel: + if ( crLabelHighlight ) { + const QCheckBox * checkbox = ( const QCheckBox * ) widget; + + if ( flags & Style_MouseOver && +#if QT_VERSION >= 0x030200 + HOVER_CHECK == hover && hoverWidget == widget && +#endif + !isFormWidget( widget ) ) { +#if QT_VERSION >= 0x030200 + QRect cr( checkbox->rect() ); + QRegion r( QRect( cr.x(), cr.y(), + visualRect( subRect( SR_CheckBoxFocusRect, widget ), widget ).width() + + pixelMetric( PM_IndicatorWidth ) + 4, cr.height() ) ); + +#else + + QRegion r( checkbox->rect() ); +#endif + + r -= visualRect( subRect( SR_CheckBoxIndicator, widget ), widget ); + p->setClipRegion( r ); + p->fillRect( checkbox->rect(), cg.background().light( QTC_HIGHLIGHT_FACTOR ) ); + p->setClipping( false ); + } + int alignment = QApplication::reverseLayout() ? AlignRight : AlignLeft; + + drawItem( p, r, alignment | AlignVCenter | ShowPrefix, cg, + flags & Style_Enabled, checkbox->pixmap(), checkbox->text() ); + + if ( checkbox->hasFocus() ) + drawPrimitive( PE_FocusRect, p, + visualRect( subRect( SR_CheckBoxFocusRect, widget ), widget ), cg, flags ); + } else + KStyle::drawControl( control, p, widget, r, cg, flags, data ); + break; + case CE_RadioButton: + formMode = isFormWidget( widget ); + drawPrimitive( PE_ExclusiveIndicator, p, r, cg, flags, data ); + formMode = false; + break; + case CE_RadioButtonLabel: + if ( crLabelHighlight ) { + const QRadioButton * radiobutton = ( const QRadioButton * ) widget; + + if ( flags & Style_MouseOver && +#if QT_VERSION >= 0x030200 + HOVER_RADIO == hover && hoverWidget == widget && +#endif + !isFormWidget( widget ) ) { +#if QT_VERSION >= 0x030200 + QRect rb( radiobutton->rect() ); + QRegion r( QRect( rb.x(), rb.y(), + visualRect( subRect( SR_RadioButtonFocusRect, widget ), widget ).width() + + pixelMetric( PM_ExclusiveIndicatorWidth ) + 4, rb.height() ) ); +#else + + QRegion r( radiobutton->rect() ); +#endif + + r -= visualRect( subRect( SR_RadioButtonIndicator, widget ), widget ); + p->setClipRegion( r ); + p->fillRect( radiobutton->rect(), cg.background().light( QTC_HIGHLIGHT_FACTOR ) ); + p->setClipping( false ); + } + + int alignment = QApplication::reverseLayout() ? AlignRight : AlignLeft; + + drawItem( p, r, alignment | AlignVCenter | ShowPrefix, cg, + flags & Style_Enabled, radiobutton->pixmap(), radiobutton->text() ); + + if ( radiobutton->hasFocus() ) + drawPrimitive( PE_FocusRect, p, + visualRect( subRect( SR_RadioButtonFocusRect, widget ), widget ), cg, flags ); + break; + } + default: + KStyle::drawControl( control, p, widget, r, cg, flags, data ); + } +} + +void KlearlookStyle::drawControlMask( ControlElement control, QPainter *p, const QWidget *widget, const QRect &r, + const QStyleOption &data ) const { + switch ( control ) { + case CE_PushButton: + if ( rounded ) { + int offset = r.width() < QTC_MIN_BTN_SIZE || r.height() < QTC_MIN_BTN_SIZE ? 1 : 2; + + p->fillRect( r, color0 ); + p->fillRect( r.x() + 1, r.y() + 1, r.width() - 2, r.height() - 2, color1 ); + p->setPen( color1 ); + p->drawLine( r.x() + offset, r.y(), r.x() + r.width() - ( offset + 1 ), r.y() ); + p->drawLine( r.x() + offset, r.y() + r.height() - 1, + r.x() + r.width() - ( offset + 1 ), r.y() + r.height() - 1 ); + p->drawLine( r.x(), r.y() + offset, r.x(), r.y() + r.height() - ( offset + 1 ) ); + p->drawLine( r.x() + r.width() - 1, r.y() + offset, + r.x() + r.width() - 1, r.y() + r.height() - ( offset + 1 ) ); + } else + p->fillRect( r, color1 ); + break; + default: + KStyle::drawControlMask( control, p, widget, r, data ); + } +} + +void KlearlookStyle::drawComplexControlMask( ComplexControl control, QPainter *p, const QWidget *widget, const QRect &r, + const QStyleOption &data ) const { + switch ( control ) { + case CC_ToolButton: + case CC_ComboBox: + drawControlMask( CE_PushButton, p, widget, r, data ); + break; + default: + KStyle::drawComplexControlMask( control, p, widget, r, data ); + } +} + +QRect KlearlookStyle::subRect( SubRect subrect, const QWidget *widget ) const { + QRect rect, + wrect( widget->rect() ); + + switch ( subrect ) { + case SR_PushButtonFocusRect: { + // const QPushButton *button=(const QPushButton *)widget; + int dbw1 = 0, + dbw2 = 0; + + // if(button->isDefault() || button->autoDefault()) + // { + dbw1 = pixelMetric( PM_ButtonDefaultIndicator, widget ); + dbw2 = dbw1 * 2; + // } + + rect.setRect( wrect.x() + 3 + dbw1, + wrect.y() +3 + dbw1, + wrect.width() - 6 - dbw2, + wrect.height() - 6 - dbw2 ); + break; + } + case SR_CheckBoxIndicator: { + int h = pixelMetric( PM_IndicatorHeight ); + + rect.setRect( ( widget->rect().height() - h ) >> 1, + ( widget->rect().height() - h ) >> 1, + pixelMetric( PM_IndicatorWidth ), + h ); + break; + } + case SR_RadioButtonIndicator: { + int h = pixelMetric( PM_ExclusiveIndicatorHeight ); + + rect.setRect( ( widget->rect().height() - h ) >> 1, + ( widget->rect().height() - h ) >> 1, + pixelMetric( PM_ExclusiveIndicatorWidth ), h ); + break; + } + case SR_ProgressBarContents: + rect = QRect( wrect.x() + 1, + wrect.y() + 1, + wrect.width() - 2, + wrect.height() - 2 ); + break; + default: + rect = KStyle::subRect( subrect, widget ); + } + + return rect; +} + +void KlearlookStyle::drawComplexControl( + ComplexControl control, + QPainter *p, + const QWidget *widget, + const QRect &r, + const QColorGroup &cg, + SFlags flags, + SCFlags controls, + SCFlags active, + const QStyleOption &data ) const +{ + if ( widget == hoverWidget ) + flags |= Style_MouseOver; + + switch ( control ) { + case CC_ToolButton: { + + const QToolButton * toolbutton = ( const QToolButton * ) widget; + QRect button ( querySubControlMetrics( control, widget, SC_ToolButton, data ) ), + menuarea( querySubControlMetrics( control, widget, SC_ToolButtonMenu, data ) ); + + SFlags bflags = flags, mflags = flags; + + if ( APP_KORN == themedApp ) { + drawPrimitive( PE_ButtonTool, p, button, cg, bflags, data ); + break; + } + + bool onControlButtons = false, + onToolbar = widget->parentWidget() && ::qt_cast( widget->parentWidget() ), + onExtender = !onToolbar && + widget->parentWidget() && + widget->parentWidget() ->inherits( "QToolBarExtensionWidget" ) && + ::qt_cast( widget->parentWidget() ->parentWidget() ); + + if ( !onToolbar && !onExtender && widget->parentWidget() && + !qstrcmp( widget->parentWidget() ->name(), "qt_maxcontrols" ) ) + onControlButtons = true; + + if ( active & SC_ToolButton ) + bflags |= Style_Down; + + if ( active & SC_ToolButtonMenu ) + mflags |= Style_Down; + + if ( controls & SC_ToolButton ) { + // If we're pressed, on, or raised... +#if KDE_VERSION >= 0x30200 + if ( bflags & ( Style_Down | Style_On | Style_Raised ) || onControlButtons ) +#else + // CPD: Style_MouseOver obove is *needed* for KDE's KToggleActions... + if ( bflags & ( Style_Down | Style_On | Style_Raised | Style_MouseOver ) || onControlButtons ) +#endif + + { + //Make sure the standalone toolbuttons have a gradient in the right direction + if ( !onToolbar && !onControlButtons ) + bflags |= Style_Horizontal; + + drawPrimitive( PE_ButtonTool, p, button, cg, bflags, data ); + } + + // Check whether to draw a background pixmap + else if ( toolbutton->parentWidget() && + toolbutton->parentWidget() ->backgroundPixmap() && + !toolbutton->parentWidget() ->backgroundPixmap() ->isNull() ) { + p->drawTiledPixmap( r, + *( toolbutton->parentWidget() ->backgroundPixmap() ), toolbutton->pos() ); + } else if ( widget->parent() ) { + if ( ::qt_cast( widget->parent() ) ) { + QToolBar * parent = ( QToolBar* ) widget->parent(); + +#ifdef QTC_GRADIENT_TOOLBARS_AND_MENUBARS + + if ( APPEARANCE_FLAT != appearance ) + drawBevelGradient( cg.background(), true, 0, + p, parent->rect(), true, SHADE_BAR_LIGHT, SHADE_BAR_DARK ); + else +#endif + + p->fillRect( parent->rect(), cg.background() ); + } else if ( widget->parent() ->inherits( "QToolBarExtensionWidget" ) ) { + QWidget * parent = ( QWidget* ) widget->parent(); + QToolBar *toolbar = ( QToolBar* ) parent->parent(); + +#ifdef QTC_GRADIENT_TOOLBARS_AND_MENUBARS + + if ( APPEARANCE_FLAT != appearance ) + drawBevelGradient( cg.background(), true, 0, p, toolbar->rect(), + true, SHADE_BAR_LIGHT, SHADE_BAR_DARK ); + else +#endif + + p->fillRect( toolbar->rect(), cg.background() ); + } + } + } + + if ( controls & SC_ToolButtonMenu ) { + if ( mflags & ( Style_Down | Style_On | Style_Raised ) ) + drawPrimitive( PE_ButtonDropDown, p, menuarea, cg, mflags, data ); + drawPrimitive( PE_ArrowDown, p, menuarea, cg, mflags, data ); + } + + if ( toolbutton->hasFocus() && !toolbutton->focusProxy() ) { + QRect fr = toolbutton->rect(); + fr.addCoords( 3, 3, -3, -3 ); + drawPrimitive( PE_FocusRect, p, fr, cg ); + } + break; + } + case CC_ComboBox: { + const QComboBox *combobox = ( const QComboBox * ) widget; + + QRect frame( QStyle::visualRect( querySubControlMetrics( CC_ComboBox, + widget,SC_ComboBoxFrame,data ),widget ) ), + + arrow( QStyle::visualRect( querySubControlMetrics( CC_ComboBox, + widget,SC_ComboBoxArrow,data),widget)), + + field( QStyle::visualRect( querySubControlMetrics(CC_ComboBox, + widget,SC_ComboBoxEditField,data),widget)); + + const QColor *use = buttonColors( cg ); + + if ( rounded && isFormWidget( widget ) ) + formMode = true; + + if ( controls & SC_ComboBoxFrame && frame.isValid() ) { + if ( controls & SC_ComboBoxEditField && field.isValid() && combobox->editable() ) { + QRect f2( field ); + QRegion reg( r ); + + f2.addCoords( -1, -1, 1, 1 ); + reg -= f2; + p->setClipRegion( reg ); + } + drawLightBevelButton( p, r, cg, flags | Style_Raised | Style_Horizontal, + true, ROUNDED_ALL, getFill( flags, use ), + use ); + p->setClipping( false ); + } + + if ( controls & SC_ComboBoxArrow && arrow.isValid() ) { + drawPrimitive( PE_ArrowDown, p, arrow, cg, flags & ~Style_MouseOver ); + p->setPen( use[ 4 ].light(70) ); + arrow.addCoords( -1, -1, -1, 1 ); + p->drawLine( arrow.left(), arrow.top(), arrow.left(), arrow.bottom() ); + } + + if ( controls & SC_ComboBoxEditField && field.isValid() ) { + if ( ( flags & Style_HasFocus ) && ( ! combobox->editable() ) ) { + QRect fr = QStyle::visualRect( subRect( SR_ComboBoxFocusRect, widget ), widget ); + + fr.addCoords( 0, 0, -2, 0 ); + drawPrimitive( PE_FocusRect, + p, fr, cg, flags | Style_FocusAtBorder, QStyleOption( cg.highlight() ) ); + } + } + + p->setPen( flags & Style_Enabled ? cg.buttonText() : cg.mid() ); + formMode = false; + break; + } + case CC_SpinWidget: { + const QSpinWidget *spinwidget = ( const QSpinWidget * ) widget; + QRect frame( querySubControlMetrics( CC_SpinWidget, widget, SC_SpinWidgetFrame, data ) ), + up( spinwidget->upRect() ), + down( spinwidget->downRect() ); + + if ( hoverWidget && spinwidget == hoverWidget ) + flags |= Style_MouseOver; + + if ( ( controls & SC_SpinWidgetFrame ) && frame.isValid() ) + qDrawShadePanel( + p, r, QColorGroup( gray[ 5 ], gray[ NUM_SHADES ], gray[ 0 ], + gray[ 5 ], gray[ 2 ], cg.text(), gray[ NUM_SHADES ] ), + true, pixelMetric( PM_SpinBoxFrameWidth ) + ); + + if ( ( controls & SC_SpinWidgetUp ) && up.isValid() ) { + PrimitiveElement pe = PE_SpinWidgetUp; + SFlags upflags = flags; + + if ( spinwidget->buttonSymbols() == QSpinWidget::PlusMinus ) + pe = PE_SpinWidgetPlus; + if ( !spinwidget->isUpEnabled() ) + upflags ^= Style_Enabled; + drawPrimitive( + pe, p, up, cg, + upflags | ( ( active == SC_SpinWidgetUp ) ? Style_On | Style_Sunken : Style_Raised ) + ); + } + + if ( ( controls & SC_SpinWidgetDown ) && down.isValid() ) { + PrimitiveElement pe = PE_SpinWidgetDown; + SFlags downflags = flags; + + if ( spinwidget->buttonSymbols() == QSpinWidget::PlusMinus ) + pe = PE_SpinWidgetMinus; + if ( !spinwidget->isDownEnabled() ) + downflags ^= Style_Enabled; + drawPrimitive( + pe, p, down, cg, + downflags | ( ( active == SC_SpinWidgetDown ) ? Style_On | Style_Sunken : Style_Raised ) + ); + } + const QColor *use = backgroundColors( cg ); + p->setPen( use[ 4 ].light(80) ); + p->drawRect( r ); + break; + } + case CC_ScrollBar: { + const QScrollBar *scrollbar = ( const QScrollBar * ) widget; + bool hw = hoverWidget == scrollbar; + QRect subline( querySubControlMetrics( control, widget, SC_ScrollBarSubLine, data ) ), + addline( querySubControlMetrics( control, widget, SC_ScrollBarAddLine, data ) ), + subpage( querySubControlMetrics( control, widget, SC_ScrollBarSubPage, data ) ), + addpage( querySubControlMetrics( control, widget, SC_ScrollBarAddPage, data ) ), + slider( querySubControlMetrics( control, widget, SC_ScrollBarSlider, data ) ), + first( querySubControlMetrics( control, widget, SC_ScrollBarFirst, data ) ), + last( querySubControlMetrics( control, widget, SC_ScrollBarLast, data ) ); + + if ( ( controls & SC_ScrollBarSubLine ) && subline.isValid() ) + drawPrimitive( + PE_ScrollBarSubLine, p, subline, cg, + ( hw && HOVER_SB_SUB == hover ? Style_MouseOver : Style_Default ) | + Style_Enabled | + ( ( active == SC_ScrollBarSubLine ) ? Style_Down : Style_Default ) | + ( ( scrollbar->orientation() == Qt::Horizontal ) ? Style_Horizontal : Style_Default ) + ); + if ( ( controls & SC_ScrollBarAddLine ) && addline.isValid() ) + drawPrimitive( + PE_ScrollBarAddLine, p, addline, cg, + ( hw && HOVER_SB_ADD == hover ? Style_MouseOver : Style_Default ) | + Style_Enabled | + ( ( active == SC_ScrollBarAddLine ) ? Style_Down : Style_Default ) | + ( ( scrollbar->orientation() == Qt::Horizontal ) ? Style_Horizontal : Style_Default ) + ); + if ( ( controls & SC_ScrollBarSubPage ) && subpage.isValid() ) + drawPrimitive( PE_ScrollBarSubPage, p, subpage, cg, + Style_Enabled | + ( ( active == SC_ScrollBarSubPage ) ? Style_Down : Style_Default ) | + ( ( scrollbar->orientation() == Qt::Horizontal ) ? Style_Horizontal : Style_Default ) ); + if ( ( controls & SC_ScrollBarAddPage ) && addpage.isValid() ) + drawPrimitive( PE_ScrollBarAddPage, p, addpage, cg, + ( ( scrollbar->minValue() == scrollbar->maxValue() ) ? Style_Default : Style_Enabled ) | + ( ( active == SC_ScrollBarAddPage ) ? Style_Down : Style_Default ) | + ( ( scrollbar->orientation() == Qt::Horizontal ) ? Style_Horizontal : Style_Default ) ); + if ( ( controls & SC_ScrollBarFirst ) && first.isValid() ) + drawPrimitive( PE_ScrollBarFirst, p, first, cg, + Style_Enabled | + ( ( active == SC_ScrollBarFirst ) ? Style_Down : Style_Default ) | + ( ( scrollbar->orientation() == Qt::Horizontal ) ? Style_Horizontal : Style_Default ) ); + if ( ( controls & SC_ScrollBarLast ) && last.isValid() ) + drawPrimitive( PE_ScrollBarLast, p, last, cg, + Style_Enabled | + ( ( active == SC_ScrollBarLast ) ? Style_Down : Style_Default ) | + ( ( scrollbar->orientation() == Qt::Horizontal ) ? Style_Horizontal : Style_Default ) ); + if ( ( controls & SC_ScrollBarSlider ) && slider.isValid() ) { + drawPrimitive( PE_ScrollBarSlider, p, slider, cg, + ( hw && HOVER_SB_SLIDER == hover ? Style_MouseOver : Style_Default ) | + Style_Enabled | + ( ( active == SC_ScrollBarSlider ) ? Style_Down : Style_Default ) | + ( ( scrollbar->orientation() == Qt::Horizontal ) ? Style_Horizontal : Style_Default ) ); + + // ### perhaps this should not be able to accept focus if maxedOut? + if ( scrollbar->hasFocus() ) + drawPrimitive( PE_FocusRect, p, + QRect( slider.x() + 2, slider.y() + 2, slider.width() - 5, slider.height() - 5 ), + cg, Style_Default ); + } + break; + } + case CC_Slider: { + QRect groove = querySubControlMetrics( CC_Slider, widget, SC_SliderGroove, data ), + handle = querySubControlMetrics( CC_Slider, widget, SC_SliderHandle, data ); + + if ( ( controls & SC_SliderGroove ) && groove.isValid() ) + drawSliderGroove( p, groove, flags, widget ); + if ( ( controls & SC_SliderHandle ) && handle.isValid() ) + drawSliderHandle( p, handle, cg, flags ); + if ( controls & SC_SliderTickmarks ) + QCommonStyle::drawComplexControl( + control, p, widget, r, cg, flags, SC_SliderTickmarks, active, data + ); + break; + } + default: + KStyle::drawComplexControl( control, p, widget, r, cg, flags, controls, active, data ); + } +} + +QRect KlearlookStyle::querySubControlMetrics( ComplexControl control, const QWidget *widget, SubControl sc, + const QStyleOption &data ) const { + switch ( control ) { + case CC_SpinWidget: { + if ( !widget ) + return QRect(); + + int fw = pixelMetric( PM_SpinBoxFrameWidth, 0 ); + QSize bs; + + bs.setHeight( widget->height() >> 1 ); + if ( bs.height() < 8 ) + bs.setHeight( 8 ); + bs.setWidth( QMIN( bs.height() * 8 / 6, widget->width() / 4 ) ); + bs = bs.expandedTo( QApplication::globalStrut() ); + + if ( !( bs.width() % 2 ) ) + bs.setWidth( bs.width() + 1 ); + + int extra = bs.height() * 2 == widget->height() ? 0 : 1; + int y = 0, + x = widget->width() - y - bs.width(), + lx = fw, + rx = x - fw * 2; + + switch ( sc ) { + case SC_SpinWidgetUp: + return QRect( x, y, bs.width(), bs.height() ); + case SC_SpinWidgetDown: + return QRect( x, y + bs.height(), bs.width(), bs.height() + extra ); + case SC_SpinWidgetButtonField: + return QRect( x, y, bs.width(), widget->height() - 2 * fw ); + case SC_SpinWidgetEditField: + return QRect( lx, fw, rx, widget->height() - 2 * fw ); + case SC_SpinWidgetFrame: + return QRect( widget->x(), widget->y(), widget->width() - bs.width(), widget->height() ); + } + } + default: + return KStyle::querySubControlMetrics( control, widget, sc, data ); + } +} + +int KlearlookStyle::pixelMetric( PixelMetric metric, const QWidget *widget ) const { + switch ( metric ) { + case PM_MenuButtonIndicator: + return 7; + case PM_MenuBarItemSpacing: { + return 5; + } + case PM_ButtonMargin: + return 5; +#if QT_VERSION >= 0x030200 + + case PM_TabBarTabShiftVertical: { + const QTabBar *tb = ::qt_cast( widget ); + + return QTabBar::RoundedAbove == tb->shape() || QTabBar::TriangularAbove == tb->shape() + ? 1 + : -1; + } + case PM_TabBarTabShiftHorizontal: + return 0; +#endif + + case PM_TabBarTabVSpace: { + const QTabBar * tb = ( const QTabBar * ) widget; + if ( tb->shape() == QTabBar::RoundedAbove || + tb->shape() == QTabBar::RoundedBelow ) + return 12; + else + return 4; + } + + + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + return 1; + case PM_ButtonDefaultIndicator: + return IND_BORDER == defBtnIndicator ? 1 : 0; + case PM_DefaultFrameWidth: + return borderFrame && widget && ( ::qt_cast( widget ) || + ::qt_cast( widget ) || + ::qt_cast( widget ) ) + ? 2 + : QTC_DEF_FRAME_WIDTH; + case PM_SpinBoxFrameWidth: + return 1; + case PM_MenuBarFrameWidth: + return 1; + case PM_IndicatorWidth: + case PM_IndicatorHeight: + return QTC_CHECK_SIZE; + case PM_ExclusiveIndicatorWidth: + case PM_ExclusiveIndicatorHeight: + return QTC_RADIO_SIZE; + case PM_TabBarTabOverlap: + return 1; + case PM_ProgressBarChunkWidth: + return 2; + case PM_DockWindowSeparatorExtent: + return 4; + case PM_DockWindowHandleExtent: + return 10; + case PM_SplitterWidth: + return 4; + case PM_ScrollBarSliderMin: + return 16; + case PM_ScrollBarExtent: + case PM_SliderControlThickness: + case PM_SliderThickness: + return 15; + case PM_SliderLength: + return 24; + case PM_MaximumDragDistance: + return -1; + default: + return KStyle::pixelMetric( metric, widget ); + } +} + +int KlearlookStyle::kPixelMetric( KStylePixelMetric kpm, const QWidget *widget ) const { + switch ( kpm ) { + case KPM_MenuItemSeparatorHeight: + return 4; + default: + return KStyle::kPixelMetric( kpm, widget ); + } +} + +QSize KlearlookStyle::sizeFromContents( ContentsType t, + const QWidget *widget, + const QSize &s, + const QStyleOption &opt ) const { + switch ( t ) { + case CT_PopupMenuItem: { + if ( !widget || opt.isDefault() ) + return s; + + const QPopupMenu *popup = dynamic_cast( widget ); + QMenuItem *mi = opt.menuItem(); + int maxpmw = opt.maxIconWidth(); + int w = s.width(), h = s.height(); + bool checkable = popup->isCheckable(); + + if ( mi->custom() ) { + w = mi->custom() ->sizeHint().width(); + h = mi->custom() ->sizeHint().height(); + if ( !mi->custom() ->fullSpan() ) + h += 4; + } else if ( mi->widget() ) { + // don't change the size in this case. + } else if ( mi->isSeparator() ) { + w = 20; + h = 8; + } else { + if ( mi->pixmap() ) { + h = QMAX( h, mi->pixmap() ->height() + 2 ); + } else { + h = QMAX( h, 21 ); + QSettings s; + if ( menuIcons ) + h = QMAX( h, popup->fontMetrics().height() + MENU_POPUP_ITEM_HIGH_HI ); + else + h = QMAX( h, popup->fontMetrics().height() + MENU_POPUP_ITEM_HIGH_LO ); + } + + if ( mi->iconSet() ) { + h = QMAX( h, mi->iconSet() ->pixmap( QIconSet::Small, QIconSet::Normal ).height() + 2 ); + } + } + + if ( !mi->text().isNull() && ( mi->text().find( '\t' ) >= 0 ) ) { + w += itemHMargin + itemFrame * 2 + 7; + } else if ( mi->popup() ) { + w += 2 * arrowHMargin; + } + + if ( maxpmw ) { + w += maxpmw + 6; + } + if ( checkable && maxpmw < 20 ) { + w += 20 - maxpmw; + } + if ( checkable || maxpmw > 0 ) { + w += 12; + } + + w += rightBorder; + + return QSize( w-25, h ); + } + + case CT_PushButton: { + const QPushButton* btn = static_cast( widget ); + + int w = s.width() + 2 * pixelMetric( PM_ButtonMargin, widget ); + int h = s.height() + 2 * pixelMetric( PM_ButtonMargin, widget ); + if ( btn->text().isEmpty() && s.width() < 32 ) + return QSize( w, h ); + // return button size + return QSize( w + 25, h + 3 ); + } + + case CT_ToolButton: { + if ( widget->parent() && ::qt_cast( widget->parent() ) ) + return QSize( s.width() + 2 * 4, s.height() + 2 * 4 ); + else { + return KStyle::sizeFromContents ( t, widget, s, opt ); + } + } + + default: + return KStyle::sizeFromContents ( t, widget, s, opt ); + } + + return KStyle::sizeFromContents ( t, widget, s, opt ); +} + + + +int KlearlookStyle::styleHint( StyleHint stylehint, const QWidget *widget, const QStyleOption &option, QStyleHintReturn *returnData ) const { + switch ( stylehint ) { + case SH_EtchDisabledText: + case SH_Slider_SnapToValue: + case SH_PrintDialog_RightAlignButtons: + case SH_FontDialog_SelectAssociatedText: + case SH_MenuBar_AltKeyNavigation: + case SH_MenuBar_MouseTracking: + case SH_PopupMenu_MouseTracking: + case SH_PopupMenu_SpaceActivatesItem: + case SH_ComboBox_ListMouseTracking: + case SH_ScrollBar_MiddleClickAbsolutePosition: + return 1; + case SH_MainWindow_SpaceBelowMenuBar: + return 0; + case SH_ComboBox_Popup: + return 0; + case SH_PopupMenu_SubMenuPopupDelay: + return 300; + case SH_PopupMenu_AllowActiveAndDisabled: + return 0; + default: + return KStyle::styleHint( stylehint, widget, option, returnData ); + } +} + +void KlearlookStyle::drawPBarOrMenu( + QPainter *p, + QRect const &r, + bool horiz, + const QColorGroup &cg, + bool menu ) const +{ + switch ( pmProfile ) { + case PROFILE_SUNKEN: + drawGradientWithBorder( p, r, horiz ); + break; + case PROFILE_RAISED: { + int flags = QStyle::Style_Raised; + + if ( horiz ) + flags |= Style_Horizontal; + + drawLightBevel( p, r, + cg, flags, true, + menu ? ROUNDED_ALL : ROUNDED_NONE, + getFill( flags, menuPbar ), + menuPbar, true + ); + + break; + } + default: + p->fillRect( r, menuPbar[ GRADIENT_BASE ] ); + break; + } +} +void KlearlookStyle::drawPBarOrMenu2( + QPainter *p, + QRect const &r, + bool horiz, + const QColorGroup &cg, + bool menu ) const +{ + switch ( pmProfile ) { + case PROFILE_SUNKEN: + drawGradientWithBorder( p, r, horiz ); + break; + case PROFILE_RAISED: { + int flags = QStyle::Style_Raised; + + if ( horiz ) + flags |= Style_Horizontal; + + drawLightBevel( p, r, + cg, flags, true, + menu ? ROUNDED_TOP : ROUNDED_NONE, + getFill( flags, menuPbar ), + menuPbar, true + ); + + break; + } + default: + p->fillRect( r, menuPbar[ GRADIENT_BASE ] ); + break; + } +} + +void KlearlookStyle::drawGradientWithBorder( + QPainter *p, + QRect const &r, + bool horiz ) const +{ + QRect r2( r ); + + drawGradient( menuPbar[ GRADIENT_TOP ], + menuPbar[ GRADIENT_BOTTOM ], true, borderFrame ? 2 : 1, p, r, horiz ); + // 3d border effect... + if ( borderFrame ) { + p->setPen( menuPbar[ GRADIENT_BASE ] ); + p->setBrush( NoBrush ); + p->drawRect( r ); + } else + r2.addCoords( -1, -1, 1, 1 ); + + p->setPen( menuPbar[ GRADIENT_LIGHT ] ); + p->drawLine( r2.left() + 1, r2.top() + 1, r2.right() - 1, r2.top() + 1 ); + p->drawLine( r2.left() + 1, r2.top() + 1, r2.left() + 1, r2.bottom() - 1 ); + p->setPen( menuPbar[ GRADIENT_DARK ] ); + p->drawLine( r2.left() + 1, r2.bottom() - 1, r2.right() - 1, r2.bottom() - 1 ); + p->drawLine( r2.right() - 1, r2.bottom() - 1, r2.right() - 1, r2.top() + 1 ); +} + +void KlearlookStyle::drawBevelGradient( + const QColor &base, + bool increase, + int border, + QPainter *p, + QRect const &r, + bool horiz, double shadeTop, double shadeBot ) +const { + //CPD TODO: Store last settings to make faster! + QColor top, bot; + + if ( equal( 1.0, shadeTop ) ) + top = base; + else + shade( base, &top, shadeTop ); + if ( equal( 1.0, shadeBot ) ) + bot = base; + else + shade( base, &bot, shadeBot ); + + drawGradient( top, bot, increase, border, p, r, horiz ); +} + +void KlearlookStyle::drawGradient( + const QColor &top, + const QColor &bot, + bool increase, + int border, + QPainter *p, + QRect const &r, + bool horiz ) const +{ + if ( r.width() > 0 && r.height() > 0 ) { + QRect grad( + r.left() + border, + r.top() + border, + r.width() - ( border * 2 ), + r.height() - ( border * 2 ) + ); + + if ( top == bot ) + p->fillRect( grad, top ); + else { + QRect grad( + r.left() + border, + r.top() + border, + r.width() - ( border * 2 ), + r.height() - ( border * 2 ) + ); + + int i, + s = horiz ? grad.top() : grad.left(), + e = horiz ? grad.bottom() : grad.right(); + + double amt = ( horiz ? grad.height() : grad.width() ) , + dr = ( ( double ) ( bot.red() - top.red() ) ) / amt , + dg = ( ( double ) ( bot.green() - top.green() ) ) / amt , + db = ( ( double ) ( bot.blue() - top.blue() ) ) / amt, + rc = 0, gc = 0, bc = 0; + + if ( increase ) + for ( i = s; i <= e ; i++ ) { + p->setPen( QColor( + limit( top.red() + rc ), + limit( top.green() + gc ), + limit( top.blue() + bc ) + )); + + if ( horiz ) + p->drawLine( grad.left(), i, grad.right(), i ); + else + p->drawLine( i, grad.top(), i, grad.bottom() ); + rc += dr; + gc += dg; + bc += db; + } + else + for ( i = e; i >= s; i-- ) { + p->setPen( + QColor( + limit( top.red() + rc ), + limit( top.green() + gc ), + limit( top.blue() + bc ) + ) ); + + if ( horiz ) + p->drawLine( grad.left(), i, grad.right(), i ); + + else + p->drawLine( i, grad.top(), i, grad.bottom() ); + + rc += dr; + gc += dg; + bc += db; + } + } + } +} + + +void KlearlookStyle::drawPopupRect( QPainter *p, const QRect &r, const QColorGroup &cg ) const +{ + const QColor *use = backgroundColors( cg ); + p->setPen( use[ 4 ].light(70) ); + p->setBrush( NoBrush ); + p->drawRect( r ); +} + +void KlearlookStyle::drawSliderHandle( + QPainter *p, + const QRect &r, + const QColorGroup &cg, QStyle::SFlags flags +) const +{ + const QColor * use = buttonColors( cg ); + + if ( r.width() > r.height() ) + flags |= Style_Horizontal; + flags |= Style_Raised; + + drawLightBevelButton( p, r, cg, flags, true, ROUNDED_ALL, getFill( flags, use ), use ); + + if ( GROOVE_NONE != sliderThumbs && + ( ( flags & Style_Horizontal && r.width() >= 14 ) || r.height() >= 14 ) ) + drawLines( p, r, + r.width() < r.height(), + 4, 3, use, 0, + GROOVE_SUNKEN == sliderThumbs, + APPEARANCE_LIGHT_GRADIENT == appearance ); +} + +void KlearlookStyle::drawSliderGroove + ( QPainter *p, + const QRect &r, + QStyle::SFlags flags, + const QWidget *widget ) const +{ + const QSlider * slider = ( const QSlider * ) widget; + QRect groove( r ); + + if ( flags & Style_HasFocus ) { + QRect fr( groove ); + + fr.addCoords( -1, -1, 1, 1 ); + drawPrimitive( PE_FocusRect, p, fr, QColorGroup() ); + } + + if ( Qt::Horizontal == slider->orientation() ) { + int dh = ( groove.height() - 5 ) >> 1; + + groove.addCoords( 0, dh, 0, -dh ); + } else { + int dw = ( groove.width() - 5 ) >> 1; + + groove.addCoords( dw, 0, -dw, 0 ); + } + p->setBrush( gray[ 2 ] ); + p->setPen( gray[ 5 ] ); + p->drawRect( groove ); + p->setPen( gray[ 4 ] ); + p->drawLine( groove.x() + 1, groove.y() + 1, groove.x() + groove.width() - 2, groove.y() + 1 ); + p->drawLine( groove.x() + 1, groove.y() + 1, groove.x() + 1, groove.y() + groove.height() - 2 ); +} + +void KlearlookStyle::shadeColors( const QColor &base, QColor *vals ) const { + QTC_SHADES + + int i; + + for ( i = 0; i < NUM_SHADES; ++i ) + shade( base, &vals[ i ], QTC_SHADE( appearance, contrast, i ) ); + + vals[ NUM_SHADES ] = base; +} + +const QColor * KlearlookStyle::buttonColors( const QColorGroup &cg ) const { + if ( cg.button() != button[ NUM_SHADES ] ) { + shadeColors( cg.button(), buttonColoured ); + return buttonColoured; + } + + return button; +} + +const QColor * KlearlookStyle::backgroundColors( const QColorGroup &cg ) const { + if ( cg.background() != gray[ NUM_SHADES ] ) { + shadeColors( cg.background(), backgroundColoured ); + return backgroundColoured; + } + + return gray; +} + +bool KlearlookStyle::redrawHoverWidget() { + if ( !hoverWidget ) + return false; + + QPoint cursor( QCursor::pos() ), + widgetZero( hoverWidget->mapToGlobal( QPoint( 0, 0 ) ) ); + +#if QT_VERSION >= 0x030200 + + // + // Qt>=3.2 sets the sensitive part of a check/radio to the image + label -> anything else + // is not sensitive. But, + // the widget can ocupy a larger area - and this whole are will react to mouse over. + // This needs to be coounteracted + // so that it looks as if only the sensitive area mouse-overs... + QRadioButton *rb = dynamic_cast( hoverWidget ); + + if ( rb ) { + QRect rect( widgetZero.x(), widgetZero.y(), + visualRect( subRect( SR_RadioButtonFocusRect, rb ), rb ).width() + + pixelMetric( PM_ExclusiveIndicatorWidth ) + 4, hoverWidget->height() ); + + hover = rect.contains( cursor ) ? HOVER_RADIO : HOVER_NONE; + return ( HOVER_NONE != hover && !rect.contains( oldCursor ) ) || + ( HOVER_NONE == hover && rect.contains( oldCursor ) ); + } else { + QCheckBox *cb = dynamic_cast( hoverWidget ); + + if ( cb ) { + QRect rect( widgetZero.x(), widgetZero.y(), + visualRect( subRect( SR_CheckBoxFocusRect, cb ), cb ).width() + + pixelMetric( PM_IndicatorWidth ) + 4, hoverWidget->height() ); + + hover = rect.contains( cursor ) ? HOVER_CHECK : HOVER_NONE; + return ( HOVER_NONE != hover && !rect.contains( oldCursor ) ) || + ( HOVER_NONE == hover && rect.contains( oldCursor ) ); + } else { +#endif + QScrollBar *sb = dynamic_cast( hoverWidget ); + + if ( sb ) // So, are we over add button, sub button, slider, or none? + { + QRect subline( querySubControlMetrics( CC_ScrollBar, hoverWidget, SC_ScrollBarSubLine ) ), + addline( querySubControlMetrics( CC_ScrollBar, hoverWidget, SC_ScrollBarAddLine ) ), + slider( querySubControlMetrics( CC_ScrollBar, hoverWidget, SC_ScrollBarSlider ) ); + + subline.moveLeft( subline.x() + widgetZero.x() ); + subline.moveTop( subline.y() + widgetZero.y() ); + addline.moveLeft( addline.x() + widgetZero.x() ); + addline.moveTop( addline.y() + widgetZero.y() ); + slider.moveLeft( slider.x() + widgetZero.x() ); + slider.moveTop( slider.y() + widgetZero.y() ); + + if ( slider.contains( cursor ) ) + hover = HOVER_SB_SLIDER; + else if ( subline.contains( cursor ) ) + hover = HOVER_SB_SUB; + else if ( addline.contains( cursor ) ) + hover = HOVER_SB_ADD; + else + hover = HOVER_NONE; + + return ( HOVER_SB_SLIDER == hover && !slider.contains( oldCursor ) ) || + ( HOVER_SB_SLIDER != hover && slider.contains( oldCursor ) ) || + ( HOVER_SB_SUB == hover && !subline.contains( oldCursor ) ) || + ( HOVER_SB_SUB != hover && subline.contains( oldCursor ) ) || + ( HOVER_SB_ADD == hover && !addline.contains( oldCursor ) ) || + ( HOVER_SB_ADD != hover && addline.contains( oldCursor ) ); + } else { +#if KDE_VERSION >= 0x30400 + QToolButton *tb = dynamic_cast( hoverWidget ); + + if ( tb ) { + hover = APP_KICKER == themedApp ? HOVER_KICKER : HOVER_NONE; + return HOVER_KICKER == hover; + } else { +#endif + QHeader *hd = dynamic_cast( hoverWidget ); + + if ( hd ) { + // Hmm... this ones tricky, as there's only 1 widget - but it has different sections... + // and the ones that aren't clickable should not highlight on mouse over! + + QRect rect( + widgetZero.x(), + widgetZero.y(), + hoverWidget->width(), + hoverWidget->height() + ); + + int s = 0; + bool redraw = false; + + hover = rect.contains( cursor ) ? HOVER_HEADER : HOVER_NONE; + hoverSect = QTC_NO_SECT; + + for ( s = 0; s < hd->count() && ( QTC_NO_SECT == hoverSect || !redraw ); ++s ) { + QRect r( hd->sectionRect( s ) ); + + r.moveLeft( r.x() + widgetZero.x() ); + r.moveTop( r.y() + widgetZero.y() ); + + bool hasNew = r.contains( cursor ); + + if ( hasNew ) + hoverSect = s; + + if ( !redraw ) { + bool hasOld = r.contains( oldCursor ); + + if ( ( hasNew && !hasOld ) || ( !hasNew && hasOld ) ) + redraw = true; + } + } + return redraw; + } else + return oldCursor == QPoint( -1, -1 ); +#if KDE_VERSION >= 0x30400 + + } +#endif + + } +#if QT_VERSION >= 0x030200 + + } + } +#endif + + return false; +} + +#define gdouble double + +EDefBtnIndicator qtc_to_ind( const char *str ) { + if ( 0 == memcmp( str, "fontcolour", 10 ) ) + return IND_FONT_COLOUR; + if ( 0 == memcmp( str, "border", 6 ) ) + return IND_BORDER; + if ( 0 == memcmp( str, "none", 4 ) ) + return IND_NONE; + return IND_CORNER; +} + +EGroove qtc_to_groove( const char *str ) { + if ( 0 == memcmp( str, "raised", 6 ) ) + return GROOVE_RAISED; + if ( 0 == memcmp( str, "none", 4 ) ) + return GROOVE_NONE; + return GROOVE_SUNKEN; +} + +ETBarBorder qtc_to_tbar_border( const char *str ) { + if ( 0 == memcmp( str, "dark", 4 ) ) + return TB_DARK; + if ( 0 == memcmp( str, "none", 4 ) ) + return TB_NONE; + if ( 0 == memcmp( str, "light", 5 ) ) + return TB_LIGHT; + return TB_LIGHT; +} + +ELvExpander qtc_to_lv_expander( const char *str ) { + return 0 == memcmp( str, "arrow", 5 ) ? LV_EXP_ARR : LV_EXP_PM; +} + +ELvLines qtc_to_lv_lines( const char *str ) { + if ( 0 == memcmp( str, "none", 4 ) ) + return LV_LINES_NONE; + if ( 0 == memcmp( str, "dotted", 6 ) ) + return LV_LINES_DOTTED; + return LV_LINES_SOLID; +} + +EProfile qtc_to_profile( const char *str ) { + if ( 0 == memcmp( str, "flat", 4 ) ) + return PROFILE_FLAT; + if ( 0 == memcmp( str, "raised", 6 ) ) + return PROFILE_RAISED; + return PROFILE_SUNKEN; +} + +EAppearance qtc_to_appearance( const char *str ) { + if ( 0 == memcmp( str, "flat", 4 ) ) + return APPEARANCE_FLAT; + if ( 0 == memcmp( str, "gradient", 8 ) ) + return APPEARANCE_GRADIENT; + return APPEARANCE_LIGHT_GRADIENT; +} + + +#include "klearlook.moc" diff --git a/src/gui/kdeext/klearlook.h b/src/gui/kdeext/klearlook.h new file mode 100644 index 0000000..dd3ab74 --- /dev/null +++ b/src/gui/kdeext/klearlook.h @@ -0,0 +1,344 @@ +/* $Id: klearlook.h,v 1.8 2006/04/26 18:55:41 jck Exp $ +*/ + +/* + + Klearlook (C) Joerg C. Koenig, 2005 jck@gmx.org + +---- + + Based upon QtCurve (C) Craig Drummond, 2003 Craig.Drummond@lycos.co.uk + Bernhard Rosenkränzer + Preston Brown + Than Ngo + + Released under the GNU General Public License (GPL) v2. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files(the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#ifndef __KLEARLOOK_H__ +#define __KLEARLOOK_H__ + +#define USE_SINGLE_STYLE 1 + +#include +#include +#include +#include + +#define QTC_CHECK_SIZE 13 +#define QTC_RADIO_SIZE 13 +#define QTC_MIN_BTN_SIZE 10 +#define QTC_LV_SIZE(X) LV_EXP_ARR==(X) ? 7 : 5 + +#define NUM_SHADES 7 + +/* Progressbar and selected menu items... */ +#define SHADE_GRADIENT_TOP 0.92 +#define SHADE_GRADIENT_BOTTOM 1.66 +#define SHADE_GRADIENT_LIGHT 1.62 +#define SHADE_GRADIENT_DARK 1.05 + +/* 3d effect - i.e. buttons, etc */ +#define QTC_SHADES \ + double shades[2][11][NUM_SHADES]=\ + { \ + { \ + { 1.01, 1.03, 0.868, 0.780, 0.752, 0.74, 1.8 }, \ + { 1.02, 1.03, 0.870, 0.781, 0.753, 0.70, 1.8 }, \ + { 1.03, 1.03, 0.872, 0.782, 0.754, 0.68, 1.8 }, \ + { 1.04, 1.04, 0.875, 0.783, 0.755, 0.64, 1.8 }, \ + { 1.05, 1.04, 0.878, 0.784, 0.756, 0.62, 1.8 }, \ + { 1.06, 1.05, 0.881, 0.785, 0.758, 0.58, 1.8 }, \ + { 1.07, 1.05, 0.884, 0.786, 0.760, 0.54, 1.8 }, \ + { 1.09, 1.06, 0.887, 0.787, 0.762, 0.50, 1.8 }, /* default */ \ + { 1.11, 1.06, 0.890, 0.788, 0.764, 0.45, 1.8 }, \ + { 1.13, 1.07, 0.893, 0.789, 0.766, 0.40, 1.8 }, \ + { 1.15, 1.07, 0.896, 0.790, 0.768, 0.35, 1.8 } \ + }, \ + { \ + { 1.08, 1.03, 0.868, 0.780, 0.800, 0.74, 1.8 }, \ + { 1.09, 1.03, 0.870, 0.781, 0.810, 0.72, 1.8 }, \ + { 1.10, 1.03, 0.872, 0.782, 0.820, 0.70, 1.8 }, \ + { 1.11, 1.04, 0.875, 0.783, 0.840, 0.68, 1.8 }, \ + { 1.12, 1.04, 0.878, 0.784, 0.860, 0.66, 1.8 }, \ + { 1.13, 1.05, 0.881, 0.785, 0.880, 0.64, 1.8 }, \ + { 1.14, 1.05, 0.884, 0.786, 0.900, 0.62, 1.8 }, \ + { 1.15, 1.06, 0.887, 0.787, 0.920, 0.60, 1.8 }, /* default */ \ + { 1.17, 1.06, 0.890, 0.788, 0.764, 0.45, 1.8 }, \ + { 1.19, 1.07, 0.893, 0.789, 0.766, 0.40, 1.8 }, \ + { 1.21, 1.07, 0.896, 0.790, 0.768, 0.35, 1.8 } \ + } \ + }; + +#define QTC_SHADE(a, c, s) \ + (c>10 || c<0 || s>=NUM_SHADES || s<0 ? 1.0 : shades[APPEARANCE_LIGHT_GRADIENT!=(a) ? 0 : 1][c][s]) + +/* Shades used when gradient effect is selected */ +#define SHADE_BEVEL_GRAD_LIGHT(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 1.0 : 1.0 ) +#define SHADE_BEVEL_GRAD_DARK(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 0.75 : 0.75 ) +#define SHADE_BEVEL_GRAD_SEL_LIGHT(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 1.1 : 1.05 ) +#define SHADE_BEVEL_GRAD_SEL_DARK(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 0.95 : 0.95 ) + +#define SHADE_BEVEL_BUTTON_GRAD_LIGHT(A) 1.00 +#define SHADE_BEVEL_BUTTON_GRAD_DARK(A) 0.89 + +#define SHADE_BEVEL_MENU_GRAD_LIGHT(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 0.97 : 0.97 ) +#define SHADE_BEVEL_MENU_GRAD_DARK(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 0.93 : 0.93 ) + +#define SHADE_TAB_LIGHT(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 1.1 : 1.0 ) +#define SHADE_TAB_DARK(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 1.0 : 1.0 ) +#define SHADE_TAB_SEL_LIGHT(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 1.0 : 1.0 ) +#define SHADE_TAB_SEL_DARK(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 1.0 : 1.0 ) + +#define SHADE_BOTTOM_TAB_LIGHT(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 1.0 : 1.0 ) +#define SHADE_BOTTOM_TAB_DARK(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 0.95 : 1.0 ) +#define SHADE_BOTTOM_TAB_SEL_LIGHT(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 1.0 : 1.0 ) +#define SHADE_BOTTOM_TAB_SEL_DARK(A) (APPEARANCE_LIGHT_GRADIENT!=(A) ? 1.0 : 1.0 ) + +typedef enum +{ + IND_BORDER, + IND_CORNER, + IND_FONT_COLOUR, + IND_NONE +} EDefBtnIndicator; + +typedef enum +{ + GROOVE_RAISED, + GROOVE_SUNKEN, + GROOVE_NONE +} EGroove; + +typedef enum +{ + TB_NONE, + TB_LIGHT, + TB_DARK +} ETBarBorder; + +typedef enum +{ + LV_EXP_PM, + LV_EXP_ARR +} ELvExpander; + +typedef enum +{ + LV_LINES_NONE, + LV_LINES_DOTTED, + LV_LINES_SOLID +} ELvLines; + +typedef enum +{ + PROFILE_FLAT, + PROFILE_RAISED, + PROFILE_SUNKEN +} EProfile; + +typedef enum +{ + APPEARANCE_FLAT, + APPEARANCE_GRADIENT, + APPEARANCE_LIGHT_GRADIENT +} EAppearance; + +#define DEF_IND_STR "corner" +#define DEF_HANDLE_STR "sunken" +#define DEF_TB_BORDER "light" +#define DEF_APPEARANCE_STR "lightgradient" +#define DEF_PROFILE_STR "raised" + +#ifdef __cplusplus +extern "C" { +#endif + + extern EDefBtnIndicator qtc_to_ind( const char * str ); + extern EGroove qtc_to_groove( const char * str ); + extern ETBarBorder qtc_to_tbar_border( const char * str ); + extern ELvExpander qtc_to_lv_expander( const char * str ); + extern ELvLines qtc_to_lv_lines( const char * str ); + extern EProfile qtc_to_profile( const char * str ); + extern EAppearance qtc_to_appearance( const char * str ); +#ifdef __cplusplus + +} +#endif + +class KlearlookStyle : public KStyle { + Q_OBJECT + + public: + + enum EApp + { + APP_KICKER, + APP_KORN, + APP_OPENOFFICE, + APP_OTHER + }; + + enum + { + GRADIENT_BASE, + GRADIENT_LIGHT, + GRADIENT_DARK, + GRADIENT_TOP, + GRADIENT_BOTTOM, + GRADIENT_NUM_COLS + }; + + enum ERound + { + ROUNDED_NONE, + ROUNDED_TOP, + ROUNDED_BOTTOM, + ROUNDED_LEFT, + ROUNDED_RIGHT, + ROUNDED_ALL + }; + + enum EHover + { + HOVER_NONE, + HOVER_CHECK, + HOVER_RADIO, + HOVER_SB_ADD, + HOVER_SB_SUB, + HOVER_SB_SLIDER, + HOVER_HEADER, + HOVER_KICKER + }; + +#ifdef USE_SINGLE_STYLE + + KlearlookStyle(); +#else + + KlearlookStyle( bool gpm, bool bb = false, bool bf = false, bool round = false, + EGroove st = GROOVE_RAISED, h = GROOVE_RAISED, + bool ge = false, bool va = true, bool bdt = false, bool crlh = true, EDefBtnIndicator dbi = IND_BORDER, + int tbb = 5, ELvExpander lve = LV_EXP_PM, lvl = LV_LINES_DOTTED, bool lvd = true, bool icon = true, + int popupmenuHighlightLevel = 130 ); +#endif + + virtual ~KlearlookStyle() {} + + void polish( QApplication *app ); + void polish( QPalette &pal ); + void polish( QWidget *widget ); + void unPolish( QWidget *widget ); + void drawLightBevel( QPainter *p, const QRect &r, const QColorGroup &cg, QStyle::SFlags flags, bool useGrad, ERound round, + const QColor &fill, const QColor *custom = NULL, bool light = false ) const; + void drawLightBevelButton( QPainter *p, const QRect &r, const QColorGroup &cg, QStyle::SFlags flags, bool useGrad, ERound round, + const QColor &fill, const QColor *custom = NULL, bool light = false ) const; + void drawArrow( QPainter *p, const QRect &r, const QColorGroup &cg, QStyle::SFlags flags, QStyle::PrimitiveElement pe, + bool small = false, bool checkActive = false ) const; + void drawPrimitive( PrimitiveElement, QPainter *, const QRect &, const QColorGroup &, SFlags = Style_Default, + const QStyleOption & = QStyleOption::Default ) const; + void drawPrimitiveMenu( PrimitiveElement, QPainter *, const QRect &, const QColorGroup &, SFlags = Style_Default, + const QStyleOption & = QStyleOption::Default ) const; + void drawKStylePrimitive( KStylePrimitive kpe, QPainter* p, const QWidget* widget, const QRect &r, + const QColorGroup &cg, SFlags flags, const QStyleOption &opt ) const; + void drawControl( ControlElement, QPainter *, const QWidget *, const QRect &, const QColorGroup &, + SFlags = Style_Default, const QStyleOption & = QStyleOption::Default ) const; + void drawControlMask( ControlElement, QPainter *, const QWidget *, const QRect &, + const QStyleOption & = QStyleOption::Default ) const; + void drawComplexControlMask( ComplexControl control, QPainter *p, const QWidget *widget, const QRect &r, + const QStyleOption &data ) const; + QRect subRect( SubRect, const QWidget * ) const; + void drawComplexControl( ComplexControl, QPainter *, const QWidget *, const QRect &, const QColorGroup &, + SFlags = Style_Default, SCFlags = SC_All, SCFlags = SC_None, + const QStyleOption & = QStyleOption::Default ) const; + QRect querySubControlMetrics( ComplexControl, const QWidget *, SubControl, + const QStyleOption & = QStyleOption::Default ) const; + int pixelMetric( PixelMetric, const QWidget *widget = 0 ) const; + int kPixelMetric( KStylePixelMetric kpm, const QWidget *widget ) const; + QSize sizeFromContents( ContentsType, const QWidget *, const QSize &, + const QStyleOption & = QStyleOption::Default ) const; + int styleHint( StyleHint, const QWidget *widget = 0, const QStyleOption & = QStyleOption::Default, + QStyleHintReturn *returnData = 0 ) const; + + protected: + + bool eventFilter( QObject *object, QEvent *event ); + void drawPBarOrMenu( QPainter *p, QRect const &r, bool horiz, const QColorGroup &cg, bool menu = false ) const; + void drawPBarOrMenu2( QPainter *p, QRect const &r, bool horiz, const QColorGroup &cg, bool menu = false ) const; + void drawGradientWithBorder( QPainter *p, QRect const &r, bool horiz = true ) const; + void drawBevelGradient( const QColor &base, bool increase, int border, QPainter *p, QRect const &r, bool horiz, + double shadeTop, double shadeBot ) const; + void drawGradient( const QColor &top, const QColor &bot, bool increase, int border, QPainter *p, QRect const &r, + bool horiz = true ) const; + void drawSliderHandle( QPainter *p, const QRect &r, const QColorGroup &cg, QStyle::SFlags flags ) const; + void drawPopupRect( QPainter *p, const QRect &r, const QColorGroup &cg) const ; + + void drawSliderGroove( QPainter *p, const QRect &r, QStyle::SFlags flags, const QWidget *widget ) const; + + + private: + + void shadeColors( const QColor &base, QColor *vals ) const; + const QColor * buttonColors( const QColorGroup &cg ) const; + const QColor * backgroundColors( const QColorGroup &cg ) const; + bool redrawHoverWidget(); + + private: + + QColor menuPbar[ GRADIENT_NUM_COLS < NUM_SHADES + 1 ? NUM_SHADES + 1 : GRADIENT_NUM_COLS ], + gray[ NUM_SHADES + 1 ], + button[ NUM_SHADES + 1 ]; // Last color = base color, for comparisons! + mutable QColor buttonColoured[ NUM_SHADES + 1 ]; + mutable QColor backgroundColoured[ NUM_SHADES + 1 ]; + EApp themedApp; + int popupmenuHighlightLevel; + bool borderButton, + menuIcons, + darkMenubar, + borderFrame, + rounded, + vArrow, + boldDefText, + crLabelHighlight, + lvDark, + borderSplitter; + EDefBtnIndicator defBtnIndicator; + EGroove sliderThumbs, + handles; + ETBarBorder toolbarBorders; + ELvExpander lvExpander; + ELvLines lvLines; + EProfile pmProfile; + EAppearance appearance; +#if KDE_VERSION >= 0x30200 + + bool isTransKicker; +#endif + + EHover hover; + int contrast; + QPoint oldCursor; + mutable bool formMode; + QWidget *hoverWidget; + int hoverSect; +}; + +#endif diff --git a/src/gui/rulers/ChordNameRuler.cpp b/src/gui/rulers/ChordNameRuler.cpp new file mode 100644 index 0000000..2fc98f2 --- /dev/null +++ b/src/gui/rulers/ChordNameRuler.cpp @@ -0,0 +1,523 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ChordNameRuler.h" +#include "misc/Debug.h" + +#include +#include "misc/Strings.h" +#include "base/AnalysisTypes.h" +#include "base/Composition.h" +#include "base/CompositionTimeSliceAdapter.h" +#include "base/Instrument.h" +#include "base/NotationTypes.h" +#include "base/Profiler.h" +#include "base/PropertyName.h" +#include "base/NotationQuantizer.h" +#include "base/RefreshStatus.h" +#include "base/RulerScale.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "base/Studio.h" +#include "base/Track.h" +#include "document/RosegardenGUIDoc.h" +#include "document/MultiViewCommandHistory.h" +#include "gui/general/GUIPalette.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +ChordNameRuler::ChordNameRuler(RulerScale *rulerScale, + RosegardenGUIDoc *doc, + double xorigin, + int height, + QWidget *parent, + const char *name) : + QWidget(parent, name), + m_xorigin(xorigin), + m_height(height), + m_currentXOffset(0), + m_width( -1), + m_ready(false), + m_rulerScale(rulerScale), + m_composition(&doc->getComposition()), + m_regetSegmentsOnChange(true), + m_currentSegment(0), + m_studio(0), + m_chordSegment(0), + m_fontMetrics(m_boldFont), + TEXT_FORMAL_X("TextFormalX"), + TEXT_ACTUAL_X("TextActualX") +{ + m_font.setPointSize(11); + m_font.setPixelSize(12); + m_boldFont.setPointSize(11); + m_boldFont.setPixelSize(12); + m_boldFont.setBold(true); + m_fontMetrics = QFontMetrics(m_boldFont); + setBackgroundColor(GUIPalette::getColour(GUIPalette::ChordNameRulerBackground)); + + m_compositionRefreshStatusId = m_composition->getNewRefreshStatusId(); + + QObject::connect(doc->getCommandHistory(), SIGNAL(commandExecuted()), + this, SLOT(update())); + + QToolTip::add + (this, i18n("Chord name ruler.\nTurn it on and off from the Settings->Rulers menu.")); + +} + +ChordNameRuler::ChordNameRuler(RulerScale *rulerScale, + RosegardenGUIDoc *doc, + std::vector &segments, + double xorigin, + int height, + QWidget *parent, + const char *name) : + QWidget(parent, name), + m_xorigin(xorigin), + m_height(height), + m_currentXOffset(0), + m_width( -1), + m_ready(false), + m_rulerScale(rulerScale), + m_composition(&doc->getComposition()), + m_regetSegmentsOnChange(false), + m_currentSegment(0), + m_studio(0), + m_chordSegment(0), + m_fontMetrics(m_boldFont), + TEXT_FORMAL_X("TextFormalX"), + TEXT_ACTUAL_X("TextActualX") +{ + m_font.setPointSize(11); + m_font.setPixelSize(12); + m_boldFont.setPointSize(11); + m_boldFont.setPixelSize(12); + m_boldFont.setBold(true); + m_fontMetrics = QFontMetrics(m_boldFont); + setBackgroundColor(GUIPalette::getColour(GUIPalette::ChordNameRulerBackground)); + + m_compositionRefreshStatusId = m_composition->getNewRefreshStatusId(); + + QObject::connect(doc->getCommandHistory(), SIGNAL(commandExecuted()), + this, SLOT(update())); + + for (std::vector::iterator i = segments.begin(); + i != segments.end(); ++i) { + m_segments.insert(SegmentRefreshMap::value_type + (*i, (*i)->getNewRefreshStatusId())); + } +} + +ChordNameRuler::~ChordNameRuler() +{ + delete m_chordSegment; +} + +void +ChordNameRuler::setReady() +{ + m_ready = true; + update(); +} + +void +ChordNameRuler::setCurrentSegment(Segment *segment) +{ + m_currentSegment = segment; +} + +void +ChordNameRuler::setStudio(Studio *studio) +{ + m_studio = studio; +} + +void +ChordNameRuler::slotScrollHoriz(int x) +{ + int w = width(), h = height(); + int dx = x - ( -m_currentXOffset); + m_currentXOffset = -x; + + if (dx == 0) + return ; + + if (dx > w*7 / 8 || dx < -w*7 / 8) { + update(); + return ; + } + + if (dx > 0) { // moving right, so the existing stuff moves left + bitBlt(this, 0, 0, this, dx, 0, w - dx, h); + repaint(w - dx, 0, dx, h); + } else { // moving left, so the existing stuff moves right + bitBlt(this, -dx, 0, this, 0, 0, w + dx, h); + repaint(0, 0, -dx, h); + } +} + +QSize +ChordNameRuler::sizeHint() const +{ + double width = + m_rulerScale->getBarPosition(m_rulerScale->getLastVisibleBar()) + + m_rulerScale->getBarWidth(m_rulerScale->getLastVisibleBar()); + + NOTATION_DEBUG << "Returning chord-label ruler width as " << width << endl; + + QSize res(std::max(int(width), m_width), m_height); + + return res; +} + +QSize +ChordNameRuler::minimumSizeHint() const +{ + double firstBarWidth = m_rulerScale->getBarWidth(0); + QSize res = QSize(int(firstBarWidth), m_height); + return res; +} + +void +ChordNameRuler::recalculate(timeT from, timeT to) +{ + if (!m_ready) + return ; + + Profiler profiler("ChordNameRuler::recalculate"); + NOTATION_DEBUG << "ChordNameRuler[" << this << "]::recalculate" << endl; + + bool regetSegments = false; + + enum RecalcLevel { RecalcNone, RecalcVisible, RecalcWhole }; + RecalcLevel level = RecalcNone; + + if (m_segments.empty()) { + + regetSegments = true; + + } else if (m_regetSegmentsOnChange) { + + RefreshStatus &rs = + m_composition->getRefreshStatus(m_compositionRefreshStatusId); + + if (rs.needsRefresh()) { + rs.setNeedsRefresh(false); + regetSegments = true; + } + } + + if (regetSegments) { + + SegmentSelection ss; + + for (Composition::iterator ci = m_composition->begin(); + ci != m_composition->end(); ++ci) { + + if (m_studio) { + + TrackId ti = (*ci)->getTrack(); + + Instrument *instr = m_studio->getInstrumentById + (m_composition->getTrackById(ti)->getInstrument()); + + if (instr && + instr->getInstrumentType() == Instrument::Midi && + instr->isPercussion()) { + continue; + } + } + + ss.insert(*ci); + } + + std::vector eraseThese; + + for (SegmentRefreshMap::iterator si = m_segments.begin(); + si != m_segments.end(); ++si) { + if (ss.find(si->first) == ss.end()) { + eraseThese.push_back(si); + level = RecalcWhole; + NOTATION_DEBUG << "Segment deleted, updating (now have " << m_segments.size() << " segments)" << endl; + } + } + + for (std::vector::iterator ei = eraseThese.begin(); + ei != eraseThese.end(); ++ei) { + m_segments.erase(*ei); + } + + + for (SegmentSelection::iterator si = ss.begin(); + si != ss.end(); ++si) { + + if (m_segments.find(*si) == m_segments.end()) { + m_segments.insert(SegmentRefreshMap::value_type + (*si, (*si)->getNewRefreshStatusId())); + level = RecalcWhole; + NOTATION_DEBUG << "Segment created, adding (now have " << m_segments.size() << " segments)" << endl; + } + } + + if (m_currentSegment && + ss.find(m_currentSegment) == ss.end()) { + m_currentSegment = 0; + level = RecalcWhole; + } + } + + if (!m_chordSegment) + m_chordSegment = new Segment(); + if (m_segments.empty()) + return ; + + SegmentRefreshStatus overallStatus; + overallStatus.setNeedsRefresh(false); + + for (SegmentRefreshMap::iterator i = m_segments.begin(); + i != m_segments.end(); ++i) { + SegmentRefreshStatus &status = + i->first->getRefreshStatus(i->second); + if (status.needsRefresh()) { + overallStatus.push(status.from(), status.to()); + } + } + + // We now have the overall area affected by these changes, across + // all segments. If it's entirely within our displayed area, just + // recalculate the displayed area; if it overlaps, calculate the + // union of the two areas; if it's entirely without, calculate + // nothing. + + if (level == RecalcNone) { + if (from == to) { + NOTATION_DEBUG << "ChordNameRuler::recalculate: from==to, recalculating all" << endl; + level = RecalcWhole; + } else if (overallStatus.from() == overallStatus.to()) { + NOTATION_DEBUG << "ChordNameRuler::recalculate: overallStatus.from==overallStatus.to, ignoring" << endl; + level = RecalcNone; + } else if (overallStatus.from() >= from && overallStatus.to() <= to) { + NOTATION_DEBUG << "ChordNameRuler::recalculate: change is " << overallStatus.from() << "->" << overallStatus.to() << ", I show " << from << "->" << to << ", recalculating visible area" << endl; + level = RecalcVisible; + } else if (overallStatus.from() >= to || overallStatus.to() <= from) { + NOTATION_DEBUG << "ChordNameRuler::recalculate: change is " << overallStatus.from() << "->" << overallStatus.to() << ", I show " << from << "->" << to << ", ignoring" << endl; + level = RecalcNone; + } else { + NOTATION_DEBUG << "ChordNameRuler::recalculate: change is " << overallStatus.from() << "->" << overallStatus.to() << ", I show " << from << "->" << to << ", recalculating whole" << endl; + level = RecalcWhole; + } + } + + if (level == RecalcNone) + return ; + + for (SegmentRefreshMap::iterator i = m_segments.begin(); + i != m_segments.end(); ++i) { + i->first->getRefreshStatus(i->second).setNeedsRefresh(false); + } + + if (!m_currentSegment) { //!!! arbitrary, must do better + //!!! need a segment starting at zero or so with a clef and key in it! + m_currentSegment = m_segments.begin()->first; + } + + /*!!! + + for (Composition::iterator ci = m_composition->begin(); + ci != m_composition->end(); ++ci) { + + if ((*ci)->getEndMarkerTime() >= from && + ((*ci)->getStartTime() <= from || + (clefKeySegment && + (*ci)->getStartTime() < clefKeySegment->getStartTime()))) { + + clefKeySegment = *ci; + } + } + + if (!clefKeySegment) return; + } + */ + + if (level == RecalcWhole) { + + m_chordSegment->clear(); + + timeT clefKeyTime = m_currentSegment->getStartTime(); + //(from < m_currentSegment->getStartTime() ? + // m_currentSegment->getStartTime() : from); + + Clef clef = m_currentSegment->getClefAtTime(clefKeyTime); + m_chordSegment->insert(clef.getAsEvent( -1)); + + ::Rosegarden::Key key = m_currentSegment->getKeyAtTime(clefKeyTime); + m_chordSegment->insert(key.getAsEvent( -1)); + + from = 0; + to = 0; + + } else { + Segment::iterator i = m_chordSegment->findTime(from); + Segment::iterator j = m_chordSegment->findTime(to); + m_chordSegment->erase(i, j); + } + + SegmentSelection selection; + for (SegmentRefreshMap::iterator si = m_segments.begin(); si != m_segments.end(); + ++si) { + selection.insert(si->first); + } + + CompositionTimeSliceAdapter adapter(m_composition, &selection, from, to); + AnalysisHelper helper; + helper.labelChords(adapter, *m_chordSegment, m_composition->getNotationQuantizer()); +} + +void +ChordNameRuler::paintEvent(QPaintEvent* e) +{ + if (!m_composition || !m_ready) + return ; + + NOTATION_DEBUG << "*** Chord Name Ruler: paintEvent" << endl; + + Profiler profiler1("ChordNameRuler::paintEvent (whole)"); + + QPainter paint(this); + paint.setPen(GUIPalette::getColour(GUIPalette::ChordNameRulerForeground)); + + paint.setClipRegion(e->region()); + paint.setClipRect(e->rect().normalize()); + + QRect clipRect = paint.clipRegion().boundingRect(); + + timeT from = m_rulerScale->getTimeForX + (clipRect.x() - m_currentXOffset - m_xorigin - 50); + timeT to = m_rulerScale->getTimeForX + (clipRect.x() + clipRect.width() - m_currentXOffset - m_xorigin + 50); + + recalculate(from, to); + + if (!m_chordSegment) + return ; + + Profiler profiler2("ChordNameRuler::paintEvent (paint)"); + + QRect boundsForHeight = m_fontMetrics.boundingRect("^j|lM"); + int fontHeight = boundsForHeight.height(); + int textY = (height() - 6) / 2 + fontHeight / 2; + + double prevX = 0; + timeT keyAt = from - 1; + std::string keyText; + + NOTATION_DEBUG << "*** Chord Name Ruler: paint " << from << " -> " << to << endl; + + for (Segment::iterator i = m_chordSegment->findTime(from); + i != m_chordSegment->findTime(to); ++i) { + + NOTATION_DEBUG << "type " << (*i)->getType() << " at " << (*i)->getAbsoluteTime() + << endl; + + if (!(*i)->isa(Text::EventType) || + !(*i)->has(Text::TextPropertyName) || + !(*i)->has(Text::TextTypePropertyName)) + continue; + + std::string text((*i)->get + (Text::TextPropertyName)); + + if ((*i)->get + (Text::TextTypePropertyName) == Text::KeyName) { + timeT myTime = (*i)->getAbsoluteTime(); + if (myTime == keyAt && text == keyText) + continue; + else { + keyAt = myTime; + keyText = text; + } + } + + double x = m_rulerScale->getXForTime((*i)->getAbsoluteTime()); + (*i)->set + (TEXT_FORMAL_X, (long)x); + + QRect textBounds = m_fontMetrics.boundingRect(strtoqstr(text)); + int width = textBounds.width(); + + x -= width / 2; + if (prevX >= x - 3) + x = prevX + 3; + (*i)->set + (TEXT_ACTUAL_X, long(x)); + prevX = x + width; + } + + for (Segment::iterator i = m_chordSegment->findTime(from); + i != m_chordSegment->findTime(to); ++i) { + + if (!(*i)->isa(Text::EventType)) + continue; + std::string text((*i)->get + (Text::TextPropertyName)); + std::string type((*i)->get + (Text::TextTypePropertyName)); + + if (!(*i)->has(TEXT_FORMAL_X)) + continue; + + long formalX = (*i)->get + (TEXT_FORMAL_X); + long actualX = (*i)->get + (TEXT_ACTUAL_X); + + formalX += m_currentXOffset + long(m_xorigin); + actualX += m_currentXOffset + long(m_xorigin); + + paint.drawLine(formalX, height() - 4, formalX, height()); + + if (type == Text::KeyName) { + paint.setFont(m_boldFont); + } else { + paint.setFont(m_font); + } + + paint.drawText(actualX, textY, strtoqstr(text)); + } +} + +} +#include "ChordNameRuler.moc" diff --git a/src/gui/rulers/ChordNameRuler.h b/src/gui/rulers/ChordNameRuler.h new file mode 100644 index 0000000..70cdc12 --- /dev/null +++ b/src/gui/rulers/ChordNameRuler.h @@ -0,0 +1,146 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CHORDNAMERULER_H_ +#define _RG_CHORDNAMERULER_H_ + +#include "base/PropertyName.h" +#include +#include +#include +#include +#include +#include +#include "base/Event.h" + + +class QPaintEvent; + + +namespace Rosegarden +{ + +class Studio; +class Segment; +class RulerScale; +class RosegardenGUIDoc; +class Composition; + + +/** + * ChordNameRuler is a widget that shows a strip of text strings + * describing the chords in a composition. + */ + +class ChordNameRuler : public QWidget +{ + Q_OBJECT + +public: + /** + * Construct a ChordNameRuler that displays the chords in the + * given Composition at positions calculated by the given + * RulerScale. Be aware that it will not be refreshed until + * setReady is called (because the first refresh is expensive). + */ + ChordNameRuler(RulerScale *rulerScale, + RosegardenGUIDoc *doc, + double xorigin = 0.0, + int height = 0, + QWidget* parent = 0, + const char *name = 0); + + /** + * Construct a ChordNameRuler that displays the chords in the + * given Segments at positions calculated by the given + * RulerScale. Be aware that it will not be refreshed until + * setReady is called (because the first refresh is expensive). + */ + ChordNameRuler(RulerScale *rulerScale, + RosegardenGUIDoc *doc, + std::vector &segments, + double xorigin = 0.0, + int height = 0, + QWidget* parent = 0, + const char *name = 0); + + ~ChordNameRuler(); + + /// Indicate that the chord-name ruler should make itself ready and refresh + void setReady(); + + // may have one of these; can be changed at any time (to any in given composition): + void setCurrentSegment(Segment *segment); + + // may have one of these (to avoid using percussion tracks in chords): + void setStudio(Studio *studio); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + void setMinimumWidth(int width) { m_width = width; } + +public slots: + void slotScrollHoriz(int x); + +protected: + virtual void paintEvent(QPaintEvent *); + +private: + void recalculate(timeT from = 0, + timeT to = 0); + + double m_xorigin; + int m_height; + int m_currentXOffset; + int m_width; + bool m_ready; + + RulerScale *m_rulerScale; + + Composition *m_composition; + unsigned int m_compositionRefreshStatusId; + + typedef std::map SegmentRefreshMap; + SegmentRefreshMap m_segments; // map to refresh status id + bool m_regetSegmentsOnChange; + + Segment *m_currentSegment; + Studio *m_studio; + + Segment *m_chordSegment; + + QFont m_font; + QFont m_boldFont; + QFontMetrics m_fontMetrics; + + const PropertyName TEXT_FORMAL_X; + const PropertyName TEXT_ACTUAL_X; +}; + + +} + +#endif diff --git a/src/gui/rulers/ControlChangeCommand.cpp b/src/gui/rulers/ControlChangeCommand.cpp new file mode 100644 index 0000000..f6f5d0e --- /dev/null +++ b/src/gui/rulers/ControlChangeCommand.cpp @@ -0,0 +1,50 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ControlChangeCommand.h" +#include "ControlItem.h" +#include "misc/Debug.h" +#include + +namespace Rosegarden { + +ControlChangeCommand::ControlChangeCommand(QCanvasItemList selectedItems, + Segment &segment, + Rosegarden::timeT start, Rosegarden::timeT end) + : BasicCommand(i18n("Control Change"), segment, start, end, true), + m_selectedItems(selectedItems) +{ + RG_DEBUG << "ControlChangeCommand : from " << start << " to " << end << endl; +} + + +void ControlChangeCommand::modifySegment() +{ + for (QCanvasItemList::Iterator it=m_selectedItems.begin(); it!=m_selectedItems.end(); ++it) { + if (ControlItem *item = dynamic_cast(*it)) + item->updateValue(); + } +} + +} diff --git a/src/gui/rulers/ControlChangeCommand.h b/src/gui/rulers/ControlChangeCommand.h new file mode 100644 index 0000000..cc334a4 --- /dev/null +++ b/src/gui/rulers/ControlChangeCommand.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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_CONTROLCHANGECOMMAND_H_ +#define _RG_CONTROLCHANGECOMMAND_H_ + +#include "document/BasicCommand.h" +#include + +namespace Rosegarden { + +/** + * Command defining a change (property change or similar) from the control ruler + */ +class ControlChangeCommand : public BasicCommand +{ +public: + + ControlChangeCommand(QCanvasItemList selectedItems, + Segment &segment, + Rosegarden::timeT start, Rosegarden::timeT end); + virtual ~ControlChangeCommand() {;} + + +protected: + + virtual void modifySegment(); + + QCanvasItemList m_selectedItems; +}; + +} + +#endif /*CONTROLCHANGECOMMAND_H_*/ diff --git a/src/gui/rulers/ControlItem.cpp b/src/gui/rulers/ControlItem.cpp new file mode 100644 index 0000000..623fbf3 --- /dev/null +++ b/src/gui/rulers/ControlItem.cpp @@ -0,0 +1,195 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ControlItem.h" +#include "ControlRuler.h" +#include "ElementAdapter.h" +#include "misc/Debug.h" + +namespace Rosegarden { + +const unsigned int ControlItem::BorderThickness = 1; +const unsigned int ControlItem::DefaultWidth = 20; +static int _canvasItemZ = 30; + +ControlItem::ControlItem(ControlRuler* ruler, ElementAdapter* elementAdapter, + int xx, int width) + : QCanvasRectangle(ruler->canvas()), + m_value(0), + m_controlRuler(ruler), + m_elementAdapter(elementAdapter) +{ + if (width < DefaultWidth/4) { + width = DefaultWidth/4; // avoid invisible zero-duration items + } + setWidth(width); + setPen(QPen(Qt::black, BorderThickness)); + setBrush(Qt::blue); + + setX(xx); + setY(canvas()->height()); + setZ(_canvasItemZ++); // we should make this work against controlruler + + updateFromValue(); + setEnabled(false); + //RG_DEBUG << "ControlItem x = " << x() << " - y = " << y() << " - width = " << width << endl; + show(); +} + +ControlItem::~ControlItem() +{ + delete m_elementAdapter; +} + + +void ControlItem::setValue(long v) +{ +// RG_DEBUG << "ControlItem::setValue(" << v << ") x = " << x() << endl; + + m_value = v; +} + +void ControlItem::updateValue() +{ + m_elementAdapter->setValue(m_value); +} + +void ControlItem::updateFromValue() +{ +// RG_DEBUG << "ControlItem::updateFromValue() : " << this << endl; + + if (m_elementAdapter->getValue(m_value)) { +// RG_DEBUG << "ControlItem::updateFromValue() : value = " << m_value << endl; + setHeight(m_controlRuler->valueToHeight(m_value)); + } +} + +typedef std::pair ItemPair; +struct ItemCmp +{ + bool operator()(const ItemPair &i1, const ItemPair &i2) + { + return i1.first > i2.first; + } +}; + +void ControlItem::draw(QPainter &painter) +{ + if (!isEnabled()) + updateFromValue(); + + setBrush(m_controlRuler->valueToColour(m_controlRuler->getMaxItemValue(), m_value)); + + QCanvasRectangle::draw(painter); + + + /* + + // Attempt to get overlapping rectangles ordered automatically - + // probably best not to do this here - rwb + + // calculate collisions and assign Z values accordingly + // + QCanvasItemList l = collisions(false); + + std::vector sortList; + + for (QCanvasItemList::Iterator it=l.begin(); it!=l.end(); ++it) { + + // skip all but rectangles + if ((*it)->rtti() != QCanvasItem::Rtti_Rectangle) continue; + + if (QCanvasRectangle *rect = dynamic_cast(*it)) + sortList.push_back(ItemPair(rect->height(), *it)); + } + + // sort the list by height + std::sort(sortList.begin(), sortList.end(), ItemCmp()); + + int z = 20; + + for (std::vector::iterator it = sortList.begin(); + it != sortList.end(); ++it) + { + RG_DEBUG << "HEIGHT = " << (*it).first << endl; + (*it).second->setZ(z++); + } + + RG_DEBUG << endl << endl; + + canvas()->update(); + + */ + +} + +void ControlItem::handleMouseButtonPress(QMouseEvent*) +{ +// RG_DEBUG << "ControlItem::handleMouseButtonPress()" << this << endl; + setEnabled(true); +} + +void ControlItem::handleMouseButtonRelease(QMouseEvent*) +{ +// RG_DEBUG << "ControlItem::handleMouseButtonRelease()" << this << endl; + setEnabled(false); +} + +void ControlItem::handleMouseMove(QMouseEvent*, int /*deltaX*/, int deltaY) +{ +// RG_DEBUG << "ControlItem::handleMouseMove()" << this << endl; + + // height is always negative + // + + m_controlRuler->applyTool(x(), deltaY); + + int absNewHeight = -(getHeight() + deltaY); + + // Make sure height is within bounds + if (absNewHeight > ControlRuler::MaxItemHeight) + absNewHeight = ControlRuler::MaxItemHeight; + else if (absNewHeight < ControlRuler::MinItemHeight) + absNewHeight = ControlRuler::MinItemHeight; + + setHeight(-absNewHeight); + setValue(m_controlRuler->heightToValue(getHeight())); +} + +void ControlItem::handleMouseWheel(QWheelEvent *) +{ +// RG_DEBUG << "ControlItem::handleMouseWheel - got wheel event" << endl; +} + +void ControlItem::setSelected(bool s) +{ + QCanvasItem::setSelected(s); + + if (s) setPen(QPen(Qt::red, BorderThickness)); + else setPen(QPen(Qt::black, BorderThickness)); + + canvas()->update(); +} + +} diff --git a/src/gui/rulers/ControlItem.h b/src/gui/rulers/ControlItem.h new file mode 100644 index 0000000..44f9e22 --- /dev/null +++ b/src/gui/rulers/ControlItem.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 , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 + +namespace Rosegarden { + +class ControlRuler; +class ElementAdapter; + +class ControlItem : public QCanvasRectangle +{ +public: + ControlItem(ControlRuler* controlRuler, + ElementAdapter* adapter, + int x, int width = DefaultWidth); + + ~ControlItem(); + + virtual void setValue(long); + int getValue() const { return m_value; } + + void setWidth(int w) { setSize(w, height()); } + void setHeight(int h) { setSize(width(), h); } + int getHeight() { return size().height(); } + + virtual void draw(QPainter &painter); + + virtual void handleMouseButtonPress(QMouseEvent *e); + virtual void handleMouseButtonRelease(QMouseEvent *e); + virtual void handleMouseMove(QMouseEvent *e, int deltaX, int deltaY); + virtual void handleMouseWheel(QWheelEvent *e); + + virtual void setSelected(bool yes); + + /// recompute height according to represented value prior to a canvas repaint + virtual void updateFromValue(); + + /// update value according to height after a user edit + virtual void updateValue(); + + ElementAdapter* getElementAdapter() { return m_elementAdapter; } + +protected: + + //--------------- Data members --------------------------------- + + long m_value; + bool m_handlingMouseMove; + + ControlRuler* m_controlRuler; + ElementAdapter* m_elementAdapter; + + static const unsigned int BorderThickness; + static const unsigned int DefaultWidth; +}; + +} diff --git a/src/gui/rulers/ControlRuler.cpp b/src/gui/rulers/ControlRuler.cpp new file mode 100644 index 0000000..12064f5 --- /dev/null +++ b/src/gui/rulers/ControlRuler.cpp @@ -0,0 +1,539 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rosegarden + A MIDI and audio sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent , + Chris Cannam , + Richard Bown + + The moral rights of Guillaume Laurent, Chris Cannam, and Richard + Bown to claim authorship of this work have been asserted. + + Other copyrights also apply to some parts of this work. Please + see the AUTHORS file and individual file headers for details. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; 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 "ControlRuler.h" + +#include "base/Event.h" +#include "misc/Debug.h" +#include "base/RulerScale.h" +#include "base/Segment.h" +#include "base/Selection.h" +#include "ControlChangeCommand.h" +#include "ControlItem.h" +#include "ControlSelector.h" +#include "ControlTool.h" +#include "DefaultVelocityColour.h" +#include "ElementAdapter.h" +#include "gui/general/EditView.h" +#include "gui/general/RosegardenCanvasView.h" +#include "gui/widgets/TextFloat.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Rosegarden +{ + +const int ControlRuler::DefaultRulerHeight = 75; +const int ControlRuler::MinItemHeight = 5; +const int ControlRuler::MaxItemHeight = 64 + 5; +const int ControlRuler::ItemHeightRange = 64; + +ControlRuler::ControlRuler(Segment *segment, + RulerScale* rulerScale, + EditViewBase* parentView, + QCanvas* c, QWidget* parent, + const char* name, WFlags f) : + RosegardenCanvasView(c, parent, name, f), + m_parentEditView(parentView), + m_mainHorizontalScrollBar(0), + m_rulerScale(rulerScale), + m_eventSelection(new EventSelection(*segment)), + m_segment(segment), + m_currentItem(0), + m_tool(0), + m_maxItemValue(127), + m_staffOffset(0), + m_currentX(0.0), + m_itemMoved(false), + m_selecting(false), + m_selector(new ControlSelector(this)), + m_selectionRect(new QCanvasRectangle(canvas())), + m_menu(0) +{ + setHScrollBarMode(QScrollView::AlwaysOff); + + m_selectionRect->setPen(Qt::red); + + setFixedHeight(sizeHint().height()); + + connect(this, SIGNAL(stateChange(const QString&, bool)), + m_parentEditView, SLOT(slotStateChanged(const QString&, bool))); + + m_numberFloat = new TextFloat(this); + m_numberFloat->hide(); + + m_segment->addObserver(this); + + emit stateChange("have_controller_item_selected", false); +} + +ControlRuler::~ControlRuler() +{ + if (m_segment) { + m_segment->removeObserver(this); + } +} + +void ControlRuler::slotUpdate() +{ + RG_DEBUG << "ControlRuler::slotUpdate()\n"; + + canvas()->setAllChanged(); // TODO: be a bit more subtle, call setChanged(